@jobshimo/browser-link 0.36.0 → 0.38.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/agent-instructions/content.js +5 -349
- package/dist/agent-instructions/content.js.map +1 -1
- package/dist/bridge/server.d.ts +5 -0
- package/dist/bridge/server.js +1 -1
- package/dist/bridge/server.js.map +1 -1
- package/dist/cdp/flow.d.ts +44 -3
- package/dist/cdp/flow.js +65 -19
- package/dist/cdp/flow.js.map +1 -1
- package/dist/cdp/inpage/builders.d.ts +110 -24
- package/dist/cdp/inpage/builders.js +203 -53
- package/dist/cdp/inpage/builders.js.map +1 -1
- package/dist/cdp/inpage/deep-query.d.ts +1 -1
- package/dist/cdp/inpage/deep-query.js +12 -9
- package/dist/cdp/inpage/deep-query.js.map +1 -1
- package/dist/cdp/inpage/observer.d.ts +36 -7
- package/dist/cdp/inpage/observer.js +242 -16
- package/dist/cdp/inpage/observer.js.map +1 -1
- package/dist/cdp/transport.js +96 -9
- package/dist/cdp/transport.js.map +1 -1
- package/dist/commands/about.js +4 -2
- package/dist/commands/about.js.map +1 -1
- package/dist/extension/background.js +97 -13
- package/dist/extension/background.js.map +1 -1
- package/dist/extension/flow.d.ts +44 -3
- package/dist/extension/flow.js +65 -19
- package/dist/extension/flow.js.map +1 -1
- package/dist/extension/inpage/builders.d.ts +110 -24
- package/dist/extension/inpage/builders.js +203 -53
- package/dist/extension/inpage/builders.js.map +1 -1
- package/dist/extension/inpage/deep-query.d.ts +1 -1
- package/dist/extension/inpage/deep-query.js +12 -9
- package/dist/extension/inpage/deep-query.js.map +1 -1
- package/dist/extension/inpage/observer.d.ts +36 -7
- package/dist/extension/inpage/observer.js +242 -16
- package/dist/extension/inpage/observer.js.map +1 -1
- package/dist/extension/manifest.json +1 -1
- package/dist/manual/core.d.ts +20 -0
- package/dist/manual/core.js +71 -0
- package/dist/manual/core.js.map +1 -0
- package/dist/manual/render.d.ts +22 -0
- package/dist/manual/render.js +56 -0
- package/dist/manual/render.js.map +1 -0
- package/dist/manual/status.d.ts +23 -0
- package/dist/manual/status.js +52 -0
- package/dist/manual/status.js.map +1 -0
- package/dist/manual/tool.d.ts +20 -0
- package/dist/manual/tool.js +106 -0
- package/dist/manual/tool.js.map +1 -0
- package/dist/manual/topics.d.ts +18 -0
- package/dist/manual/topics.js +60 -0
- package/dist/manual/topics.js.map +1 -0
- package/dist/map/queries.d.ts +2 -0
- package/dist/map/queries.js +23 -1
- package/dist/map/queries.js.map +1 -1
- package/dist/map/tools.js +26 -4
- package/dist/map/tools.js.map +1 -1
- package/dist/permissions.js +6 -0
- package/dist/permissions.js.map +1 -1
- package/dist/server.js +11 -14
- package/dist/server.js.map +1 -1
- package/dist/tools/browser-definitions.js +113 -23
- package/dist/tools/browser-definitions.js.map +1 -1
- package/dist/tools/browser-dispatch.d.ts +24 -26
- package/dist/tools/browser-dispatch.js +169 -14
- package/dist/tools/browser-dispatch.js.map +1 -1
- package/dist/tools/server-instructions.d.ts +24 -13
- package/dist/tools/server-instructions.js +25 -303
- package/dist/tools/server-instructions.js.map +1 -1
- package/package.json +1 -1
|
@@ -14,7 +14,7 @@ import { OBSERVER_GLOBAL } from './observer.js';
|
|
|
14
14
|
* injected verbatim into `buildSnapshotJs`, `buildFindJs` and `buildStateJs`.
|
|
15
15
|
* The observer global is looked up ONCE per evaluation and used only when it
|
|
16
16
|
* is present, not torn down, and exposes `refFor`:
|
|
17
|
-
* - `refOf(el)` returns the observer's
|
|
17
|
+
* - `refOf(el)` returns the observer's `<doc_tag>:e<N>` ref for `el`, or `null`;
|
|
18
18
|
* - `withDocId(result)` adds `doc_id` to `result` and returns it;
|
|
19
19
|
* - `snapshotStore()` returns the observer when it keeps snapshot records
|
|
20
20
|
* (`recordSnapshot` / `getSnapshot`, used by `snapshot({ since })`), else
|
|
@@ -54,6 +54,53 @@ export const PAGE_OBSERVER_JS = `
|
|
|
54
54
|
return null;
|
|
55
55
|
}
|
|
56
56
|
`;
|
|
57
|
+
/**
|
|
58
|
+
* In-page action-target resolution (spec v4 §6.3), injected after
|
|
59
|
+
* `DEEP_QUERY_JS` and `PAGE_OBSERVER_JS` into the builders that take a
|
|
60
|
+
* `selector` or a `ref` (click, type, focus, element rect, `wait_for` by
|
|
61
|
+
* ref, `find` by ref):
|
|
62
|
+
* `resolveTarget(opts)` with a non-empty `opts.ref` asks the observer
|
|
63
|
+
* (`resolveRefDetailed`, which re-resolves a replaced node by its semantic
|
|
64
|
+
* key) and answers `{ ok: true, el, reresolved }`, `{ ok: false, reason:
|
|
65
|
+
* 'ref-stale', ref, doc_id }` or `{ ok: false, reason: 'observer-unavailable'
|
|
66
|
+
* }`; otherwise it resolves `opts.selector` exactly as the selector builders
|
|
67
|
+
* always did (`invalid-selector` checked against `document` first, then
|
|
68
|
+
* `deepQueryFirst`, `not-found`). Kept out of `PAGE_OBSERVER_JS` so the
|
|
69
|
+
* snapshot and state sources do not carry code they never call.
|
|
70
|
+
*/
|
|
71
|
+
export const RESOLVE_TARGET_JS = `
|
|
72
|
+
function resolveTarget(opts) {
|
|
73
|
+
if (typeof opts.ref === 'string' && opts.ref.length > 0) {
|
|
74
|
+
if (!pageObserver || typeof pageObserver.resolveRefDetailed !== 'function') {
|
|
75
|
+
return { ok: false, reason: 'observer-unavailable' };
|
|
76
|
+
}
|
|
77
|
+
let found = null;
|
|
78
|
+
try {
|
|
79
|
+
found = pageObserver.resolveRefDetailed(opts.ref);
|
|
80
|
+
} catch (e) {
|
|
81
|
+
found = null;
|
|
82
|
+
}
|
|
83
|
+
if (!found || !found.el) return withDocId({ ok: false, reason: 'ref-stale', ref: opts.ref });
|
|
84
|
+
return { ok: true, el: found.el, reresolved: found.reresolved === true };
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
document.querySelector(opts.selector);
|
|
88
|
+
} catch (e) {
|
|
89
|
+
return { ok: false, reason: 'invalid-selector', error: e && e.message ? e.message : String(e) };
|
|
90
|
+
}
|
|
91
|
+
const el = deepQueryFirst(opts.selector);
|
|
92
|
+
if (!el) return { ok: false, reason: 'not-found' };
|
|
93
|
+
return { ok: true, el: el, reresolved: false };
|
|
94
|
+
}
|
|
95
|
+
`;
|
|
96
|
+
/** JSON for the `selector` / `ref` pair, embedded as data in the built
|
|
97
|
+
* source; `ref` is included only when given so a selector build keeps the
|
|
98
|
+
* same options object as before. */
|
|
99
|
+
function targetJson(opts) {
|
|
100
|
+
return typeof opts.ref === 'string' && opts.ref.length > 0
|
|
101
|
+
? { selector: opts.selector, ref: opts.ref }
|
|
102
|
+
: { selector: opts.selector };
|
|
103
|
+
}
|
|
57
104
|
export function buildSnapshotJs(opts = {}) {
|
|
58
105
|
const optsJson = JSON.stringify({
|
|
59
106
|
withinSelector: typeof opts.within_selector === 'string' ? opts.within_selector : null,
|
|
@@ -246,6 +293,16 @@ export function buildSnapshotJs(opts = {}) {
|
|
|
246
293
|
})()
|
|
247
294
|
`;
|
|
248
295
|
}
|
|
296
|
+
/** Only the documented key fields reach the built source, as JSON data. */
|
|
297
|
+
function findKeyJson(key) {
|
|
298
|
+
return Array.isArray(key.path)
|
|
299
|
+
? {
|
|
300
|
+
role: key.role,
|
|
301
|
+
name: key.name,
|
|
302
|
+
path: key.path.map((step) => ({ tag: step.tag, index: step.index })),
|
|
303
|
+
}
|
|
304
|
+
: { role: key.role, name: key.name };
|
|
305
|
+
}
|
|
249
306
|
export function buildFindJs(opts) {
|
|
250
307
|
const optsJson = JSON.stringify({
|
|
251
308
|
text: opts.text,
|
|
@@ -253,13 +310,98 @@ export function buildFindJs(opts) {
|
|
|
253
310
|
exact: opts.exact === true,
|
|
254
311
|
candidateLimit: 5,
|
|
255
312
|
nearMissLimit: 3,
|
|
313
|
+
...(typeof opts.ref === 'string' && opts.ref.length > 0 ? { ref: opts.ref } : {}),
|
|
314
|
+
...(opts.key ? { key: findKeyJson(opts.key) } : {}),
|
|
256
315
|
});
|
|
257
316
|
return `
|
|
258
317
|
(() => {
|
|
259
318
|
${DEEP_QUERY_JS}
|
|
260
319
|
${DOM_HELPERS_JS}
|
|
261
320
|
${PAGE_OBSERVER_JS}
|
|
321
|
+
${typeof opts.ref === 'string' && opts.ref.length > 0 ? RESOLVE_TARGET_JS : ''}
|
|
262
322
|
const opts = ${optsJson};
|
|
323
|
+
function semanticKeyOf(el) {
|
|
324
|
+
if (!pageObserver || typeof pageObserver.semanticKeyFor !== 'function') return null;
|
|
325
|
+
try {
|
|
326
|
+
const key = pageObserver.semanticKeyFor(el);
|
|
327
|
+
return key && typeof key === 'object' ? key : null;
|
|
328
|
+
} catch (e) {
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
function singleMatch(el, ref, reresolved) {
|
|
333
|
+
const center = viewportCenterOf(el);
|
|
334
|
+
const selInfo = genSelectorInfo(el);
|
|
335
|
+
const result = { matched: true, selector: selInfo.selector };
|
|
336
|
+
if (ref) result.ref = ref;
|
|
337
|
+
result.coords = {
|
|
338
|
+
x: Math.round(center.x),
|
|
339
|
+
y: Math.round(center.y),
|
|
340
|
+
};
|
|
341
|
+
result.tag = el.tagName.toLowerCase();
|
|
342
|
+
result.text = shortText(el);
|
|
343
|
+
if (selInfo.ambiguous) result.ambiguous = true;
|
|
344
|
+
const frame = frameSelectorFor(el);
|
|
345
|
+
if (frame) result.frame = frame;
|
|
346
|
+
if (reresolved) result.reresolved = true;
|
|
347
|
+
const key = semanticKeyOf(el);
|
|
348
|
+
if (key) result.semantic_key = key;
|
|
349
|
+
return withDocId(result);
|
|
350
|
+
}
|
|
351
|
+
if (typeof opts.ref === 'string') {
|
|
352
|
+
const target = resolveTarget(opts);
|
|
353
|
+
if (!target.ok) {
|
|
354
|
+
const failure = { matched: false, reason: target.reason, ref: opts.ref };
|
|
355
|
+
return withDocId(failure);
|
|
356
|
+
}
|
|
357
|
+
return singleMatch(target.el, opts.ref, target.reresolved);
|
|
358
|
+
}
|
|
359
|
+
if (opts.key) {
|
|
360
|
+
if (!pageObserver || typeof pageObserver.semanticKeyFor !== 'function') {
|
|
361
|
+
return { matched: false, reason: 'observer-unavailable' };
|
|
362
|
+
}
|
|
363
|
+
const wanted = opts.key;
|
|
364
|
+
const candidates = [];
|
|
365
|
+
for (const el of deepQueryAll('*')) {
|
|
366
|
+
const key = semanticKeyOf(el);
|
|
367
|
+
if (key && key.role === wanted.role && key.name === wanted.name) {
|
|
368
|
+
candidates.push({ el: el, path: Array.isArray(key.path) ? key.path : [] });
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
const count = candidates.length;
|
|
372
|
+
const shared = 'key: ' + count + ' candidates share role and name; ';
|
|
373
|
+
let picked = null;
|
|
374
|
+
let error = count > 1 ? shared + 'pass path to disambiguate' : null;
|
|
375
|
+
if (!Array.isArray(wanted.path)) {
|
|
376
|
+
if (count === 1) picked = candidates[0].el;
|
|
377
|
+
} else {
|
|
378
|
+
// Same rule as the observer's re-resolution: identical tag path and
|
|
379
|
+
// exactly one such candidate; identical rows are refused, never guessed.
|
|
380
|
+
let sameTags = 0;
|
|
381
|
+
let only = null;
|
|
382
|
+
for (const c of candidates) {
|
|
383
|
+
if (c.path.length !== wanted.path.length) continue;
|
|
384
|
+
let same = true;
|
|
385
|
+
for (let i = 0; i < c.path.length && same; i++) {
|
|
386
|
+
if (c.path[i].tag !== wanted.path[i].tag) same = false;
|
|
387
|
+
}
|
|
388
|
+
if (!same) continue;
|
|
389
|
+
sameTags++;
|
|
390
|
+
only = c.el;
|
|
391
|
+
}
|
|
392
|
+
if (sameTags === 1) {
|
|
393
|
+
picked = only;
|
|
394
|
+
} else if (sameTags > 1) {
|
|
395
|
+
error = 'key: ' + sameTags + ' candidates share role, name and tag path';
|
|
396
|
+
} else if (count > 0) {
|
|
397
|
+
error = shared + 'none has the same tag path';
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
if (picked) return singleMatch(picked, refOf(picked), false);
|
|
401
|
+
const failure = { matched: false, reason: 'not-found' };
|
|
402
|
+
if (error) failure.error = error;
|
|
403
|
+
return withDocId(failure);
|
|
404
|
+
}
|
|
263
405
|
const needle = opts.text.toLowerCase();
|
|
264
406
|
const needleTokens = needle.split(/[^a-z0-9]+/).filter(Boolean);
|
|
265
407
|
const ROLE_SELECTORS = {
|
|
@@ -378,37 +520,21 @@ export function buildFindJs(opts) {
|
|
|
378
520
|
});
|
|
379
521
|
}
|
|
380
522
|
const el = matches[0];
|
|
381
|
-
|
|
382
|
-
const selInfo = genSelectorInfo(el);
|
|
383
|
-
const result = { matched: true, selector: selInfo.selector };
|
|
384
|
-
const ref = refOf(el);
|
|
385
|
-
if (ref) result.ref = ref;
|
|
386
|
-
result.coords = {
|
|
387
|
-
x: Math.round(center.x),
|
|
388
|
-
y: Math.round(center.y),
|
|
389
|
-
};
|
|
390
|
-
result.tag = el.tagName.toLowerCase();
|
|
391
|
-
result.text = shortText(el);
|
|
392
|
-
if (selInfo.ambiguous) result.ambiguous = true;
|
|
393
|
-
const frame = frameSelectorFor(el);
|
|
394
|
-
if (frame) result.frame = frame;
|
|
395
|
-
return withDocId(result);
|
|
523
|
+
return singleMatch(el, refOf(el), false);
|
|
396
524
|
})()
|
|
397
525
|
`;
|
|
398
526
|
}
|
|
399
527
|
export function buildClickResolveJs(opts) {
|
|
400
|
-
const optsJson = JSON.stringify({
|
|
528
|
+
const optsJson = JSON.stringify({ ...targetJson(opts), force: opts.force });
|
|
401
529
|
return `
|
|
402
530
|
(() => {
|
|
403
531
|
${DEEP_QUERY_JS}
|
|
532
|
+
${PAGE_OBSERVER_JS}
|
|
533
|
+
${RESOLVE_TARGET_JS}
|
|
404
534
|
const opts = ${optsJson};
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
return { ok: false, reason: 'invalid-selector', error: e && e.message ? e.message : String(e) };
|
|
409
|
-
}
|
|
410
|
-
const el = deepQueryFirst(opts.selector);
|
|
411
|
-
if (!el) return { ok: false, reason: 'not-found' };
|
|
535
|
+
const target = resolveTarget(opts);
|
|
536
|
+
if (!target.ok) return target;
|
|
537
|
+
const el = target.el;
|
|
412
538
|
const chain = frameElementChain(el);
|
|
413
539
|
for (const fe of chain) {
|
|
414
540
|
if (fe.scrollIntoView) fe.scrollIntoView({ block: 'center', inline: 'center' });
|
|
@@ -418,46 +544,67 @@ export function buildClickResolveJs(opts) {
|
|
|
418
544
|
const tag = el.tagName.toLowerCase();
|
|
419
545
|
const occlusion = checkOcclusion(el, center.localX, center.localY);
|
|
420
546
|
if (!occlusion.allowed && !opts.force) {
|
|
547
|
+
if (occlusion.retargeted) {
|
|
548
|
+
return { ok: false, reason: 'retargeted', hit_element: occlusion.hit };
|
|
549
|
+
}
|
|
421
550
|
return { ok: false, reason: 'occluded', blocker: occlusion.blocker };
|
|
422
551
|
}
|
|
423
552
|
const result = { ok: true, x: center.x, y: center.y, tag };
|
|
424
|
-
const hit = occlusion.
|
|
553
|
+
const hit = occlusion.hit || occlusion.blocker;
|
|
425
554
|
if (hit) result.hit_element = hit;
|
|
555
|
+
if (target.reresolved) result.reresolved = true;
|
|
426
556
|
return result;
|
|
427
557
|
})()
|
|
428
558
|
`;
|
|
429
559
|
}
|
|
430
560
|
export function buildTypeResolveJs(opts) {
|
|
431
|
-
const optsJson = JSON.stringify(
|
|
561
|
+
const optsJson = JSON.stringify(targetJson(opts));
|
|
432
562
|
return `
|
|
433
563
|
(() => {
|
|
434
564
|
${DEEP_QUERY_JS}
|
|
565
|
+
${PAGE_OBSERVER_JS}
|
|
566
|
+
${RESOLVE_TARGET_JS}
|
|
435
567
|
const opts = ${optsJson};
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
return { ok: false, reason: 'invalid-selector', error: e && e.message ? e.message : String(e) };
|
|
440
|
-
}
|
|
441
|
-
const el = deepQueryFirst(opts.selector);
|
|
442
|
-
if (!el) return { ok: false, reason: 'not-found' };
|
|
568
|
+
const target = resolveTarget(opts);
|
|
569
|
+
if (!target.ok) return target;
|
|
570
|
+
const el = target.el;
|
|
443
571
|
el.focus();
|
|
444
572
|
${opts.clear ? "if ('value' in el) { el.value = ''; el.dispatchEvent(new Event('input', { bubbles: true })); }" : ''}
|
|
445
|
-
return { ok: true };
|
|
573
|
+
return target.reresolved ? { ok: true, reresolved: true } : { ok: true };
|
|
446
574
|
})()`;
|
|
447
575
|
}
|
|
448
576
|
export function buildFocusJs(opts) {
|
|
449
|
-
const optsJson = JSON.stringify(
|
|
577
|
+
const optsJson = JSON.stringify(targetJson(opts));
|
|
450
578
|
return `
|
|
451
579
|
(() => {
|
|
452
580
|
${DEEP_QUERY_JS}
|
|
581
|
+
${PAGE_OBSERVER_JS}
|
|
582
|
+
${RESOLVE_TARGET_JS}
|
|
453
583
|
const opts = ${optsJson};
|
|
454
|
-
const
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
584
|
+
const target = resolveTarget(opts);
|
|
585
|
+
const byRef = typeof opts.ref === 'string';
|
|
586
|
+
if (!target.ok) return byRef ? target : false;
|
|
587
|
+
target.el.focus();
|
|
588
|
+
if (!byRef) return true;
|
|
589
|
+
return target.reresolved ? { ok: true, reresolved: true } : { ok: true };
|
|
458
590
|
})()`;
|
|
459
591
|
}
|
|
460
|
-
export function buildWaitForSelectorJs(
|
|
592
|
+
export function buildWaitForSelectorJs(target, condition) {
|
|
593
|
+
if (typeof target !== 'string') {
|
|
594
|
+
const refOptsJson = JSON.stringify({ ref: target.ref, condition });
|
|
595
|
+
return `
|
|
596
|
+
(() => {
|
|
597
|
+
${DEEP_QUERY_JS}
|
|
598
|
+
${PAGE_OBSERVER_JS}
|
|
599
|
+
${RESOLVE_TARGET_JS}
|
|
600
|
+
const opts = ${refOptsJson};
|
|
601
|
+
const target = resolveTarget(opts);
|
|
602
|
+
if (!target.ok && target.reason === 'observer-unavailable') return target;
|
|
603
|
+
const el = target.ok ? target.el : null;
|
|
604
|
+
${WAIT_FOR_CONDITION_JS}
|
|
605
|
+
})()`;
|
|
606
|
+
}
|
|
607
|
+
const selector = target;
|
|
461
608
|
const optsJson = JSON.stringify({ selector, condition });
|
|
462
609
|
return `
|
|
463
610
|
(() => {
|
|
@@ -465,7 +612,12 @@ export function buildWaitForSelectorJs(selector, condition) {
|
|
|
465
612
|
const opts = ${optsJson};
|
|
466
613
|
document.querySelector(opts.selector);
|
|
467
614
|
const el = deepQueryFirst(opts.selector);
|
|
468
|
-
|
|
615
|
+
${WAIT_FOR_CONDITION_JS}
|
|
616
|
+
})()`;
|
|
617
|
+
}
|
|
618
|
+
/** The condition check shared by both `wait_for` probe targets; `el` is
|
|
619
|
+
* the resolved element or `null`. */
|
|
620
|
+
const WAIT_FOR_CONDITION_JS = `if (opts.condition === 'attached') return Boolean(el);
|
|
469
621
|
if (opts.condition === 'detached') return !el;
|
|
470
622
|
const wantHidden = opts.condition === 'hidden';
|
|
471
623
|
if (!el) return wantHidden;
|
|
@@ -474,22 +626,18 @@ export function buildWaitForSelectorJs(selector, condition) {
|
|
|
474
626
|
if (s.display === 'none' || s.visibility === 'hidden' || s.opacity === '0') return wantHidden;
|
|
475
627
|
const r = el.getBoundingClientRect();
|
|
476
628
|
const shown = r.width > 0 && r.height > 0;
|
|
477
|
-
return wantHidden ? !shown : shown
|
|
478
|
-
})()`;
|
|
479
|
-
}
|
|
629
|
+
return wantHidden ? !shown : shown;`;
|
|
480
630
|
export function buildElementRectJs(opts) {
|
|
481
|
-
const optsJson = JSON.stringify(
|
|
631
|
+
const optsJson = JSON.stringify(targetJson(opts));
|
|
482
632
|
return `
|
|
483
633
|
(() => {
|
|
484
634
|
${DEEP_QUERY_JS}
|
|
635
|
+
${PAGE_OBSERVER_JS}
|
|
636
|
+
${RESOLVE_TARGET_JS}
|
|
485
637
|
const opts = ${optsJson};
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
return { ok: false, reason: 'invalid-selector', error: e && e.message ? e.message : String(e) };
|
|
490
|
-
}
|
|
491
|
-
const el = deepQueryFirst(opts.selector);
|
|
492
|
-
if (!el) return { ok: false, reason: 'not-found' };
|
|
638
|
+
const target = resolveTarget(opts);
|
|
639
|
+
if (!target.ok) return target;
|
|
640
|
+
const el = target.el;
|
|
493
641
|
const chain = frameElementChain(el);
|
|
494
642
|
// behavior 'instant' overrides CSS scroll-behavior: smooth, which would
|
|
495
643
|
// leave the rect below stale while the scroll is still animating.
|
|
@@ -514,13 +662,15 @@ export function buildElementRectJs(opts) {
|
|
|
514
662
|
scrollX = top.scrollX || 0;
|
|
515
663
|
scrollY = top.scrollY || 0;
|
|
516
664
|
} catch (_) {}
|
|
517
|
-
|
|
665
|
+
const result = {
|
|
518
666
|
ok: true,
|
|
519
667
|
x: offsetX + rect.left + scrollX,
|
|
520
668
|
y: offsetY + rect.top + scrollY,
|
|
521
669
|
width: rect.width,
|
|
522
670
|
height: rect.height,
|
|
523
671
|
};
|
|
672
|
+
if (target.reresolved) result.reresolved = true;
|
|
673
|
+
return result;
|
|
524
674
|
})()`;
|
|
525
675
|
}
|
|
526
676
|
export function buildDragProbeJs(opts) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builders.js","sourceRoot":"","sources":["../../src/inpage/builders.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhD;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG;;;yBAGP,IAAI,CAAC,SAAS,CAAC,eAAe,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BvD,CAAC;
|
|
1
|
+
{"version":3,"file":"builders.js","sourceRoot":"","sources":["../../src/inpage/builders.ts"],"names":[],"mappings":"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"}
|
|
@@ -37,4 +37,4 @@
|
|
|
37
37
|
* `dom-helpers.ts`), snapshot/find mark the affected entry with
|
|
38
38
|
* `ambiguous: true` so agents know not to cache that selector.
|
|
39
39
|
*/
|
|
40
|
-
export declare const DEEP_QUERY_JS = "\n function collectSearchRoots(start) {\n // start: a Document, Element or ShadowRoot to scope the search to.\n // Returns [start, ...every open shadow root and same-origin iframe\n // document reachable inside start's subtree, recursively]. The caller\n // runs .querySelectorAll()/.querySelector() on each entry directly \u2014\n // each entry's own querySelectorAll does NOT pierce into nested shadow\n // roots or iframes on its own, which is why we enumerate them here.\n var roots = [start];\n function walk(scope, depth) {\n // Depth guard, same pattern as the other recursive helpers here:\n // pathological nesting (or a cycle introduced by a broken mock)\n // must degrade to a truncated result, never a stack overflow.\n if (depth > 20) return;\n var all;\n try {\n all = scope.querySelectorAll('*');\n } catch (_) {\n return;\n }\n for (var i = 0; i < all.length; i++) {\n var el = all[i];\n if (el.shadowRoot) {\n roots.push(el.shadowRoot);\n walk(el.shadowRoot, depth + 1);\n }\n if (el.tagName === 'IFRAME') {\n var doc = null;\n try {\n doc = el.contentDocument;\n } catch (_) {\n doc = null;\n }\n if (doc) {\n roots.push(doc);\n walk(doc, depth + 1);\n }\n }\n }\n }\n walk(start, 0);\n return roots;\n }\n\n function deepWalkAll(callback, start) {\n // Tree-walk primitive: invoke callback(el) for every element reachable\n // from start (default: document) across the top document, every open\n // shadow root, and every same-origin iframe document.\n var root = start || document;\n var roots = collectSearchRoots(root);\n for (var r = 0; r < roots.length; r++) {\n var all;\n try {\n all = roots[r].querySelectorAll('*');\n } catch (_) {\n continue;\n }\n for (var i = 0; i < all.length; i++) callback(all[i]);\n }\n }\n\n function deepQueryAll(selector, start) {\n var root = start || document;\n var roots = collectSearchRoots(root);\n var out = [];\n for (var r = 0; r < roots.length; r++) {\n var found;\n try {\n found = roots[r].querySelectorAll(selector);\n } catch (_) {\n continue;\n }\n for (var i = 0; i < found.length; i++) out.push(found[i]);\n }\n return out;\n }\n\n function deepQueryFirst(selector, start) {\n var root = start || document;\n var roots = collectSearchRoots(root);\n for (var r = 0; r < roots.length; r++) {\n var el = null;\n try {\n el = roots[r].querySelector(selector);\n } catch (_) {\n el = null;\n }\n if (el) return el;\n }\n return null;\n }\n\n function frameElementChain(el) {\n // Walk up from el's own window to the top window, collecting the\n // <iframe> element that hosts each intermediate document. Outermost\n // frame first. Same-origin only \u2014 frameElement throws/returns null\n // across an origin boundary, at which point the chain simply stops\n // (the coordinate math below degrades gracefully rather than throwing).\n var chain = [];\n var doc = el.ownerDocument;\n var win = doc && doc.defaultView;\n if (!win) return chain;\n var topWin;\n try {\n topWin = win.top;\n } catch (_) {\n topWin = win;\n }\n var guard = 0;\n while (win && win !== topWin && guard < 20) {\n guard++;\n var fe = null;\n try {\n fe = win.frameElement;\n } catch (_) {\n fe = null;\n }\n if (!fe) break;\n chain.unshift(fe);\n win = fe.ownerDocument && fe.ownerDocument.defaultView;\n }\n return chain;\n }\n\n function frameViewportOriginOffset(fe) {\n // The embedded document's viewport origin sits at the frame element's\n // CONTENT box, but getBoundingClientRect() returns the BORDER box \u2014\n // border and padding must be added on top of rect.left/rect.top or\n // every coordinate inside a styled iframe lands short. clientLeft/\n // clientTop are the border widths; padding comes from computed style\n // (read through the frame's own view \u2014 same-origin, so reachable).\n var borderX = fe.clientLeft || 0;\n var borderY = fe.clientTop || 0;\n var padX = 0;\n var padY = 0;\n try {\n var view = (fe.ownerDocument && fe.ownerDocument.defaultView) || window;\n var style = view.getComputedStyle(fe);\n padX = parseFloat(style.paddingLeft) || 0;\n padY = parseFloat(style.paddingTop) || 0;\n } catch (_) {}\n return { x: borderX + padX, y: borderY + padY };\n }\n\n function clickRectOf(el) {\n // For a line-wrapped INLINE element (a link spanning two lines), the\n // center of getBoundingClientRect() can land in the unpainted gap\n // between the line boxes \u2014 elementFromPoint would report whatever sits\n // behind, and a dispatched click would miss. The first client rect is\n // always a painted fragment, so its center is a safe click point.\n // Block-level elements have exactly one client rect, identical to the\n // bounding rect \u2014 no behavior change for them.\n var rects = null;\n try {\n rects = el.getClientRects ? el.getClientRects() : null;\n } catch (_) {\n rects = null;\n }\n if (rects && rects.length > 0) return rects[0];\n return el.getBoundingClientRect();\n }\n\n function viewportCenterOf(el) {\n // Returns the element's click point in two coordinate spaces:\n // - localX/localY: relative to el's OWN document viewport (what\n // that document's elementFromPoint expects).\n // - x/y: relative to the TOP-LEVEL viewport (what CDP\n // Input.dispatchMouseEvent expects) \u2014 localX/localY plus the\n // accumulated content-box origin of every ancestor <iframe>\n // element (border-box rect + border + padding). Shadow DOM\n // boundaries need no offset: they do not introduce a new\n // coordinate space.\n // Both spaces derive from the SAME point (first-client-rect center),\n // so the occlusion hit-test and the CDP dispatch always agree.\n var rect = clickRectOf(el);\n var localX = rect.left + rect.width / 2;\n var localY = rect.top + rect.height / 2;\n var chain = frameElementChain(el);\n var offsetX = 0;\n var offsetY = 0;\n for (var i = 0; i < chain.length; i++) {\n var frameRect = chain[i].getBoundingClientRect();\n var origin = frameViewportOriginOffset(chain[i]);\n offsetX += frameRect.left + origin.x;\n offsetY += frameRect.top + origin.y;\n }\n return { x: offsetX + localX, y: offsetY + localY, localX: localX, localY: localY };\n }\n\n function composedContains(ancestor, node) {\n // Node.contains() does not cross shadow boundaries. This walks the\n // COMPOSED tree: when climbing hits a ShadowRoot (no parentNode), jump\n // to its host and keep climbing.\n var cur = node;\n var guard = 0;\n while (cur && guard < 1000) {\n guard++;\n if (cur === ancestor) return true;\n if (cur.parentNode) {\n cur = cur.parentNode;\n continue;\n }\n if (cur.host) {\n cur = cur.host;\n continue;\n }\n break;\n }\n return false;\n }\n\n function describeElement(el) {\n if (!el || !el.tagName) return 'an unknown element';\n var desc = el.tagName.toLowerCase();\n if (el.id) desc += '#' + el.id;\n if (el.classList && el.classList.length) {\n var classes = [];\n for (var i = 0; i < el.classList.length && i < 2; i++) classes.push(el.classList[i]);\n if (classes.length) desc += '.' + classes.join('.');\n }\n return desc;\n }\n\n function hitTestDeep(root, x, y) {\n var node = null;\n try {\n node = root && root.elementFromPoint ? root.elementFromPoint(x, y) : null;\n } catch (_) {\n node = null;\n }\n // The hit element may itself host a nested open shadow root whose\n // content visually covers the point \u2014 descend until elementFromPoint\n // stops finding a deeper shadow tree.\n var guard = 0;\n while (node && node.shadowRoot && guard < 20) {\n guard++;\n var inner = null;\n try {\n inner = node.shadowRoot.elementFromPoint ? node.shadowRoot.elementFromPoint(x, y) : null;\n } catch (_) {\n inner = null;\n }\n if (!inner || inner === node) break;\n node = inner;\n }\n return node;\n }\n\n function computedPointerEvents(el) {\n // Read through the element's own window (cross-document\n // getComputedStyle is unreliable); any failure fails open (not 'none').\n try {\n var view = (el.ownerDocument && el.ownerDocument.defaultView) || window;\n return view.getComputedStyle(el).pointerEvents;\n } catch (_) {\n return '';\n }\n }\n\n function checkOcclusion(target, localX, localY) {\n // Hit-test starting from the element's OWN root (its closest shadow\n // root, or the document that owns it when not in shadow DOM) using\n // LOCAL coordinates \u2014 the same coordinate space that root's own\n // elementFromPoint expects (an iframe document's viewport starts at\n // its own top-left, not the top-level page's).\n var root = target.getRootNode ? target.getRootNode() : document;\n var hit = hitTestDeep(root, localX, localY);\n if (!hit) {\n // Nothing hit-tested at all (e.g. elementFromPoint unsupported in\n // this environment, or the point is outside the rendered area).\n // Fail open rather than block a click we cannot actually verify.\n return { allowed: true };\n }\n if (hit === target || composedContains(target, hit)) {\n return { allowed: true };\n }\n if (composedContains(hit, target)) {\n // ANCESTOR hit: the click targets the ancestor and never reaches\n // the element itself (events bubble upward, not down). Allowed as\n // before, but surfaced so click results can report hit_element.\n return { allowed: true, hit: describeElement(hit) };\n }\n if (computedPointerEvents(target) === 'none') {\n // A pointer-events:none target can never be the hit-test result \u2014 a\n // real user click at this exact point reaches hit instead, by the\n // page's own design (invisible a11y layer over the visual control's\n // hit-target sibling). The hit is not a\n // blocker to dismiss; it IS what a real click activates, so allow\n // the dispatch and report the true recipient.\n return { allowed: true, hit: describeElement(hit) };\n }\n return { allowed: false, blocker: describeElement(hit) };\n }\n";
|
|
40
|
+
export declare const DEEP_QUERY_JS = "\n function collectSearchRoots(start) {\n // start: a Document, Element or ShadowRoot to scope the search to.\n // Returns [start, ...every open shadow root and same-origin iframe\n // document reachable inside start's subtree, recursively]. The caller\n // runs .querySelectorAll()/.querySelector() on each entry directly \u2014\n // each entry's own querySelectorAll does NOT pierce into nested shadow\n // roots or iframes on its own, which is why we enumerate them here.\n var roots = [start];\n function walk(scope, depth) {\n // Depth guard, same pattern as the other recursive helpers here:\n // pathological nesting (or a cycle introduced by a broken mock)\n // must degrade to a truncated result, never a stack overflow.\n if (depth > 20) return;\n var all;\n try {\n all = scope.querySelectorAll('*');\n } catch (_) {\n return;\n }\n for (var i = 0; i < all.length; i++) {\n var el = all[i];\n if (el.shadowRoot) {\n roots.push(el.shadowRoot);\n walk(el.shadowRoot, depth + 1);\n }\n if (el.tagName === 'IFRAME') {\n var doc = null;\n try {\n doc = el.contentDocument;\n } catch (_) {\n doc = null;\n }\n if (doc) {\n roots.push(doc);\n walk(doc, depth + 1);\n }\n }\n }\n }\n walk(start, 0);\n return roots;\n }\n\n function deepWalkAll(callback, start) {\n // Tree-walk primitive: invoke callback(el) for every element reachable\n // from start (default: document) across the top document, every open\n // shadow root, and every same-origin iframe document.\n var root = start || document;\n var roots = collectSearchRoots(root);\n for (var r = 0; r < roots.length; r++) {\n var all;\n try {\n all = roots[r].querySelectorAll('*');\n } catch (_) {\n continue;\n }\n for (var i = 0; i < all.length; i++) callback(all[i]);\n }\n }\n\n function deepQueryAll(selector, start) {\n var root = start || document;\n var roots = collectSearchRoots(root);\n var out = [];\n for (var r = 0; r < roots.length; r++) {\n var found;\n try {\n found = roots[r].querySelectorAll(selector);\n } catch (_) {\n continue;\n }\n for (var i = 0; i < found.length; i++) out.push(found[i]);\n }\n return out;\n }\n\n function deepQueryFirst(selector, start) {\n var root = start || document;\n var roots = collectSearchRoots(root);\n for (var r = 0; r < roots.length; r++) {\n var el = null;\n try {\n el = roots[r].querySelector(selector);\n } catch (_) {\n el = null;\n }\n if (el) return el;\n }\n return null;\n }\n\n function frameElementChain(el) {\n // Walk up from el's own window to the top window, collecting the\n // <iframe> element that hosts each intermediate document. Outermost\n // frame first. Same-origin only \u2014 frameElement throws/returns null\n // across an origin boundary, at which point the chain simply stops\n // (the coordinate math below degrades gracefully rather than throwing).\n var chain = [];\n var doc = el.ownerDocument;\n var win = doc && doc.defaultView;\n if (!win) return chain;\n var topWin;\n try {\n topWin = win.top;\n } catch (_) {\n topWin = win;\n }\n var guard = 0;\n while (win && win !== topWin && guard < 20) {\n guard++;\n var fe = null;\n try {\n fe = win.frameElement;\n } catch (_) {\n fe = null;\n }\n if (!fe) break;\n chain.unshift(fe);\n win = fe.ownerDocument && fe.ownerDocument.defaultView;\n }\n return chain;\n }\n\n function frameViewportOriginOffset(fe) {\n // The embedded document's viewport origin sits at the frame element's\n // CONTENT box, but getBoundingClientRect() returns the BORDER box \u2014\n // border and padding must be added on top of rect.left/rect.top or\n // every coordinate inside a styled iframe lands short. clientLeft/\n // clientTop are the border widths; padding comes from computed style\n // (read through the frame's own view \u2014 same-origin, so reachable).\n var borderX = fe.clientLeft || 0;\n var borderY = fe.clientTop || 0;\n var padX = 0;\n var padY = 0;\n try {\n var view = (fe.ownerDocument && fe.ownerDocument.defaultView) || window;\n var style = view.getComputedStyle(fe);\n padX = parseFloat(style.paddingLeft) || 0;\n padY = parseFloat(style.paddingTop) || 0;\n } catch (_) {}\n return { x: borderX + padX, y: borderY + padY };\n }\n\n function clickRectOf(el) {\n // For a line-wrapped INLINE element (a link spanning two lines), the\n // center of getBoundingClientRect() can land in the unpainted gap\n // between the line boxes \u2014 elementFromPoint would report whatever sits\n // behind, and a dispatched click would miss. The first client rect is\n // always a painted fragment, so its center is a safe click point.\n // Block-level elements have exactly one client rect, identical to the\n // bounding rect \u2014 no behavior change for them.\n var rects = null;\n try {\n rects = el.getClientRects ? el.getClientRects() : null;\n } catch (_) {\n rects = null;\n }\n if (rects && rects.length > 0) return rects[0];\n return el.getBoundingClientRect();\n }\n\n function viewportCenterOf(el) {\n // Returns the element's click point in two coordinate spaces:\n // - localX/localY: relative to el's OWN document viewport (what\n // that document's elementFromPoint expects).\n // - x/y: relative to the TOP-LEVEL viewport (what CDP\n // Input.dispatchMouseEvent expects) \u2014 localX/localY plus the\n // accumulated content-box origin of every ancestor <iframe>\n // element (border-box rect + border + padding). Shadow DOM\n // boundaries need no offset: they do not introduce a new\n // coordinate space.\n // Both spaces derive from the SAME point (first-client-rect center),\n // so the occlusion hit-test and the CDP dispatch always agree.\n var rect = clickRectOf(el);\n var localX = rect.left + rect.width / 2;\n var localY = rect.top + rect.height / 2;\n var chain = frameElementChain(el);\n var offsetX = 0;\n var offsetY = 0;\n for (var i = 0; i < chain.length; i++) {\n var frameRect = chain[i].getBoundingClientRect();\n var origin = frameViewportOriginOffset(chain[i]);\n offsetX += frameRect.left + origin.x;\n offsetY += frameRect.top + origin.y;\n }\n return { x: offsetX + localX, y: offsetY + localY, localX: localX, localY: localY };\n }\n\n function composedContains(ancestor, node) {\n // Node.contains() does not cross shadow boundaries. This walks the\n // COMPOSED tree: when climbing hits a ShadowRoot (no parentNode), jump\n // to its host and keep climbing.\n var cur = node;\n var guard = 0;\n while (cur && guard < 1000) {\n guard++;\n if (cur === ancestor) return true;\n if (cur.parentNode) {\n cur = cur.parentNode;\n continue;\n }\n if (cur.host) {\n cur = cur.host;\n continue;\n }\n break;\n }\n return false;\n }\n\n function describeElement(el) {\n if (!el || !el.tagName) return 'an unknown element';\n var desc = el.tagName.toLowerCase();\n if (el.id) desc += '#' + el.id;\n if (el.classList && el.classList.length) {\n var classes = [];\n for (var i = 0; i < el.classList.length && i < 2; i++) classes.push(el.classList[i]);\n if (classes.length) desc += '.' + classes.join('.');\n }\n return desc;\n }\n\n function hitTestDeep(root, x, y) {\n var node = null;\n try {\n node = root && root.elementFromPoint ? root.elementFromPoint(x, y) : null;\n } catch (_) {\n node = null;\n }\n // The hit element may itself host a nested open shadow root whose\n // content visually covers the point \u2014 descend until elementFromPoint\n // stops finding a deeper shadow tree.\n var guard = 0;\n while (node && node.shadowRoot && guard < 20) {\n guard++;\n var inner = null;\n try {\n inner = node.shadowRoot.elementFromPoint ? node.shadowRoot.elementFromPoint(x, y) : null;\n } catch (_) {\n inner = null;\n }\n if (!inner || inner === node) break;\n node = inner;\n }\n return node;\n }\n\n function computedPointerEvents(el) {\n // Read through the element's own window (cross-document\n // getComputedStyle is unreliable); any failure fails open (not 'none').\n try {\n var view = (el.ownerDocument && el.ownerDocument.defaultView) || window;\n return view.getComputedStyle(el).pointerEvents;\n } catch (_) {\n return '';\n }\n }\n\n function checkOcclusion(target, localX, localY) {\n // Hit-test starting from the element's OWN root (its closest shadow\n // root, or the document that owns it when not in shadow DOM) using\n // LOCAL coordinates \u2014 the same coordinate space that root's own\n // elementFromPoint expects (an iframe document's viewport starts at\n // its own top-left, not the top-level page's).\n var root = target.getRootNode ? target.getRootNode() : document;\n var hit = hitTestDeep(root, localX, localY);\n if (!hit) {\n // Nothing hit-tested at all (e.g. elementFromPoint unsupported in\n // this environment, or the point is outside the rendered area).\n // Fail open rather than block a click we cannot actually verify.\n return { allowed: true };\n }\n if (hit === target || composedContains(target, hit)) {\n return { allowed: true };\n }\n if (computedPointerEvents(target) === 'none') {\n // A pointer-events:none target can never be the hit-test result \u2014 a\n // real user click at this exact point reaches hit instead, by the\n // page's own design (an icon inside its button, or an invisible a11y\n // layer over the visual control's hit-target sibling). The hit is not\n // a blocker to dismiss; it IS what a real click activates, so allow\n // the dispatch and report the true recipient.\n return { allowed: true, hit: describeElement(hit) };\n }\n if (composedContains(hit, target)) {\n // ANCESTOR hit on a target that does take pointer events: the target\n // is painted but not at its own centre (clipped by an overflow\n // container, zero-size, covered by its own parent). The click would\n // land on the ancestor and never reach the element itself (events\n // bubble upward, not down), so refuse it as 'retargeted' and report\n // where it would land.\n return { allowed: false, retargeted: true, hit: describeElement(hit) };\n }\n return { allowed: false, blocker: describeElement(hit) };\n }\n";
|
|
@@ -313,21 +313,24 @@ export const DEEP_QUERY_JS = `
|
|
|
313
313
|
if (hit === target || composedContains(target, hit)) {
|
|
314
314
|
return { allowed: true };
|
|
315
315
|
}
|
|
316
|
-
if (composedContains(hit, target)) {
|
|
317
|
-
// ANCESTOR hit: the click targets the ancestor and never reaches
|
|
318
|
-
// the element itself (events bubble upward, not down). Allowed as
|
|
319
|
-
// before, but surfaced so click results can report hit_element.
|
|
320
|
-
return { allowed: true, hit: describeElement(hit) };
|
|
321
|
-
}
|
|
322
316
|
if (computedPointerEvents(target) === 'none') {
|
|
323
317
|
// A pointer-events:none target can never be the hit-test result — a
|
|
324
318
|
// real user click at this exact point reaches hit instead, by the
|
|
325
|
-
// page's own design (
|
|
326
|
-
// hit-target sibling). The hit is not
|
|
327
|
-
// blocker to dismiss; it IS what a real click activates, so allow
|
|
319
|
+
// page's own design (an icon inside its button, or an invisible a11y
|
|
320
|
+
// layer over the visual control's hit-target sibling). The hit is not
|
|
321
|
+
// a blocker to dismiss; it IS what a real click activates, so allow
|
|
328
322
|
// the dispatch and report the true recipient.
|
|
329
323
|
return { allowed: true, hit: describeElement(hit) };
|
|
330
324
|
}
|
|
325
|
+
if (composedContains(hit, target)) {
|
|
326
|
+
// ANCESTOR hit on a target that does take pointer events: the target
|
|
327
|
+
// is painted but not at its own centre (clipped by an overflow
|
|
328
|
+
// container, zero-size, covered by its own parent). The click would
|
|
329
|
+
// land on the ancestor and never reach the element itself (events
|
|
330
|
+
// bubble upward, not down), so refuse it as 'retargeted' and report
|
|
331
|
+
// where it would land.
|
|
332
|
+
return { allowed: false, retargeted: true, hit: describeElement(hit) };
|
|
333
|
+
}
|
|
331
334
|
return { allowed: false, blocker: describeElement(hit) };
|
|
332
335
|
}
|
|
333
336
|
`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deep-query.js","sourceRoot":"","sources":["../../src/inpage/deep-query.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG
|
|
1
|
+
{"version":3,"file":"deep-query.js","sourceRoot":"","sources":["../../src/inpage/deep-query.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwS5B,CAAC"}
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* `cdp/builders-parity.test.ts` checks the built output.
|
|
23
23
|
*/
|
|
24
24
|
/** Bump whenever the in-page protocol (global shape, messages, methods) changes. */
|
|
25
|
-
export declare const OBSERVER_VERSION =
|
|
25
|
+
export declare const OBSERVER_VERSION = 3;
|
|
26
26
|
/** Name of the `Runtime.addBinding` function the observer pushes messages through. */
|
|
27
27
|
export declare const OBSERVER_BINDING = "__blPageObserverPush";
|
|
28
28
|
/** Well-known global that holds the installed observer instance. */
|
|
@@ -44,10 +44,30 @@ export interface ObserverInstallOpts {
|
|
|
44
44
|
* `{ ok: true, installed, version, doc_id, coverage }`.
|
|
45
45
|
*
|
|
46
46
|
* The instance also owns element identity (spec v4 §5.6): `refFor(el)` issues
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
47
|
+
* `<doc_tag>:e<N>` (`doc_tag` = the first 6 characters of `doc_id`, N
|
|
48
|
+
* monotonic per document instance, from 1) the first time an element is seen
|
|
49
|
+
* and reuses it while the node lives; `resolveRef(ref)` returns the element
|
|
50
|
+
* while it is connected, else `null`. Refs are only valid for the document
|
|
51
|
+
* instance that issued them: a ref whose tag is not this instance's tag, or a
|
|
52
|
+
* bare `e<N>` with no tag, is `null` everywhere (resolve, re-resolve, key)
|
|
53
|
+
* without consulting the registries. Neither method throws.
|
|
54
|
+
*
|
|
55
|
+
* Secondary identity (spec v4 §5.6, decided by lab 10): `semanticKeyFor(el)`
|
|
56
|
+
* returns `{ role, name, path }` — role from the `role` attribute or a small
|
|
57
|
+
* implicit table, accessible-ish name (aria-label, aria-labelledby, associated
|
|
58
|
+
* label, text ≤ 80 chars, placeholder, title, button-like input value) and up
|
|
59
|
+
* to 8 `{ tag, index }` ancestor steps (1-based same-tag sibling index, a
|
|
60
|
+
* shadow boundary as `{ tag: '#shadow', index: 0 }`). `refFor` stores the key
|
|
61
|
+
* when it issues a ref. `resolveRefDetailed(ref)` returns `{ el, reresolved }`:
|
|
62
|
+
* the live node with `reresolved: false`, else the element with the same role,
|
|
63
|
+
* name and tag-only path when exactly one exists, rebinding the ref to it
|
|
64
|
+
* (`reresolved: true`); zero or several candidates give `null` (sibling
|
|
65
|
+
* indices are kept in the key as data but never pick among candidates). Candidates come from the document, every open shadow root reachable
|
|
66
|
+
* from it, and every root the `attachShadow` patch observed (closed ones
|
|
67
|
+
* included); closed roots attached before install are not reachable.
|
|
68
|
+
* `resolveRef(ref)` is `resolveRefDetailed(ref)?.el ?? null`. `keyForRef(ref)`
|
|
69
|
+
* returns a copy of the stored key or `null`. None of these throws.
|
|
70
|
+
* `nextSnapshotId()`
|
|
51
71
|
* issues `<doc_id>:s<N>` (N monotonic per document instance, from 1) for each
|
|
52
72
|
* `snapshot` result and returns `null` once the instance is torn down; the
|
|
53
73
|
* `doc_id` prefix lets a stale id from another document be rejected.
|
|
@@ -76,10 +96,19 @@ export declare function buildObserverChangesJs(mark: number, docId?: string): st
|
|
|
76
96
|
/**
|
|
77
97
|
* Build the ref-resolve expression, for tests and debugging. Evaluates to
|
|
78
98
|
* `{ ok: true, found: true, tag }` when `ref` names an element of the current
|
|
79
|
-
* document that is still connected
|
|
80
|
-
*
|
|
99
|
+
* document that is still connected — plus `reresolved: true` when the node was
|
|
100
|
+
* gone and the ref was just rebound by its semantic key —
|
|
101
|
+
* `{ ok: true, found: false }` when it is unknown or stale, or
|
|
102
|
+
* `{ ok: false, reason: 'observer-unavailable' }`.
|
|
81
103
|
*/
|
|
82
104
|
export declare function buildObserverRefResolveJs(ref: string): string;
|
|
105
|
+
/**
|
|
106
|
+
* Build the semantic-key read expression. Evaluates to `{ ok: true, key }`
|
|
107
|
+
* with the `{ role, name, path }` stored when `ref` was issued, `{ ok: true,
|
|
108
|
+
* key: null }` for a ref this document instance does not know, or
|
|
109
|
+
* `{ ok: false, reason: 'observer-unavailable' }`.
|
|
110
|
+
*/
|
|
111
|
+
export declare function buildObserverSemanticKeyJs(ref: string): string;
|
|
83
112
|
/**
|
|
84
113
|
* Build the teardown expression: disconnects every observer, restores the
|
|
85
114
|
* `attachShadow` and `history` patches when they are still the observer's,
|