@surea11y/core 1.1.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/CHANGELOG.md +48 -4
  2. package/LICENSE +373 -21
  3. package/README.md +70 -1
  4. package/bin/core.js +240 -11
  5. package/docs/API_STABILITY.md +61 -0
  6. package/docs/BASELINE.md +66 -0
  7. package/docs/BINDING_AUTHORS_GUIDE.md +9 -9
  8. package/docs/CI_INTEGRATIONS.md +103 -0
  9. package/docs/CLI.md +80 -1
  10. package/docs/ENGINE_OPTIONS.md +4 -0
  11. package/docs/INTEGRATION.md +19 -1
  12. package/docs/OUTPUT_SCHEMA.md +2 -2
  13. package/docs/REPORT.md +33 -0
  14. package/docs/RULE_AUTHORING.md +31 -0
  15. package/docs/RULE_CATALOG.md +1 -1
  16. package/docs/SARIF.md +59 -0
  17. package/package.json +15 -4
  18. package/src/baseline.js +0 -0
  19. package/src/catalogs/composites.wcag.js +414 -450
  20. package/src/checks/automatic/area-alt-present.js +59 -25
  21. package/src/checks/automatic/aria-allowed-attr.js +193 -33
  22. package/src/checks/automatic/aria-allowed-role.js +21 -7
  23. package/src/checks/automatic/aria-braille-equivalent.js +32 -10
  24. package/src/checks/automatic/aria-conditional-attr.js +24 -7
  25. package/src/checks/automatic/aria-deprecated-role.js +22 -8
  26. package/src/checks/automatic/aria-hidden-body.js +50 -19
  27. package/src/checks/automatic/aria-hidden-focus.js +408 -53
  28. package/src/checks/automatic/aria-prohibited-attr.js +296 -22
  29. package/src/checks/automatic/aria-prohibited-children.js +125 -29
  30. package/src/checks/automatic/aria-required-attr.js +23 -8
  31. package/src/checks/automatic/aria-required-children.js +37 -14
  32. package/src/checks/automatic/aria-required-parent.js +48 -14
  33. package/src/checks/automatic/aria-role-name-present.js +47 -21
  34. package/src/checks/automatic/aria-roles-valid.js +22 -12
  35. package/src/checks/automatic/aria-valid-attr-value.js +28 -7
  36. package/src/checks/automatic/aria-valid-attr.js +17 -5
  37. package/src/checks/automatic/autocomplete-valid.js +74 -16
  38. package/src/checks/automatic/avoid-inline-spacing.js +20 -6
  39. package/src/checks/automatic/binary-control-name-present.js +60 -50
  40. package/src/checks/automatic/button-name-present.js +48 -18
  41. package/src/checks/automatic/bypass-blocks-present.js +50 -25
  42. package/src/checks/automatic/canvas-text-alternative-present.js +57 -26
  43. package/src/checks/automatic/combobox-name-present.js +38 -45
  44. package/src/checks/automatic/contrast-computable.js +361 -341
  45. package/src/checks/automatic/contrast-enhanced.js +487 -466
  46. package/src/checks/automatic/contrast-minimum.js +486 -465
  47. package/src/checks/automatic/css-orientation-lock.js +39 -9
  48. package/src/checks/automatic/definition-list-children-valid.js +40 -19
  49. package/src/checks/automatic/deprecated-elements-not-used.js +21 -7
  50. package/src/checks/automatic/dialog-name-present.js +37 -75
  51. package/src/checks/automatic/dlitem-parent-valid.js +23 -8
  52. package/src/checks/automatic/duplicate-id-aria.js +24 -6
  53. package/src/checks/automatic/embed-text-alternative-present.js +86 -35
  54. package/src/checks/automatic/form-control-programmatic-label-present.js +79 -196
  55. package/src/checks/automatic/form-control-single-label.js +47 -10
  56. package/src/checks/automatic/html-xml-lang-mismatch.js +43 -19
  57. package/src/checks/automatic/iframe-focusable-content.js +26 -11
  58. package/src/checks/automatic/iframe-name-present.js +31 -9
  59. package/src/checks/automatic/iframe-title-unique.js +29 -8
  60. package/src/checks/automatic/img-alt-present.js +47 -43
  61. package/src/checks/automatic/input-image-alt-present.js +143 -112
  62. package/src/checks/automatic/label-in-name.js +50 -22
  63. package/src/checks/automatic/language-page-present.js +117 -109
  64. package/src/checks/automatic/link-in-text-block.js +59 -19
  65. package/src/checks/automatic/link-name-present.js +45 -14
  66. package/src/checks/automatic/list-children-valid.js +26 -9
  67. package/src/checks/automatic/listbox-name-present.js +39 -19
  68. package/src/checks/automatic/listitem-parent-valid.js +18 -6
  69. package/src/checks/automatic/menuitem-name-present.js +39 -61
  70. package/src/checks/automatic/meta-refresh-no-exceptions.js +37 -8
  71. package/src/checks/automatic/meta-refresh-timing-absent.js +28 -6
  72. package/src/checks/automatic/meta-viewport-zoom-enabled.js +32 -7
  73. package/src/checks/automatic/meter-name-present.js +36 -33
  74. package/src/checks/automatic/nested-interactive-controls-absent.js +31 -10
  75. package/src/checks/automatic/object-text-alternative-present.js +91 -39
  76. package/src/checks/automatic/option-name-present.js +38 -21
  77. package/src/checks/automatic/page-title-present.js +26 -7
  78. package/src/checks/automatic/progressbar-name-present.js +41 -34
  79. package/src/checks/automatic/role-img-alt-present.js +209 -157
  80. package/src/checks/automatic/searchbox-name-present.js +39 -19
  81. package/src/checks/automatic/server-side-image-map-absent.js +23 -8
  82. package/src/checks/automatic/slider-name-present.js +40 -47
  83. package/src/checks/automatic/spinbutton-name-present.js +39 -19
  84. package/src/checks/automatic/summary-name-present.js +37 -17
  85. package/src/checks/automatic/svg-image-text-alternative-present.js +114 -47
  86. package/src/checks/automatic/svg-text-alternative-present.js +246 -226
  87. package/src/checks/automatic/tab-name-present.js +37 -60
  88. package/src/checks/automatic/table-headers-attr-valid.js +24 -8
  89. package/src/checks/automatic/table-th-has-data-cells.js +22 -8
  90. package/src/checks/automatic/target-size-minimum.js +118 -48
  91. package/src/checks/automatic/td-has-header.js +29 -11
  92. package/src/checks/automatic/textbox-name-present.js +39 -19
  93. package/src/checks/automatic/tooltip-name-present.js +37 -18
  94. package/src/checks/automatic/treeitem-name-present.js +38 -21
  95. package/src/checks/automatic/valid-lang.js +20 -6
  96. package/src/checks/automatic/video-poster-text-alternative-present.js +79 -36
  97. package/src/checks/manual/accesskeys-manual.js +14 -5
  98. package/src/checks/manual/area-alt-decorative-manual.js +192 -193
  99. package/src/checks/manual/area-alt-quality-manual.js +182 -141
  100. package/src/checks/manual/aria-checked-state-mismatch-manual.js +34 -11
  101. package/src/checks/manual/aria-text-manual.js +14 -6
  102. package/src/checks/manual/canvas-text-alternative-quality-manual.js +149 -114
  103. package/src/checks/manual/css-hidden-focus.js +196 -165
  104. package/src/checks/manual/embed-text-alternative-quality-manual.js +171 -160
  105. package/src/checks/manual/empty-heading-manual.js +24 -12
  106. package/src/checks/manual/empty-table-header-manual.js +21 -14
  107. package/src/checks/manual/focus-order-semantics-manual.js +45 -10
  108. package/src/checks/manual/form-control-programmatic-label-quality-manual.js +207 -246
  109. package/src/checks/manual/heading-order-manual.js +22 -12
  110. package/src/checks/manual/identical-links-same-purpose-manual.js +34 -12
  111. package/src/checks/manual/image-redundant-alt-manual.js +17 -7
  112. package/src/checks/manual/img-alt-decorative-manual.js +131 -96
  113. package/src/checks/manual/img-alt-quality-manual.js +176 -127
  114. package/src/checks/manual/input-image-alt-decorative-manual.js +125 -92
  115. package/src/checks/manual/input-image-alt-quality-manual.js +125 -92
  116. package/src/checks/manual/label-title-only-manual.js +14 -5
  117. package/src/checks/manual/landmark-banner-is-top-level-manual.js +95 -29
  118. package/src/checks/manual/landmark-contentinfo-is-top-level-manual.js +82 -29
  119. package/src/checks/manual/landmark-main-is-top-level-manual.js +64 -23
  120. package/src/checks/manual/landmark-no-duplicate-banner-manual.js +54 -23
  121. package/src/checks/manual/landmark-no-duplicate-contentinfo-manual.js +54 -23
  122. package/src/checks/manual/landmark-no-duplicate-main-manual.js +17 -8
  123. package/src/checks/manual/landmark-one-main-manual.js +35 -21
  124. package/src/checks/manual/landmark-unique-manual.js +73 -45
  125. package/src/checks/manual/link-name-quality-manual.js +43 -12
  126. package/src/checks/manual/media-transcript-present-manual.js +35 -22
  127. package/src/checks/manual/meta-viewport-large-manual.js +25 -6
  128. package/src/checks/manual/mouse-only-event-handlers-manual.js +38 -11
  129. package/src/checks/manual/no-autoplay-audio-manual.js +20 -6
  130. package/src/checks/manual/object-text-alternative-quality-manual.js +175 -154
  131. package/src/checks/manual/p-as-heading-manual.js +22 -7
  132. package/src/checks/manual/page-has-heading-one-manual.js +40 -23
  133. package/src/checks/manual/page-title-patterns-manual.js +87 -51
  134. package/src/checks/manual/presentation-role-conflict-manual.js +59 -19
  135. package/src/checks/manual/region-manual.js +275 -62
  136. package/src/checks/manual/scope-attr-valid-manual.js +11 -9
  137. package/src/checks/manual/scrollable-region-focusable-manual.js +37 -11
  138. package/src/checks/manual/skip-link-manual.js +35 -12
  139. package/src/checks/manual/svg-text-alternative-quality-manual.js +206 -165
  140. package/src/checks/manual/tabindex-manual.js +11 -9
  141. package/src/checks/manual/table-duplicate-name-manual.js +17 -7
  142. package/src/checks/manual/table-fake-caption-manual.js +24 -7
  143. package/src/checks/manual/video-caption-manual.js +15 -4
  144. package/src/checks/manual-review.js +56 -12
  145. package/src/core/aria-helpers.js +1128 -823
  146. package/src/core/contrast-helpers.js +1217 -1062
  147. package/src/core/dom-helpers.js +4176 -3886
  148. package/src/core/dom-runner.js +720 -596
  149. package/src/core/frame-messaging.js +189 -138
  150. package/src/core/frame-scan.js +94 -82
  151. package/src/core/rollup-composites.js +94 -102
  152. package/src/core/rule-meta.js +71 -35
  153. package/src/core.js +37939 -29121
  154. package/src/i18n/en.js +1194 -887
  155. package/src/i18n/fr.js +1136 -793
  156. package/src/policy/contracts.js +13 -13
  157. package/src/policy/resolvePolicy.js +48 -44
  158. package/src/report.js +502 -0
  159. package/src/sarif.js +175 -0
  160. package/surea11y.browser.js +36042 -0
