@a11y-pulse/tab-orchestrator 0.1.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.
- package/LICENSE.md +166 -0
- package/README.md +93 -0
- package/dist/adaptor.d.ts +47 -0
- package/dist/adaptors/puppeteer.d.ts +14 -0
- package/dist/adaptors/puppeteer.js +52 -0
- package/dist/adaptors/puppeteer.js.map +1 -0
- package/dist/audit-scaffold.d.ts +50 -0
- package/dist/browser-scripts.d.ts +148 -0
- package/dist/clip.d.ts +7 -0
- package/dist/context-signals.d.ts +7 -0
- package/dist/focus-style.d.ts +5 -0
- package/dist/get-selector.d.ts +11 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +1312 -0
- package/dist/index.js.map +1 -0
- package/dist/orchestrator.d.ts +19 -0
- package/dist/truncate-html.d.ts +9 -0
- package/dist/types.d.ts +65 -0
- package/dist/unfocused-pair.d.ts +14 -0
- package/package.json +63 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1312 @@
|
|
|
1
|
+
// src/audit-scaffold.ts
|
|
2
|
+
var DEFAULT_ELEMENT_LIMIT = 1024;
|
|
3
|
+
var DEFAULT_SCREENSHOT_SETTLE_DELAY = 33;
|
|
4
|
+
var DEFAULT_FAILED_ELEMENT_LIMIT = 0;
|
|
5
|
+
var DEFAULT_TIMEOUT = 0;
|
|
6
|
+
function createAuditSelfDisconnect() {
|
|
7
|
+
let timer;
|
|
8
|
+
let disconnectedSelf = false;
|
|
9
|
+
const clear = () => {
|
|
10
|
+
if (timer !== void 0) {
|
|
11
|
+
clearTimeout(timer);
|
|
12
|
+
timer = void 0;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
const disconnect = (session) => {
|
|
16
|
+
disconnectedSelf = true;
|
|
17
|
+
clear();
|
|
18
|
+
session.disconnect();
|
|
19
|
+
};
|
|
20
|
+
return {
|
|
21
|
+
get disconnectedSelf() {
|
|
22
|
+
return disconnectedSelf;
|
|
23
|
+
},
|
|
24
|
+
armTimeout(timeout, session, onTimeout) {
|
|
25
|
+
if (timeout > 0) {
|
|
26
|
+
timer = setTimeout(() => {
|
|
27
|
+
timer = void 0;
|
|
28
|
+
onTimeout();
|
|
29
|
+
disconnect(session);
|
|
30
|
+
}, timeout);
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
disconnect,
|
|
34
|
+
clear
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// src/browser-scripts.ts
|
|
39
|
+
function baselineScript(props, markerAttr, limit) {
|
|
40
|
+
const truncateValue = (value) => {
|
|
41
|
+
const MAX_STYLE_VALUE_LENGTH = 512;
|
|
42
|
+
if (value.length <= MAX_STYLE_VALUE_LENGTH) {
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
let hash = 5381;
|
|
46
|
+
for (let i = 0; i < value.length; i++) {
|
|
47
|
+
hash = (hash * 33 ^ value.charCodeAt(i)) >>> 0;
|
|
48
|
+
}
|
|
49
|
+
return `${value.slice(0, MAX_STYLE_VALUE_LENGTH)}#len=${value.length}#h=${hash.toString(36)}`;
|
|
50
|
+
};
|
|
51
|
+
const snapshot = (el) => {
|
|
52
|
+
const read = (pseudo) => {
|
|
53
|
+
const cs = getComputedStyle(el, pseudo);
|
|
54
|
+
const out = {};
|
|
55
|
+
for (const prop of props) {
|
|
56
|
+
out[prop] = truncateValue(cs.getPropertyValue(prop));
|
|
57
|
+
}
|
|
58
|
+
if (pseudo) {
|
|
59
|
+
out.content = truncateValue(cs.getPropertyValue("content"));
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
};
|
|
63
|
+
return {
|
|
64
|
+
element: read(void 0),
|
|
65
|
+
before: read("::before"),
|
|
66
|
+
after: read("::after")
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
const candidates = [];
|
|
70
|
+
const focusableSelector = [
|
|
71
|
+
"a[href]",
|
|
72
|
+
"button",
|
|
73
|
+
"input",
|
|
74
|
+
"select",
|
|
75
|
+
"textarea",
|
|
76
|
+
"[tabindex]",
|
|
77
|
+
'[contenteditable=""]',
|
|
78
|
+
'[contenteditable="true"]'
|
|
79
|
+
].join(", ");
|
|
80
|
+
const collect = (root) => {
|
|
81
|
+
for (const el of Array.from(root.querySelectorAll("*"))) {
|
|
82
|
+
if (el.matches(focusableSelector)) {
|
|
83
|
+
candidates.push(el);
|
|
84
|
+
}
|
|
85
|
+
if (el.shadowRoot) {
|
|
86
|
+
collect(el.shadowRoot);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
collect(document);
|
|
91
|
+
const styles = [];
|
|
92
|
+
const styleIndexByKey = /* @__PURE__ */ new Map();
|
|
93
|
+
const entries = [];
|
|
94
|
+
let idx = 0;
|
|
95
|
+
for (const el of candidates) {
|
|
96
|
+
if (idx >= limit) {
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
if (el.hasAttribute("disabled")) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (el.tabIndex < 0) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const visibilityCheck = el;
|
|
106
|
+
if (typeof visibilityCheck.checkVisibility === "function") {
|
|
107
|
+
if (!visibilityCheck.checkVisibility({ checkVisibilityCSS: true })) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
const cs = getComputedStyle(el);
|
|
112
|
+
if (cs.display === "none" || cs.visibility === "hidden") {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
el.setAttribute(markerAttr, String(idx));
|
|
117
|
+
const rect = el.getBoundingClientRect();
|
|
118
|
+
const snap = snapshot(el);
|
|
119
|
+
const key = JSON.stringify(snap);
|
|
120
|
+
let styleIndex = styleIndexByKey.get(key);
|
|
121
|
+
if (styleIndex === void 0) {
|
|
122
|
+
styleIndex = styles.length;
|
|
123
|
+
styles.push(snap);
|
|
124
|
+
styleIndexByKey.set(key, styleIndex);
|
|
125
|
+
}
|
|
126
|
+
entries.push({
|
|
127
|
+
index: idx,
|
|
128
|
+
styleIndex,
|
|
129
|
+
rect: {
|
|
130
|
+
x: rect.left + window.scrollX,
|
|
131
|
+
y: rect.top + window.scrollY,
|
|
132
|
+
width: rect.width,
|
|
133
|
+
height: rect.height
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
idx++;
|
|
137
|
+
}
|
|
138
|
+
return { styles, entries };
|
|
139
|
+
}
|
|
140
|
+
function probeActiveElementScript(props, markerAttr) {
|
|
141
|
+
let el = document.activeElement;
|
|
142
|
+
while (el?.shadowRoot?.activeElement) {
|
|
143
|
+
el = el.shadowRoot.activeElement;
|
|
144
|
+
}
|
|
145
|
+
if (!el || el === document.body) {
|
|
146
|
+
return {
|
|
147
|
+
index: null,
|
|
148
|
+
isBody: true,
|
|
149
|
+
isIframe: false,
|
|
150
|
+
html: "",
|
|
151
|
+
styles: { element: {}, before: {}, after: {} },
|
|
152
|
+
rect: { x: 0, y: 0, width: 0, height: 0 }
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
const truncateValue = (value) => {
|
|
156
|
+
const MAX_STYLE_VALUE_LENGTH = 512;
|
|
157
|
+
if (value.length <= MAX_STYLE_VALUE_LENGTH) {
|
|
158
|
+
return value;
|
|
159
|
+
}
|
|
160
|
+
let hash = 5381;
|
|
161
|
+
for (let i = 0; i < value.length; i++) {
|
|
162
|
+
hash = (hash * 33 ^ value.charCodeAt(i)) >>> 0;
|
|
163
|
+
}
|
|
164
|
+
return `${value.slice(0, MAX_STYLE_VALUE_LENGTH)}#len=${value.length}#h=${hash.toString(36)}`;
|
|
165
|
+
};
|
|
166
|
+
const read = (pseudo) => {
|
|
167
|
+
const cs = getComputedStyle(el, pseudo);
|
|
168
|
+
const out = {};
|
|
169
|
+
for (const prop of props) {
|
|
170
|
+
out[prop] = truncateValue(cs.getPropertyValue(prop));
|
|
171
|
+
}
|
|
172
|
+
if (pseudo) {
|
|
173
|
+
out.content = truncateValue(cs.getPropertyValue("content"));
|
|
174
|
+
}
|
|
175
|
+
return out;
|
|
176
|
+
};
|
|
177
|
+
const idxAttr = el.getAttribute(markerAttr);
|
|
178
|
+
const rect = el.getBoundingClientRect();
|
|
179
|
+
const openingTag = el.cloneNode(false);
|
|
180
|
+
openingTag.removeAttribute(markerAttr);
|
|
181
|
+
return {
|
|
182
|
+
index: idxAttr === null ? null : Number(idxAttr),
|
|
183
|
+
isBody: false,
|
|
184
|
+
isIframe: el.tagName === "IFRAME",
|
|
185
|
+
html: openingTag.outerHTML,
|
|
186
|
+
styles: {
|
|
187
|
+
element: read(void 0),
|
|
188
|
+
before: read("::before"),
|
|
189
|
+
after: read("::after")
|
|
190
|
+
},
|
|
191
|
+
rect: {
|
|
192
|
+
x: rect.left + window.scrollX,
|
|
193
|
+
y: rect.top + window.scrollY,
|
|
194
|
+
width: rect.width,
|
|
195
|
+
height: rect.height
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function activeElementHandleScript() {
|
|
200
|
+
let el = document.activeElement;
|
|
201
|
+
while (el?.shadowRoot?.activeElement) {
|
|
202
|
+
el = el.shadowRoot.activeElement;
|
|
203
|
+
}
|
|
204
|
+
return el;
|
|
205
|
+
}
|
|
206
|
+
function pageDimensionsScript() {
|
|
207
|
+
return {
|
|
208
|
+
width: document.documentElement.scrollWidth,
|
|
209
|
+
height: document.documentElement.scrollHeight
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function elementRectScript(el) {
|
|
213
|
+
if (!el) {
|
|
214
|
+
return { x: 0, y: 0, width: 0, height: 0 };
|
|
215
|
+
}
|
|
216
|
+
const rect = el.getBoundingClientRect();
|
|
217
|
+
return {
|
|
218
|
+
x: rect.left + window.scrollX,
|
|
219
|
+
y: rect.top + window.scrollY,
|
|
220
|
+
width: rect.width,
|
|
221
|
+
height: rect.height
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function isCenterObscuredScript(el) {
|
|
225
|
+
if (!el) {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
const rect = el.getBoundingClientRect();
|
|
229
|
+
const x = Math.min(
|
|
230
|
+
Math.max(rect.left + rect.width / 2, 0),
|
|
231
|
+
window.innerWidth - 1
|
|
232
|
+
);
|
|
233
|
+
const y = Math.min(
|
|
234
|
+
Math.max(rect.top + rect.height / 2, 0),
|
|
235
|
+
window.innerHeight - 1
|
|
236
|
+
);
|
|
237
|
+
let hit = document.elementFromPoint(x, y);
|
|
238
|
+
while (hit?.shadowRoot) {
|
|
239
|
+
const inner = hit.shadowRoot.elementFromPoint(x, y);
|
|
240
|
+
if (!inner || inner === hit) {
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
hit = inner;
|
|
244
|
+
}
|
|
245
|
+
if (!hit) {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
return hit !== el && !el.contains(hit) && !hit.contains(el);
|
|
249
|
+
}
|
|
250
|
+
function scrollToCenterScript(el) {
|
|
251
|
+
el?.scrollIntoView({
|
|
252
|
+
block: "center",
|
|
253
|
+
inline: "nearest",
|
|
254
|
+
behavior: "instant"
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
function blurScript(el) {
|
|
258
|
+
el?.blur();
|
|
259
|
+
}
|
|
260
|
+
function focusScript(el) {
|
|
261
|
+
el?.focus({ preventScroll: true });
|
|
262
|
+
}
|
|
263
|
+
function elementStylesScript(el, props) {
|
|
264
|
+
if (!el) {
|
|
265
|
+
return { element: {}, before: {}, after: {} };
|
|
266
|
+
}
|
|
267
|
+
const truncateValue = (value) => {
|
|
268
|
+
const MAX_STYLE_VALUE_LENGTH = 512;
|
|
269
|
+
if (value.length <= MAX_STYLE_VALUE_LENGTH) {
|
|
270
|
+
return value;
|
|
271
|
+
}
|
|
272
|
+
let hash = 5381;
|
|
273
|
+
for (let i = 0; i < value.length; i++) {
|
|
274
|
+
hash = (hash * 33 ^ value.charCodeAt(i)) >>> 0;
|
|
275
|
+
}
|
|
276
|
+
return `${value.slice(0, MAX_STYLE_VALUE_LENGTH)}#len=${value.length}#h=${hash.toString(36)}`;
|
|
277
|
+
};
|
|
278
|
+
const read = (pseudo) => {
|
|
279
|
+
const cs = getComputedStyle(el, pseudo);
|
|
280
|
+
const out = {};
|
|
281
|
+
for (const prop of props) {
|
|
282
|
+
out[prop] = truncateValue(cs.getPropertyValue(prop));
|
|
283
|
+
}
|
|
284
|
+
if (pseudo) {
|
|
285
|
+
out.content = truncateValue(cs.getPropertyValue("content"));
|
|
286
|
+
}
|
|
287
|
+
return out;
|
|
288
|
+
};
|
|
289
|
+
return {
|
|
290
|
+
element: read(void 0),
|
|
291
|
+
before: read("::before"),
|
|
292
|
+
after: read("::after")
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
function clearMarkersScript(markerAttr) {
|
|
296
|
+
const clear = (root) => {
|
|
297
|
+
for (const el of Array.from(root.querySelectorAll(`[${markerAttr}]`))) {
|
|
298
|
+
el.removeAttribute(markerAttr);
|
|
299
|
+
}
|
|
300
|
+
for (const el of Array.from(root.querySelectorAll("*"))) {
|
|
301
|
+
if (el.shadowRoot) {
|
|
302
|
+
clear(el.shadowRoot);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
clear(document);
|
|
307
|
+
document.activeElement?.blur();
|
|
308
|
+
}
|
|
309
|
+
function measureObscuringScript(el, obscurerAttr) {
|
|
310
|
+
const empty = {
|
|
311
|
+
coveredFraction: 0,
|
|
312
|
+
fullyObscured: false,
|
|
313
|
+
offscreen: false,
|
|
314
|
+
opacity: "opaque",
|
|
315
|
+
obscuredByHtml: null,
|
|
316
|
+
hasObscurer: false
|
|
317
|
+
};
|
|
318
|
+
if (!el) {
|
|
319
|
+
return empty;
|
|
320
|
+
}
|
|
321
|
+
for (const marked of Array.from(
|
|
322
|
+
document.querySelectorAll(`[${obscurerAttr}]`)
|
|
323
|
+
)) {
|
|
324
|
+
marked.removeAttribute(obscurerAttr);
|
|
325
|
+
}
|
|
326
|
+
const rect = el.getBoundingClientRect();
|
|
327
|
+
const left = Math.max(rect.left, 0);
|
|
328
|
+
const top = Math.max(rect.top, 0);
|
|
329
|
+
const right = Math.min(rect.right, window.innerWidth);
|
|
330
|
+
const bottom = Math.min(rect.bottom, window.innerHeight);
|
|
331
|
+
if (right <= left || bottom <= top) {
|
|
332
|
+
return { ...empty, offscreen: true };
|
|
333
|
+
}
|
|
334
|
+
const width = right - left;
|
|
335
|
+
const height = bottom - top;
|
|
336
|
+
const LARGE_ELEMENT_THRESHOLD_PX = 80;
|
|
337
|
+
const points = [
|
|
338
|
+
{ x: left, y: top },
|
|
339
|
+
{ x: right - 0.5, y: top },
|
|
340
|
+
{ x: left, y: bottom - 0.5 },
|
|
341
|
+
{ x: right - 0.5, y: bottom - 0.5 },
|
|
342
|
+
{ x: left + width / 2, y: top },
|
|
343
|
+
{ x: left + width / 2, y: bottom - 0.5 },
|
|
344
|
+
{ x: left, y: top + height / 2 },
|
|
345
|
+
{ x: right - 0.5, y: top + height / 2 },
|
|
346
|
+
{ x: left + width / 2, y: top + height / 2 }
|
|
347
|
+
];
|
|
348
|
+
if (width > LARGE_ELEMENT_THRESHOLD_PX || height > LARGE_ELEMENT_THRESHOLD_PX) {
|
|
349
|
+
points.push(
|
|
350
|
+
{ x: left + width * 0.25, y: top + height * 0.25 },
|
|
351
|
+
{ x: left + width * 0.75, y: top + height * 0.25 },
|
|
352
|
+
{ x: left + width * 0.25, y: top + height * 0.75 },
|
|
353
|
+
{ x: left + width * 0.75, y: top + height * 0.75 }
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
const hitAt = (x, y) => {
|
|
357
|
+
const px = Math.min(Math.max(x, 0), window.innerWidth - 1);
|
|
358
|
+
const py = Math.min(Math.max(y, 0), window.innerHeight - 1);
|
|
359
|
+
const stack = typeof document.elementsFromPoint === "function" ? document.elementsFromPoint(px, py) : [document.elementFromPoint(px, py)].filter(Boolean);
|
|
360
|
+
for (let candidate of stack) {
|
|
361
|
+
while (candidate?.shadowRoot) {
|
|
362
|
+
const inner = candidate.shadowRoot.elementFromPoint(px, py);
|
|
363
|
+
if (!inner || inner === candidate) {
|
|
364
|
+
break;
|
|
365
|
+
}
|
|
366
|
+
candidate = inner;
|
|
367
|
+
}
|
|
368
|
+
if (!candidate) {
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
if (candidate === el || el.contains(candidate) || candidate.contains(el)) {
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
return candidate;
|
|
375
|
+
}
|
|
376
|
+
return null;
|
|
377
|
+
};
|
|
378
|
+
let covered = 0;
|
|
379
|
+
const coverCounts = /* @__PURE__ */ new Map();
|
|
380
|
+
for (const point of points) {
|
|
381
|
+
const cover = hitAt(point.x, point.y);
|
|
382
|
+
if (cover) {
|
|
383
|
+
covered++;
|
|
384
|
+
coverCounts.set(cover, (coverCounts.get(cover) ?? 0) + 1);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
const coveredFraction = points.length === 0 ? 0 : covered / points.length;
|
|
388
|
+
if (coveredFraction < 1) {
|
|
389
|
+
return {
|
|
390
|
+
...empty,
|
|
391
|
+
coveredFraction,
|
|
392
|
+
fullyObscured: false
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
let topCover = null;
|
|
396
|
+
let topCount = 0;
|
|
397
|
+
for (const [candidate, count] of coverCounts) {
|
|
398
|
+
if (count > topCount) {
|
|
399
|
+
topCover = candidate;
|
|
400
|
+
topCount = count;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
if (!topCover) {
|
|
404
|
+
return {
|
|
405
|
+
...empty,
|
|
406
|
+
coveredFraction: 1,
|
|
407
|
+
fullyObscured: false,
|
|
408
|
+
opacity: "unknown"
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
const coverRect = topCover.getBoundingClientRect();
|
|
412
|
+
const containsFully = coverRect.left <= rect.left && coverRect.top <= rect.top && coverRect.right >= rect.right && coverRect.bottom >= rect.bottom;
|
|
413
|
+
if (!containsFully) {
|
|
414
|
+
return {
|
|
415
|
+
coveredFraction: 1,
|
|
416
|
+
fullyObscured: true,
|
|
417
|
+
offscreen: false,
|
|
418
|
+
opacity: "unknown",
|
|
419
|
+
obscuredByHtml: null,
|
|
420
|
+
hasObscurer: false
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
const classifyOpacity = (node) => {
|
|
424
|
+
const cs = getComputedStyle(node);
|
|
425
|
+
const opacity2 = Number.parseFloat(cs.opacity);
|
|
426
|
+
if (!Number.isFinite(opacity2)) {
|
|
427
|
+
return "unknown";
|
|
428
|
+
}
|
|
429
|
+
if (opacity2 < 1) {
|
|
430
|
+
return "semi-transparent";
|
|
431
|
+
}
|
|
432
|
+
const bg = cs.backgroundColor;
|
|
433
|
+
const rgba = bg.match(
|
|
434
|
+
/rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)(?:\s*,\s*([\d.]+))?\s*\)/
|
|
435
|
+
);
|
|
436
|
+
if (rgba) {
|
|
437
|
+
const alpha = rgba[4] === void 0 ? 1 : Number.parseFloat(rgba[4]);
|
|
438
|
+
if (alpha < 1) {
|
|
439
|
+
return alpha <= 0 ? "semi-transparent" : "semi-transparent";
|
|
440
|
+
}
|
|
441
|
+
return "opaque";
|
|
442
|
+
}
|
|
443
|
+
if (cs.backgroundImage && cs.backgroundImage !== "none") {
|
|
444
|
+
return "unknown";
|
|
445
|
+
}
|
|
446
|
+
return "unknown";
|
|
447
|
+
};
|
|
448
|
+
const opacity = classifyOpacity(topCover);
|
|
449
|
+
const openingTag = topCover.cloneNode(false);
|
|
450
|
+
openingTag.removeAttribute(obscurerAttr);
|
|
451
|
+
topCover.setAttribute(obscurerAttr, "1");
|
|
452
|
+
return {
|
|
453
|
+
coveredFraction: 1,
|
|
454
|
+
fullyObscured: true,
|
|
455
|
+
offscreen: false,
|
|
456
|
+
opacity,
|
|
457
|
+
obscuredByHtml: openingTag.outerHTML,
|
|
458
|
+
hasObscurer: true
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
function obscurerHandleScript() {
|
|
462
|
+
return document.querySelector("[data-a11y-obscurer]");
|
|
463
|
+
}
|
|
464
|
+
function clearObscurerScript(obscurerAttr) {
|
|
465
|
+
for (const marked of Array.from(
|
|
466
|
+
document.querySelectorAll(`[${obscurerAttr}]`)
|
|
467
|
+
)) {
|
|
468
|
+
marked.removeAttribute(obscurerAttr);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
function installContextObserverScript() {
|
|
472
|
+
if (window.__a11yContextObserver?.installed) {
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
const state = {
|
|
476
|
+
baselineHref: location.href,
|
|
477
|
+
openedWindow: false,
|
|
478
|
+
submittedForm: false,
|
|
479
|
+
focusIns: [],
|
|
480
|
+
softUrlChange: false,
|
|
481
|
+
installed: true
|
|
482
|
+
};
|
|
483
|
+
window.__a11yContextObserver = state;
|
|
484
|
+
const originalOpen = window.open.bind(window);
|
|
485
|
+
window.open = (..._args) => {
|
|
486
|
+
state.openedWindow = true;
|
|
487
|
+
return {
|
|
488
|
+
closed: true,
|
|
489
|
+
close() {
|
|
490
|
+
},
|
|
491
|
+
focus() {
|
|
492
|
+
},
|
|
493
|
+
blur() {
|
|
494
|
+
},
|
|
495
|
+
opener: null,
|
|
496
|
+
location: { href: "" }
|
|
497
|
+
};
|
|
498
|
+
};
|
|
499
|
+
void originalOpen;
|
|
500
|
+
document.addEventListener(
|
|
501
|
+
"submit",
|
|
502
|
+
(event) => {
|
|
503
|
+
state.submittedForm = true;
|
|
504
|
+
event.preventDefault();
|
|
505
|
+
event.stopPropagation();
|
|
506
|
+
},
|
|
507
|
+
true
|
|
508
|
+
);
|
|
509
|
+
document.addEventListener(
|
|
510
|
+
"focus",
|
|
511
|
+
(event) => {
|
|
512
|
+
const target = event.target;
|
|
513
|
+
if (target && target.nodeType === 1) {
|
|
514
|
+
state.focusIns.push(target);
|
|
515
|
+
}
|
|
516
|
+
},
|
|
517
|
+
true
|
|
518
|
+
);
|
|
519
|
+
const noteSoftNav = () => {
|
|
520
|
+
if (location.href !== state.baselineHref) {
|
|
521
|
+
state.softUrlChange = true;
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
window.addEventListener("hashchange", noteSoftNav);
|
|
525
|
+
window.addEventListener("popstate", noteSoftNav);
|
|
526
|
+
const wrapHistory = (method) => {
|
|
527
|
+
const original = history[method].bind(history);
|
|
528
|
+
history[method] = ((...args) => {
|
|
529
|
+
const result = original(...args);
|
|
530
|
+
noteSoftNav();
|
|
531
|
+
return result;
|
|
532
|
+
});
|
|
533
|
+
};
|
|
534
|
+
wrapHistory("pushState");
|
|
535
|
+
wrapHistory("replaceState");
|
|
536
|
+
}
|
|
537
|
+
function drainContextObserverScript(markerAttr) {
|
|
538
|
+
const attributedAttr = "data-a11y-ctx-attr";
|
|
539
|
+
const empty = {
|
|
540
|
+
openedWindow: false,
|
|
541
|
+
submittedForm: false,
|
|
542
|
+
focusRemoved: false,
|
|
543
|
+
redirect: null,
|
|
544
|
+
softUrlChange: false,
|
|
545
|
+
navigation: false,
|
|
546
|
+
attributedHtml: null,
|
|
547
|
+
hasAttributed: false
|
|
548
|
+
};
|
|
549
|
+
const state = window.__a11yContextObserver;
|
|
550
|
+
if (!state) {
|
|
551
|
+
return empty;
|
|
552
|
+
}
|
|
553
|
+
for (const marked of Array.from(
|
|
554
|
+
document.querySelectorAll(`[${attributedAttr}]`)
|
|
555
|
+
)) {
|
|
556
|
+
marked.removeAttribute(attributedAttr);
|
|
557
|
+
}
|
|
558
|
+
const openedWindow = state.openedWindow;
|
|
559
|
+
const submittedForm = state.submittedForm;
|
|
560
|
+
const softUrlChange = state.softUrlChange;
|
|
561
|
+
const focusIns = state.focusIns.slice();
|
|
562
|
+
state.openedWindow = false;
|
|
563
|
+
state.submittedForm = false;
|
|
564
|
+
state.softUrlChange = false;
|
|
565
|
+
state.focusIns = [];
|
|
566
|
+
let active = document.activeElement;
|
|
567
|
+
while (active?.shadowRoot?.activeElement) {
|
|
568
|
+
active = active.shadowRoot.activeElement;
|
|
569
|
+
}
|
|
570
|
+
const intended = focusIns.find(
|
|
571
|
+
(el) => el !== document.body && el !== document.documentElement
|
|
572
|
+
) ?? null;
|
|
573
|
+
const openingHtml = (node) => {
|
|
574
|
+
const clone = node.cloneNode(false);
|
|
575
|
+
clone.removeAttribute(markerAttr);
|
|
576
|
+
clone.removeAttribute(attributedAttr);
|
|
577
|
+
return clone.outerHTML;
|
|
578
|
+
};
|
|
579
|
+
let focusRemoved = false;
|
|
580
|
+
let redirect = null;
|
|
581
|
+
let attributedHtml = null;
|
|
582
|
+
let hasAttributed = false;
|
|
583
|
+
const bodyOrNone = !active || active === document.body || active === document.documentElement;
|
|
584
|
+
if (intended && bodyOrNone) {
|
|
585
|
+
focusRemoved = true;
|
|
586
|
+
attributedHtml = openingHtml(intended);
|
|
587
|
+
intended.setAttribute(attributedAttr, "1");
|
|
588
|
+
hasAttributed = true;
|
|
589
|
+
} else if (intended && active && intended !== active) {
|
|
590
|
+
const composedContains = (ancestor, node) => {
|
|
591
|
+
let current = node;
|
|
592
|
+
while (current) {
|
|
593
|
+
if (current === ancestor) {
|
|
594
|
+
return true;
|
|
595
|
+
}
|
|
596
|
+
const parent = current.parentNode;
|
|
597
|
+
if (parent) {
|
|
598
|
+
current = parent;
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
if (current instanceof ShadowRoot) {
|
|
602
|
+
current = current.host;
|
|
603
|
+
continue;
|
|
604
|
+
}
|
|
605
|
+
break;
|
|
606
|
+
}
|
|
607
|
+
return false;
|
|
608
|
+
};
|
|
609
|
+
const same = composedContains(intended, active) || composedContains(active, intended);
|
|
610
|
+
redirect = same ? "same-subtree" : "outside";
|
|
611
|
+
attributedHtml = openingHtml(intended);
|
|
612
|
+
intended.setAttribute(attributedAttr, "1");
|
|
613
|
+
hasAttributed = true;
|
|
614
|
+
}
|
|
615
|
+
return {
|
|
616
|
+
openedWindow,
|
|
617
|
+
submittedForm,
|
|
618
|
+
focusRemoved,
|
|
619
|
+
redirect,
|
|
620
|
+
softUrlChange,
|
|
621
|
+
navigation: false,
|
|
622
|
+
attributedHtml,
|
|
623
|
+
hasAttributed
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
function attributedHandleScript() {
|
|
627
|
+
return document.querySelector("[data-a11y-ctx-attr]");
|
|
628
|
+
}
|
|
629
|
+
function clearAttributedScript() {
|
|
630
|
+
for (const marked of Array.from(
|
|
631
|
+
document.querySelectorAll("[data-a11y-ctx-attr]")
|
|
632
|
+
)) {
|
|
633
|
+
marked.removeAttribute("data-a11y-ctx-attr");
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
function clearContextFocusInsScript() {
|
|
637
|
+
const state = window.__a11yContextObserver;
|
|
638
|
+
if (state) {
|
|
639
|
+
state.focusIns = [];
|
|
640
|
+
state.openedWindow = false;
|
|
641
|
+
state.submittedForm = false;
|
|
642
|
+
state.softUrlChange = false;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
function locationHrefScript() {
|
|
646
|
+
return location.href;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// src/clip.ts
|
|
650
|
+
function bufferedClip(rect, pageWidth, pageHeight, buffer) {
|
|
651
|
+
const left = Math.max(0, Math.min(buffer, rect.x));
|
|
652
|
+
const top = Math.max(0, Math.min(buffer, rect.y));
|
|
653
|
+
const right = Math.max(0, Math.min(buffer, pageWidth - rect.x - rect.width));
|
|
654
|
+
const bottom = Math.max(
|
|
655
|
+
0,
|
|
656
|
+
Math.min(buffer, pageHeight - rect.y - rect.height)
|
|
657
|
+
);
|
|
658
|
+
return {
|
|
659
|
+
x: Math.max(0, rect.x - left),
|
|
660
|
+
y: Math.max(0, rect.y - top),
|
|
661
|
+
width: rect.width + left + right,
|
|
662
|
+
height: rect.height + top + bottom
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// src/focus-style.ts
|
|
667
|
+
var FOCUS_STYLE_PROPERTIES = [
|
|
668
|
+
"background-color",
|
|
669
|
+
"background-image",
|
|
670
|
+
"border-bottom-color",
|
|
671
|
+
"border-bottom-style",
|
|
672
|
+
"border-bottom-width",
|
|
673
|
+
"border-left-color",
|
|
674
|
+
"border-left-style",
|
|
675
|
+
"border-left-width",
|
|
676
|
+
"border-right-color",
|
|
677
|
+
"border-right-style",
|
|
678
|
+
"border-right-width",
|
|
679
|
+
"border-top-color",
|
|
680
|
+
"border-top-style",
|
|
681
|
+
"border-top-width",
|
|
682
|
+
"box-shadow",
|
|
683
|
+
"color",
|
|
684
|
+
"font-weight",
|
|
685
|
+
"outline-color",
|
|
686
|
+
"outline-style",
|
|
687
|
+
"outline-width",
|
|
688
|
+
"text-decoration"
|
|
689
|
+
];
|
|
690
|
+
|
|
691
|
+
// src/get-selector.ts
|
|
692
|
+
function getSelector(root) {
|
|
693
|
+
const MAX_LENGTH = 128;
|
|
694
|
+
const esc = (value) => typeof CSS !== "undefined" && typeof CSS.escape === "function" ? CSS.escape(value) : value;
|
|
695
|
+
const tagName = (node2) => node2.nodeType === 1 ? node2.nodeName.toLowerCase() : node2.nodeName.toUpperCase().replace(/^#/, "");
|
|
696
|
+
const rootOf = (element) => {
|
|
697
|
+
const node2 = element.getRootNode?.();
|
|
698
|
+
return node2 && typeof node2.querySelectorAll === "function" ? node2 : null;
|
|
699
|
+
};
|
|
700
|
+
const matchCount = (queryRoot2, selector) => {
|
|
701
|
+
try {
|
|
702
|
+
return queryRoot2.querySelectorAll(selector).length;
|
|
703
|
+
} catch {
|
|
704
|
+
return Number.POSITIVE_INFINITY;
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
const safeMatches = (element, selector) => {
|
|
708
|
+
try {
|
|
709
|
+
return typeof element.matches === "function" && element.matches(selector);
|
|
710
|
+
} catch {
|
|
711
|
+
return false;
|
|
712
|
+
}
|
|
713
|
+
};
|
|
714
|
+
const idIsUnique = (element, id) => {
|
|
715
|
+
const queryRoot2 = rootOf(element);
|
|
716
|
+
return queryRoot2 === null || matchCount(queryRoot2, `#${esc(id)}`) === 1;
|
|
717
|
+
};
|
|
718
|
+
const classFeature = (cls) => ({
|
|
719
|
+
feature: `.${esc(cls)}`,
|
|
720
|
+
sharedBy: (sibling) => Array.from(sibling.classList ?? []).includes(cls)
|
|
721
|
+
});
|
|
722
|
+
const rarestFeature = (element, queryRoot2) => {
|
|
723
|
+
const classes = Array.from(element.classList).slice(0, 8);
|
|
724
|
+
if (!queryRoot2) {
|
|
725
|
+
const cls = classes[0];
|
|
726
|
+
return cls === void 0 ? null : classFeature(cls);
|
|
727
|
+
}
|
|
728
|
+
let best = null;
|
|
729
|
+
let bestCount = Number.POSITIVE_INFINITY;
|
|
730
|
+
for (const cls of classes) {
|
|
731
|
+
const candidate = classFeature(cls);
|
|
732
|
+
if (!safeMatches(element, candidate.feature)) {
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
const count = matchCount(queryRoot2, candidate.feature);
|
|
736
|
+
if (count < bestCount) {
|
|
737
|
+
best = candidate;
|
|
738
|
+
bestCount = count;
|
|
739
|
+
}
|
|
740
|
+
if (count === 1) {
|
|
741
|
+
return best;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
const href = typeof element.getAttribute === "function" ? element.getAttribute("href") : null;
|
|
745
|
+
if (href && href.length <= 100 && !/["\\?]/.test(href)) {
|
|
746
|
+
const feature = `[href="${href}"]`;
|
|
747
|
+
if (safeMatches(element, feature) && matchCount(queryRoot2, feature) < bestCount) {
|
|
748
|
+
return {
|
|
749
|
+
feature,
|
|
750
|
+
sharedBy: (sibling) => typeof sibling.getAttribute === "function" && sibling.getAttribute("href") === href
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
return best;
|
|
755
|
+
};
|
|
756
|
+
const positionAmongSiblings = (element, sharedBy) => {
|
|
757
|
+
const siblings = element.parentNode?.children;
|
|
758
|
+
if (!siblings) {
|
|
759
|
+
return { nth: 0, ambiguous: false };
|
|
760
|
+
}
|
|
761
|
+
let nth = 0;
|
|
762
|
+
let count = 0;
|
|
763
|
+
let shared = 0;
|
|
764
|
+
for (const sibling of Array.from(siblings)) {
|
|
765
|
+
if (sibling.nodeName !== element.nodeName) {
|
|
766
|
+
continue;
|
|
767
|
+
}
|
|
768
|
+
count++;
|
|
769
|
+
if (sibling === element) {
|
|
770
|
+
nth = count;
|
|
771
|
+
}
|
|
772
|
+
if (sharedBy(sibling)) {
|
|
773
|
+
shared++;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
return { nth, ambiguous: shared > 1 };
|
|
777
|
+
};
|
|
778
|
+
const segmentFor = (element, queryRoot2) => {
|
|
779
|
+
if (element.id && idIsUnique(element, element.id)) {
|
|
780
|
+
return { segment: `#${esc(element.id)}`, anchored: true };
|
|
781
|
+
}
|
|
782
|
+
const feature = rarestFeature(element, queryRoot2);
|
|
783
|
+
let segment = tagName(element) + (feature === null ? "" : feature.feature);
|
|
784
|
+
const { nth, ambiguous } = positionAmongSiblings(
|
|
785
|
+
element,
|
|
786
|
+
feature === null ? () => true : feature.sharedBy
|
|
787
|
+
);
|
|
788
|
+
if (ambiguous && nth > 0) {
|
|
789
|
+
segment += `:nth-of-type(${nth})`;
|
|
790
|
+
}
|
|
791
|
+
return { segment, anchored: false };
|
|
792
|
+
};
|
|
793
|
+
const degradedSegmentFor = (element) => {
|
|
794
|
+
let segment = tagName(element);
|
|
795
|
+
const { nth, ambiguous } = positionAmongSiblings(element, () => true);
|
|
796
|
+
if (ambiguous && nth > 0) {
|
|
797
|
+
segment += `:nth-of-type(${nth})`;
|
|
798
|
+
}
|
|
799
|
+
return segment;
|
|
800
|
+
};
|
|
801
|
+
const segments = [];
|
|
802
|
+
let node = root;
|
|
803
|
+
const queryRoot = root && root.nodeType === 1 ? rootOf(root) : null;
|
|
804
|
+
try {
|
|
805
|
+
while (node && node.nodeType === 1) {
|
|
806
|
+
const element = node;
|
|
807
|
+
let { segment, anchored } = segmentFor(element, queryRoot);
|
|
808
|
+
const joinedWith = (candidate) => segments.length ? candidate.length + 1 + segments.join(">").length : candidate.length;
|
|
809
|
+
if (joinedWith(segment) > MAX_LENGTH - 1) {
|
|
810
|
+
segment = degradedSegmentFor(element);
|
|
811
|
+
anchored = false;
|
|
812
|
+
if (joinedWith(segment) > MAX_LENGTH - 1) {
|
|
813
|
+
break;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
segments.unshift(segment);
|
|
817
|
+
if (anchored) {
|
|
818
|
+
break;
|
|
819
|
+
}
|
|
820
|
+
if (queryRoot && matchCount(queryRoot, segments.join(">")) === 1) {
|
|
821
|
+
break;
|
|
822
|
+
}
|
|
823
|
+
node = element.parentNode;
|
|
824
|
+
}
|
|
825
|
+
} catch {
|
|
826
|
+
}
|
|
827
|
+
return segments.join(">");
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// src/truncate-html.ts
|
|
831
|
+
var DEFAULT_MAX_ATTRIBUTE_VALUE_LENGTH = 50;
|
|
832
|
+
var DEFAULT_MAX_LENGTH = 300;
|
|
833
|
+
var ELLIPSIS = "...";
|
|
834
|
+
function truncateHtml(html, options = {}) {
|
|
835
|
+
if (!html) {
|
|
836
|
+
return "";
|
|
837
|
+
}
|
|
838
|
+
const maxAttributeValueLength = options.maxAttributeValueLength ?? DEFAULT_MAX_ATTRIBUTE_VALUE_LENGTH;
|
|
839
|
+
const maxLength = options.maxLength ?? DEFAULT_MAX_LENGTH;
|
|
840
|
+
const closingBracket = html.indexOf(">");
|
|
841
|
+
const openingTag = closingBracket === -1 ? html : html.slice(0, closingBracket + 1);
|
|
842
|
+
const trimmed = openingTag.replace(
|
|
843
|
+
/=(["'])([\s\S]*?)\1/g,
|
|
844
|
+
(match, quote, value) => {
|
|
845
|
+
if (value.length <= maxAttributeValueLength) {
|
|
846
|
+
return match;
|
|
847
|
+
}
|
|
848
|
+
return `=${quote}${value.slice(0, maxAttributeValueLength)}${ELLIPSIS}${quote}`;
|
|
849
|
+
}
|
|
850
|
+
);
|
|
851
|
+
if (trimmed.length > maxLength) {
|
|
852
|
+
return `${trimmed.slice(0, maxLength)}${ELLIPSIS}`;
|
|
853
|
+
}
|
|
854
|
+
return trimmed;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// src/unfocused-pair.ts
|
|
858
|
+
async function captureUnfocusedPair(adaptor, options, handle) {
|
|
859
|
+
if (await adaptor.evaluate(isCenterObscuredScript, handle)) {
|
|
860
|
+
await adaptor.evaluate(scrollToCenterScript, handle);
|
|
861
|
+
await new Promise(
|
|
862
|
+
(resolve) => setTimeout(resolve, options.screenshotSettleDelay)
|
|
863
|
+
);
|
|
864
|
+
}
|
|
865
|
+
const { width: pageWidth, height: pageHeight } = await adaptor.evaluate(pageDimensionsScript);
|
|
866
|
+
const focusedRect = await adaptor.evaluate(elementRectScript, handle);
|
|
867
|
+
const focusedClip = bufferedClip(
|
|
868
|
+
focusedRect,
|
|
869
|
+
pageWidth,
|
|
870
|
+
pageHeight,
|
|
871
|
+
options.screenshotClipBuffer
|
|
872
|
+
);
|
|
873
|
+
const scale = adaptor.screenshotClipScale ?? 1;
|
|
874
|
+
const focusedScreenshot = await adaptor.screenshotClip(focusedClip, scale);
|
|
875
|
+
await adaptor.evaluate(blurScript, handle);
|
|
876
|
+
const unfocusedRect = await adaptor.evaluate(elementRectScript, handle);
|
|
877
|
+
const unfocusedClip = bufferedClip(
|
|
878
|
+
unfocusedRect,
|
|
879
|
+
pageWidth,
|
|
880
|
+
pageHeight,
|
|
881
|
+
options.screenshotClipBuffer
|
|
882
|
+
);
|
|
883
|
+
const unfocusedScreenshot = await adaptor.screenshotClip(
|
|
884
|
+
unfocusedClip,
|
|
885
|
+
scale
|
|
886
|
+
);
|
|
887
|
+
const unfocusedStyles = await adaptor.evaluate(elementStylesScript, handle, [
|
|
888
|
+
...options.styleProps
|
|
889
|
+
]);
|
|
890
|
+
await adaptor.evaluate(focusScript, handle);
|
|
891
|
+
return {
|
|
892
|
+
focusedScreenshot,
|
|
893
|
+
unfocusedScreenshot,
|
|
894
|
+
unfocusedStyles,
|
|
895
|
+
focusedRect,
|
|
896
|
+
unfocusedRect,
|
|
897
|
+
focusedClip,
|
|
898
|
+
unfocusedClip,
|
|
899
|
+
scale
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// src/orchestrator.ts
|
|
904
|
+
function isContextDestroyedError(error) {
|
|
905
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
906
|
+
return /Execution context was destroyed|Target closed|frame was detached|navigat/i.test(
|
|
907
|
+
message
|
|
908
|
+
);
|
|
909
|
+
}
|
|
910
|
+
var DEFAULT_SCREENSHOT_SETTLE_DELAY2 = 33;
|
|
911
|
+
var DEFAULT_SCREENSHOT_CLIP_BUFFER = 10;
|
|
912
|
+
var DEFAULT_MARKER_LIMIT = 1024;
|
|
913
|
+
var MARKER_ATTR = "data-a11y-focus-idx";
|
|
914
|
+
var OBSCURER_ATTR = "data-a11y-obscurer";
|
|
915
|
+
var OBSCURING_RECHECK_DELAY_MS = 250;
|
|
916
|
+
function createTabOrchestrator(adaptor, options = {}) {
|
|
917
|
+
const consumers = [];
|
|
918
|
+
let started = false;
|
|
919
|
+
let ran = false;
|
|
920
|
+
return {
|
|
921
|
+
attach(consumer) {
|
|
922
|
+
if (started) {
|
|
923
|
+
throw new Error("Cannot attach after run() has started");
|
|
924
|
+
}
|
|
925
|
+
consumers.push(consumer);
|
|
926
|
+
},
|
|
927
|
+
async run() {
|
|
928
|
+
if (ran) {
|
|
929
|
+
throw new Error("run() has already been called");
|
|
930
|
+
}
|
|
931
|
+
ran = true;
|
|
932
|
+
started = true;
|
|
933
|
+
if (consumers.length === 0) {
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
const attached = new Set(consumers);
|
|
937
|
+
const screenshotSettleDelay = options.screenshotSettleDelay ?? DEFAULT_SCREENSHOT_SETTLE_DELAY2;
|
|
938
|
+
const screenshotClipBuffer = options.screenshotClipBuffer ?? DEFAULT_SCREENSHOT_CLIP_BUFFER;
|
|
939
|
+
const markerLimit = Math.max(
|
|
940
|
+
options.markerLimit ?? DEFAULT_MARKER_LIMIT,
|
|
941
|
+
options.baselineElementLimit ?? 0
|
|
942
|
+
);
|
|
943
|
+
const styleProps = [...FOCUS_STYLE_PROPERTIES];
|
|
944
|
+
const resolved = {
|
|
945
|
+
screenshotSettleDelay,
|
|
946
|
+
screenshotClipBuffer,
|
|
947
|
+
styleProps
|
|
948
|
+
};
|
|
949
|
+
let settleTimer;
|
|
950
|
+
let resolveSettle;
|
|
951
|
+
const pairByStop = { current: null };
|
|
952
|
+
let activeHandle;
|
|
953
|
+
let lastHasAttributed = false;
|
|
954
|
+
let baselineHref = null;
|
|
955
|
+
const remainingHas = (capability) => [...attached].some((c) => c.capabilities.has(capability));
|
|
956
|
+
const contextSignalsWanted = remainingHas("contextSignals");
|
|
957
|
+
const handleFor = (consumer) => ({
|
|
958
|
+
disconnect() {
|
|
959
|
+
attached.delete(consumer);
|
|
960
|
+
if (attached.size === 0 && settleTimer !== void 0) {
|
|
961
|
+
clearTimeout(settleTimer);
|
|
962
|
+
settleTimer = void 0;
|
|
963
|
+
resolveSettle?.();
|
|
964
|
+
resolveSettle = void 0;
|
|
965
|
+
}
|
|
966
|
+
},
|
|
967
|
+
ensureUnfocusedPair: () => {
|
|
968
|
+
if (!consumer.capabilities.has("unfocusedPair")) {
|
|
969
|
+
throw new Error("Consumer did not declare unfocusedPair");
|
|
970
|
+
}
|
|
971
|
+
if (!remainingHas("unfocusedPair")) {
|
|
972
|
+
throw new Error("Consumer did not declare unfocusedPair");
|
|
973
|
+
}
|
|
974
|
+
if (activeHandle === void 0) {
|
|
975
|
+
throw new Error("unfocusedPair capture not implemented");
|
|
976
|
+
}
|
|
977
|
+
pairByStop.current ??= captureUnfocusedPair(
|
|
978
|
+
adaptor,
|
|
979
|
+
resolved,
|
|
980
|
+
activeHandle
|
|
981
|
+
);
|
|
982
|
+
return pairByStop.current;
|
|
983
|
+
}
|
|
984
|
+
});
|
|
985
|
+
const end = (reason) => {
|
|
986
|
+
for (const consumer of attached) {
|
|
987
|
+
consumer.onSessionEnd?.(reason);
|
|
988
|
+
}
|
|
989
|
+
attached.clear();
|
|
990
|
+
};
|
|
991
|
+
async function checkContextNavigation() {
|
|
992
|
+
let href;
|
|
993
|
+
try {
|
|
994
|
+
href = await adaptor.evaluate(locationHrefScript);
|
|
995
|
+
} catch (error) {
|
|
996
|
+
if (!isContextDestroyedError(error)) {
|
|
997
|
+
throw error;
|
|
998
|
+
}
|
|
999
|
+
return { navigation: true };
|
|
1000
|
+
}
|
|
1001
|
+
if (baselineHref === null || href === baselineHref) {
|
|
1002
|
+
return { navigation: false, raw: null };
|
|
1003
|
+
}
|
|
1004
|
+
let raw = null;
|
|
1005
|
+
try {
|
|
1006
|
+
raw = await adaptor.evaluate(drainContextObserverScript, MARKER_ATTR);
|
|
1007
|
+
} catch {
|
|
1008
|
+
raw = null;
|
|
1009
|
+
}
|
|
1010
|
+
if (raw === null || !raw.softUrlChange) {
|
|
1011
|
+
return { navigation: true };
|
|
1012
|
+
}
|
|
1013
|
+
baselineHref = href;
|
|
1014
|
+
return { navigation: false, raw };
|
|
1015
|
+
}
|
|
1016
|
+
let failure;
|
|
1017
|
+
try {
|
|
1018
|
+
await adaptor.ensureFocusReporting();
|
|
1019
|
+
const payload = await adaptor.evaluate(
|
|
1020
|
+
baselineScript,
|
|
1021
|
+
styleProps,
|
|
1022
|
+
MARKER_ATTR,
|
|
1023
|
+
markerLimit
|
|
1024
|
+
);
|
|
1025
|
+
const interned = /* @__PURE__ */ new Map();
|
|
1026
|
+
for (const entry of payload.entries) {
|
|
1027
|
+
const styles = payload.styles[entry.styleIndex];
|
|
1028
|
+
if (styles !== void 0) {
|
|
1029
|
+
interned.set(entry.index, styles);
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
if (contextSignalsWanted) {
|
|
1033
|
+
baselineHref = await adaptor.evaluate(locationHrefScript);
|
|
1034
|
+
await adaptor.evaluate(installContextObserverScript);
|
|
1035
|
+
}
|
|
1036
|
+
for (const consumer of [...attached]) {
|
|
1037
|
+
await consumer.onSessionStart?.(handleFor(consumer));
|
|
1038
|
+
}
|
|
1039
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1040
|
+
let tabIndex = 0;
|
|
1041
|
+
while (attached.size > 0) {
|
|
1042
|
+
pairByStop.current = null;
|
|
1043
|
+
lastHasAttributed = false;
|
|
1044
|
+
const hasFocus = await adaptor.evaluate(() => document.hasFocus());
|
|
1045
|
+
if (!hasFocus) {
|
|
1046
|
+
end("lostFocus");
|
|
1047
|
+
break;
|
|
1048
|
+
}
|
|
1049
|
+
await adaptor.pressTab();
|
|
1050
|
+
await new Promise((resolve) => {
|
|
1051
|
+
resolveSettle = resolve;
|
|
1052
|
+
settleTimer = setTimeout(() => {
|
|
1053
|
+
settleTimer = void 0;
|
|
1054
|
+
resolveSettle = void 0;
|
|
1055
|
+
resolve();
|
|
1056
|
+
}, screenshotSettleDelay);
|
|
1057
|
+
});
|
|
1058
|
+
if (attached.size === 0) {
|
|
1059
|
+
break;
|
|
1060
|
+
}
|
|
1061
|
+
let base;
|
|
1062
|
+
try {
|
|
1063
|
+
base = await adaptor.evaluate(
|
|
1064
|
+
probeActiveElementScript,
|
|
1065
|
+
styleProps,
|
|
1066
|
+
MARKER_ATTR
|
|
1067
|
+
);
|
|
1068
|
+
} catch (error) {
|
|
1069
|
+
if (remainingHas("contextSignals") && isContextDestroyedError(error)) {
|
|
1070
|
+
end("navigation");
|
|
1071
|
+
break;
|
|
1072
|
+
}
|
|
1073
|
+
throw error;
|
|
1074
|
+
}
|
|
1075
|
+
let contextNav = null;
|
|
1076
|
+
if (remainingHas("contextSignals")) {
|
|
1077
|
+
const outcome = await checkContextNavigation();
|
|
1078
|
+
if (outcome.navigation) {
|
|
1079
|
+
end("navigation");
|
|
1080
|
+
break;
|
|
1081
|
+
}
|
|
1082
|
+
contextNav = outcome;
|
|
1083
|
+
}
|
|
1084
|
+
if (base === null || base.isBody) {
|
|
1085
|
+
if (remainingHas("contextSignals")) {
|
|
1086
|
+
let raw;
|
|
1087
|
+
try {
|
|
1088
|
+
raw = contextNav?.raw ?? await adaptor.evaluate(
|
|
1089
|
+
drainContextObserverScript,
|
|
1090
|
+
MARKER_ATTR
|
|
1091
|
+
);
|
|
1092
|
+
} catch (error) {
|
|
1093
|
+
if (!isContextDestroyedError(error)) {
|
|
1094
|
+
throw error;
|
|
1095
|
+
}
|
|
1096
|
+
end("navigation");
|
|
1097
|
+
break;
|
|
1098
|
+
}
|
|
1099
|
+
if (raw.hasAttributed) {
|
|
1100
|
+
const attributedRef = await adaptor.evaluateHandle(
|
|
1101
|
+
attributedHandleScript
|
|
1102
|
+
);
|
|
1103
|
+
let selector;
|
|
1104
|
+
try {
|
|
1105
|
+
selector = await adaptor.evaluate(getSelector, attributedRef);
|
|
1106
|
+
} finally {
|
|
1107
|
+
await adaptor.disposeRef(attributedRef);
|
|
1108
|
+
}
|
|
1109
|
+
const activeElement = {
|
|
1110
|
+
index: null,
|
|
1111
|
+
isBody: false,
|
|
1112
|
+
isIframe: false,
|
|
1113
|
+
selector,
|
|
1114
|
+
html: truncateHtml(raw.attributedHtml ?? ""),
|
|
1115
|
+
styles: { element: {}, before: {}, after: {} },
|
|
1116
|
+
rect: { x: 0, y: 0, width: 0, height: 0 }
|
|
1117
|
+
};
|
|
1118
|
+
const snapshot = {
|
|
1119
|
+
tabIndex: ++tabIndex,
|
|
1120
|
+
activeElement,
|
|
1121
|
+
contextSignals: {
|
|
1122
|
+
signals: {
|
|
1123
|
+
openedWindow: raw.openedWindow,
|
|
1124
|
+
submittedForm: raw.submittedForm,
|
|
1125
|
+
focusRemoved: raw.focusRemoved,
|
|
1126
|
+
redirect: raw.redirect,
|
|
1127
|
+
softUrlChange: raw.softUrlChange,
|
|
1128
|
+
navigation: raw.navigation
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
};
|
|
1132
|
+
const recipients = [...attached].filter(
|
|
1133
|
+
(consumer) => consumer.capabilities.has("contextSignals")
|
|
1134
|
+
);
|
|
1135
|
+
try {
|
|
1136
|
+
for (const consumer of recipients) {
|
|
1137
|
+
await consumer.onTabStop(snapshot, handleFor(consumer));
|
|
1138
|
+
}
|
|
1139
|
+
} finally {
|
|
1140
|
+
await adaptor.evaluate(clearAttributedScript);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
end("completed");
|
|
1145
|
+
break;
|
|
1146
|
+
}
|
|
1147
|
+
if (base.isIframe) {
|
|
1148
|
+
continue;
|
|
1149
|
+
}
|
|
1150
|
+
if (base.index !== null) {
|
|
1151
|
+
if (visited.has(base.index)) {
|
|
1152
|
+
end("completed");
|
|
1153
|
+
break;
|
|
1154
|
+
}
|
|
1155
|
+
visited.add(base.index);
|
|
1156
|
+
}
|
|
1157
|
+
const ref = await adaptor.evaluateHandle(activeElementHandleScript);
|
|
1158
|
+
activeHandle = ref;
|
|
1159
|
+
try {
|
|
1160
|
+
const selector = await adaptor.evaluate(getSelector, ref);
|
|
1161
|
+
const activeElement = {
|
|
1162
|
+
...base,
|
|
1163
|
+
html: truncateHtml(base.html),
|
|
1164
|
+
selector
|
|
1165
|
+
};
|
|
1166
|
+
const snapshot = {
|
|
1167
|
+
tabIndex: ++tabIndex,
|
|
1168
|
+
activeElement
|
|
1169
|
+
};
|
|
1170
|
+
if (remainingHas("baselineStyles") && activeElement.index !== null) {
|
|
1171
|
+
const baselineStyles = interned.get(activeElement.index);
|
|
1172
|
+
if (baselineStyles !== void 0) {
|
|
1173
|
+
snapshot.baselineStyles = baselineStyles;
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
if (remainingHas("contextSignals")) {
|
|
1177
|
+
let signals;
|
|
1178
|
+
try {
|
|
1179
|
+
const raw = contextNav?.raw ?? await adaptor.evaluate(
|
|
1180
|
+
drainContextObserverScript,
|
|
1181
|
+
MARKER_ATTR
|
|
1182
|
+
);
|
|
1183
|
+
signals = {
|
|
1184
|
+
openedWindow: raw.openedWindow,
|
|
1185
|
+
submittedForm: raw.submittedForm,
|
|
1186
|
+
focusRemoved: raw.focusRemoved,
|
|
1187
|
+
redirect: raw.redirect,
|
|
1188
|
+
softUrlChange: raw.softUrlChange,
|
|
1189
|
+
navigation: raw.navigation
|
|
1190
|
+
};
|
|
1191
|
+
lastHasAttributed = raw.hasAttributed;
|
|
1192
|
+
} catch (error) {
|
|
1193
|
+
if (!isContextDestroyedError(error)) {
|
|
1194
|
+
throw error;
|
|
1195
|
+
}
|
|
1196
|
+
signals = {
|
|
1197
|
+
openedWindow: false,
|
|
1198
|
+
submittedForm: false,
|
|
1199
|
+
focusRemoved: false,
|
|
1200
|
+
redirect: null,
|
|
1201
|
+
softUrlChange: false,
|
|
1202
|
+
navigation: true
|
|
1203
|
+
};
|
|
1204
|
+
lastHasAttributed = false;
|
|
1205
|
+
}
|
|
1206
|
+
snapshot.contextSignals = { signals };
|
|
1207
|
+
}
|
|
1208
|
+
if (remainingHas("obscuring")) {
|
|
1209
|
+
let raw = await adaptor.evaluate(
|
|
1210
|
+
measureObscuringScript,
|
|
1211
|
+
ref,
|
|
1212
|
+
OBSCURER_ATTR
|
|
1213
|
+
);
|
|
1214
|
+
if (raw.fullyObscured) {
|
|
1215
|
+
await new Promise(
|
|
1216
|
+
(resolve) => setTimeout(resolve, OBSCURING_RECHECK_DELAY_MS)
|
|
1217
|
+
);
|
|
1218
|
+
raw = await adaptor.evaluate(
|
|
1219
|
+
measureObscuringScript,
|
|
1220
|
+
ref,
|
|
1221
|
+
OBSCURER_ATTR
|
|
1222
|
+
);
|
|
1223
|
+
}
|
|
1224
|
+
let obscuredBy = null;
|
|
1225
|
+
if (raw.hasObscurer) {
|
|
1226
|
+
const obscurer = await adaptor.evaluateHandle(obscurerHandleScript);
|
|
1227
|
+
try {
|
|
1228
|
+
const obscurerSelector = await adaptor.evaluate(
|
|
1229
|
+
getSelector,
|
|
1230
|
+
obscurer
|
|
1231
|
+
);
|
|
1232
|
+
obscuredBy = {
|
|
1233
|
+
selector: obscurerSelector,
|
|
1234
|
+
html: truncateHtml(raw.obscuredByHtml ?? "")
|
|
1235
|
+
};
|
|
1236
|
+
} finally {
|
|
1237
|
+
await adaptor.disposeRef(obscurer);
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
snapshot.obscuring = {
|
|
1241
|
+
coveredFraction: raw.coveredFraction,
|
|
1242
|
+
fullyObscured: raw.fullyObscured,
|
|
1243
|
+
offscreen: raw.offscreen,
|
|
1244
|
+
opacity: raw.opacity,
|
|
1245
|
+
obscuredBy
|
|
1246
|
+
};
|
|
1247
|
+
await adaptor.evaluate(clearObscurerScript, OBSCURER_ATTR);
|
|
1248
|
+
}
|
|
1249
|
+
const recipients = [...attached];
|
|
1250
|
+
for (const consumer of recipients) {
|
|
1251
|
+
await consumer.onTabStop(snapshot, handleFor(consumer));
|
|
1252
|
+
}
|
|
1253
|
+
if (snapshot.contextSignals?.signals.navigation) {
|
|
1254
|
+
end("navigation");
|
|
1255
|
+
break;
|
|
1256
|
+
}
|
|
1257
|
+
} finally {
|
|
1258
|
+
try {
|
|
1259
|
+
if (pairByStop.current !== null) {
|
|
1260
|
+
await pairByStop.current;
|
|
1261
|
+
if (contextSignalsWanted) {
|
|
1262
|
+
await adaptor.evaluate(clearContextFocusInsScript);
|
|
1263
|
+
if (lastHasAttributed) {
|
|
1264
|
+
await adaptor.evaluate(clearAttributedScript);
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
} catch {
|
|
1269
|
+
}
|
|
1270
|
+
activeHandle = void 0;
|
|
1271
|
+
await adaptor.disposeRef(ref);
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
} catch (error) {
|
|
1275
|
+
failure = error;
|
|
1276
|
+
} finally {
|
|
1277
|
+
try {
|
|
1278
|
+
await adaptor.evaluate(clearMarkersScript, MARKER_ATTR);
|
|
1279
|
+
} catch {
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
if (failure !== void 0) {
|
|
1283
|
+
end("failed");
|
|
1284
|
+
throw failure;
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
};
|
|
1288
|
+
}
|
|
1289
|
+
export {
|
|
1290
|
+
DEFAULT_ELEMENT_LIMIT,
|
|
1291
|
+
DEFAULT_FAILED_ELEMENT_LIMIT,
|
|
1292
|
+
DEFAULT_SCREENSHOT_SETTLE_DELAY,
|
|
1293
|
+
DEFAULT_TIMEOUT,
|
|
1294
|
+
FOCUS_STYLE_PROPERTIES,
|
|
1295
|
+
activeElementHandleScript,
|
|
1296
|
+
baselineScript,
|
|
1297
|
+
blurScript,
|
|
1298
|
+
bufferedClip,
|
|
1299
|
+
clearMarkersScript,
|
|
1300
|
+
createAuditSelfDisconnect,
|
|
1301
|
+
createTabOrchestrator,
|
|
1302
|
+
elementRectScript,
|
|
1303
|
+
elementStylesScript,
|
|
1304
|
+
focusScript,
|
|
1305
|
+
getSelector,
|
|
1306
|
+
isCenterObscuredScript,
|
|
1307
|
+
pageDimensionsScript,
|
|
1308
|
+
probeActiveElementScript,
|
|
1309
|
+
scrollToCenterScript,
|
|
1310
|
+
truncateHtml
|
|
1311
|
+
};
|
|
1312
|
+
//# sourceMappingURL=index.js.map
|