@orkestrel/browser 0.0.29 → 0.0.30
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/dist/bin/main.js +1 -2
- package/dist/src/browser/index.d.ts +86 -6
- package/dist/src/browser/index.js +359 -271
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/core/index.cjs +2281 -1745
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +342 -156
- package/dist/src/core/index.d.ts +342 -156
- package/dist/src/core/index.js +2261 -1744
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +377 -344
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +47 -9
- package/dist/src/server/index.d.ts +47 -9
- package/dist/src/server/index.js +376 -344
- package/dist/src/server/index.js.map +1 -1
- package/package.json +27 -26
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { attempt, isInteger, isJSONValue, isObject, isString } from "@orkestrel/contract";
|
|
2
|
-
import { BROWSER_DEFAULT_TIMEOUT_MS, BROWSER_INTERACTIVE_ROLES, BROWSER_OUTLINE_LIMIT, BROWSER_REFERENCE_PREFIX, BROWSER_RESULT_LIMIT, BROWSER_WAIT_EVENTS, BrowserError, BrowserTransition,
|
|
2
|
+
import { BROWSER_DEFAULT_TIMEOUT_MS, BROWSER_INTERACTIVE_ROLES, BROWSER_OUTLINE_LIMIT, BROWSER_REFERENCE_PREFIX, BROWSER_RESULT_LIMIT, BROWSER_WAIT_EVENTS, BrowserError, BrowserTransition, createBrowserContent, describeBrowserRefusal, filterBrowserOutline, normalizeBrowserName, parseBrowserReference, renderBrowserElement, renderBrowserOutline, validateBrowserTimeout } from "../core/index.js";
|
|
3
3
|
import { Emitter } from "@orkestrel/emitter";
|
|
4
|
-
|
|
4
|
+
// src/browser/constants.ts
|
|
5
5
|
/**
|
|
6
6
|
* Sets the default `wait` deadline for DOM waits in a browser document toolset, `5_000`
|
|
7
7
|
* milliseconds.
|
|
@@ -180,8 +180,87 @@ var BROWSER_TYPED_INPUTS = Object.freeze(/* @__PURE__ */ new Set([
|
|
|
180
180
|
* `controls`, `input` other than `hidden`, `label`, `select`, and `textarea`.
|
|
181
181
|
*/
|
|
182
182
|
var BROWSER_INTERACTIVE_CONTENT = "a[href], audio[controls], button, details, embed, iframe, img[usemap], img[controls], input:not([type=\"hidden\" i]), label, select, textarea, video[controls]";
|
|
183
|
-
|
|
184
|
-
|
|
183
|
+
// src/browser/BrowserAncestors.ts
|
|
184
|
+
/** Traverses ancestors lazily while retaining the previously yielded node. */
|
|
185
|
+
var BrowserAncestors = class extends Iterator {
|
|
186
|
+
#parent;
|
|
187
|
+
#current;
|
|
188
|
+
#previous;
|
|
189
|
+
#started = false;
|
|
190
|
+
constructor(node, parent) {
|
|
191
|
+
super();
|
|
192
|
+
this.#current = node;
|
|
193
|
+
this.#previous = node;
|
|
194
|
+
this.#parent = parent;
|
|
195
|
+
}
|
|
196
|
+
next() {
|
|
197
|
+
if (this.#started && this.#current !== null) {
|
|
198
|
+
this.#previous = this.#current;
|
|
199
|
+
this.#current = this.#parent(this.#current);
|
|
200
|
+
}
|
|
201
|
+
this.#started = true;
|
|
202
|
+
return this.#current === null ? {
|
|
203
|
+
done: true,
|
|
204
|
+
value: void 0
|
|
205
|
+
} : {
|
|
206
|
+
done: false,
|
|
207
|
+
value: this.#current
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
get previous() {
|
|
211
|
+
return this.#previous;
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
// src/browser/BrowserCaptureSiblings.ts
|
|
215
|
+
/** Traverses paired live and inert siblings while preserving pointers across pruning. */
|
|
216
|
+
var BrowserCaptureSiblings = class extends Iterator {
|
|
217
|
+
#element;
|
|
218
|
+
#counterpart;
|
|
219
|
+
#child;
|
|
220
|
+
#mirror;
|
|
221
|
+
#previous;
|
|
222
|
+
constructor(live, twin) {
|
|
223
|
+
super();
|
|
224
|
+
this.#element = live.lastElementChild;
|
|
225
|
+
this.#counterpart = twin.lastElementChild;
|
|
226
|
+
this.#child = live.lastChild;
|
|
227
|
+
this.#mirror = twin.lastChild;
|
|
228
|
+
}
|
|
229
|
+
next() {
|
|
230
|
+
if (this.#previous !== void 0) {
|
|
231
|
+
if (this.#child?.nodeType === 1) {
|
|
232
|
+
this.#element = this.#previous.element;
|
|
233
|
+
this.#counterpart = this.#previous.counterpart;
|
|
234
|
+
}
|
|
235
|
+
this.#child = this.#previous.child;
|
|
236
|
+
this.#mirror = this.#previous.mirror;
|
|
237
|
+
}
|
|
238
|
+
if (this.#child === null || this.#mirror === null) {
|
|
239
|
+
this.#previous = void 0;
|
|
240
|
+
this.#child = null;
|
|
241
|
+
return {
|
|
242
|
+
done: true,
|
|
243
|
+
value: void 0
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
this.#previous = {
|
|
247
|
+
child: this.#child.previousSibling,
|
|
248
|
+
mirror: this.#mirror.previousSibling,
|
|
249
|
+
element: this.#element?.previousElementSibling ?? null,
|
|
250
|
+
counterpart: this.#counterpart?.previousElementSibling ?? null
|
|
251
|
+
};
|
|
252
|
+
return {
|
|
253
|
+
done: false,
|
|
254
|
+
value: {
|
|
255
|
+
child: this.#child,
|
|
256
|
+
mirror: this.#mirror,
|
|
257
|
+
element: this.#element,
|
|
258
|
+
counterpart: this.#counterpart
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
// src/browser/helpers.ts
|
|
185
264
|
/**
|
|
186
265
|
* Narrows a value to a `Document` attached to a window.
|
|
187
266
|
*
|
|
@@ -198,8 +277,8 @@ function isBrowserDocument(value) {
|
|
|
198
277
|
* @remarks
|
|
199
278
|
* The measure is the length of `JSON.stringify({ url, title, html })`, the form the CDP capture
|
|
200
279
|
* guard compares with `BROWSER_RESULT_LIMIT`, so a character JSON escapes counts as its escape
|
|
201
|
-
* sequence. A reading a caller builds with `
|
|
202
|
-
* {@link
|
|
280
|
+
* sequence. A reading a caller builds with `createBrowserContent` passes through no limit.
|
|
281
|
+
* {@link BrowserContentInput} defines the inert capture, lowering, redaction, and limits.
|
|
203
282
|
*
|
|
204
283
|
* @param node - The root whose rendered markup is captured; its owner document supplies `URL`
|
|
205
284
|
* and `title`
|
|
@@ -217,15 +296,19 @@ function readBrowserCapture(node) {
|
|
|
217
296
|
const view = document.defaultView;
|
|
218
297
|
const rendered = view !== null && node.isConnected;
|
|
219
298
|
const inert = document.implementation.createHTMLDocument("");
|
|
220
|
-
|
|
299
|
+
const root = inert.importNode(node, true);
|
|
300
|
+
const output = inert.createDocumentFragment();
|
|
301
|
+
output.append(root);
|
|
221
302
|
const pending = [{
|
|
222
303
|
live: node,
|
|
223
304
|
twin: root,
|
|
224
305
|
visited: false
|
|
225
306
|
}];
|
|
226
307
|
if (rendered) {
|
|
227
|
-
|
|
228
|
-
for (
|
|
308
|
+
const ancestors = new BrowserAncestors(node, readBrowserParent);
|
|
309
|
+
for (const parent of ancestors) {
|
|
310
|
+
if (parent.ownerDocument !== document) break;
|
|
311
|
+
const child = ancestors.previous ?? node;
|
|
229
312
|
const style = view.getComputedStyle(parent);
|
|
230
313
|
const outside = parent !== node && (style.contentVisibility === "hidden" || [
|
|
231
314
|
"canvas",
|
|
@@ -249,11 +332,10 @@ function readBrowserCapture(node) {
|
|
|
249
332
|
].includes(parent.localName) || parent.localName === "details" && !parent.hasAttribute("open") && child !== parent.querySelector(":scope > summary"));
|
|
250
333
|
const unassigned = child.parentElement?.shadowRoot !== null && child.parentElement?.shadowRoot !== void 0 && child.assignedSlot === null;
|
|
251
334
|
if (style.display === "none" || outside || unassigned) {
|
|
252
|
-
root
|
|
335
|
+
root.remove();
|
|
253
336
|
pending.length = 0;
|
|
254
337
|
break;
|
|
255
338
|
}
|
|
256
|
-
child = parent;
|
|
257
339
|
}
|
|
258
340
|
}
|
|
259
341
|
while (pending.length > 0) {
|
|
@@ -279,14 +361,12 @@ function readBrowserCapture(node) {
|
|
|
279
361
|
"math"
|
|
280
362
|
].includes(tag) || tag === "input" && ["password", "hidden"].includes(input)) {
|
|
281
363
|
twin.remove();
|
|
282
|
-
if (twin === root) root = void 0;
|
|
283
364
|
continue;
|
|
284
365
|
}
|
|
285
366
|
const style = entry.style ?? (rendered ? view.getComputedStyle(live) : void 0);
|
|
286
367
|
const invisible = style?.visibility === "hidden" || style?.visibility === "collapse";
|
|
287
368
|
if (style?.display === "none" || invisible && !live.hasChildNodes()) {
|
|
288
369
|
twin.remove();
|
|
289
|
-
if (twin === root) root = void 0;
|
|
290
370
|
continue;
|
|
291
371
|
}
|
|
292
372
|
if (!visited) {
|
|
@@ -313,35 +393,13 @@ function readBrowserCapture(node) {
|
|
|
313
393
|
const summary = tag === "details" && !live.hasAttribute("open") ? live.querySelector(":scope > summary") : void 0;
|
|
314
394
|
const conditional = tag === "switch" && live.namespaceURI === "http://www.w3.org/2000/svg";
|
|
315
395
|
const branch = rendered && conditional ? Array.from(live.children).find((candidate) => candidate.getClientRects().length > 0) ?? live.firstElementChild : void 0;
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
const previousElement = element?.previousElementSibling ?? null;
|
|
324
|
-
const previousCounterpart = counterpart?.previousElementSibling ?? null;
|
|
325
|
-
let omitted = rendered && (summary !== void 0 && child !== summary || conditional && child !== branch);
|
|
326
|
-
if (rendered && live.shadowRoot !== null) {
|
|
327
|
-
const slot = child instanceof view.Element || child instanceof view.Text ? child.assignedSlot : null;
|
|
328
|
-
if (slot === null) omitted = true;
|
|
329
|
-
else for (let parent = slot; parent !== null && parent !== live; parent = readBrowserParent(parent)) if (view.getComputedStyle(parent).display === "none") omitted = true;
|
|
330
|
-
}
|
|
331
|
-
if (omitted || child.nodeType === 3 && invisible) mirror.parentNode?.removeChild(mirror);
|
|
332
|
-
else if (child.nodeType === 1) {
|
|
333
|
-
if (element !== null && counterpart !== null) pending.push({
|
|
334
|
-
live: element,
|
|
335
|
-
twin: counterpart,
|
|
336
|
-
visited: false
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
if (child.nodeType === 1) {
|
|
340
|
-
element = previousElement;
|
|
341
|
-
counterpart = previousCounterpart;
|
|
342
|
-
}
|
|
343
|
-
child = previous;
|
|
344
|
-
mirror = previousMirror;
|
|
396
|
+
for (const { child, mirror, element, counterpart } of new BrowserCaptureSiblings(live, twin)) if (readBrowserCaptureOmission(live, child, view, rendered, rendered && (summary !== void 0 && child !== summary || conditional && child !== branch)) || child.nodeType === 3 && invisible) mirror.parentNode?.removeChild(mirror);
|
|
397
|
+
else if (child.nodeType === 1) {
|
|
398
|
+
if (element !== null && counterpart !== null) pending.push({
|
|
399
|
+
live: element,
|
|
400
|
+
twin: counterpart,
|
|
401
|
+
visited: false
|
|
402
|
+
});
|
|
345
403
|
}
|
|
346
404
|
continue;
|
|
347
405
|
}
|
|
@@ -354,82 +412,10 @@ function readBrowserCapture(node) {
|
|
|
354
412
|
twin.prepend(inert.createElement("br"));
|
|
355
413
|
twin.append(inert.createElement("br"));
|
|
356
414
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
lines = [];
|
|
362
|
-
if ([
|
|
363
|
-
"text",
|
|
364
|
-
"email",
|
|
365
|
-
"search",
|
|
366
|
-
"tel",
|
|
367
|
-
"url",
|
|
368
|
-
"number"
|
|
369
|
-
].includes(live.type)) {
|
|
370
|
-
if (live.type !== "number" || !live.validity.badInput) {
|
|
371
|
-
const placeholder = view.getComputedStyle(live, "::placeholder");
|
|
372
|
-
lines = [live.value || (live.matches(":placeholder-shown") && placeholder.visibility === "visible" && placeholder.opacity !== "0" && placeholder.color !== "rgba(0, 0, 0, 0)" ? live.placeholder : "")];
|
|
373
|
-
}
|
|
374
|
-
} else if ([
|
|
375
|
-
"submit",
|
|
376
|
-
"reset",
|
|
377
|
-
"button"
|
|
378
|
-
].includes(live.type)) lines = [live.value];
|
|
379
|
-
else if (live.type === "file" && live.files?.length === 1) lines = Array.from(live.files ?? [], (file) => file.name);
|
|
380
|
-
else if (live.type === "image" && live.getAttribute("aria-hidden") !== "true") lines = [live.getAttribute("aria-label")?.trim() || live.alt];
|
|
381
|
-
} else if (live instanceof view.HTMLTextAreaElement) {
|
|
382
|
-
const placeholder = view.getComputedStyle(live, "::placeholder");
|
|
383
|
-
lines = (live.value || (live.matches(":placeholder-shown") && placeholder.visibility === "visible" && placeholder.opacity !== "0" && placeholder.color !== "rgba(0, 0, 0, 0)" ? live.placeholder : "")).split(/\r?\n/);
|
|
384
|
-
} else if (live instanceof view.HTMLSelectElement || live instanceof view.HTMLOptionElement || live instanceof view.HTMLOptGroupElement) {
|
|
385
|
-
lines = [];
|
|
386
|
-
const select = live instanceof view.HTMLSelectElement ? live : live.closest("select");
|
|
387
|
-
if (select !== null) {
|
|
388
|
-
if (!select.multiple && select.size <= 1) {
|
|
389
|
-
const option = select.selectedOptions[0];
|
|
390
|
-
if (option !== void 0 && (live === select || live === option || live.contains(option))) lines.push(option.label);
|
|
391
|
-
} else {
|
|
392
|
-
const box = select.getBoundingClientRect();
|
|
393
|
-
const top = box.top + select.clientTop;
|
|
394
|
-
const bottom = top + select.clientHeight;
|
|
395
|
-
const rows = live === select ? select.querySelectorAll("option,optgroup") : [live, ...live.querySelectorAll("option")];
|
|
396
|
-
for (const row of rows) {
|
|
397
|
-
const rowStyle = view.getComputedStyle(row);
|
|
398
|
-
const groupStyle = row.parentElement instanceof view.HTMLOptGroupElement ? view.getComputedStyle(row.parentElement) : void 0;
|
|
399
|
-
if (rowStyle.display === "none" || rowStyle.visibility !== "visible" || groupStyle?.display === "none") continue;
|
|
400
|
-
const rect = row.getBoundingClientRect();
|
|
401
|
-
if ((row instanceof view.HTMLOptGroupElement ? Array.from(row.querySelectorAll("option")).find((option) => option.getClientRects().length > 0)?.getBoundingClientRect().top ?? rect.bottom : rect.bottom) > top && rect.top < bottom && rect.right > box.left + select.clientLeft && rect.left < box.left + select.clientLeft + select.clientWidth && (row instanceof view.HTMLOptionElement || row instanceof view.HTMLOptGroupElement)) lines.push(row.label);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
} else if (tag === "svg") {
|
|
406
|
-
if (twin.parentElement?.closest("svg, foreignObject")?.localName === "svg") continue;
|
|
407
|
-
lines = [];
|
|
408
|
-
fragments = [];
|
|
409
|
-
for (const content of twin.querySelectorAll("text, foreignObject")) {
|
|
410
|
-
if (content.parentElement?.closest("svg, foreignObject")?.localName === "foreignObject") continue;
|
|
411
|
-
if (content.localName === "text" && content.parentElement?.closest("text")?.closest("svg") === content.closest("svg")) continue;
|
|
412
|
-
if (content.localName === "foreignObject") fragments.push(...content.childNodes);
|
|
413
|
-
else if (content.textContent) fragments.push(inert.createTextNode(content.textContent));
|
|
414
|
-
}
|
|
415
|
-
if (fragments.length === 0) fragments = void 0;
|
|
416
|
-
if (!invisible && fragments === void 0 && live.getAttribute("aria-hidden") !== "true") lines.push(live.getAttribute("aria-label")?.trim() || live.querySelector(":scope > title")?.textContent || "");
|
|
417
|
-
} else if ([
|
|
418
|
-
"form",
|
|
419
|
-
"dialog",
|
|
420
|
-
"button"
|
|
421
|
-
].includes(tag)) {
|
|
422
|
-
container = true;
|
|
423
|
-
if (live instanceof view.HTMLButtonElement && !invisible && live.innerText.trim() === "") {
|
|
424
|
-
const name = live.getAttribute("aria-label")?.trim();
|
|
425
|
-
if (name) {
|
|
426
|
-
lines = [name];
|
|
427
|
-
container = false;
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
if (lines === void 0 && !container) continue;
|
|
432
|
-
if (invisible && !container && tag !== "svg") lines = [];
|
|
415
|
+
const value = readBrowserCaptureValue(live, twin, view, inert, invisible, tag);
|
|
416
|
+
if (value === void 0) continue;
|
|
417
|
+
const { fragments, container } = value;
|
|
418
|
+
const lines = invisible && !container && tag !== "svg" ? [] : value.lines;
|
|
433
419
|
const block = !style?.display.startsWith("inline") && style?.display !== "contents";
|
|
434
420
|
const carrier = inert.createElement(block ? "div" : "span");
|
|
435
421
|
if (container) carrier.append(...twin.childNodes);
|
|
@@ -443,10 +429,9 @@ function readBrowserCapture(node) {
|
|
|
443
429
|
carrier.append(inert.createElement("br"));
|
|
444
430
|
}
|
|
445
431
|
twin.replaceWith(carrier);
|
|
446
|
-
if (twin === root) root = carrier;
|
|
447
432
|
}
|
|
448
|
-
for (const input of
|
|
449
|
-
capture.html =
|
|
433
|
+
for (const input of output.firstElementChild?.querySelectorAll("input[type=\"password\" i],input[type=\"hidden\" i]") ?? []) input.remove();
|
|
434
|
+
capture.html = output.firstElementChild?.outerHTML ?? "";
|
|
450
435
|
const length = JSON.stringify(capture).length;
|
|
451
436
|
if (length > BROWSER_RESULT_LIMIT) throw new BrowserError("RESULT_LIMIT", "Document capture exceeds BROWSER_RESULT_LIMIT", {
|
|
452
437
|
length,
|
|
@@ -605,16 +590,17 @@ function computeBrowserText(root, context) {
|
|
|
605
590
|
function readBrowserContent(root, context) {
|
|
606
591
|
const view = root.ownerDocument?.defaultView ?? null;
|
|
607
592
|
const shadow = view !== null && root instanceof view.Element ? root.shadowRoot : null;
|
|
608
|
-
|
|
609
|
-
let text = "";
|
|
610
|
-
let previous = false;
|
|
611
|
-
for (const child of children) {
|
|
593
|
+
return (shadow !== null ? Array.from(shadow.childNodes) : view !== null && root instanceof view.HTMLSlotElement ? root.assignedNodes({ flatten: true }) : Array.from(root.childNodes)).reduce((state, child) => {
|
|
612
594
|
const piece = computeBrowserAlternative(child, context);
|
|
613
595
|
const block = view !== null && child instanceof view.Element && matchesBrowserBlock(child);
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
596
|
+
return {
|
|
597
|
+
text: piece === "" ? state.text : state.text + (state.text !== "" && (block || state.previous) ? ` ${piece}` : piece),
|
|
598
|
+
previous: block
|
|
599
|
+
};
|
|
600
|
+
}, {
|
|
601
|
+
text: "",
|
|
602
|
+
previous: false
|
|
603
|
+
}).text;
|
|
618
604
|
}
|
|
619
605
|
/**
|
|
620
606
|
* Computes the text one node contributes to an accessible name: its own text alternative, or its
|
|
@@ -734,7 +720,7 @@ function matchesBrowserInvisible(element) {
|
|
|
734
720
|
* ```
|
|
735
721
|
*/
|
|
736
722
|
function matchesBrowserOmitted(element) {
|
|
737
|
-
for (
|
|
723
|
+
for (const current of new BrowserAncestors(element, readBrowserParent)) if (matchesBrowserHidden(current)) return true;
|
|
738
724
|
return false;
|
|
739
725
|
}
|
|
740
726
|
/**
|
|
@@ -793,7 +779,9 @@ function matchesBrowserBlock(element) {
|
|
|
793
779
|
function collectBrowserRoots(root) {
|
|
794
780
|
const roots = [root.getRootNode()];
|
|
795
781
|
const queue = [root];
|
|
796
|
-
|
|
782
|
+
while (queue.length > 0) {
|
|
783
|
+
const current = queue.shift();
|
|
784
|
+
if (current === void 0) continue;
|
|
797
785
|
const view = (isBrowserDocument(current) ? current : current.ownerDocument)?.defaultView ?? null;
|
|
798
786
|
const own = view !== null && current instanceof view.Element ? [current] : [];
|
|
799
787
|
for (const element of [...own, ...current.querySelectorAll("*")]) {
|
|
@@ -842,20 +830,15 @@ function matchesBrowserPopup(element) {
|
|
|
842
830
|
const type = Reflect.get(element, "type");
|
|
843
831
|
const submitter = tag === "button" && type === "submit" || tag === "input" && (type === "submit" || type === "image");
|
|
844
832
|
const form = tag === "form" ? element : Reflect.get(element, "form");
|
|
845
|
-
|
|
846
|
-
if ((tag === "a" || tag === "area") && element.hasAttribute("href")) target = element.getAttribute("target") ?? base;
|
|
847
|
-
else if (view !== null && form instanceof view.HTMLFormElement && (tag === "form" || submitter)) target = (submitter ? element.getAttribute("formtarget") : null) ?? form.getAttribute("target") ?? base;
|
|
833
|
+
const target = (tag === "a" || tag === "area") && element.hasAttribute("href") ? element.getAttribute("target") ?? base : view !== null && form instanceof view.HTMLFormElement && (tag === "form" || submitter) ? (submitter ? element.getAttribute("formtarget") : null) ?? form.getAttribute("target") ?? base : void 0;
|
|
848
834
|
if (target === void 0) return false;
|
|
849
835
|
const keyword = target.trim().toLowerCase();
|
|
850
836
|
if (BROWSER_CONTEXT_TARGETS.has(keyword)) return false;
|
|
851
837
|
if (keyword === "_blank") return true;
|
|
852
838
|
const selector = `iframe[name="${CSS.escape(target)}"], frame[name="${CSS.escape(target)}"]`;
|
|
853
|
-
|
|
854
|
-
while (current !== null) {
|
|
855
|
-
const frame = current;
|
|
839
|
+
for (const frame of new BrowserAncestors(view, (entry) => entry.parent === entry ? null : entry.parent)) {
|
|
856
840
|
const found = attempt(() => frame.name === target || frame.document.querySelector(selector) !== null);
|
|
857
841
|
if (found.success && found.value) return false;
|
|
858
|
-
current = frame.parent === frame ? null : frame.parent;
|
|
859
842
|
}
|
|
860
843
|
return true;
|
|
861
844
|
}
|
|
@@ -881,7 +864,10 @@ function matchesBrowserPopup(element) {
|
|
|
881
864
|
* ```
|
|
882
865
|
*/
|
|
883
866
|
function matchesBrowserActivation(target, label) {
|
|
884
|
-
for (
|
|
867
|
+
for (const current of new BrowserAncestors(target, (entry) => entry.parentElement)) {
|
|
868
|
+
if (current === label) break;
|
|
869
|
+
if (current.matches("a[href], audio[controls], button, details, embed, iframe, img[usemap], img[controls], input:not([type=\"hidden\" i]), label, select, textarea, video[controls]")) return false;
|
|
870
|
+
}
|
|
885
871
|
return true;
|
|
886
872
|
}
|
|
887
873
|
/**
|
|
@@ -898,12 +884,11 @@ function matchesBrowserActivation(target, label) {
|
|
|
898
884
|
* ```
|
|
899
885
|
*/
|
|
900
886
|
function skipBrowserSubtree(walker) {
|
|
901
|
-
|
|
902
|
-
|
|
887
|
+
while (true) {
|
|
888
|
+
const node = walker.nextSibling();
|
|
889
|
+
if (node !== null) return node;
|
|
903
890
|
if (walker.parentNode() === null) return null;
|
|
904
|
-
node = walker.nextSibling();
|
|
905
891
|
}
|
|
906
|
-
return node;
|
|
907
892
|
}
|
|
908
893
|
/**
|
|
909
894
|
* Reads the nearest block container of an element, the unit that separates runs of text.
|
|
@@ -923,10 +908,9 @@ function skipBrowserSubtree(walker) {
|
|
|
923
908
|
* ```
|
|
924
909
|
*/
|
|
925
910
|
function readBrowserBlock(element, root) {
|
|
926
|
-
|
|
927
|
-
|
|
911
|
+
for (const current of new BrowserAncestors(element, (entry) => entry.parentElement)) {
|
|
912
|
+
if (current === root) break;
|
|
928
913
|
if (matchesBrowserBlock(current)) return current;
|
|
929
|
-
current = current.parentElement;
|
|
930
914
|
}
|
|
931
915
|
return root;
|
|
932
916
|
}
|
|
@@ -1011,8 +995,100 @@ function readBrowserStates(element, role) {
|
|
|
1011
995
|
}
|
|
1012
996
|
return states;
|
|
1013
997
|
}
|
|
1014
|
-
|
|
1015
|
-
|
|
998
|
+
/** Derives rendered form values and SVG fragments for an inert capture. */
|
|
999
|
+
function readBrowserCaptureValue(live, twin, view, inert, invisible, tag) {
|
|
1000
|
+
if (live instanceof view.HTMLInputElement) {
|
|
1001
|
+
if ([
|
|
1002
|
+
"text",
|
|
1003
|
+
"email",
|
|
1004
|
+
"search",
|
|
1005
|
+
"tel",
|
|
1006
|
+
"url",
|
|
1007
|
+
"number"
|
|
1008
|
+
].includes(live.type)) {
|
|
1009
|
+
if (live.type !== "number" || !live.validity.badInput) {
|
|
1010
|
+
const placeholder = view.getComputedStyle(live, "::placeholder");
|
|
1011
|
+
return { lines: [live.value || (live.matches(":placeholder-shown") && placeholder.visibility === "visible" && placeholder.opacity !== "0" && placeholder.color !== "rgba(0, 0, 0, 0)" ? live.placeholder : "")] };
|
|
1012
|
+
}
|
|
1013
|
+
} else if ([
|
|
1014
|
+
"submit",
|
|
1015
|
+
"reset",
|
|
1016
|
+
"button"
|
|
1017
|
+
].includes(live.type)) return { lines: [live.value] };
|
|
1018
|
+
else if (live.type === "file" && live.files?.length === 1) return { lines: Array.from(live.files ?? [], (file) => file.name) };
|
|
1019
|
+
else if (live.type === "image" && live.getAttribute("aria-hidden") !== "true") return { lines: [live.getAttribute("aria-label")?.trim() || live.alt] };
|
|
1020
|
+
return { lines: [] };
|
|
1021
|
+
} else if (live instanceof view.HTMLTextAreaElement) {
|
|
1022
|
+
const placeholder = view.getComputedStyle(live, "::placeholder");
|
|
1023
|
+
return { lines: (live.value || (live.matches(":placeholder-shown") && placeholder.visibility === "visible" && placeholder.opacity !== "0" && placeholder.color !== "rgba(0, 0, 0, 0)" ? live.placeholder : "")).split(/\r?\n/) };
|
|
1024
|
+
} else if (live instanceof view.HTMLSelectElement || live instanceof view.HTMLOptionElement || live instanceof view.HTMLOptGroupElement) {
|
|
1025
|
+
const lines = [];
|
|
1026
|
+
const select = live instanceof view.HTMLSelectElement ? live : live.closest("select");
|
|
1027
|
+
if (select !== null) {
|
|
1028
|
+
if (!select.multiple && select.size <= 1) {
|
|
1029
|
+
const option = select.selectedOptions[0];
|
|
1030
|
+
if (option !== void 0 && (live === select || live === option || live.contains(option))) lines.push(option.label);
|
|
1031
|
+
} else {
|
|
1032
|
+
const box = select.getBoundingClientRect();
|
|
1033
|
+
const top = box.top + select.clientTop;
|
|
1034
|
+
const bottom = top + select.clientHeight;
|
|
1035
|
+
const rows = live === select ? select.querySelectorAll("option,optgroup") : [live, ...live.querySelectorAll("option")];
|
|
1036
|
+
for (const row of rows) {
|
|
1037
|
+
const rowStyle = view.getComputedStyle(row);
|
|
1038
|
+
const groupStyle = row.parentElement instanceof view.HTMLOptGroupElement ? view.getComputedStyle(row.parentElement) : void 0;
|
|
1039
|
+
if (rowStyle.display === "none" || rowStyle.visibility !== "visible" || groupStyle?.display === "none") continue;
|
|
1040
|
+
const rect = row.getBoundingClientRect();
|
|
1041
|
+
if ((row instanceof view.HTMLOptGroupElement ? Array.from(row.querySelectorAll("option")).find((option) => option.getClientRects().length > 0)?.getBoundingClientRect().top ?? rect.bottom : rect.bottom) > top && rect.top < bottom && rect.right > box.left + select.clientLeft && rect.left < box.left + select.clientLeft + select.clientWidth && (row instanceof view.HTMLOptionElement || row instanceof view.HTMLOptGroupElement)) lines.push(row.label);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
return { lines };
|
|
1046
|
+
} else if (tag === "svg") {
|
|
1047
|
+
if (twin.parentElement?.closest("svg, foreignObject")?.localName === "svg") return void 0;
|
|
1048
|
+
const lines = [];
|
|
1049
|
+
const fragments = [];
|
|
1050
|
+
for (const content of twin.querySelectorAll("text, foreignObject")) {
|
|
1051
|
+
if (content.parentElement?.closest("svg, foreignObject")?.localName === "foreignObject") continue;
|
|
1052
|
+
if (content.localName === "text" && content.parentElement?.closest("text")?.closest("svg") === content.closest("svg")) continue;
|
|
1053
|
+
if (content.localName === "foreignObject") fragments.push(...content.childNodes);
|
|
1054
|
+
else if (content.textContent) fragments.push(inert.createTextNode(content.textContent));
|
|
1055
|
+
}
|
|
1056
|
+
if (!invisible && fragments.length === 0 && live.getAttribute("aria-hidden") !== "true") lines.push(live.getAttribute("aria-label")?.trim() || live.querySelector(":scope > title")?.textContent || "");
|
|
1057
|
+
return {
|
|
1058
|
+
lines,
|
|
1059
|
+
...fragments.length === 0 ? {} : { fragments }
|
|
1060
|
+
};
|
|
1061
|
+
} else if ([
|
|
1062
|
+
"form",
|
|
1063
|
+
"dialog",
|
|
1064
|
+
"button"
|
|
1065
|
+
].includes(tag)) {
|
|
1066
|
+
if (live instanceof view.HTMLButtonElement && !invisible && live.innerText.trim() === "") {
|
|
1067
|
+
const name = live.getAttribute("aria-label")?.trim();
|
|
1068
|
+
if (name) return { lines: [name] };
|
|
1069
|
+
}
|
|
1070
|
+
return { container: true };
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
/** Checks shadow-slot ancestors for capture omission without skipping style reads. */
|
|
1074
|
+
function readBrowserCaptureOmission(live, child, view, rendered, excluded) {
|
|
1075
|
+
if (!rendered || live.shadowRoot === null || view === null) return excluded;
|
|
1076
|
+
const slot = child instanceof view.Element || child instanceof view.Text ? child.assignedSlot : null;
|
|
1077
|
+
if (slot === null) return true;
|
|
1078
|
+
return new BrowserAncestors(slot, (entry) => entry === live ? null : readBrowserParent(entry)).reduce((omitted, parent) => parent === live ? omitted : view.getComputedStyle(parent).display === "none" || omitted, excluded);
|
|
1079
|
+
}
|
|
1080
|
+
// src/browser/BrowserDOMSubmission.ts
|
|
1081
|
+
/** Observes whether one form submission dispatches its submit event. */
|
|
1082
|
+
var BrowserDOMSubmission = class {
|
|
1083
|
+
#submitted = false;
|
|
1084
|
+
get submitted() {
|
|
1085
|
+
return this.#submitted;
|
|
1086
|
+
}
|
|
1087
|
+
handleEvent() {
|
|
1088
|
+
this.#submitted = true;
|
|
1089
|
+
}
|
|
1090
|
+
};
|
|
1091
|
+
// src/browser/elements/BrowserDOMElement.ts
|
|
1016
1092
|
/**
|
|
1017
1093
|
* Drives a referenced element of a DOM document without trusted input.
|
|
1018
1094
|
*
|
|
@@ -1122,7 +1198,7 @@ var BrowserDOMElement = class {
|
|
|
1122
1198
|
}
|
|
1123
1199
|
async read(options) {
|
|
1124
1200
|
const [node] = this.#current(options);
|
|
1125
|
-
return
|
|
1201
|
+
return createBrowserContent({
|
|
1126
1202
|
...readBrowserCapture(node),
|
|
1127
1203
|
navigation: this.#input.navigation
|
|
1128
1204
|
});
|
|
@@ -1144,17 +1220,15 @@ var BrowserDOMElement = class {
|
|
|
1144
1220
|
reference: this.reference,
|
|
1145
1221
|
reason: "UNTRUSTED"
|
|
1146
1222
|
});
|
|
1147
|
-
|
|
1223
|
+
const submission = new BrowserDOMSubmission();
|
|
1148
1224
|
const release = new AbortController();
|
|
1149
|
-
owner.addEventListener("submit",
|
|
1150
|
-
submitted = true;
|
|
1151
|
-
}, { signal: release.signal });
|
|
1225
|
+
owner.addEventListener("submit", submission, { signal: release.signal });
|
|
1152
1226
|
try {
|
|
1153
1227
|
owner.requestSubmit(submitter ?? null);
|
|
1154
1228
|
} finally {
|
|
1155
1229
|
release.abort();
|
|
1156
1230
|
}
|
|
1157
|
-
if (submitted) return;
|
|
1231
|
+
if (submission.submitted) return;
|
|
1158
1232
|
const invalid = Array.from(owner.elements).find((control) => control.localName !== "fieldset" && control.matches(":invalid"));
|
|
1159
1233
|
if (invalid === void 0) throw new BrowserError("DOCUMENT_SUBMIT", "did not submit", { reference: this.reference });
|
|
1160
1234
|
const field = computeBrowserName(invalid) || (invalid.getAttribute("name") ?? invalid.localName);
|
|
@@ -1200,8 +1274,118 @@ var BrowserDOMElement = class {
|
|
|
1200
1274
|
});
|
|
1201
1275
|
}
|
|
1202
1276
|
};
|
|
1203
|
-
|
|
1204
|
-
|
|
1277
|
+
// src/browser/BrowserDOMOutline.ts
|
|
1278
|
+
/** Captures one flat-tree subtree while retaining its text and visibility state. */
|
|
1279
|
+
var BrowserDOMOutline = class {
|
|
1280
|
+
#input;
|
|
1281
|
+
#text = "";
|
|
1282
|
+
#block;
|
|
1283
|
+
#silenced;
|
|
1284
|
+
#node = null;
|
|
1285
|
+
constructor(input) {
|
|
1286
|
+
this.#input = input;
|
|
1287
|
+
}
|
|
1288
|
+
capture() {
|
|
1289
|
+
const { root, rows, muted } = this.#input;
|
|
1290
|
+
this.#text = "";
|
|
1291
|
+
this.#block = void 0;
|
|
1292
|
+
this.#silenced = void 0;
|
|
1293
|
+
const document = root.ownerDocument;
|
|
1294
|
+
const view = document.defaultView;
|
|
1295
|
+
if (view === null) return;
|
|
1296
|
+
if (root instanceof view.Element && matchesBrowserHidden(root)) return;
|
|
1297
|
+
this.#input.watch(document);
|
|
1298
|
+
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, (node) => node instanceof view.Element && matchesBrowserHidden(node) ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT);
|
|
1299
|
+
this.#node = root instanceof view.Element ? root : walker.nextNode();
|
|
1300
|
+
while (this.#node !== null) {
|
|
1301
|
+
if (this.#silenced !== void 0 && !this.#silenced.contains(this.#node)) this.#silenced = void 0;
|
|
1302
|
+
const quiet = muted || this.#silenced !== void 0;
|
|
1303
|
+
if (!(this.#node instanceof view.Element)) {
|
|
1304
|
+
const parent = this.#node.parentElement;
|
|
1305
|
+
const data = this.#node.textContent ?? "";
|
|
1306
|
+
const blank = data.trim() === "";
|
|
1307
|
+
if (!quiet && (!blank || this.#text !== "") && (parent === null || !matchesBrowserInvisible(parent))) {
|
|
1308
|
+
const container = readBrowserBlock(parent, root);
|
|
1309
|
+
if (!blank) {
|
|
1310
|
+
if (this.#block !== void 0 && this.#block !== container) this.#text = this.#flush(rows, this.#text, this.#block);
|
|
1311
|
+
this.#text += data;
|
|
1312
|
+
this.#block = container;
|
|
1313
|
+
} else if (container === this.#block) this.#text += data;
|
|
1314
|
+
}
|
|
1315
|
+
this.#node = walker.nextNode();
|
|
1316
|
+
continue;
|
|
1317
|
+
}
|
|
1318
|
+
const invisible = matchesBrowserInvisible(this.#node);
|
|
1319
|
+
if (this.#node.localName === "br") {
|
|
1320
|
+
if (!quiet && this.#text !== "") this.#text += "\n";
|
|
1321
|
+
this.#node = walker.nextNode();
|
|
1322
|
+
continue;
|
|
1323
|
+
}
|
|
1324
|
+
if (this.#node instanceof view.HTMLIFrameElement) {
|
|
1325
|
+
this.#text = this.#flush(rows, this.#text, this.#block);
|
|
1326
|
+
const child = this.#node.contentDocument;
|
|
1327
|
+
if (invisible) {
|
|
1328
|
+
this.#node = skipBrowserSubtree(walker);
|
|
1329
|
+
continue;
|
|
1330
|
+
}
|
|
1331
|
+
if (child === null) this.#input.text(rows, `iframe ${JSON.stringify(computeBrowserName(this.#node))} (cross-origin, not readable)`);
|
|
1332
|
+
else this.#input.walk(child.documentElement, rows, quiet);
|
|
1333
|
+
this.#node = skipBrowserSubtree(walker);
|
|
1334
|
+
continue;
|
|
1335
|
+
}
|
|
1336
|
+
if (this.#node instanceof view.HTMLSlotElement) {
|
|
1337
|
+
this.#text = this.#flush(rows, this.#text, this.#block);
|
|
1338
|
+
for (const assigned of this.#node.assignedNodes({ flatten: true })) if (assigned instanceof view.Element) this.#input.walk(assigned, rows, quiet);
|
|
1339
|
+
else if (!quiet) this.#flush(rows, assigned.textContent ?? "");
|
|
1340
|
+
this.#node = skipBrowserSubtree(walker);
|
|
1341
|
+
continue;
|
|
1342
|
+
}
|
|
1343
|
+
const tool = this.#node.localName === "form" ? this.#node.getAttribute("toolname") ?? "" : "";
|
|
1344
|
+
const role = tool === "" ? computeBrowserRole(this.#node) : "form";
|
|
1345
|
+
if (!invisible && role !== void 0 && (BROWSER_INTERACTIVE_ROLES.has(role) || [
|
|
1346
|
+
"heading",
|
|
1347
|
+
"image",
|
|
1348
|
+
"img",
|
|
1349
|
+
"listitem",
|
|
1350
|
+
"row",
|
|
1351
|
+
"cell",
|
|
1352
|
+
"gridcell",
|
|
1353
|
+
"columnheader",
|
|
1354
|
+
"rowheader"
|
|
1355
|
+
].includes(role) || tool !== "")) {
|
|
1356
|
+
this.#text = this.#flush(rows, this.#text, this.#block);
|
|
1357
|
+
const row = this.#input.row(this.#node, role, view, String(rows.length));
|
|
1358
|
+
rows.push(row);
|
|
1359
|
+
this.#input.positions.set(this.#node, row.id);
|
|
1360
|
+
if (this.#silenced === void 0 && BROWSER_CONTENT_NAMED_ROLES.has(role) && ![
|
|
1361
|
+
"row",
|
|
1362
|
+
"cell",
|
|
1363
|
+
"gridcell",
|
|
1364
|
+
"columnheader",
|
|
1365
|
+
"rowheader"
|
|
1366
|
+
].includes(role)) this.#silenced = this.#node;
|
|
1367
|
+
if (this.#node.localName === "textarea") {
|
|
1368
|
+
this.#node = skipBrowserSubtree(walker);
|
|
1369
|
+
continue;
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
const shadow = this.#node.shadowRoot;
|
|
1373
|
+
if (shadow !== null) {
|
|
1374
|
+
this.#text = this.#flush(rows, this.#text, this.#block);
|
|
1375
|
+
this.#input.walk(shadow, rows, muted || this.#silenced !== void 0);
|
|
1376
|
+
this.#node = skipBrowserSubtree(walker);
|
|
1377
|
+
continue;
|
|
1378
|
+
}
|
|
1379
|
+
this.#node = walker.nextNode();
|
|
1380
|
+
}
|
|
1381
|
+
this.#flush(rows, this.#text, this.#block);
|
|
1382
|
+
}
|
|
1383
|
+
#flush(rows, text, parent) {
|
|
1384
|
+
if (normalizeBrowserName(text) !== "") this.#input.text(rows, text, parent);
|
|
1385
|
+
return "";
|
|
1386
|
+
}
|
|
1387
|
+
};
|
|
1388
|
+
// src/browser/BrowserDOMWait.ts
|
|
1205
1389
|
/**
|
|
1206
1390
|
* Parks one condition on DOM mutations and finished transitions and animations until it holds, with one deadline and coalesced wake tasks.
|
|
1207
1391
|
*
|
|
@@ -1367,8 +1551,7 @@ var BrowserDOMWait = class {
|
|
|
1367
1551
|
this.#observers.clear();
|
|
1368
1552
|
}
|
|
1369
1553
|
};
|
|
1370
|
-
|
|
1371
|
-
//#region src/browser/elements/BrowserDOMElementManager.ts
|
|
1554
|
+
// src/browser/elements/BrowserDOMElementManager.ts
|
|
1372
1555
|
/**
|
|
1373
1556
|
* Outlines a DOM document from its elements and binds stable references to the interactive ones.
|
|
1374
1557
|
*
|
|
@@ -1497,98 +1680,16 @@ var BrowserDOMElementManager = class {
|
|
|
1497
1680
|
return collectBrowserRoots(this.#root(this.#input.document(), within));
|
|
1498
1681
|
}
|
|
1499
1682
|
#walk(root, rows, muted) {
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
while (node !== null) {
|
|
1511
|
-
if (silenced !== void 0 && !silenced.contains(node)) silenced = void 0;
|
|
1512
|
-
const quiet = muted || silenced !== void 0;
|
|
1513
|
-
if (!(node instanceof view.Element)) {
|
|
1514
|
-
const parent = node.parentElement;
|
|
1515
|
-
const data = node.textContent ?? "";
|
|
1516
|
-
const blank = data.trim() === "";
|
|
1517
|
-
if (!quiet && (!blank || text !== "") && (parent === null || !matchesBrowserInvisible(parent))) {
|
|
1518
|
-
const container = readBrowserBlock(parent, root);
|
|
1519
|
-
if (!blank) {
|
|
1520
|
-
if (block !== void 0 && block !== container) text = this.#flush(rows, text, block);
|
|
1521
|
-
text += data;
|
|
1522
|
-
block = container;
|
|
1523
|
-
} else if (container === block) text += data;
|
|
1524
|
-
}
|
|
1525
|
-
node = walker.nextNode();
|
|
1526
|
-
continue;
|
|
1527
|
-
}
|
|
1528
|
-
const invisible = matchesBrowserInvisible(node);
|
|
1529
|
-
if (node.localName === "br") {
|
|
1530
|
-
if (!quiet && text !== "") text += "\n";
|
|
1531
|
-
node = walker.nextNode();
|
|
1532
|
-
continue;
|
|
1533
|
-
}
|
|
1534
|
-
if (node instanceof view.HTMLIFrameElement) {
|
|
1535
|
-
text = this.#flush(rows, text, block);
|
|
1536
|
-
const child = node.contentDocument;
|
|
1537
|
-
if (invisible) {
|
|
1538
|
-
node = skipBrowserSubtree(walker);
|
|
1539
|
-
continue;
|
|
1540
|
-
}
|
|
1541
|
-
if (child === null) this.#text(rows, `iframe ${JSON.stringify(computeBrowserName(node))} (cross-origin, not readable)`);
|
|
1542
|
-
else this.#walk(child.documentElement, rows, quiet);
|
|
1543
|
-
node = skipBrowserSubtree(walker);
|
|
1544
|
-
continue;
|
|
1545
|
-
}
|
|
1546
|
-
if (node instanceof view.HTMLSlotElement) {
|
|
1547
|
-
text = this.#flush(rows, text, block);
|
|
1548
|
-
for (const assigned of node.assignedNodes({ flatten: true })) if (assigned instanceof view.Element) this.#walk(assigned, rows, quiet);
|
|
1549
|
-
else if (!quiet) this.#flush(rows, assigned.textContent ?? "");
|
|
1550
|
-
node = skipBrowserSubtree(walker);
|
|
1551
|
-
continue;
|
|
1552
|
-
}
|
|
1553
|
-
const tool = node.localName === "form" ? node.getAttribute("toolname") ?? "" : "";
|
|
1554
|
-
const role = tool === "" ? computeBrowserRole(node) : "form";
|
|
1555
|
-
if (!invisible && role !== void 0 && (BROWSER_INTERACTIVE_ROLES.has(role) || [
|
|
1556
|
-
"heading",
|
|
1557
|
-
"image",
|
|
1558
|
-
"img",
|
|
1559
|
-
"listitem",
|
|
1560
|
-
"row",
|
|
1561
|
-
"cell",
|
|
1562
|
-
"gridcell",
|
|
1563
|
-
"columnheader",
|
|
1564
|
-
"rowheader"
|
|
1565
|
-
].includes(role) || tool !== "")) {
|
|
1566
|
-
text = this.#flush(rows, text, block);
|
|
1567
|
-
const row = this.#row(node, role, view, String(rows.length));
|
|
1568
|
-
rows.push(row);
|
|
1569
|
-
this.#positions.set(node, row.id);
|
|
1570
|
-
if (silenced === void 0 && BROWSER_CONTENT_NAMED_ROLES.has(role) && ![
|
|
1571
|
-
"row",
|
|
1572
|
-
"cell",
|
|
1573
|
-
"gridcell",
|
|
1574
|
-
"columnheader",
|
|
1575
|
-
"rowheader"
|
|
1576
|
-
].includes(role)) silenced = node;
|
|
1577
|
-
if (node.localName === "textarea") {
|
|
1578
|
-
node = skipBrowserSubtree(walker);
|
|
1579
|
-
continue;
|
|
1580
|
-
}
|
|
1581
|
-
}
|
|
1582
|
-
const shadow = node.shadowRoot;
|
|
1583
|
-
if (shadow !== null) {
|
|
1584
|
-
text = this.#flush(rows, text, block);
|
|
1585
|
-
this.#walk(shadow, rows, muted || silenced !== void 0);
|
|
1586
|
-
node = skipBrowserSubtree(walker);
|
|
1587
|
-
continue;
|
|
1588
|
-
}
|
|
1589
|
-
node = walker.nextNode();
|
|
1590
|
-
}
|
|
1591
|
-
this.#flush(rows, text, block);
|
|
1683
|
+
new BrowserDOMOutline({
|
|
1684
|
+
root,
|
|
1685
|
+
rows,
|
|
1686
|
+
muted,
|
|
1687
|
+
positions: this.#positions,
|
|
1688
|
+
watch: this.#watch.bind(this),
|
|
1689
|
+
walk: this.#walk.bind(this),
|
|
1690
|
+
text: this.#text.bind(this),
|
|
1691
|
+
row: this.#row.bind(this)
|
|
1692
|
+
}).capture();
|
|
1592
1693
|
}
|
|
1593
1694
|
#watch(document) {
|
|
1594
1695
|
const view = document.defaultView;
|
|
@@ -1610,10 +1711,6 @@ var BrowserDOMElementManager = class {
|
|
|
1610
1711
|
const target = document.deref();
|
|
1611
1712
|
return target === void 0 ? -1 : this.#input.navigation() + (this.#epochs.get(target) ?? 0);
|
|
1612
1713
|
}
|
|
1613
|
-
#flush(rows, text, parent) {
|
|
1614
|
-
if (normalizeBrowserName(text) !== "") this.#text(rows, text, parent);
|
|
1615
|
-
return "";
|
|
1616
|
-
}
|
|
1617
1714
|
#text(rows, text, parent) {
|
|
1618
1715
|
rows.push({
|
|
1619
1716
|
id: String(rows.length),
|
|
@@ -1639,11 +1736,7 @@ var BrowserDOMElementManager = class {
|
|
|
1639
1736
|
const checked = input !== void 0 && (input.type === "checkbox" || input.type === "radio") ? input.checked : element.getAttribute("aria-checked") === "true";
|
|
1640
1737
|
const root = element.getRootNode();
|
|
1641
1738
|
const focused = (root instanceof view.Document || root instanceof view.ShadowRoot) && root.activeElement === element;
|
|
1642
|
-
|
|
1643
|
-
if (element instanceof view.HTMLSelectElement) value = Array.from(element.selectedOptions, (option) => option.label).join(", ");
|
|
1644
|
-
else if (element instanceof view.HTMLTextAreaElement) value = element.value;
|
|
1645
|
-
else if (input?.type === "password") value = "•".repeat(input.value.length);
|
|
1646
|
-
else if (input !== void 0 && input.type !== "password" && (BROWSER_TYPED_INPUTS.has(input.type) || input.type === "range")) value = input.value;
|
|
1739
|
+
const value = element instanceof view.HTMLSelectElement ? Array.from(element.selectedOptions, (option) => option.label).join(", ") : element instanceof view.HTMLTextAreaElement ? element.value : input?.type === "password" ? "•".repeat(input.value.length) : input === void 0 ? void 0 : input.type === "password" ? void 0 : BROWSER_TYPED_INPUTS.has(input.type) || input.type === "range" ? input.value : void 0;
|
|
1647
1740
|
return {
|
|
1648
1741
|
id: reference ?? id,
|
|
1649
1742
|
parent: this.#parent(element.parentNode),
|
|
@@ -1670,10 +1763,9 @@ var BrowserDOMElementManager = class {
|
|
|
1670
1763
|
};
|
|
1671
1764
|
}
|
|
1672
1765
|
#parent(node) {
|
|
1673
|
-
|
|
1674
|
-
const id = this.#positions.get(
|
|
1766
|
+
for (const current of new BrowserAncestors(node, (entry) => entry.parentNode)) {
|
|
1767
|
+
const id = this.#positions.get(current);
|
|
1675
1768
|
if (id !== void 0) return id;
|
|
1676
|
-
node = node.parentNode;
|
|
1677
1769
|
}
|
|
1678
1770
|
}
|
|
1679
1771
|
#bind(element, role, name) {
|
|
@@ -1715,8 +1807,7 @@ var BrowserDOMElementManager = class {
|
|
|
1715
1807
|
return this.#descriptions.get(node) ?? captured;
|
|
1716
1808
|
}
|
|
1717
1809
|
};
|
|
1718
|
-
|
|
1719
|
-
//#region src/browser/BrowserDOMView.ts
|
|
1810
|
+
// src/browser/BrowserDOMView.ts
|
|
1720
1811
|
/**
|
|
1721
1812
|
* Reads and drives a DOM document from a realm that can reach it, without trusted input.
|
|
1722
1813
|
*
|
|
@@ -1781,7 +1872,7 @@ var BrowserDOMView = class {
|
|
|
1781
1872
|
async read(options) {
|
|
1782
1873
|
this.#release.signal.throwIfAborted();
|
|
1783
1874
|
options?.signal?.throwIfAborted();
|
|
1784
|
-
return
|
|
1875
|
+
return createBrowserContent({
|
|
1785
1876
|
...readBrowserCapture(this.#current().documentElement),
|
|
1786
1877
|
navigation: this.#navigation.bind(this)
|
|
1787
1878
|
});
|
|
@@ -1832,8 +1923,7 @@ var BrowserDOMView = class {
|
|
|
1832
1923
|
listenBrowserNavigation(this.#window, this.#navigateHandler, this.#listeners.signal);
|
|
1833
1924
|
}
|
|
1834
1925
|
};
|
|
1835
|
-
|
|
1836
|
-
//#region src/browser/transports/SocketCDPTransport.ts
|
|
1926
|
+
// src/browser/transports/SocketCDPTransport.ts
|
|
1837
1927
|
/**
|
|
1838
1928
|
* Provides a raw CDP text transport over the browser's native `WebSocket`.
|
|
1839
1929
|
*
|
|
@@ -1961,8 +2051,7 @@ var SocketCDPTransport = class {
|
|
|
1961
2051
|
return new BrowserError("CONNECTION", `Socket CDP connection to ${this.#url} ${reason}`, { url: this.#url });
|
|
1962
2052
|
}
|
|
1963
2053
|
};
|
|
1964
|
-
|
|
1965
|
-
//#region src/browser/factories.ts
|
|
2054
|
+
// src/browser/factories.ts
|
|
1966
2055
|
/**
|
|
1967
2056
|
* Creates a view that reads and drives a DOM document without trusted input.
|
|
1968
2057
|
*
|
|
@@ -2000,7 +2089,6 @@ function createBrowserDOMView(options) {
|
|
|
2000
2089
|
function createSocketCDPTransport(options) {
|
|
2001
2090
|
return new SocketCDPTransport(options);
|
|
2002
2091
|
}
|
|
2003
|
-
|
|
2004
|
-
export { BROWSER_CONTENT_NAMED_ROLES, BROWSER_CONTEXT_TARGETS, BROWSER_DOCUMENT_TIMEOUT_MS, BROWSER_EXPANDED_ROLES, BROWSER_IMPLICIT_ROLES, BROWSER_INTERACTIVE_CONTENT, BROWSER_SELECTED_ROLES, BROWSER_TYPED_INPUTS, BrowserDOMView, BrowserDOMWait, SocketCDPTransport, collectBrowserRoots, computeBrowserAlternative, computeBrowserName, computeBrowserRole, computeBrowserText, createBrowserDOMView, createSocketCDPTransport, isBrowserDocument, listenBrowserNavigation, matchesBrowserActivation, matchesBrowserBlock, matchesBrowserHidden, matchesBrowserInvisible, matchesBrowserOmitted, matchesBrowserPopup, readBrowserBlock, readBrowserCapture, readBrowserContent, readBrowserParent, readBrowserStates, readBrowserToken, skipBrowserSubtree };
|
|
2092
|
+
export { BROWSER_CONTENT_NAMED_ROLES, BROWSER_CONTEXT_TARGETS, BROWSER_DOCUMENT_TIMEOUT_MS, BROWSER_EXPANDED_ROLES, BROWSER_IMPLICIT_ROLES, BROWSER_INTERACTIVE_CONTENT, BROWSER_SELECTED_ROLES, BROWSER_TYPED_INPUTS, BrowserAncestors, BrowserCaptureSiblings, BrowserDOMOutline, BrowserDOMSubmission, BrowserDOMView, BrowserDOMWait, SocketCDPTransport, collectBrowserRoots, computeBrowserAlternative, computeBrowserName, computeBrowserRole, computeBrowserText, createBrowserDOMView, createSocketCDPTransport, isBrowserDocument, listenBrowserNavigation, matchesBrowserActivation, matchesBrowserBlock, matchesBrowserHidden, matchesBrowserInvisible, matchesBrowserOmitted, matchesBrowserPopup, readBrowserBlock, readBrowserCapture, readBrowserCaptureOmission, readBrowserCaptureValue, readBrowserContent, readBrowserParent, readBrowserStates, readBrowserToken, skipBrowserSubtree };
|
|
2005
2093
|
|
|
2006
2094
|
//# sourceMappingURL=index.js.map
|