@@ -11,7 +11,7 @@
11
11
  * loaded directly into a page with no automation driver -- see
12
12
  * docs/INTEGRATION.md's "Browser extension context" section). A Playwright-
13
13
  * driven scan doesn't need any of this: Playwright reaches cross-origin
14
- * frames unconditionally via CDP (see surea11y-playwright's ROADMAP.md gap
14
+ * frames unconditionally via CDP (see @surea11y/playwright's ROADMAP.md gap
15
15
  * #1), which is strictly better than what a cooperative postMessage protocol
16
16
  * can achieve. This exists for when there IS no automation driver.
17
17
  *
@@ -27,79 +27,112 @@
27
27
  const FRAME_RPC_CHANNEL = '__frame_rpc_v1__';
28
28
 
29
29
  function getFrameRpcRegistry(win) {
30
- if (!win.__a11yCoreFrameRpc__) {
31
- win.__a11yCoreFrameRpc__ = {
32
- pending: new Map(),
33
- seq: 0,
34
- responder: null,
35
- listening: false
36
- };
37
- }
38
- return win.__a11yCoreFrameRpc__;
30
+ if (!win.__a11yCoreFrameRpc__) {
31
+ win.__a11yCoreFrameRpc__ = {
32
+ pending: new Map(),
33
+ seq: 0,
34
+ responder: null,
35
+ listening: false
36
+ };
37
+ }
38
+ return win.__a11yCoreFrameRpc__;
39
39
  }
40
40
 
41
41
  function installFrameRpcListener(win, channel) {
42
- const registry = getFrameRpcRegistry(win);
43
- if (registry.listening) return registry;
44
-
45
- win.addEventListener('message', function a11yCoreFrameRpcListener(event) {
46
- const data = event && event.data;
47
- if (!data || data.__a11ycore !== true || data.channel !== channel) return;
48
-
49
- if (data.type === 'ping') {
50
- try {
51
- event.source && event.source.postMessage(
52
- { __a11ycore: true, channel: channel, requestId: data.requestId, type: 'pong' },
53
- '*'
54
- );
55
- } catch (e) { /* target gone/closed -- nothing to do */ }
56
- return;
57
- }
58
-
59
- if (data.type === 'run') {
60
- const responder = registry.responder;
61
- if (typeof responder !== 'function') return; // no responder enabled here: unreachable, same as a widely-used reference engine's own limitation
62
- Promise.resolve()
63
- .then(function () { return responder(data.payload); })
64
- .then(function (result) {
65
- try {
66
- event.source && event.source.postMessage(
67
- { __a11ycore: true, channel: channel, requestId: data.requestId, type: 'result', payload: result },
68
- '*'
69
- );
70
- } catch (e) { /* target gone/closed */ }
71
- })
72
- .catch(function (err) {
73
- try {
74
- event.source && event.source.postMessage(
75
- { __a11ycore: true, channel: channel, requestId: data.requestId, type: 'error', payload: String(err && err.message ? err.message : err) },
76
- '*'
77
- );
78
- } catch (e) { /* target gone/closed */ }
79
- });
80
- return;
81
- }
82
-
83
- // 'pong' | 'result' | 'error' -- resolve whichever pending request this replies to.
84
- const pending = registry.pending.get(data.requestId);
85
- if (!pending) return;
86
- if (data.type === 'pong') {
87
- pending.onPong();
88
- return;
89
- }
90
- registry.pending.delete(data.requestId);
91
- if (data.type === 'result') pending.resolve(data.payload);
92
- else pending.reject(new Error(typeof data.payload === 'string' ? data.payload : 'surea11y frame RPC error'));
93
- });
42
+ const registry = getFrameRpcRegistry(win);
43
+ if (registry.listening) return registry;
44
+
45
+ win.addEventListener('message', function a11yCoreFrameRpcListener(event) {
46
+ const data = event && event.data;
47
+ if (!data || data.__a11ycore !== true || data.channel !== channel) return;
48
+
49
+ if (data.type === 'ping') {
50
+ try {
51
+ event.source &&
52
+ event.source.postMessage(
53
+ { __a11ycore: true, channel: channel, requestId: data.requestId, type: 'pong' },
54
+ '*'
55
+ );
56
+ } catch (e) {
57
+ /* target gone/closed -- nothing to do */
58
+ }
59
+ return;
60
+ }
61
+
62
+ if (data.type === 'run') {
63
+ const responder = registry.responder;
64
+ if (typeof responder !== 'function') return; // no responder enabled here: unreachable, same as a widely-used reference engine's own limitation
65
+ Promise.resolve()
66
+ .then(function () {
67
+ return responder(data.payload);
68
+ })
69
+ .then(function (result) {
70
+ try {
71
+ event.source &&
72
+ event.source.postMessage(
73
+ {
74
+ __a11ycore: true,
75
+ channel: channel,
76
+ requestId: data.requestId,
77
+ type: 'result',
78
+ payload: result
79
+ },
80
+ '*'
81
+ );
82
+ } catch (e) {
83
+ /* target gone/closed */
84
+ }
85
+ })
86
+ .catch(function (err) {
87
+ try {
88
+ event.source &&
89
+ event.source.postMessage(
90
+ {
91
+ __a11ycore: true,
92
+ channel: channel,
93
+ requestId: data.requestId,
94
+ type: 'error',
95
+ payload: String(err && err.message ? err.message : err)
96
+ },
97
+ '*'
98
+ );
99
+ } catch (e) {
100
+ /* target gone/closed */
101
+ }
102
+ });
103
+ return;
104
+ }
105
+
106
+ // 'pong' | 'result' | 'error' -- resolve whichever pending request this replies to.
107
+ const pending = registry.pending.get(data.requestId);
108
+ if (!pending) return;
109
+ if (data.type === 'pong') {
110
+ pending.onPong();
111
+ return;
112
+ }
113
+ registry.pending.delete(data.requestId);
114
+ if (data.type === 'result') pending.resolve(data.payload);
115
+ else
116
+ pending.reject(
117
+ new Error(typeof data.payload === 'string' ? data.payload : 'surea11y frame RPC error')
118
+ );
119
+ });
94
120
 
95
- registry.listening = true;
96
- return registry;
121
+ registry.listening = true;
122
+ return registry;
97
123
  }
98
124
 
99
125
  function nextFrameRpcRequestId(win) {
100
- const registry = getFrameRpcRegistry(win);
101
- registry.seq += 1;
102
- return 'req_' + Date.now().toString(36) + '_' + registry.seq + '_' + Math.random().toString(36).slice(2, 8);
126
+ const registry = getFrameRpcRegistry(win);
127
+ registry.seq += 1;
128
+ return (
129
+ 'req_' +
130
+ Date.now().toString(36) +
131
+ '_' +
132
+ registry.seq +
133
+ '_' +
134
+ Math.random().toString(36).slice(2, 8)
135
+ );
103
136
  }
104
137
 
105
138
  /**
@@ -110,43 +143,46 @@ function nextFrameRpcRequestId(win) {
110
143
  * all), not an error.
111
144
  */
112
145
  function pingFrame(win, targetWindow, pingWaitTime) {
113
- installFrameRpcListener(win, FRAME_RPC_CHANNEL);
114
- const registry = getFrameRpcRegistry(win);
115
- const requestId = nextFrameRpcRequestId(win);
116
- const waitMs = typeof pingWaitTime === 'number' ? pingWaitTime : 500;
117
-
118
- return new Promise(function (resolve) {
119
- let settled = false;
120
- const timeout = setTimeout(function () {
121
- if (settled) return;
122
- settled = true;
123
- registry.pending.delete(requestId);
124
- resolve(false);
125
- }, waitMs);
126
-
127
- registry.pending.set(requestId, {
128
- onPong: function () {
129
- if (settled) return;
130
- settled = true;
131
- clearTimeout(timeout);
132
- registry.pending.delete(requestId);
133
- resolve(true);
134
- },
135
- resolve: function () {},
136
- reject: function () {}
137
- });
146
+ installFrameRpcListener(win, FRAME_RPC_CHANNEL);
147
+ const registry = getFrameRpcRegistry(win);
148
+ const requestId = nextFrameRpcRequestId(win);
149
+ const waitMs = typeof pingWaitTime === 'number' ? pingWaitTime : 500;
150
+
151
+ return new Promise(function (resolve) {
152
+ let settled = false;
153
+ const timeout = setTimeout(function () {
154
+ if (settled) return;
155
+ settled = true;
156
+ registry.pending.delete(requestId);
157
+ resolve(false);
158
+ }, waitMs);
138
159
 
139
- try {
140
- targetWindow.postMessage({ __a11ycore: true, channel: FRAME_RPC_CHANNEL, requestId: requestId, type: 'ping' }, '*');
141
- } catch (e) {
142
- if (!settled) {
143
- settled = true;
144
- clearTimeout(timeout);
145
- registry.pending.delete(requestId);
146
- resolve(false);
147
- }
148
- }
160
+ registry.pending.set(requestId, {
161
+ onPong: function () {
162
+ if (settled) return;
163
+ settled = true;
164
+ clearTimeout(timeout);
165
+ registry.pending.delete(requestId);
166
+ resolve(true);
167
+ },
168
+ resolve: function () {},
169
+ reject: function () {}
149
170
  });
171
+
172
+ try {
173
+ targetWindow.postMessage(
174
+ { __a11ycore: true, channel: FRAME_RPC_CHANNEL, requestId: requestId, type: 'ping' },
175
+ '*'
176
+ );
177
+ } catch (e) {
178
+ if (!settled) {
179
+ settled = true;
180
+ clearTimeout(timeout);
181
+ registry.pending.delete(requestId);
182
+ resolve(false);
183
+ }
184
+ }
185
+ });
150
186
  }
151
187
 
152
188
  /**
@@ -156,31 +192,46 @@ function pingFrame(win, targetWindow, pingWaitTime) {
156
192
  * explicit error reply.
157
193
  */
158
194
  function sendFrameRunCommand(win, targetWindow, payload, frameWaitTime) {
159
- installFrameRpcListener(win, FRAME_RPC_CHANNEL);
160
- const registry = getFrameRpcRegistry(win);
161
- const requestId = nextFrameRpcRequestId(win);
162
- const waitMs = typeof frameWaitTime === 'number' ? frameWaitTime : 60000;
163
-
164
- return new Promise(function (resolve, reject) {
165
- const timeout = setTimeout(function () {
166
- registry.pending.delete(requestId);
167
- reject(new Error('surea11y frame RPC timed out waiting for a run result'));
168
- }, waitMs);
169
-
170
- registry.pending.set(requestId, {
171
- onPong: function () {},
172
- resolve: function (result) { clearTimeout(timeout); resolve(result); },
173
- reject: function (err) { clearTimeout(timeout); reject(err); }
174
- });
195
+ installFrameRpcListener(win, FRAME_RPC_CHANNEL);
196
+ const registry = getFrameRpcRegistry(win);
197
+ const requestId = nextFrameRpcRequestId(win);
198
+ const waitMs = typeof frameWaitTime === 'number' ? frameWaitTime : 60000;
199
+
200
+ return new Promise(function (resolve, reject) {
201
+ const timeout = setTimeout(function () {
202
+ registry.pending.delete(requestId);
203
+ reject(new Error('surea11y frame RPC timed out waiting for a run result'));
204
+ }, waitMs);
175
205
 
176
- try {
177
- targetWindow.postMessage({ __a11ycore: true, channel: FRAME_RPC_CHANNEL, requestId: requestId, type: 'run', payload: payload }, '*');
178
- } catch (e) {
179
- clearTimeout(timeout);
180
- registry.pending.delete(requestId);
181
- reject(e);
182
- }
206
+ registry.pending.set(requestId, {
207
+ onPong: function () {},
208
+ resolve: function (result) {
209
+ clearTimeout(timeout);
210
+ resolve(result);
211
+ },
212
+ reject: function (err) {
213
+ clearTimeout(timeout);
214
+ reject(err);
215
+ }
183
216
  });
217
+
218
+ try {
219
+ targetWindow.postMessage(
220
+ {
221
+ __a11ycore: true,
222
+ channel: FRAME_RPC_CHANNEL,
223
+ requestId: requestId,
224
+ type: 'run',
225
+ payload: payload
226
+ },
227
+ '*'
228
+ );
229
+ } catch (e) {
230
+ clearTimeout(timeout);
231
+ registry.pending.delete(requestId);
232
+ reject(e);
233
+ }
234
+ });
184
235
  }
185
236
 
186
237
  /**
@@ -191,20 +242,20 @@ function sendFrameRunCommand(win, targetWindow, payload, frameWaitTime) {
191
242
  * later re-enable doesn't need to re-attach it).
192
243
  */
193
244
  function enableFrameRpcResponder(win, handler) {
194
- installFrameRpcListener(win, FRAME_RPC_CHANNEL);
195
- const registry = getFrameRpcRegistry(win);
196
- registry.responder = handler;
197
- return function disable() {
198
- if (registry.responder === handler) registry.responder = null;
199
- };
245
+ installFrameRpcListener(win, FRAME_RPC_CHANNEL);
246
+ const registry = getFrameRpcRegistry(win);
247
+ registry.responder = handler;
248
+ return function disable() {
249
+ if (registry.responder === handler) registry.responder = null;
250
+ };
200
251
  }
201
252
 
202
253
  module.exports = {
203
- FRAME_RPC_CHANNEL,
204
- getFrameRpcRegistry,
205
- installFrameRpcListener,
206
- nextFrameRpcRequestId,
207
- pingFrame,
208
- sendFrameRunCommand,
209
- enableFrameRpcResponder
254
+ FRAME_RPC_CHANNEL,
255
+ getFrameRpcRegistry,
256
+ installFrameRpcListener,
257
+ nextFrameRpcRequestId,
258
+ pingFrame,
259
+ sendFrameRunCommand,
260
+ enableFrameRpcResponder
210
261
  };
@@ -5,7 +5,7 @@
5
5
  * mode (surea11y loaded directly into a page with no automation driver --
6
6
  * see docs/INTEGRATION.md's "Browser extension context" section). A
7
7
  * Playwright-driven scan doesn't need any of this (see
8
- * surea11y-playwright's ROADMAP.md gap #1 -- CDP-level frame access is
8
+ * @surea11y/playwright's ROADMAP.md gap #1 -- CDP-level frame access is
9
9
  * unconditional, strictly better than what a cooperative protocol like this
10
10
  * one can achieve). This exists for when there is no automation driver, the
11
11
  * same situation a widely-used reference engine itself is built around.
@@ -31,37 +31,41 @@
31
31
  * bundle instead.
32
32
  */
33
33
 
34
+ /* global runCore, resolveContextRoots, resolveEffectiveRunOnly, pingFrame,
35
+ sendFrameRunCommand, enableFrameRpcResponder, CHECK_DEFS, RULE_IMPLS,
36
+ ENGINE_TAG, SCHEMA_VERSION, COMPOSITE_RULES */
37
+
34
38
  function findChildFrameElements(roots) {
35
- const seen = new Set();
36
- const out = [];
37
- for (const root of roots) {
38
- if (!root || typeof root.querySelectorAll !== 'function') continue;
39
- let matches = [];
40
- try {
41
- matches = root.querySelectorAll('iframe, frame');
42
- } catch (e) {
43
- matches = [];
44
- }
45
- for (const el of matches) {
46
- if (el && !seen.has(el)) {
47
- seen.add(el);
48
- out.push(el);
49
- }
50
- }
39
+ const seen = new Set();
40
+ const out = [];
41
+ for (const root of roots) {
42
+ if (!root || typeof root.querySelectorAll !== 'function') continue;
43
+ let matches;
44
+ try {
45
+ matches = root.querySelectorAll('iframe, frame');
46
+ } catch (e) {
47
+ matches = [];
48
+ }
49
+ for (const el of matches) {
50
+ if (el && !seen.has(el)) {
51
+ seen.add(el);
52
+ out.push(el);
53
+ }
51
54
  }
52
- return out;
55
+ }
56
+ return out;
53
57
  }
54
58
 
55
59
  function getFrameElementUrl(el) {
56
- try {
57
- if (el.contentWindow && el.contentWindow.location && el.contentWindow.location.href) {
58
- return el.contentWindow.location.href;
59
- }
60
- } catch (e) {
61
- // Cross-origin: reading contentWindow.location.href itself throws. Fall
62
- // back to the authored src attribute (always readable, any origin).
60
+ try {
61
+ if (el.contentWindow && el.contentWindow.location && el.contentWindow.location.href) {
62
+ return el.contentWindow.location.href;
63
63
  }
64
- return el.getAttribute ? (el.getAttribute('src') || null) : null;
64
+ } catch (e) {
65
+ // Cross-origin: reading contentWindow.location.href itself throws. Fall
66
+ // back to the authored src attribute (always readable, any origin).
67
+ }
68
+ return el.getAttribute ? el.getAttribute('src') || null : null;
65
69
  }
66
70
 
67
71
  /**
@@ -84,60 +88,63 @@ function getFrameElementUrl(el) {
84
88
  * @returns {Promise<{ topFrame: object, frames: Array<{url:string|null, topFrame?:object, frames?:Array, error?:string}> }>}
85
89
  */
86
90
  function runa11yCoreAcrossFrames(pageUrl, contextSelector, engineOptions, runOnly) {
87
- const topFrame = runCore(
88
- pageUrl,
89
- contextSelector,
90
- engineOptions,
91
- resolveEffectiveRunOnly(engineOptions, runOnly),
92
- CHECK_DEFS,
93
- RULE_IMPLS,
94
- ENGINE_TAG,
95
- SCHEMA_VERSION,
96
- COMPOSITE_RULES
97
- );
91
+ const topFrame = runCore(
92
+ pageUrl,
93
+ contextSelector,
94
+ engineOptions,
95
+ resolveEffectiveRunOnly(engineOptions, runOnly),
96
+ CHECK_DEFS,
97
+ RULE_IMPLS,
98
+ ENGINE_TAG,
99
+ SCHEMA_VERSION,
100
+ COMPOSITE_RULES
101
+ );
98
102
 
99
- const eo = (engineOptions && typeof engineOptions === 'object') ? engineOptions : {};
100
- const pingWaitTime = typeof eo.pingWaitTime === 'number' ? eo.pingWaitTime : undefined;
101
- const frameWaitTime = typeof eo.frameWaitTime === 'number' ? eo.frameWaitTime : undefined;
103
+ const eo = engineOptions && typeof engineOptions === 'object' ? engineOptions : {};
104
+ const pingWaitTime = typeof eo.pingWaitTime === 'number' ? eo.pingWaitTime : undefined;
105
+ const frameWaitTime = typeof eo.frameWaitTime === 'number' ? eo.frameWaitTime : undefined;
102
106
 
103
- const { roots } = resolveContextRoots(document, contextSelector);
104
- const frameElements = findChildFrameElements(roots);
107
+ const { roots } = resolveContextRoots(document, contextSelector);
108
+ const frameElements = findChildFrameElements(roots);
105
109
 
106
- const framePromises = frameElements.map(function (el) {
107
- const url = getFrameElementUrl(el);
108
- let targetWindow = null;
109
- try {
110
- targetWindow = el.contentWindow || null;
111
- } catch (e) {
112
- targetWindow = null;
113
- }
114
- if (!targetWindow) {
115
- return Promise.resolve({ url: url, error: 'frame has no accessible contentWindow' });
116
- }
110
+ const framePromises = frameElements.map(function (el) {
111
+ const url = getFrameElementUrl(el);
112
+ let targetWindow = null;
113
+ try {
114
+ targetWindow = el.contentWindow || null;
115
+ } catch (e) {
116
+ targetWindow = null;
117
+ }
118
+ if (!targetWindow) {
119
+ return Promise.resolve({ url: url, error: 'frame has no accessible contentWindow' });
120
+ }
117
121
 
118
- return pingFrame(window, targetWindow, pingWaitTime).then(function (reachable) {
119
- if (!reachable) {
120
- return {
121
- url: url,
122
- error: 'no surea11y frame responder detected (that frame never called a11yCoreEnableFrameResponder(), or has not finished loading yet)'
123
- };
124
- }
125
- return sendFrameRunCommand(
126
- window,
127
- targetWindow,
128
- { pageUrl: url, contextSelector: null, engineOptions: eo, runOnly: runOnly },
129
- frameWaitTime
130
- ).then(function (result) {
131
- return { url: url, topFrame: result.topFrame, frames: result.frames };
132
- }).catch(function (err) {
133
- return { url: url, error: String(err && err.message ? err.message : err) };
134
- });
122
+ return pingFrame(window, targetWindow, pingWaitTime).then(function (reachable) {
123
+ if (!reachable) {
124
+ return {
125
+ url: url,
126
+ error:
127
+ 'no surea11y frame responder detected (that frame never called a11yCoreEnableFrameResponder(), or has not finished loading yet)'
128
+ };
129
+ }
130
+ return sendFrameRunCommand(
131
+ window,
132
+ targetWindow,
133
+ { pageUrl: url, contextSelector: null, engineOptions: eo, runOnly: runOnly },
134
+ frameWaitTime
135
+ )
136
+ .then(function (result) {
137
+ return { url: url, topFrame: result.topFrame, frames: result.frames };
138
+ })
139
+ .catch(function (err) {
140
+ return { url: url, error: String(err && err.message ? err.message : err) };
135
141
  });
136
142
  });
143
+ });
137
144
 
138
- return Promise.all(framePromises).then(function (frames) {
139
- return { topFrame: topFrame, frames: frames };
140
- });
145
+ return Promise.all(framePromises).then(function (frames) {
146
+ return { topFrame: topFrame, frames: frames };
147
+ });
141
148
  }
142
149
 
143
150
  /**
@@ -165,14 +172,19 @@ function runa11yCoreAcrossFrames(pageUrl, contextSelector, engineOptions, runOnl
165
172
  * @returns {function(): void} disable() -- stops responding to future scans
166
173
  */
167
174
  function a11yCoreEnableFrameResponder() {
168
- return enableFrameRpcResponder(window, function (payload) {
169
- return runa11yCoreAcrossFrames(
170
- payload ? payload.pageUrl : null,
171
- payload ? payload.contextSelector : null,
172
- payload ? payload.engineOptions : {},
173
- payload ? payload.runOnly : null
174
- );
175
- });
175
+ return enableFrameRpcResponder(window, function (payload) {
176
+ return runa11yCoreAcrossFrames(
177
+ payload ? payload.pageUrl : null,
178
+ payload ? payload.contextSelector : null,
179
+ payload ? payload.engineOptions : {},
180
+ payload ? payload.runOnly : null
181
+ );
182
+ });
176
183
  }
177
184
 
178
- module.exports = { findChildFrameElements, getFrameElementUrl, runa11yCoreAcrossFrames, a11yCoreEnableFrameResponder };
185
+ module.exports = {
186
+ findChildFrameElements,
187
+ getFrameElementUrl,
188
+ runa11yCoreAcrossFrames,
189
+ a11yCoreEnableFrameResponder
190
+ };