@reticlehq/browser 3.8.2 → 3.9.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/dist/actions/actions-dom.d.ts +1 -0
- package/dist/actions/actions-dom.js +4 -1
- package/dist/actions/actions.js +13 -3
- package/dist/commands/commands.js +4 -4
- package/dist/dom/a11y.d.ts +1 -1
- package/dist/dom/a11y.js +7 -7
- package/dist/dom/query.d.ts +5 -2
- package/dist/dom/query.js +88 -4
- package/dist/dom/text-match.js +2 -1
- package/dist/dom/visibility.d.ts +1 -0
- package/dist/dom/visibility.js +86 -3
- package/dist/observers/network.js +3 -1
- package/dist/reticle-inject.js +367 -367
- package/dist/reticle.js +2 -2
- package/dist/review/annotator.js +4 -0
- package/dist/session-continuity.d.ts +1 -0
- package/dist/session-continuity.js +18 -0
- package/package.json +2 -2
|
@@ -25,6 +25,7 @@ export declare function fireClickSequence(el: ActionTarget, hold?: {
|
|
|
25
25
|
prevented: boolean;
|
|
26
26
|
heldMs: number;
|
|
27
27
|
}>;
|
|
28
|
+
export declare function scrollIntoViewNow(el: Element, options?: ScrollIntoViewOptions): void;
|
|
28
29
|
export declare function isOffViewport(el: Element, rect: DOMRect): boolean;
|
|
29
30
|
export declare function clickGeometry(el: ActionTarget): ClickGeometry;
|
|
30
31
|
export declare function firePointer(el: Element, type: string, relatedTarget?: EventTarget | null): void;
|
|
@@ -88,6 +88,9 @@ export async function fireClickSequence(el, hold, detail, input = PLAIN_CLICK) {
|
|
|
88
88
|
function isMeasurable(rect) {
|
|
89
89
|
return rect.width > 0 || rect.height > 0;
|
|
90
90
|
}
|
|
91
|
+
export function scrollIntoViewNow(el, options = {}) {
|
|
92
|
+
el.scrollIntoView({ ...options, behavior: 'instant' });
|
|
93
|
+
}
|
|
91
94
|
export function isOffViewport(el, rect) {
|
|
92
95
|
const win = el.ownerDocument.defaultView;
|
|
93
96
|
if (null === win) {
|
|
@@ -128,7 +131,7 @@ export function clickGeometry(el) {
|
|
|
128
131
|
}
|
|
129
132
|
let scrolledIntoView = false;
|
|
130
133
|
if (isOffViewport(el, rect) && 'function' === typeof el.scrollIntoView) {
|
|
131
|
-
el
|
|
134
|
+
scrollIntoViewNow(el, { block: 'center', inline: 'center' });
|
|
132
135
|
scrolledIntoView = true;
|
|
133
136
|
rect = el.getBoundingClientRect();
|
|
134
137
|
}
|
package/dist/actions/actions.js
CHANGED
|
@@ -100,7 +100,7 @@ function optionValueFor(options, named) {
|
|
|
100
100
|
const byLabel = options.filter((option) => option.label.trim() === named.trim());
|
|
101
101
|
return 1 === byLabel.length ? (byLabel[0]?.value ?? named) : named;
|
|
102
102
|
}
|
|
103
|
-
import { NO_GEOMETRY, fireClickSequence, fireTapSequence, clickGeometry, firePointer, firePointerNonBubbling, dragElement, mouseEventFor, assertNotLeaving, clickInputOf, } from './actions-dom.js';
|
|
103
|
+
import { NO_GEOMETRY, fireClickSequence, fireTapSequence, clickGeometry, firePointer, firePointerNonBubbling, dragElement, mouseEventFor, assertNotLeaving, scrollIntoViewNow, clickInputOf, } from './actions-dom.js';
|
|
104
104
|
import { scrollFrom } from './scroll.js';
|
|
105
105
|
import { clickHitsNoControl } from './click-target.js';
|
|
106
106
|
function enabledOf(el) {
|
|
@@ -290,7 +290,7 @@ async function dispatchOther(el, action, args) {
|
|
|
290
290
|
return !down;
|
|
291
291
|
}
|
|
292
292
|
case ActionType.SCROLL_INTO_VIEW:
|
|
293
|
-
el
|
|
293
|
+
scrollIntoViewNow(el);
|
|
294
294
|
return false;
|
|
295
295
|
case ActionType.ZOOM:
|
|
296
296
|
throw new Error(ZOOM_NEEDS_REAL_BROWSER_MSG);
|
|
@@ -358,9 +358,19 @@ export async function executeAction(ref, action, args = {}) {
|
|
|
358
358
|
assertNotLeaving(el);
|
|
359
359
|
}
|
|
360
360
|
const anchor = named ? anchorOf(el) : {};
|
|
361
|
+
const prevFocus = activeRef(el);
|
|
362
|
+
if ((ActionType.CLICK === action || ActionType.DBLCLICK === action) && !enabledOf(el)) {
|
|
363
|
+
const active = el.ownerDocument.activeElement;
|
|
364
|
+
if (active !== null &&
|
|
365
|
+
active !== el &&
|
|
366
|
+
active !== el.ownerDocument.body &&
|
|
367
|
+
isActionTarget(active)) {
|
|
368
|
+
active.blur();
|
|
369
|
+
await Promise.resolve();
|
|
370
|
+
}
|
|
371
|
+
}
|
|
361
372
|
const visible = isVisible(el);
|
|
362
373
|
const enabled = enabledOf(el);
|
|
363
|
-
const prevFocus = activeRef(el);
|
|
364
374
|
const valueBefore = valueOf(el);
|
|
365
375
|
const actedField = watchActedField(el);
|
|
366
376
|
const geometry = CLICK_LIKE.has(action) ? clickGeometry(el) : NO_GEOMETRY;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ComponentStateReason, ElementQuerySchema, INSPECT_IN_VIEWPORT_FIELD, INSPECT_REVEAL_ARG, ReticleCommand, SnapshotMode, TRANSPORT_LIMITS, selectPath, capDepth, } from '@reticlehq/core';
|
|
1
|
+
import { ComponentStateReason, ElementQuerySchema, INSPECT_IN_VIEWPORT_FIELD, INSPECT_REVEAL_ARG, MatchArg, ReticleCommand, SnapshotMode, TRANSPORT_LIMITS, selectPath, capDepth, } from '@reticlehq/core';
|
|
2
2
|
import { buildSnapshot } from '../dom/snapshot.js';
|
|
3
3
|
import { paintContextOf } from '../dom/paint-context.js';
|
|
4
4
|
import { matchQuery, runQuery } from '../dom/query.js';
|
|
@@ -19,7 +19,7 @@ import { sanitizeWithReport } from '../security/serialization.js';
|
|
|
19
19
|
import { getCapabilities } from '../registry/capabilities.js';
|
|
20
20
|
import { freezeClock, advanceClock, resetClock, isClockFrozen } from '../timers/clock.js';
|
|
21
21
|
import { scrollContainer } from '../actions/scroll.js';
|
|
22
|
-
import { isOffViewport } from '../actions/actions-dom.js';
|
|
22
|
+
import { isOffViewport, scrollIntoViewNow } from '../actions/actions-dom.js';
|
|
23
23
|
export const RELOAD_CACHE_BUST_PARAM = '_reticle_reload';
|
|
24
24
|
function str(value) {
|
|
25
25
|
return 'string' === typeof value ? value : undefined;
|
|
@@ -69,7 +69,7 @@ function inspect(ref, reveal = false) {
|
|
|
69
69
|
'function' === typeof el.scrollIntoView &&
|
|
70
70
|
isOffViewport(el, el.getBoundingClientRect());
|
|
71
71
|
if (scrolledIntoView) {
|
|
72
|
-
el
|
|
72
|
+
scrollIntoViewNow(el, { block: 'center', inline: 'center' });
|
|
73
73
|
}
|
|
74
74
|
const rect = el.getBoundingClientRect();
|
|
75
75
|
const component = identifyComponent(el);
|
|
@@ -216,7 +216,7 @@ export function createCommandRegistry() {
|
|
|
216
216
|
const limit = args['limit'];
|
|
217
217
|
return runQuery(queryFromArgs(args), 'number' === typeof limit ? limit : undefined);
|
|
218
218
|
});
|
|
219
|
-
reg.set(ReticleCommand.MATCH, (args) => matchQuery(ElementQuerySchema.parse(record(args['query'])), str(args['state']), undefined, undefined, true === args['diagnose']));
|
|
219
|
+
reg.set(ReticleCommand.MATCH, (args) => matchQuery(ElementQuerySchema.parse(record(args['query'])), str(args['state']), undefined, undefined, true === args['diagnose'], true === args[MatchArg.FULL_TEXT]));
|
|
220
220
|
reg.set(ReticleCommand.ACT, async (args) => {
|
|
221
221
|
const action = str(args['action']) ?? '';
|
|
222
222
|
const result = await executeAction(str(args['ref']) ?? '', action, record(args['args']));
|
package/dist/dom/a11y.d.ts
CHANGED
|
@@ -4,4 +4,4 @@ export declare function getAccessibleName(el: Element): string;
|
|
|
4
4
|
export declare function getStates(el: Element, visible?: boolean): ElementState[];
|
|
5
5
|
export declare function isSensitiveField(el: Element): boolean;
|
|
6
6
|
export declare function getValue(el: Element): string | undefined;
|
|
7
|
-
export declare function describe(el: Element, memo?: Map<Element, boolean
|
|
7
|
+
export declare function describe(el: Element, memo?: Map<Element, boolean>, fullText?: boolean): ElementDescriptor;
|
package/dist/dom/a11y.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readTestId } from './addressing/testid-attr.js';
|
|
2
|
-
import { DEFAULT_TESTID_ATTR, ElementState, REDACTED_VALUE, } from '@reticlehq/core';
|
|
2
|
+
import { DEFAULT_TESTID_ATTR, ElementState, REDACTED_VALUE, TRANSPORT_LIMITS, } from '@reticlehq/core';
|
|
3
3
|
import { isButton, isImage, isInput, isMeter, isOutput, isProgress, isSelect, isTextArea, } from './realm.js';
|
|
4
4
|
import { refs } from './addressing/refs.js';
|
|
5
5
|
import { inspectChart } from './chart.js';
|
|
@@ -360,14 +360,14 @@ export function getValue(el) {
|
|
|
360
360
|
const valueNow = el.getAttribute('aria-valuenow');
|
|
361
361
|
return valueNow ?? undefined;
|
|
362
362
|
}
|
|
363
|
-
|
|
364
|
-
function getVisibleText(el) {
|
|
363
|
+
function getVisibleText(el, fullText) {
|
|
365
364
|
const text = collapse(el.textContent ?? '');
|
|
366
|
-
|
|
365
|
+
const max = fullText ? TRANSPORT_LIMITS.MAX_FULL_TEXT : TRANSPORT_LIMITS.MAX_DESCRIBED_TEXT;
|
|
366
|
+
return text.length > max ? `${text.slice(0, max)}…` : text;
|
|
367
367
|
}
|
|
368
|
-
export function describe(el, memo) {
|
|
368
|
+
export function describe(el, memo, fullText = false) {
|
|
369
369
|
const value = getValue(el);
|
|
370
|
-
const text = getVisibleText(el);
|
|
370
|
+
const text = getVisibleText(el, fullText);
|
|
371
371
|
const name = getAccessibleName(el);
|
|
372
372
|
const visible = isVisible(el, memo);
|
|
373
373
|
const base = {
|
|
@@ -380,7 +380,7 @@ export function describe(el, memo) {
|
|
|
380
380
|
if (value !== undefined && value.length > 0) {
|
|
381
381
|
base.value = value;
|
|
382
382
|
}
|
|
383
|
-
if (text.length > 0 && text !== name) {
|
|
383
|
+
if (text.length > 0 && (fullText || text !== name)) {
|
|
384
384
|
base.text = text;
|
|
385
385
|
}
|
|
386
386
|
const source = formatSource(sourceFromDom(el));
|
package/dist/dom/query.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import { ElementState, type ElementQuery, type MatchResult, type QueryResult } from '@reticlehq/core';
|
|
1
|
+
import { ElementState, type ElementQuery, type MatchResult, type NearestTarget, type QueryResult } from '@reticlehq/core';
|
|
2
2
|
export declare const ATTR_VALUE_MAX = 512;
|
|
3
|
-
export declare function matchQuery(query: ElementQuery, state?: ElementState, limit?: number, wholePhrase?: boolean, diagnose?: boolean): MatchResult;
|
|
3
|
+
export declare function matchQuery(query: ElementQuery, state?: ElementState, limit?: number, wholePhrase?: boolean, diagnose?: boolean, fullText?: boolean): MatchResult;
|
|
4
|
+
export declare const MAX_NEAREST_NAME = 60;
|
|
5
|
+
export declare function nameSimilarity(a: string, b: string): number;
|
|
6
|
+
export declare function nearestTargets(container: HTMLElement, query: ElementQuery): NearestTarget[];
|
|
4
7
|
export declare function elementsMatching(query: ElementQuery): HTMLElement[];
|
|
5
8
|
export declare function runQuery(query: ElementQuery, limit?: number): QueryResult;
|
package/dist/dom/query.js
CHANGED
|
@@ -12,6 +12,7 @@ import { stableSelectorFor } from './stable-selector.js';
|
|
|
12
12
|
import { fuzzyVisibleText, normaliseVisibleText, comparableText, foldTypography, textMatches, unreadableMatch, } from './text-match.js';
|
|
13
13
|
import { pageRoute } from '../security/page-location.js';
|
|
14
14
|
import { elementsUnder, findIn, isNonRendered, queryByRoleAndName } from './query-finders.js';
|
|
15
|
+
import { INTERACTIVE } from './snapshot.js';
|
|
15
16
|
const MAX_PRESENT_TESTIDS = 12;
|
|
16
17
|
function visibleTextOf(el, memo) {
|
|
17
18
|
if (isNonRendered(el) || isWordsIgnored(el)) {
|
|
@@ -149,7 +150,7 @@ function inState(el, state, memo) {
|
|
|
149
150
|
return getStates(el, isVisible(el, memo)).includes(state);
|
|
150
151
|
}
|
|
151
152
|
const MAX_DESCRIBED = TRANSPORT_LIMITS.MAX_COLLECTION_ITEMS;
|
|
152
|
-
export function matchQuery(query, state, limit = MAX_DESCRIBED, wholePhrase = true, diagnose = true) {
|
|
153
|
+
export function matchQuery(query, state, limit = MAX_DESCRIBED, wholePhrase = true, diagnose = true, fullText = false) {
|
|
153
154
|
const found = findCandidates(query);
|
|
154
155
|
const scopeMissing = found.scopeMissing;
|
|
155
156
|
const wanted = wholePhrase && textOnly(query) ? wantedTextOf(query) : undefined;
|
|
@@ -159,10 +160,13 @@ export function matchQuery(query, state, limit = MAX_DESCRIBED, wholePhrase = tr
|
|
|
159
160
|
const visMemo = new Map();
|
|
160
161
|
const filtered = state === undefined ? elements : elements.filter((el) => inState(el, state, visMemo));
|
|
161
162
|
const attrs = query.attrs;
|
|
162
|
-
const
|
|
163
|
+
const cap = fullText
|
|
164
|
+
? Math.min(MAX_DESCRIBED, TRANSPORT_LIMITS.MAX_FULL_TEXT_ELEMENTS)
|
|
165
|
+
: MAX_DESCRIBED;
|
|
166
|
+
const described = filtered.slice(0, Math.max(0, Math.min(limit, cap)));
|
|
163
167
|
const stampViewport = described.length > 1 || ElementState.IN_VIEWPORT === state;
|
|
164
168
|
const descriptors = described.map((el) => {
|
|
165
|
-
let base = describe(el, visMemo);
|
|
169
|
+
let base = describe(el, visMemo, fullText);
|
|
166
170
|
if (stampViewport &&
|
|
167
171
|
isInViewport(el, visMemo) &&
|
|
168
172
|
!base.states.includes(ElementState.IN_VIEWPORT)) {
|
|
@@ -180,6 +184,7 @@ export function matchQuery(query, state, limit = MAX_DESCRIBED, wholePhrase = tr
|
|
|
180
184
|
matched: filtered.length > 0,
|
|
181
185
|
count: filtered.length,
|
|
182
186
|
elements: descriptors,
|
|
187
|
+
...(fullText ? { fullText: true } : {}),
|
|
183
188
|
...(scopeMissing ? { scopeMissing: true } : {}),
|
|
184
189
|
...(covered === undefined ? {} : { coveredBy: covered }),
|
|
185
190
|
...(unreadable === undefined ? {} : { unreadable }),
|
|
@@ -346,6 +351,81 @@ function nameNearMisses(container, query) {
|
|
|
346
351
|
}
|
|
347
352
|
return out;
|
|
348
353
|
}
|
|
354
|
+
const MAX_NEAREST_TARGETS = 3;
|
|
355
|
+
export const MAX_NEAREST_NAME = 60;
|
|
356
|
+
function bigrams(value) {
|
|
357
|
+
const padded = ` ${value} `;
|
|
358
|
+
const out = new Set();
|
|
359
|
+
for (let i = 0; i < padded.length - 1; i += 1) {
|
|
360
|
+
out.add(padded.slice(i, i + 2));
|
|
361
|
+
}
|
|
362
|
+
return out;
|
|
363
|
+
}
|
|
364
|
+
export function nameSimilarity(a, b) {
|
|
365
|
+
const left = bigrams(foldTypography(a).toLowerCase());
|
|
366
|
+
const right = bigrams(foldTypography(b).toLowerCase());
|
|
367
|
+
let shared = 0;
|
|
368
|
+
for (const gram of left) {
|
|
369
|
+
if (right.has(gram)) {
|
|
370
|
+
shared += 1;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return (2 * shared) / (left.size + right.size);
|
|
374
|
+
}
|
|
375
|
+
function askedRole(query) {
|
|
376
|
+
return QueryBy.ROLE === query.by ? query.value : query.role;
|
|
377
|
+
}
|
|
378
|
+
function askedName(query) {
|
|
379
|
+
const value = QueryBy.ROLE === query.by ? undefined : query.value;
|
|
380
|
+
const asked = query.name ??
|
|
381
|
+
value ??
|
|
382
|
+
query.text ??
|
|
383
|
+
query.label ??
|
|
384
|
+
query.placeholder ??
|
|
385
|
+
query.testid ??
|
|
386
|
+
query.alt;
|
|
387
|
+
return asked === undefined ? '' : normaliseVisibleText(asked);
|
|
388
|
+
}
|
|
389
|
+
export function nearestTargets(container, query) {
|
|
390
|
+
const role = askedRole(query);
|
|
391
|
+
const name = askedName(query);
|
|
392
|
+
if (role === undefined && 0 === name.length) {
|
|
393
|
+
return [];
|
|
394
|
+
}
|
|
395
|
+
const memo = new Map();
|
|
396
|
+
const scored = [];
|
|
397
|
+
for (const el of elementsUnder(container)) {
|
|
398
|
+
const elRole = getRole(el);
|
|
399
|
+
const roleMatch = role !== undefined && elRole === role;
|
|
400
|
+
if (!roleMatch && !INTERACTIVE.has(elRole)) {
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
if (isIgnored(el) || !isVisible(el, memo)) {
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
const elName = normaliseVisibleText(getAccessibleName(el));
|
|
407
|
+
if (0 === elName.length) {
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
const score = 0 === name.length ? 0 : nameSimilarity(name, elName);
|
|
411
|
+
if (!roleMatch && 0 === score) {
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
scored.push({ el, role: elRole, name: elName, roleMatch, score });
|
|
415
|
+
}
|
|
416
|
+
scored.sort((a, b) => Number(b.roleMatch) - Number(a.roleMatch) || b.score - a.score);
|
|
417
|
+
return scored.slice(0, MAX_NEAREST_TARGETS).map((c) => {
|
|
418
|
+
const target = {
|
|
419
|
+
ref: refs.refFor(c.el),
|
|
420
|
+
role: c.role,
|
|
421
|
+
name: c.name.slice(0, MAX_NEAREST_NAME),
|
|
422
|
+
};
|
|
423
|
+
if (c.name.length > MAX_NEAREST_NAME) {
|
|
424
|
+
target.nameTruncated = true;
|
|
425
|
+
}
|
|
426
|
+
return target;
|
|
427
|
+
});
|
|
428
|
+
}
|
|
349
429
|
function testidFoundUnder(container, query) {
|
|
350
430
|
if (QueryBy.TESTID !== query.by || undefined === query.value) {
|
|
351
431
|
return undefined;
|
|
@@ -452,6 +532,10 @@ function buildEmptyHint(query, state, diagnose = true) {
|
|
|
452
532
|
if (elsewhere !== undefined) {
|
|
453
533
|
hint.testidFoundUnder = elsewhere;
|
|
454
534
|
}
|
|
535
|
+
const nearest = diagnose ? nearestTargets(container, query) : [];
|
|
536
|
+
if (nearest.length > 0) {
|
|
537
|
+
hint.nearest = nearest;
|
|
538
|
+
}
|
|
455
539
|
return hint;
|
|
456
540
|
}
|
|
457
541
|
export function elementsMatching(query) {
|
|
@@ -464,7 +548,7 @@ export function runQuery(query, limit) {
|
|
|
464
548
|
return {
|
|
465
549
|
elements: result.elements,
|
|
466
550
|
count: result.count,
|
|
467
|
-
hint: buildEmptyHint(query),
|
|
551
|
+
hint: result.hint ?? buildEmptyHint(query),
|
|
468
552
|
...scopeFields,
|
|
469
553
|
};
|
|
470
554
|
}
|
package/dist/dom/text-match.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { parentAcrossShadowBoundary } from './shadow-registry.js';
|
|
2
|
+
import { stalledFade } from './visibility.js';
|
|
2
3
|
export function normaliseVisibleText(value) {
|
|
3
4
|
return value.replace(/\s+/g, ' ').trim().normalize('NFC');
|
|
4
5
|
}
|
|
@@ -81,7 +82,7 @@ export function unreadableText(el) {
|
|
|
81
82
|
let opaque = false;
|
|
82
83
|
for (let node = el; null !== node; node = parentAcrossShadowBoundary(node)) {
|
|
83
84
|
const style = view.getComputedStyle(node);
|
|
84
|
-
const alpha = Number.parseFloat(style.opacity);
|
|
85
|
+
const alpha = stalledFade(node) ? 1 : Number.parseFloat(style.opacity);
|
|
85
86
|
opacity *= Number.isNaN(alpha) ? 1 : alpha;
|
|
86
87
|
if (opaque) {
|
|
87
88
|
continue;
|
package/dist/dom/visibility.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export declare const HTML_DETAILS_TAG = "details";
|
|
2
2
|
export declare const HTML_DETAILS_OPEN_ATTRIBUTE = "open";
|
|
3
3
|
export declare const HTML_SUMMARY_TAG = "summary";
|
|
4
|
+
export declare function stalledFade(el: Element): boolean;
|
|
4
5
|
export declare function isHiddenByAriaOnly(el: Element): boolean;
|
|
5
6
|
export declare function isRenderedOutsideAriaHidden(el: Element): boolean;
|
|
6
7
|
export declare function isInViewport(el: Element, memo?: Map<Element, boolean>): boolean;
|
package/dist/dom/visibility.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { RETICLE_OVERLAY_SELECTOR } from '@reticlehq/core';
|
|
1
2
|
import { isHtmlElement } from './realm.js';
|
|
2
3
|
import { parentAcrossShadowBoundary } from './shadow-registry.js';
|
|
3
4
|
import { clippingBox, overflowAxes, scrolledInto, } from './paint-context.js';
|
|
@@ -16,15 +17,45 @@ function hiddenInsideClosedDetails(el) {
|
|
|
16
17
|
const summary = Array.from(details.children).find((child) => HTML_SUMMARY_TAG === child.localName);
|
|
17
18
|
return summary === undefined || !summary.contains(el);
|
|
18
19
|
}
|
|
19
|
-
function stalledFade(el) {
|
|
20
|
+
export function stalledFade(el) {
|
|
20
21
|
if (!el.ownerDocument.hidden || !('getAnimations' in el)) {
|
|
21
22
|
return false;
|
|
22
23
|
}
|
|
23
24
|
return el.getAnimations().some((a) => 'running' === a.playState || a.pending);
|
|
24
25
|
}
|
|
25
26
|
const OVERLAY_ARIA_HIDDEN = 'data-aria-hidden';
|
|
27
|
+
const OPEN_LAYER = '[role="dialog"], [role="alertdialog"], [role="menu"], [role="listbox"], dialog[open], [aria-modal="true"]';
|
|
28
|
+
const UNPAINTED_TAGS = new Set(['script', 'style', 'template', 'noscript', 'link', 'meta']);
|
|
26
29
|
function hiddenByAnOverlay(el) {
|
|
27
|
-
|
|
30
|
+
if (el.hasAttribute(OVERLAY_ARIA_HIDDEN)) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
return hiddenByAModalManager(el);
|
|
34
|
+
}
|
|
35
|
+
function hiddenByAModalManager(el) {
|
|
36
|
+
const body = el.ownerDocument.body;
|
|
37
|
+
if (el.parentElement !== body) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
const others = Array.from(body.children).filter((child) => child !== el &&
|
|
41
|
+
!UNPAINTED_TAGS.has(child.localName) &&
|
|
42
|
+
!child.matches(RETICLE_OVERLAY_SELECTOR));
|
|
43
|
+
const open = others.filter((child) => 'true' !== child.getAttribute('aria-hidden'));
|
|
44
|
+
const layer = open[0];
|
|
45
|
+
return 1 === open.length && layer !== undefined && holdsOpenLayer(layer);
|
|
46
|
+
}
|
|
47
|
+
function holdsOpenLayer(portal) {
|
|
48
|
+
const layer = portal.matches(OPEN_LAYER) ? portal : portal.querySelector(OPEN_LAYER);
|
|
49
|
+
for (let node = layer; null !== node; node = node.parentElement) {
|
|
50
|
+
const style = node.ownerDocument.defaultView?.getComputedStyle(node) ?? null;
|
|
51
|
+
if (selfCssHidden(node, style)) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
if (node === portal) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
28
59
|
}
|
|
29
60
|
function selfCssHidden(el, style) {
|
|
30
61
|
if (isHtmlElement(el) && el.hidden) {
|
|
@@ -106,6 +137,56 @@ export function isRenderedOutsideAriaHidden(el) {
|
|
|
106
137
|
}
|
|
107
138
|
return true;
|
|
108
139
|
}
|
|
140
|
+
const BACKFACE_VISIBILITY_HIDDEN = 'hidden';
|
|
141
|
+
function localTransform(style) {
|
|
142
|
+
if ('undefined' === typeof DOMMatrix) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
const transform = style?.transform;
|
|
147
|
+
return undefined === transform || '' === transform || 'none' === transform
|
|
148
|
+
? new DOMMatrix()
|
|
149
|
+
: new DOMMatrix(transform);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function backFaceTowardViewer(matrix) {
|
|
156
|
+
return Number.isFinite(matrix.m33) && 0 > matrix.m33;
|
|
157
|
+
}
|
|
158
|
+
function backfaceHidden(el, memo) {
|
|
159
|
+
const chain = [];
|
|
160
|
+
let declares = false;
|
|
161
|
+
for (let current = el; null !== current; current = parentAcrossShadowBoundary(current)) {
|
|
162
|
+
const style = ownVisibilityInfo(current, memo).style;
|
|
163
|
+
if (BACKFACE_VISIBILITY_HIDDEN === style?.backfaceVisibility) {
|
|
164
|
+
declares = true;
|
|
165
|
+
}
|
|
166
|
+
chain.push(current);
|
|
167
|
+
}
|
|
168
|
+
if (!declares) {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
let composed = null;
|
|
172
|
+
for (let index = chain.length - 1; index >= 0; index -= 1) {
|
|
173
|
+
const current = chain[index];
|
|
174
|
+
if (undefined === current) {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
const style = ownVisibilityInfo(current, memo).style;
|
|
178
|
+
const local = localTransform(style);
|
|
179
|
+
if (null === local) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
composed = null === composed ? local : composed.multiply(local);
|
|
183
|
+
if (BACKFACE_VISIBILITY_HIDDEN === style?.backfaceVisibility &&
|
|
184
|
+
backFaceTowardViewer(composed)) {
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
109
190
|
const CLIPPING_OVERFLOW = new Set(['hidden', 'clip']);
|
|
110
191
|
const SCROLLING_OVERFLOW = new Set(['auto', 'scroll']);
|
|
111
192
|
const NON_CLIPPING_DISPLAY = new Set([
|
|
@@ -314,7 +395,9 @@ export function isVisible(el, memo) {
|
|
|
314
395
|
return cached;
|
|
315
396
|
}
|
|
316
397
|
const info = visibilityInfoMemo(memo);
|
|
317
|
-
const result = visibleBounds(el, info, undefined, true) !== null &&
|
|
398
|
+
const result = visibleBounds(el, info, undefined, true) !== null &&
|
|
399
|
+
!collapsed(el, info) &&
|
|
400
|
+
!backfaceHidden(el, info);
|
|
318
401
|
if (memo !== undefined) {
|
|
319
402
|
memo.set(el, result);
|
|
320
403
|
}
|
|
@@ -228,6 +228,7 @@ export function installNetwork(emit, opts = {}) {
|
|
|
228
228
|
}
|
|
229
229
|
const owner = {};
|
|
230
230
|
pageSlots()[PAGE_CAPTURE] = owner;
|
|
231
|
+
const installedAt = performance.now();
|
|
231
232
|
const captureBodies = true === opts.captureBodies;
|
|
232
233
|
const bodyCapture = new AbortController();
|
|
233
234
|
const captureErrorBodies = false !== opts.captureErrorBodies;
|
|
@@ -550,7 +551,8 @@ export function installNetwork(emit, opts = {}) {
|
|
|
550
551
|
return;
|
|
551
552
|
}
|
|
552
553
|
const type = entry.initiatorType || 'other';
|
|
553
|
-
|
|
554
|
+
const patched = 'fetch' === type || 'xmlhttprequest' === type;
|
|
555
|
+
if (patched && entry.startTime >= installedAt) {
|
|
554
556
|
continue;
|
|
555
557
|
}
|
|
556
558
|
const rawUrl = entry.name;
|