@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.
Files changed (69) hide show
  1. package/dist/agent-instructions/content.js +5 -349
  2. package/dist/agent-instructions/content.js.map +1 -1
  3. package/dist/bridge/server.d.ts +5 -0
  4. package/dist/bridge/server.js +1 -1
  5. package/dist/bridge/server.js.map +1 -1
  6. package/dist/cdp/flow.d.ts +44 -3
  7. package/dist/cdp/flow.js +65 -19
  8. package/dist/cdp/flow.js.map +1 -1
  9. package/dist/cdp/inpage/builders.d.ts +110 -24
  10. package/dist/cdp/inpage/builders.js +203 -53
  11. package/dist/cdp/inpage/builders.js.map +1 -1
  12. package/dist/cdp/inpage/deep-query.d.ts +1 -1
  13. package/dist/cdp/inpage/deep-query.js +12 -9
  14. package/dist/cdp/inpage/deep-query.js.map +1 -1
  15. package/dist/cdp/inpage/observer.d.ts +36 -7
  16. package/dist/cdp/inpage/observer.js +242 -16
  17. package/dist/cdp/inpage/observer.js.map +1 -1
  18. package/dist/cdp/transport.js +96 -9
  19. package/dist/cdp/transport.js.map +1 -1
  20. package/dist/commands/about.js +4 -2
  21. package/dist/commands/about.js.map +1 -1
  22. package/dist/extension/background.js +97 -13
  23. package/dist/extension/background.js.map +1 -1
  24. package/dist/extension/flow.d.ts +44 -3
  25. package/dist/extension/flow.js +65 -19
  26. package/dist/extension/flow.js.map +1 -1
  27. package/dist/extension/inpage/builders.d.ts +110 -24
  28. package/dist/extension/inpage/builders.js +203 -53
  29. package/dist/extension/inpage/builders.js.map +1 -1
  30. package/dist/extension/inpage/deep-query.d.ts +1 -1
  31. package/dist/extension/inpage/deep-query.js +12 -9
  32. package/dist/extension/inpage/deep-query.js.map +1 -1
  33. package/dist/extension/inpage/observer.d.ts +36 -7
  34. package/dist/extension/inpage/observer.js +242 -16
  35. package/dist/extension/inpage/observer.js.map +1 -1
  36. package/dist/extension/manifest.json +1 -1
  37. package/dist/manual/core.d.ts +20 -0
  38. package/dist/manual/core.js +71 -0
  39. package/dist/manual/core.js.map +1 -0
  40. package/dist/manual/render.d.ts +22 -0
  41. package/dist/manual/render.js +56 -0
  42. package/dist/manual/render.js.map +1 -0
  43. package/dist/manual/status.d.ts +23 -0
  44. package/dist/manual/status.js +52 -0
  45. package/dist/manual/status.js.map +1 -0
  46. package/dist/manual/tool.d.ts +20 -0
  47. package/dist/manual/tool.js +106 -0
  48. package/dist/manual/tool.js.map +1 -0
  49. package/dist/manual/topics.d.ts +18 -0
  50. package/dist/manual/topics.js +60 -0
  51. package/dist/manual/topics.js.map +1 -0
  52. package/dist/map/queries.d.ts +2 -0
  53. package/dist/map/queries.js +23 -1
  54. package/dist/map/queries.js.map +1 -1
  55. package/dist/map/tools.js +26 -4
  56. package/dist/map/tools.js.map +1 -1
  57. package/dist/permissions.js +6 -0
  58. package/dist/permissions.js.map +1 -1
  59. package/dist/server.js +11 -14
  60. package/dist/server.js.map +1 -1
  61. package/dist/tools/browser-definitions.js +113 -23
  62. package/dist/tools/browser-definitions.js.map +1 -1
  63. package/dist/tools/browser-dispatch.d.ts +24 -26
  64. package/dist/tools/browser-dispatch.js +169 -14
  65. package/dist/tools/browser-dispatch.js.map +1 -1
  66. package/dist/tools/server-instructions.d.ts +24 -13
  67. package/dist/tools/server-instructions.js +25 -303
  68. package/dist/tools/server-instructions.js.map +1 -1
  69. package/package.json +1 -1
@@ -11,7 +11,7 @@
11
11
  * `cdp/builders-parity.test.ts` checks the built output.
12
12
  */
13
13
  /** Bump whenever the in-page protocol (global shape, messages, methods) changes. */
14
- export const OBSERVER_VERSION = 2;
14
+ export const OBSERVER_VERSION = 3;
15
15
  /** Name of the `Runtime.addBinding` function the observer pushes messages through. */
16
16
  export const OBSERVER_BINDING = '__blPageObserverPush';
17
17
  /** Well-known global that holds the installed observer instance. */
