@surea11y/core 1.2.0 → 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 (157) hide show
  1. package/CHANGELOG.md +37 -7
  2. package/LICENSE +373 -21
  3. package/README.md +67 -1
  4. package/bin/core.js +144 -19
  5. package/docs/API_STABILITY.md +1 -1
  6. package/docs/BINDING_AUTHORS_GUIDE.md +9 -9
  7. package/docs/CI_INTEGRATIONS.md +103 -0
  8. package/docs/CLI.md +54 -1
  9. package/docs/ENGINE_OPTIONS.md +2 -0
  10. package/docs/INTEGRATION.md +18 -0
  11. package/docs/OUTPUT_SCHEMA.md +1 -1
  12. package/docs/RULE_CATALOG.md +1 -1
  13. package/docs/SARIF.md +59 -0
  14. package/package.json +15 -4
  15. package/src/baseline.js +0 -0
  16. package/src/catalogs/composites.wcag.js +414 -450
  17. package/src/checks/automatic/area-alt-present.js +59 -25
  18. package/src/checks/automatic/aria-allowed-attr.js +193 -33
  19. package/src/checks/automatic/aria-allowed-role.js +21 -7
  20. package/src/checks/automatic/aria-braille-equivalent.js +32 -10
  21. package/src/checks/automatic/aria-conditional-attr.js +24 -7
  22. package/src/checks/automatic/aria-deprecated-role.js +22 -8
  23. package/src/checks/automatic/aria-hidden-body.js +42 -19
  24. package/src/checks/automatic/aria-hidden-focus.js +408 -53
  25. package/src/checks/automatic/aria-prohibited-attr.js +296 -22
  26. package/src/checks/automatic/aria-prohibited-children.js +55 -16
  27. package/src/checks/automatic/aria-required-attr.js +23 -8
  28. package/src/checks/automatic/aria-required-children.js +37 -14
  29. package/src/checks/automatic/aria-required-parent.js +48 -14
  30. package/src/checks/automatic/aria-role-name-present.js +47 -21
  31. package/src/checks/automatic/aria-roles-valid.js +22 -12
  32. package/src/checks/automatic/aria-valid-attr-value.js +28 -7
  33. package/src/checks/automatic/aria-valid-attr.js +17 -5
  34. package/src/checks/automatic/autocomplete-valid.js +74 -16
  35. package/src/checks/automatic/avoid-inline-spacing.js +20 -6
  36. package/src/checks/automatic/binary-control-name-present.js +60 -50
  37. package/src/checks/automatic/button-name-present.js +48 -18
  38. package/src/checks/automatic/bypass-blocks-present.js +42 -25
  39. package/src/checks/automatic/canvas-text-alternative-present.js +57 -26
  40. package/src/checks/automatic/combobox-name-present.js +38 -45
  41. package/src/checks/automatic/contrast-computable.js +361 -341
  42. package/src/checks/automatic/contrast-enhanced.js +487 -466
  43. package/src/checks/automatic/contrast-minimum.js +486 -465
  44. package/src/checks/automatic/css-orientation-lock.js +30 -8
  45. package/src/checks/automatic/definition-list-children-valid.js +40 -19
  46. package/src/checks/automatic/deprecated-elements-not-used.js +21 -7
  47. package/src/checks/automatic/dialog-name-present.js +37 -75
  48. package/src/checks/automatic/dlitem-parent-valid.js +23 -8
  49. package/src/checks/automatic/duplicate-id-aria.js +24 -6
  50. package/src/checks/automatic/embed-text-alternative-present.js +86 -35
  51. package/src/checks/automatic/form-control-programmatic-label-present.js +79 -196
  52. package/src/checks/automatic/form-control-single-label.js +47 -10
  53. package/src/checks/automatic/html-xml-lang-mismatch.js +34 -18
  54. package/src/checks/automatic/iframe-focusable-content.js +26 -11
  55. package/src/checks/automatic/iframe-name-present.js +31 -9
  56. package/src/checks/automatic/iframe-title-unique.js +29 -8
  57. package/src/checks/automatic/img-alt-present.js +47 -43
  58. package/src/checks/automatic/input-image-alt-present.js +143 -112
  59. package/src/checks/automatic/label-in-name.js +50 -22
  60. package/src/checks/automatic/language-page-present.js +109 -109
  61. package/src/checks/automatic/link-in-text-block.js +59 -19
  62. package/src/checks/automatic/link-name-present.js +45 -14
  63. package/src/checks/automatic/list-children-valid.js +26 -9
  64. package/src/checks/automatic/listbox-name-present.js +39 -19
  65. package/src/checks/automatic/listitem-parent-valid.js +18 -6
  66. package/src/checks/automatic/menuitem-name-present.js +39 -61
  67. package/src/checks/automatic/meta-refresh-no-exceptions.js +28 -7
  68. package/src/checks/automatic/meta-refresh-timing-absent.js +20 -6
  69. package/src/checks/automatic/meta-viewport-zoom-enabled.js +24 -7
  70. package/src/checks/automatic/meter-name-present.js +36 -33
  71. package/src/checks/automatic/nested-interactive-controls-absent.js +31 -10
  72. package/src/checks/automatic/object-text-alternative-present.js +91 -39
  73. package/src/checks/automatic/option-name-present.js +38 -21
  74. package/src/checks/automatic/page-title-present.js +17 -6
  75. package/src/checks/automatic/progressbar-name-present.js +41 -34
  76. package/src/checks/automatic/role-img-alt-present.js +209 -157
  77. package/src/checks/automatic/searchbox-name-present.js +39 -19
  78. package/src/checks/automatic/server-side-image-map-absent.js +23 -8
  79. package/src/checks/automatic/slider-name-present.js +40 -47
  80. package/src/checks/automatic/spinbutton-name-present.js +39 -19
  81. package/src/checks/automatic/summary-name-present.js +37 -17
  82. package/src/checks/automatic/svg-image-text-alternative-present.js +114 -47
  83. package/src/checks/automatic/svg-text-alternative-present.js +246 -226
  84. package/src/checks/automatic/tab-name-present.js +37 -60
  85. package/src/checks/automatic/table-headers-attr-valid.js +24 -8
  86. package/src/checks/automatic/table-th-has-data-cells.js +22 -8
  87. package/src/checks/automatic/target-size-minimum.js +118 -48
  88. package/src/checks/automatic/td-has-header.js +29 -11
  89. package/src/checks/automatic/textbox-name-present.js +39 -19
  90. package/src/checks/automatic/tooltip-name-present.js +37 -18
  91. package/src/checks/automatic/treeitem-name-present.js +38 -21
  92. package/src/checks/automatic/valid-lang.js +20 -6
  93. package/src/checks/automatic/video-poster-text-alternative-present.js +79 -36
  94. package/src/checks/manual/accesskeys-manual.js +14 -5
  95. package/src/checks/manual/area-alt-decorative-manual.js +192 -193
  96. package/src/checks/manual/area-alt-quality-manual.js +182 -141
  97. package/src/checks/manual/aria-checked-state-mismatch-manual.js +34 -11
  98. package/src/checks/manual/aria-text-manual.js +14 -6
  99. package/src/checks/manual/canvas-text-alternative-quality-manual.js +149 -114
  100. package/src/checks/manual/css-hidden-focus.js +196 -165
  101. package/src/checks/manual/embed-text-alternative-quality-manual.js +171 -160
  102. package/src/checks/manual/empty-heading-manual.js +24 -7
  103. package/src/checks/manual/empty-table-header-manual.js +17 -6
  104. package/src/checks/manual/focus-order-semantics-manual.js +45 -10
  105. package/src/checks/manual/form-control-programmatic-label-quality-manual.js +207 -246
  106. package/src/checks/manual/heading-order-manual.js +22 -7
  107. package/src/checks/manual/identical-links-same-purpose-manual.js +34 -12
  108. package/src/checks/manual/image-redundant-alt-manual.js +17 -7
  109. package/src/checks/manual/img-alt-decorative-manual.js +131 -96
  110. package/src/checks/manual/img-alt-quality-manual.js +176 -127
  111. package/src/checks/manual/input-image-alt-decorative-manual.js +125 -92
  112. package/src/checks/manual/input-image-alt-quality-manual.js +125 -92
  113. package/src/checks/manual/label-title-only-manual.js +14 -5
  114. package/src/checks/manual/landmark-banner-is-top-level-manual.js +66 -16
  115. package/src/checks/manual/landmark-contentinfo-is-top-level-manual.js +53 -16
  116. package/src/checks/manual/landmark-main-is-top-level-manual.js +35 -10
  117. package/src/checks/manual/landmark-no-duplicate-banner-manual.js +29 -10
  118. package/src/checks/manual/landmark-no-duplicate-contentinfo-manual.js +29 -10
  119. package/src/checks/manual/landmark-no-duplicate-main-manual.js +17 -8
  120. package/src/checks/manual/landmark-one-main-manual.js +26 -20
  121. package/src/checks/manual/landmark-unique-manual.js +41 -15
  122. package/src/checks/manual/link-name-quality-manual.js +43 -12
  123. package/src/checks/manual/media-transcript-present-manual.js +35 -22
  124. package/src/checks/manual/meta-viewport-large-manual.js +16 -5
  125. package/src/checks/manual/mouse-only-event-handlers-manual.js +38 -11
  126. package/src/checks/manual/no-autoplay-audio-manual.js +20 -6
  127. package/src/checks/manual/object-text-alternative-quality-manual.js +175 -154
  128. package/src/checks/manual/p-as-heading-manual.js +22 -7
  129. package/src/checks/manual/page-has-heading-one-manual.js +31 -22
  130. package/src/checks/manual/page-title-patterns-manual.js +78 -50
  131. package/src/checks/manual/presentation-role-conflict-manual.js +59 -19
  132. package/src/checks/manual/region-manual.js +241 -48
  133. package/src/checks/manual/scope-attr-valid-manual.js +10 -3
  134. package/src/checks/manual/scrollable-region-focusable-manual.js +37 -11
  135. package/src/checks/manual/skip-link-manual.js +35 -12
  136. package/src/checks/manual/svg-text-alternative-quality-manual.js +206 -165
  137. package/src/checks/manual/tabindex-manual.js +10 -3
  138. package/src/checks/manual/table-duplicate-name-manual.js +17 -7
  139. package/src/checks/manual/table-fake-caption-manual.js +24 -7
  140. package/src/checks/manual/video-caption-manual.js +15 -4
  141. package/src/checks/manual-review.js +56 -12
  142. package/src/core/aria-helpers.js +1127 -886
  143. package/src/core/contrast-helpers.js +1217 -1062
  144. package/src/core/dom-helpers.js +4175 -3917
  145. package/src/core/dom-runner.js +720 -604
  146. package/src/core/frame-messaging.js +189 -138
  147. package/src/core/frame-scan.js +94 -82
  148. package/src/core/rollup-composites.js +94 -102
  149. package/src/core/rule-meta.js +58 -41
  150. package/src/core.js +36552 -29111
  151. package/src/i18n/en.js +1194 -889
  152. package/src/i18n/fr.js +1136 -795
  153. package/src/policy/contracts.js +13 -13
  154. package/src/policy/resolvePolicy.js +48 -44
  155. package/src/report.js +63 -43
  156. package/src/sarif.js +175 -0
  157. 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
+ };