@@ -32,10 +32,30 @@ function clampInt(value, fallback, min, max) {
32
32
  * `{ ok: true, installed, version, doc_id, coverage }`.
33
33
  *
34
34
  * The instance also owns element identity (spec v4 §5.6): `refFor(el)` issues
35
- * `e<N>` (monotonic per document instance, from `e1`) the first time an
36
- * element is seen and reuses it while the node lives; `resolveRef(ref)`
37
- * returns the element while it is connected, else `null`. Refs are only valid
38
- * for the `doc_id` that issued them. Neither method throws. `nextSnapshotId()`
35
+ * `<doc_tag>:e<N>` (`doc_tag` = the first 6 characters of `doc_id`, N
36
+ * monotonic per document instance, from 1) the first time an element is seen
37
+ * and reuses it while the node lives; `resolveRef(ref)` returns the element
38
+ * while it is connected, else `null`. Refs are only valid for the document
39
+ * instance that issued them: a ref whose tag is not this instance's tag, or a
40
+ * bare `e<N>` with no tag, is `null` everywhere (resolve, re-resolve, key)
41
+ * without consulting the registries. Neither method throws.
42
+ *
43
+ * Secondary identity (spec v4 §5.6, decided by lab 10): `semanticKeyFor(el)`
44
+ * returns `{ role, name, path }` — role from the `role` attribute or a small
45
+ * implicit table, accessible-ish name (aria-label, aria-labelledby, associated
46
+ * label, text ≤ 80 chars, placeholder, title, button-like input value) and up
47
+ * to 8 `{ tag, index }` ancestor steps (1-based same-tag sibling index, a
48
+ * shadow boundary as `{ tag: '#shadow', index: 0 }`). `refFor` stores the key
49
+ * when it issues a ref. `resolveRefDetailed(ref)` returns `{ el, reresolved }`:
50
+ * the live node with `reresolved: false`, else the element with the same role,
51
+ * name and tag-only path when exactly one exists, rebinding the ref to it
52
+ * (`reresolved: true`); zero or several candidates give `null` (sibling
53
+ * indices are kept in the key as data but never pick among candidates). Candidates come from the document, every open shadow root reachable
54
+ * from it, and every root the `attachShadow` patch observed (closed ones
55
+ * included); closed roots attached before install are not reachable.
56
+ * `resolveRef(ref)` is `resolveRefDetailed(ref)?.el ?? null`. `keyForRef(ref)`
57
+ * returns a copy of the stored key or `null`. None of these throws.
58
+ * `nextSnapshotId()`
39
59
  * issues `<doc_id>:s<N>` (N monotonic per document instance, from 1) for each
40
60
  * `snapshot` result and returns `null` once the instance is torn down; the
41
61
  * `doc_id` prefix lets a stale id from another document be rejected.
@@ -86,7 +106,14 @@ export function buildObserverInstallJs(opts = {}) {
86
106
  const observed = new WeakSet();
87
107
  let refs = new WeakMap();
88
108
  const refNodes = new Map();
109
+ // Semantic key captured when each ref is issued; kept while the ref lives so
110
+ // a replaced node can be found again (spec v4 §5.6 secondary identity).
111
+ const refKeys = new Map();
89
112
  let refSeq = 0;
113
+ // Every ref carries the issuing document's tag, so a ref from another
114
+ // document instance (or a bare e<N>) never reaches the registries.
115
+ const refPrefix = g.doc_id.slice(0, 6) + ':e';
116
+ const ownsRef = (ref) => typeof ref === 'string' && ref.startsWith(refPrefix);
90
117
  let snapshotSeq = 0;
91
118
  // Last SNAPSHOT_KEEP snapshot records (opaque to the observer; the snapshot
92
119
  // builder owns their shape), keyed by snapshot_id in insertion order.
@@ -214,7 +241,7 @@ export function buildObserverInstallJs(opts = {}) {
214
241
  try {
215
242
  const mo = new MutationObserver(onRecords(kind));
216
243
  mo.observe(root, { subtree: true, childList: true, attributes: true, characterData: true });
217
- observers.push({ mo: mo, kind: kind });
244
+ observers.push({ mo: mo, kind: kind, root: root });
218
245
  g.roots++;
219
246
  } catch (e) {}
220
247
  }
@@ -282,13 +309,179 @@ export function buildObserverInstallJs(opts = {}) {
282
309
 
283
310
  g.flush = flush;
284
311
 
312
+ const KEY_PATH_STEPS = 8;
313
+ const KEY_TEXT_CAP = 80;
314
+ // Elements an HTML <label> can be associated with.
315
+ const LABELABLE = ['button', 'input', 'meter', 'output', 'progress', 'select', 'textarea'];
316
+ const BUTTON_INPUT_TYPES = ['button', 'submit', 'reset'];
317
+
318
+ function tagOf(el) {
319
+ return el.localName || String(el.nodeName).toLowerCase();
320
+ }
321
+
322
+ function collapse(text) {
323
+ return String(text || '').replace(/\\s+/g, ' ').trim();
324
+ }
325
+
326
+ function inputType(el) {
327
+ return (el.getAttribute('type') || 'text').toLowerCase();
328
+ }
329
+
330
+ function roleOf(el) {
331
+ const explicit = (el.getAttribute('role') || '').trim();
332
+ if (explicit) return explicit;
333
+ const tag = tagOf(el);
334
+ if (tag === 'a' && el.hasAttribute('href')) return 'link';
335
+ if (tag === 'button' || tag === 'summary') return 'button';
336
+ if (tag === 'input') {
337
+ const type = inputType(el);
338
+ if (type === 'checkbox' || type === 'radio') return type;
339
+ if (BUTTON_INPUT_TYPES.indexOf(type) >= 0) return 'button';
340
+ return 'textbox';
341
+ }
342
+ if (tag === 'textarea') return 'textbox';
343
+ if (tag === 'select') return 'combobox';
344
+ if (el.hasAttribute('contenteditable') && el.getAttribute('contenteditable') !== 'false') return 'textbox';
345
+ return tag;
346
+ }
347
+
348
+ function labelTextOf(el, root) {
349
+ if (LABELABLE.indexOf(tagOf(el)) < 0) return '';
350
+ const id = el.getAttribute('id');
351
+ if (id && root && typeof root.querySelectorAll === 'function') {
352
+ for (const label of root.querySelectorAll('label')) {
353
+ if (label.getAttribute('for') !== id) continue;
354
+ const text = collapse(label.textContent);
355
+ if (text) return text;
356
+ }
357
+ }
358
+ const wrapping = typeof el.closest === 'function' ? el.closest('label') : null;
359
+ return wrapping ? collapse(wrapping.textContent) : '';
360
+ }
361
+
362
+ function nameOf(el) {
363
+ const ariaLabel = collapse(el.getAttribute('aria-label'));
364
+ if (ariaLabel) return ariaLabel;
365
+ const root = typeof el.getRootNode === 'function' ? el.getRootNode() : document;
366
+ const labelledBy = collapse(el.getAttribute('aria-labelledby'));
367
+ if (labelledBy && root && typeof root.getElementById === 'function') {
368
+ const parts = [];
369
+ for (const id of labelledBy.split(' ')) {
370
+ const target = root.getElementById(id);
371
+ const text = target ? collapse(target.textContent) : '';
372
+ if (text) parts.push(text);
373
+ }
374
+ if (parts.length > 0) return parts.join(' ');
375
+ }
376
+ const label = labelTextOf(el, root);
377
+ if (label) return label;
378
+ const text = collapse(el.textContent).slice(0, KEY_TEXT_CAP);
379
+ if (text) return text;
380
+ const placeholder = collapse(el.getAttribute('placeholder'));
381
+ if (placeholder) return placeholder;
382
+ const title = collapse(el.getAttribute('title'));
383
+ if (title) return title;
384
+ if (tagOf(el) === 'input' && BUTTON_INPUT_TYPES.indexOf(inputType(el)) >= 0) return collapse(el.value);
385
+ return '';
386
+ }
387
+
388
+ function sameTagIndex(el) {
389
+ const tag = el.localName;
390
+ let index = 1;
391
+ for (let s = el.previousElementSibling; s; s = s.previousElementSibling) {
392
+ if (s.localName === tag) index++;
393
+ }
394
+ return index;
395
+ }
396
+
397
+ // Unlike pathOf (descriptors, pinned settle output), this path carries the
398
+ // same-tag sibling index: role + name alone collides (spec v4 §4.6).
399
+ function keyPathOf(el) {
400
+ const steps = [];
401
+ let n = el;
402
+ while (n && steps.length < KEY_PATH_STEPS) {
403
+ if (n.nodeType === 1) {
404
+ if (n === document.documentElement) break;
405
+ steps.unshift({ tag: tagOf(n), index: sameTagIndex(n) });
406
+ n = n.parentNode;
407
+ } else if (n.nodeType === 11 && n.host) {
408
+ steps.unshift({ tag: '#shadow', index: 0 });
409
+ n = n.host;
410
+ } else {
411
+ break;
412
+ }
413
+ }
414
+ return steps;
415
+ }
416
+
417
+ function semanticKeyFor(el) {
418
+ try {
419
+ if (!el || el.nodeType !== 1) return null;
420
+ return { role: roleOf(el), name: nameOf(el), path: keyPathOf(el) };
421
+ } catch (e) {
422
+ return null;
423
+ }
424
+ }
425
+
426
+ // The document, every open shadow root reachable from it, and every root the
427
+ // attachShadow patch observed (closed ones included) whose host is connected.
428
+ function searchRoots() {
429
+ const roots = [document];
430
+ const seen = new Set(roots);
431
+ const add = (root) => {
432
+ if (!root || seen.has(root)) return;
433
+ if (root.nodeType === 11 && !(root.host && root.host.isConnected)) return;
434
+ seen.add(root);
435
+ roots.push(root);
436
+ };
437
+ for (const o of observers) add(o.root);
438
+ for (let i = 0; i < roots.length; i++) {
439
+ let all;
440
+ try { all = roots[i].querySelectorAll('*'); } catch (e) { continue; }
441
+ for (const el of all) {
442
+ if (el.shadowRoot) add(el.shadowRoot);
443
+ }
444
+ }
445
+ return roots;
446
+ }
447
+
448
+ // Same role and name and an identical tag-only path, and exactly one such
449
+ // element: identical rows are refused, never guessed, so a ref never jumps
450
+ // across rows. Sibling indices stay in the key but select nothing.
451
+ function findByKey(key) {
452
+ const last = key.path[key.path.length - 1];
453
+ if (!last) return null;
454
+ let found = null;
455
+ for (const root of searchRoots()) {
456
+ let all;
457
+ try { all = root.querySelectorAll('*'); } catch (e) { continue; }
458
+ for (const el of all) {
459
+ if (tagOf(el) !== last.tag || roleOf(el) !== key.role || nameOf(el) !== key.name) continue;
460
+ const path = keyPathOf(el);
461
+ if (path.length !== key.path.length) continue;
462
+ let same = true;
463
+ for (let i = 0; i < path.length && same; i++) {
464
+ if (path[i].tag !== key.path[i].tag) same = false;
465
+ }
466
+ if (!same) continue;
467
+ if (found) return null;
468
+ found = el;
469
+ }
470
+ }
471
+ return found;
472
+ }
473
+
474
+ g.semanticKeyFor = semanticKeyFor;
475
+
285
476
  g.refFor = function (el) {
286
477
  try {
287
478
  if (g.dead || !el || el.nodeType !== 1) return null;
288
479
  let ref = refs.get(el);
289
480
  if (!ref) {
290
- ref = 'e' + ++refSeq;
481
+ ref = refPrefix + ++refSeq;
291
482
  refs.set(el, ref);
483
+ const key = semanticKeyFor(el);
484
+ if (key) refKeys.set(ref, key);
292
485
  }
293
486
  // Same node, same ref: re-register one pruned while it was detached.
294
487
  if (!refNodes.has(ref)) refNodes.set(ref, holdWeakly(el));
@@ -298,20 +491,40 @@ export function buildObserverInstallJs(opts = {}) {
298
491
  }
299
492
  };
300
493
 
301
- g.resolveRef = function (ref) {
494
+ g.resolveRefDetailed = function (ref) {
302
495
  try {
303
- if (typeof ref !== 'string') return null;
496
+ if (g.dead || !ownsRef(ref)) return null;
304
497
  const held = refNodes.get(ref);
305
- if (!held) return null;
306
- const el = held.deref();
307
- if (el && el.isConnected) return el;
498
+ const old = held ? held.deref() : null;
499
+ if (old && old.isConnected) return { el: old, reresolved: false };
308
500
  refNodes.delete(ref);
309
- return null;
501
+ const key = refKeys.get(ref);
502
+ if (!key) return null;
503
+ const el = findByKey(key);
504
+ if (!el) return null;
505
+ if (old && refs.get(old) === ref) refs.delete(old);
506
+ // A node that already has its own ref keeps it for refFor; this ref
507
+ // still resolves to it through refNodes.
508
+ if (!refs.has(el)) refs.set(el, ref);
509
+ refNodes.set(ref, holdWeakly(el));
510
+ return { el: el, reresolved: true };
310
511
  } catch (e) {
311
512
  return null;
312
513
  }
313
514
  };
314
515
 
516
+ g.resolveRef = function (ref) {
517
+ const result = g.resolveRefDetailed(ref);
518
+ return result ? result.el : null;
519
+ };
520
+
521
+ g.keyForRef = function (ref) {
522
+ if (g.dead || !ownsRef(ref)) return null;
523
+ const key = refKeys.get(ref);
524
+ if (!key) return null;
525
+ return { role: key.role, name: key.name, path: key.path.map((s) => ({ tag: s.tag, index: s.index })) };
526
+ };
527
+
315
528
  g.nextSnapshotId = function () {
316
529
  if (g.dead) return null;
317
530
  return g.doc_id + ':s' + ++snapshotSeq;
@@ -411,6 +624,7 @@ export function buildObserverInstallJs(opts = {}) {
411
624
  g.baselines.clear();
412
625
  refs = new WeakMap();
413
626
  refNodes.clear();
627
+ refKeys.clear();
414
628
  snapshotSeq = 0;
415
629
  snapshots.clear();
416
630
  if (window[opts.key] === g) {
@@ -479,12 +693,24 @@ function jsStringLiteral(value) {
479
693
  /**
480
694
  * Build the ref-resolve expression, for tests and debugging. Evaluates to
481
695
  * `{ ok: true, found: true, tag }` when `ref` names an element of the current
482
- * document that is still connected, `{ ok: true, found: false }` when it is
483
- * unknown or stale, or `{ ok: false, reason: 'observer-unavailable' }`.
696
+ * document that is still connected — plus `reresolved: true` when the node was
697
+ * gone and the ref was just rebound by its semantic key —
698
+ * `{ ok: true, found: false }` when it is unknown or stale, or
699
+ * `{ ok: false, reason: 'observer-unavailable' }`.
484
700
  */
485
701
  export function buildObserverRefResolveJs(ref) {
486
702
  const refJson = jsStringLiteral(typeof ref === 'string' ? ref : '');
487
- return callInstance(`((el) => el ? { ok: true, found: true, tag: el.localName || String(el.nodeName).toLowerCase() } : { ok: true, found: false })(g.resolveRef(${refJson}))`);
703
+ return callInstance(`((r) => { if (!r) return { ok: true, found: false }; const tag = r.el.localName || String(r.el.nodeName).toLowerCase(); return r.reresolved ? { ok: true, found: true, tag: tag, reresolved: true } : { ok: true, found: true, tag: tag }; })(g.resolveRefDetailed(${refJson}))`);
704
+ }
705
+ /**
706
+ * Build the semantic-key read expression. Evaluates to `{ ok: true, key }`
707
+ * with the `{ role, name, path }` stored when `ref` was issued, `{ ok: true,
708
+ * key: null }` for a ref this document instance does not know, or
709
+ * `{ ok: false, reason: 'observer-unavailable' }`.
710
+ */
711
+ export function buildObserverSemanticKeyJs(ref) {
712
+ const refJson = jsStringLiteral(typeof ref === 'string' ? ref : '');
713
+ return callInstance(`({ ok: true, key: g.keyForRef(${refJson}) })`);
488
714
  }
489
715
  /**
490
716
  * Build the teardown expression: disconnects every observer, restores the
@@ -1 +1 @@
1
- {"version":3,"file":"observer.js","sourceRoot":"","sources":["../../../src/cdp/inpage/observer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"observer.js","sourceRoot":"","sources":["../../../src/cdp/inpage/observer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,oFAAoF;AACpF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAElC,sFAAsF;AACtF,MAAM,CAAC,MAAM,gBAAgB,GAAG,sBAAsB,CAAC;AAEvD,oEAAoE;AACpE,MAAM,CAAC,MAAM,eAAe,GAAG,kBAAkB,CAAC;AAElD,MAAM,sBAAsB,GAAG,EAAE,CAAC;AAClC,MAAM,kBAAkB,GAAG,GAAG,CAAC;AAC/B,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAC7B,MAAM,aAAa,GAAG,IAAI,CAAC;AAa3B,SAAS,QAAQ,CAAC,KAAc,EAAE,QAAgB,EAAE,GAAW,EAAE,GAAW;IAC1E,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,QAAQ,CAAC;IAC1E,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACzD,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,MAAM,UAAU,sBAAsB,CAAC,OAA4B,EAAE;IACnE,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC;QAC9B,GAAG,EAAE,eAAe;QACpB,OAAO,EACL,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,gBAAgB;QAC/F,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,gBAAgB,EAAE,CAAC,EAAE,MAAM,CAAC,gBAAgB,CAAC;QAC7E,GAAG,EAAE,QAAQ,CAAC,IAAI,CAAC,cAAc,EAAE,sBAAsB,EAAE,CAAC,EAAE,kBAAkB,CAAC;QACjF,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,iBAAiB,EAAE,CAAC,EAAE,aAAa,CAAC;KACxE,CAAC,CAAC;IACH,OAAO;;iBAEQ,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAijBpB,CAAC;AACN,CAAC;AAED,MAAM,WAAW,GAAG,+CAA+C,CAAC;AAEpE,SAAS,YAAY,CAAC,IAAY;IAChC,OAAO;;qBAEY,IAAI,CAAC,SAAS,CAAC,eAAe,CAAC;6DACS,WAAW;;aAE3D,IAAI;;aAEJ,WAAW;;KAEnB,CAAC;AACN,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB;IACjC,OAAO,YAAY,CAAC,UAAU,CAAC,CAAC;AAClC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAAY,EAAE,KAAc;IACjE,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC;IAC/E,MAAM,OAAO,GAAG,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAChG,OAAO,YAAY,CAAC,aAAa,QAAQ,KAAK,OAAO,GAAG,CAAC,CAAC;AAC5D,CAAC;AAED;;;;;GAKG;AACH,SAAS,eAAe,CAAC,KAAa;IACpC,yEAAyE;IACzE,0EAA0E;IAC1E,MAAM,aAAa,GAAG,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;IAClD,MAAM,kBAAkB,GAAG,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;IACvD,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;SACzB,KAAK,CAAC,aAAa,CAAC;SACpB,IAAI,CAAC,SAAS,CAAC;SACf,KAAK,CAAC,kBAAkB,CAAC;SACzB,IAAI,CAAC,SAAS,CAAC;SACf,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;AAC9B,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,yBAAyB,CAAC,GAAW;IACnD,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACpE,OAAO,YAAY,CACjB,sQAAsQ,OAAO,IAAI,CAClR,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,0BAA0B,CAAC,GAAW;IACpD,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACpE,OAAO,YAAY,CAAC,iCAAiC,OAAO,MAAM,CAAC,CAAC;AACtE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,uBAAuB;IACrC,OAAO;;gBAEO,IAAI,CAAC,SAAS,CAAC,eAAe,CAAC;;;;;;;;;;KAU1C,CAAC;AACN,CAAC"}
@@ -30,7 +30,7 @@ import { resolveSettleParams, settleSafely } from './settle.js';
30
30
  import { beginExpect, resolveExpectParams, verifyExpect } from './expect.js';
31
31
  import { buildKeyEventSequence, modifiersToBitmask, resolveKey } from './keymap.js';
32
32
  import { cancelDetachedFlow, detachedFlowForTab, detachedFlowStatus, finishDetachedFlow, registerDetachedFlow, reportDetachedProgress, shouldStopDetachedFlow, } from './detached-flows.js';
33
- import { runFlow, } from './flow.js';
33
+ import { refFailureMessage, retargetedClickMessage, runFlow, } from './flow.js';
34
34
  // The declarative v1 support table lives in `./support.ts` (zero
35
35
  // dependencies, importable by `tools/browser-dispatch.ts` without pulling
36
36
  // in this whole transport) — re-exported here so existing callers of
@@ -375,6 +375,18 @@ async function dispatchKeyEventSequence(client, def, modifiers) {
375
375
  }
376
376
  async function performFind(client, params) {
377
377
  try {
378
+ if (params.key !== undefined) {
379
+ const value = await evaluateInTab(client, buildFindJs({ key: params.key }));
380
+ return { ok: true, result: value };
381
+ }
382
+ if (params.ref !== undefined) {
383
+ const ref = params.ref;
384
+ const found = await evaluateInTab(client, buildFindJs({ ref }));
385
+ const refError = found.matched ? null : refFailureMessage(found, ref);
386
+ if (refError)
387
+ return { ok: false, error: refError };
388
+ return { ok: true, result: found };
389
+ }
378
390
  if (!params.text)
379
391
  return { ok: false, error: 'find: text required' };
380
392
  const opts = { text: params.text, role: params.role, exact: params.exact === true };
@@ -387,26 +399,35 @@ async function performFind(client, params) {
387
399
  }
388
400
  async function performClick(client, params) {
389
401
  try {
390
- const { selector, force = false } = params;
402
+ const { selector, ref, force = false } = params;
391
403
  const settle = resolveSettleParams({
392
404
  settle_ms: params.settle_ms,
393
405
  settle_timeout_ms: params.settle_timeout_ms,
394
406
  });
395
407
  const expectSpec = resolveExpectParams(params.expect);
396
408
  const evaluate = (expression) => evaluateInTab(client, expression);
397
- const resolveOpts = { selector, force };
409
+ const resolveOpts = ref !== undefined ? { ref, force } : { selector, force };
398
410
  const resolved = await evaluateInTab(client, buildClickResolveJs(resolveOpts));
399
411
  if (!resolved.ok) {
412
+ const refError = ref !== undefined ? refFailureMessage(resolved, ref) : null;
413
+ if (refError)
414
+ return { ok: false, error: refError };
400
415
  if (resolved.reason === 'occluded') {
401
416
  return {
402
417
  ok: false,
403
418
  error: `Element covered by ${resolved.blocker} — click the covering element or dismiss it first`,
404
419
  };
405
420
  }
421
+ if (resolved.reason === 'retargeted') {
422
+ return {
423
+ ok: false,
424
+ error: retargetedClickMessage(resolved.hit_element, ref ?? selector ?? ''),
425
+ };
426
+ }
406
427
  if (resolved.reason === 'invalid-selector') {
407
428
  return { ok: false, error: `Invalid selector "${selector}": ${resolved.error}` };
408
429
  }
409
- return { ok: false, error: `Element not found: ${selector}` };
430
+ return { ok: false, error: `Element not found: ${ref ?? selector}` };
410
431
  }
411
432
  const mark = await takeObserverMark(client, settle);
412
433
  const baseline = await beginExpect(evaluate, expectSpec);
@@ -433,7 +454,11 @@ async function performClick(client, params) {
433
454
  const verdict = await verifyExpect(evaluate, expectSpec, baseline, settleResult);
434
455
  if (!verdict.ok)
435
456
  return { ok: false, error: verdict.error };
436
- const result = { clicked: selector, tag: resolved.tag };
457
+ const result = { clicked: ref ?? selector ?? '', tag: resolved.tag };
458
+ if (ref !== undefined)
459
+ result.ref = ref;
460
+ if (resolved.reresolved === true)
461
+ result.reresolved = true;
437
462
  if (resolved.hit_element)
438
463
  result.hit_element = resolved.hit_element;
439
464
  if (settleResult)
@@ -448,14 +473,26 @@ async function performClick(client, params) {
448
473
  }
449
474
  async function performType(client, params) {
450
475
  try {
451
- const { selector, text, clear = false } = params;
476
+ const { selector, ref, text, clear = false } = params;
452
477
  const settle = resolveSettleParams({
453
478
  settle_ms: params.settle_ms,
454
479
  settle_timeout_ms: params.settle_timeout_ms,
455
480
  });
456
481
  const expectSpec = resolveExpectParams(params.expect);
457
482
  const evaluate = (expression) => evaluateInTab(client, expression);
458
- if (selector !== undefined) {
483
+ let reresolved = false;
484
+ if (ref !== undefined) {
485
+ const opts = { ref, clear };
486
+ const resolved = await evaluateInTab(client, buildTypeResolveJs(opts));
487
+ if (!resolved.ok) {
488
+ return {
489
+ ok: false,
490
+ error: refFailureMessage(resolved, ref) ?? `Element not found: ${ref}`,
491
+ };
492
+ }
493
+ reresolved = resolved.reresolved === true;
494
+ }
495
+ else if (selector !== undefined) {
459
496
  const opts = { selector, clear };
460
497
  const resolved = await evaluateInTab(client, buildTypeResolveJs(opts));
461
498
  if (!resolved.ok) {
@@ -475,6 +512,10 @@ async function performType(client, params) {
475
512
  const result = { typed: text.length };
476
513
  if (selector !== undefined)
477
514
  result.selector = selector;
515
+ if (ref !== undefined)
516
+ result.ref = ref;
517
+ if (reresolved)
518
+ result.reresolved = true;
478
519
  if (settleResult)
479
520
  result.settle = settleResult;
480
521
  if (verdict.expect)
@@ -501,7 +542,16 @@ async function performPress(client, params) {
501
542
  });
502
543
  const expectSpec = resolveExpectParams(params.expect);
503
544
  const evaluate = (expression) => evaluateInTab(client, expression);
504
- if (params.selector) {
545
+ const ref = params.ref;
546
+ let reresolved = false;
547
+ if (ref !== undefined) {
548
+ const focused = await evaluateInTab(client, buildFocusJs({ ref }));
549
+ if (!focused.ok) {
550
+ return { ok: false, error: refFailureMessage(focused, ref) ?? `Element not found: ${ref}` };
551
+ }
552
+ reresolved = focused.reresolved === true;
553
+ }
554
+ else if (params.selector) {
505
555
  const focused = await evaluateInTab(client, buildFocusJs({ selector: params.selector }));
506
556
  if (!focused) {
507
557
  return { ok: false, error: `Element not found: ${params.selector}` };
@@ -515,6 +565,10 @@ async function performPress(client, params) {
515
565
  if (!verdict.ok)
516
566
  return { ok: false, error: verdict.error };
517
567
  const result = { key, modifiers: modifierNames };
568
+ if (ref !== undefined)
569
+ result.ref = ref;
570
+ if (reresolved)
571
+ result.reresolved = true;
518
572
  if (settleResult)
519
573
  result.settle = settleResult;
520
574
  if (verdict.expect)
@@ -570,6 +624,10 @@ shouldCancel) {
570
624
  ? MAX_POLL_MS
571
625
  : requestedPoll;
572
626
  let check = null;
627
+ // Set when a ref probe reports that the page has no observer: the ref
628
+ // can never resolve, so the wait fails instead of timing out (or, for
629
+ // `detached` / `hidden`, matching a ref it never looked up).
630
+ const refProbe = { unavailable: false };
573
631
  if (waitSelector !== undefined) {
574
632
  const expr = buildWaitForSelectorJs(waitSelector, waitCondition);
575
633
  check = async () => {
@@ -582,6 +640,22 @@ shouldCancel) {
582
640
  }
583
641
  };
584
642
  }
643
+ else if (params.ref !== undefined) {
644
+ const expr = buildWaitForSelectorJs({ ref: params.ref }, waitCondition);
645
+ check = async () => {
646
+ try {
647
+ const value = await evaluateInTab(client, expr);
648
+ if (typeof value === 'object' && value !== null) {
649
+ refProbe.unavailable = true;
650
+ return true;
651
+ }
652
+ return value === true;
653
+ }
654
+ catch {
655
+ return false;
656
+ }
657
+ };
658
+ }
585
659
  else if (waitExpression !== undefined) {
586
660
  const wrapped = `Boolean(${waitExpression})`;
587
661
  check = async () => {
@@ -613,6 +687,12 @@ shouldCancel) {
613
687
  await sleep(pollIntervalMs);
614
688
  }
615
689
  const elapsedMs = Date.now() - startedAt;
690
+ if (refProbe.unavailable && params.ref !== undefined) {
691
+ return {
692
+ ok: false,
693
+ error: refFailureMessage({ reason: 'observer-unavailable' }, params.ref) ?? '',
694
+ };
695
+ }
616
696
  // 'cancelled' is deliberately distinct from 'timeout': the step
617
697
  // executor turns a timeout into a flow FAILURE (with a recovery
618
698
  // snapshot) and a cancellation into a clean `stopped_by: 'cancelled'`.
@@ -739,14 +819,18 @@ export async function callCdpTool(tabId, tool, params, timeoutMs) {
739
819
  text: typeof p.text === 'string' ? p.text : '',
740
820
  role: typeof p.role === 'string' ? p.role : undefined,
741
821
  exact: p.exact === true,
822
+ ref: typeof p.ref === 'string' ? p.ref : undefined,
823
+ key: typeof p.key === 'object' && p.key !== null ? p.key : undefined,
742
824
  });
743
825
  if (!outcome.ok)
744
826
  throw new Error(outcome.error);
745
827
  return outcome.result;
746
828
  }
747
829
  case 'click': {
830
+ const ref = typeof p.ref === 'string' ? p.ref : undefined;
748
831
  const outcome = await performClick(client, {
749
- selector: typeof p.selector === 'string' ? p.selector : '',
832
+ selector: typeof p.selector === 'string' ? p.selector : ref !== undefined ? undefined : '',
833
+ ref,
750
834
  force: p.force === true,
751
835
  settle_ms: typeof p.settle_ms === 'number' ? p.settle_ms : undefined,
752
836
  settle_timeout_ms: typeof p.settle_timeout_ms === 'number' ? p.settle_timeout_ms : undefined,
@@ -759,6 +843,7 @@ export async function callCdpTool(tabId, tool, params, timeoutMs) {
759
843
  case 'type': {
760
844
  const outcome = await performType(client, {
761
845
  selector: typeof p.selector === 'string' ? p.selector : undefined,
846
+ ref: typeof p.ref === 'string' ? p.ref : undefined,
762
847
  text: typeof p.text === 'string' ? p.text : '',
763
848
  clear: p.clear === true,
764
849
  settle_ms: typeof p.settle_ms === 'number' ? p.settle_ms : undefined,
@@ -777,6 +862,7 @@ export async function callCdpTool(tabId, tool, params, timeoutMs) {
777
862
  key: typeof p.key === 'string' ? p.key : undefined,
778
863
  modifiers: modifierNames,
779
864
  selector: typeof p.selector === 'string' ? p.selector : undefined,
865
+ ref: typeof p.ref === 'string' ? p.ref : undefined,
780
866
  settle_ms: typeof p.settle_ms === 'number' ? p.settle_ms : undefined,
781
867
  settle_timeout_ms: typeof p.settle_timeout_ms === 'number' ? p.settle_timeout_ms : undefined,
782
868
  expect: p.expect,
@@ -792,6 +878,7 @@ export async function callCdpTool(tabId, tool, params, timeoutMs) {
792
878
  case 'wait_for': {
793
879
  const outcome = await performWaitFor(client, {
794
880
  selector: typeof p.selector === 'string' ? p.selector : undefined,
881
+ ref: typeof p.ref === 'string' ? p.ref : undefined,
795
882
  expression: typeof p.expression === 'string' ? p.expression : undefined,
796
883
  network_url: typeof p.network_url === 'string' ? p.network_url : undefined,
797
884
  condition: typeof p.condition === 'string' ? p.condition : undefined,