@jobshimo/browser-link 0.34.1 → 0.36.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 (38) hide show
  1. package/dist/agent-instructions/content.js +19 -6
  2. package/dist/agent-instructions/content.js.map +1 -1
  3. package/dist/bridge/events.d.ts +1 -1
  4. package/dist/bridge/events.js +8 -1
  5. package/dist/bridge/events.js.map +1 -1
  6. package/dist/bridge/ws-bridge.d.ts +17 -0
  7. package/dist/bridge/ws-bridge.js +34 -0
  8. package/dist/bridge/ws-bridge.js.map +1 -1
  9. package/dist/cdp/inpage/builders.d.ts +70 -3
  10. package/dist/cdp/inpage/builders.js +197 -36
  11. package/dist/cdp/inpage/builders.js.map +1 -1
  12. package/dist/cdp/inpage/observer.d.ts +77 -0
  13. package/dist/cdp/inpage/observer.js +509 -0
  14. package/dist/cdp/inpage/observer.js.map +1 -0
  15. package/dist/cdp/observer-host.d.ts +137 -0
  16. package/dist/cdp/observer-host.js +188 -0
  17. package/dist/cdp/observer-host.js.map +1 -0
  18. package/dist/cdp/transport.js +161 -25
  19. package/dist/cdp/transport.js.map +1 -1
  20. package/dist/extension/background.js +183 -33
  21. package/dist/extension/background.js.map +1 -1
  22. package/dist/extension/inpage/builders.d.ts +70 -3
  23. package/dist/extension/inpage/builders.js +196 -35
  24. package/dist/extension/inpage/builders.js.map +1 -1
  25. package/dist/extension/inpage/observer.d.ts +88 -0
  26. package/dist/extension/inpage/observer.js +520 -0
  27. package/dist/extension/inpage/observer.js.map +1 -0
  28. package/dist/extension/manifest.json +1 -1
  29. package/dist/extension/observer-host.d.ts +84 -0
  30. package/dist/extension/observer-host.js +172 -0
  31. package/dist/extension/observer-host.js.map +1 -0
  32. package/dist/tools/browser-definitions.js +16 -6
  33. package/dist/tools/browser-definitions.js.map +1 -1
  34. package/dist/tools/browser-dispatch.js +14 -2
  35. package/dist/tools/browser-dispatch.js.map +1 -1
  36. package/dist/tools/server-instructions.js +19 -5
  37. package/dist/tools/server-instructions.js.map +1 -1
  38. package/package.json +1 -1
@@ -8,6 +8,52 @@
8
8
  */
9
9
  import { DEEP_QUERY_JS } from './deep-query.js';
10
10
  import { DOM_HELPERS_JS } from './dom-helpers.js';
11
+ import { OBSERVER_GLOBAL } from './observer.js';
12
+ /**
13
+ * In-page access to the page observer's element identity (spec v4 §5.6),
14
+ * injected verbatim into `buildSnapshotJs`, `buildFindJs` and `buildStateJs`.
15
+ * The observer global is looked up ONCE per evaluation and used only when it
16
+ * is present, not torn down, and exposes `refFor`:
17
+ * - `refOf(el)` returns the observer's `e<N>` ref for `el`, or `null`;
18
+ * - `withDocId(result)` adds `doc_id` to `result` and returns it;
19
+ * - `snapshotStore()` returns the observer when it keeps snapshot records
20
+ * (`recordSnapshot` / `getSnapshot`, used by `snapshot({ since })`), else
21
+ * `null`.
22
+ * Without the observer (injection failed, pre-observer document) all are
23
+ * no-ops, so every result is byte-identical to the pre-ref output.
24
+ */
25
+ export const PAGE_OBSERVER_JS = `
26
+ const pageObserver = (() => {
27
+ try {
28
+ const g = window[${JSON.stringify(OBSERVER_GLOBAL)}];
29
+ if (g && !g.dead && typeof g.refFor === 'function') return g;
30
+ } catch (e) {}
31
+ return null;
32
+ })();
33
+ function refOf(el) {
34
+ if (!pageObserver) return null;
35
+ try {
36
+ const ref = pageObserver.refFor(el);
37
+ return typeof ref === 'string' ? ref : null;
38
+ } catch (e) {
39
+ return null;
40
+ }
41
+ }
42
+ function withDocId(result) {
43
+ if (pageObserver && typeof pageObserver.doc_id === 'string') result.doc_id = pageObserver.doc_id;
44
+ return result;
45
+ }
46
+ function snapshotStore() {
47
+ if (
48
+ pageObserver &&
49
+ typeof pageObserver.recordSnapshot === 'function' &&
50
+ typeof pageObserver.getSnapshot === 'function'
51
+ ) {
52
+ return pageObserver;
53
+ }
54
+ return null;
55
+ }
56
+ `;
11
57
  export function buildSnapshotJs(opts = {}) {
12
58
  const optsJson = JSON.stringify({
13
59
  withinSelector: typeof opts.within_selector === 'string' ? opts.within_selector : null,
@@ -16,23 +62,25 @@ export function buildSnapshotJs(opts = {}) {
16
62
  maxInteractive: typeof opts.max_interactive === 'number' && opts.max_interactive > 0
17
63
  ? Math.min(opts.max_interactive, 500)
18
64
  : 120,
65
+ since: typeof opts.since === 'string' ? opts.since : null,
19
66
  });
20
67
  return `
21
68
  (() => {
22
69
  ${DEEP_QUERY_JS}
23
70
  ${DOM_HELPERS_JS}
71
+ ${PAGE_OBSERVER_JS}
24
72
  const opts = ${optsJson};
25
73
  let root = document;
26
74
  let notice = '';
27
75
  if (opts.withinSelector) {
28
76
  const sub = deepQueryFirst(opts.withinSelector);
29
77
  if (!sub) {
30
- return {
78
+ return withDocId({
31
79
  title: document.title,
32
80
  url: location.href,
33
81
  interactive: [],
34
82
  notice: 'within_selector did not match any element',
35
- };
83
+ });
36
84
  }
37
85
  root = sub;
38
86
  }
@@ -75,6 +123,8 @@ export function buildSnapshotJs(opts = {}) {
75
123
  const role = el.getAttribute('role') || tag;
76
124
  const selInfo = genSelectorInfo(el);
77
125
  const entry = { tag, role, selector: selInfo.selector };
126
+ const ref = refOf(el);
127
+ if (ref) entry.ref = ref;
78
128
  if (selInfo.ambiguous) entry.ambiguous = true;
79
129
  const txt = shortText(el);
80
130
  if (txt) entry.text = txt;
@@ -94,26 +144,105 @@ export function buildSnapshotJs(opts = {}) {
94
144
  if (frame) entry.frame = frame;
95
145
  interactive.push(entry);
96
146
  });
97
- const result = {
98
- title: document.title,
99
- url: location.href,
100
- interactive: interactive.slice(0, opts.maxInteractive),
101
- };
147
+ let headings = null;
148
+ let visibleText = '';
102
149
  if (!opts.onlyInteractive) {
103
- const headings = [];
150
+ const found = [];
104
151
  deepQueryAll('h1, h2, h3', root).forEach((h) => {
105
152
  if (!isVisible(h)) return;
106
153
  if (inExcludedLandmark(h)) return;
107
154
  const t = shortText(h);
108
- if (t) headings.push({ level: h.tagName, text: t });
155
+ if (t) found.push({ level: h.tagName, text: t });
109
156
  });
110
- result.headings = headings.slice(0, 30);
157
+ headings = found.slice(0, 30);
111
158
  const textRoot = (root === document) ? (document.body || document) : root;
112
- const visibleText = (textRoot && textRoot.innerText) ? textRoot.innerText.slice(0, 4000) : '';
159
+ visibleText = (textRoot && textRoot.innerText) ? textRoot.innerText.slice(0, 4000) : '';
160
+ }
161
+ let snapshotId = null;
162
+ if (withDocId({}).doc_id && typeof pageObserver.nextSnapshotId === 'function') {
163
+ const issued = pageObserver.nextSnapshotId();
164
+ if (typeof issued === 'string') snapshotId = issued;
165
+ }
166
+ function fullResult() {
167
+ const result = {
168
+ title: document.title,
169
+ url: location.href,
170
+ interactive: interactive.slice(0, opts.maxInteractive),
171
+ };
172
+ if (headings) result.headings = headings;
113
173
  if (visibleText) result.text = visibleText;
174
+ if (notice) result.notice = notice;
175
+ withDocId(result);
176
+ if (snapshotId) result.snapshot_id = snapshotId;
177
+ return result;
114
178
  }
115
- if (notice) result.notice = notice;
116
- return result;
179
+ // Record every snapshot (with or without since) so a later call can diff
180
+ // against it. Entries are keyed by ref; the hash is the entry serialised
181
+ // without its ref, in emission order. Capped at the first 500 entries.
182
+ const store = snapshotId ? snapshotStore() : null;
183
+ // A snapshot_id is \`<doc_id>:s<N>\`: a since whose prefix (before the LAST
184
+ // ':') is not this document's doc_id, or that has no ':', came from another
185
+ // document and is never looked up.
186
+ const sinceCut = opts.since === null ? -1 : opts.since.lastIndexOf(':');
187
+ const sinceSameDoc =
188
+ sinceCut >= 0 && withDocId({}).doc_id === opts.since.slice(0, sinceCut);
189
+ const previous = store && sinceSameDoc ? store.getSnapshot(opts.since) : null;
190
+ const current = new Map();
191
+ const headingsHash = headings ? JSON.stringify(headings) : null;
192
+ const textHash = opts.onlyInteractive ? null : visibleText;
193
+ if (store) {
194
+ for (const entry of interactive) {
195
+ if (current.size >= 500) break;
196
+ if (typeof entry.ref !== 'string') continue;
197
+ const copy = {};
198
+ for (const key of Object.keys(entry)) if (key !== 'ref') copy[key] = entry[key];
199
+ current.set(entry.ref, { hash: JSON.stringify(copy), entry });
200
+ }
201
+ store.recordSnapshot(snapshotId, { entries: current, headings_hash: headingsHash, text_hash: textHash });
202
+ }
203
+ if (opts.since === null) return fullResult();
204
+ if (!store) {
205
+ const result = fullResult();
206
+ result.since = opts.since;
207
+ result.since_status = 'observer-unavailable';
208
+ return result;
209
+ }
210
+ if (!previous || !previous.entries || typeof previous.entries.get !== 'function') {
211
+ const result = fullResult();
212
+ result.since = opts.since;
213
+ result.since_status = sinceSameDoc ? 'evicted' : 'doc-changed';
214
+ return result;
215
+ }
216
+ const added = [];
217
+ const changed = [];
218
+ const removed = [];
219
+ current.forEach((item, ref) => {
220
+ const old = previous.entries.get(ref);
221
+ if (!old) added.push(item.entry);
222
+ else if (old.hash !== item.hash) changed.push(item.entry);
223
+ });
224
+ previous.entries.forEach((item, ref) => {
225
+ if (current.has(ref) || removed.length >= 500) return;
226
+ const gone = { ref, tag: item.entry.tag };
227
+ if (item.entry.text) gone.text = item.entry.text;
228
+ removed.push(gone);
229
+ });
230
+ // max_interactive applies after diffing (added first, then changed), so
231
+ // a window shift never masquerades as change.
232
+ const listed = added.concat(changed);
233
+ const kept = listed.slice(0, opts.maxInteractive);
234
+ const diff = withDocId({ title: document.title, url: location.href });
235
+ diff.snapshot_id = snapshotId;
236
+ diff.since = opts.since;
237
+ diff.added = kept.slice(0, Math.min(added.length, kept.length));
238
+ diff.removed = removed;
239
+ diff.changed = kept.slice(diff.added.length);
240
+ if (listed.length > kept.length) diff.overflow = listed.length - kept.length;
241
+ if (!opts.onlyInteractive) {
242
+ if (headingsHash !== previous.headings_hash) diff.headings = headings;
243
+ if (textHash !== previous.text_hash) diff.text = visibleText;
244
+ }
245
+ return diff;
117
246
  })()
118
247
  `;
119
248
  }
@@ -129,6 +258,7 @@ export function buildFindJs(opts) {
129
258
  (() => {
130
259
  ${DEEP_QUERY_JS}
131
260
  ${DOM_HELPERS_JS}
261
+ ${PAGE_OBSERVER_JS}
132
262
  const opts = ${optsJson};
133
263
  const needle = opts.text.toLowerCase();
134
264
  const needleTokens = needle.split(/[^a-z0-9]+/).filter(Boolean);
@@ -189,6 +319,8 @@ export function buildFindJs(opts) {
189
319
  }
190
320
  function toNearMiss(el) {
191
321
  const entry = { text: snippetOf(el), selector: genSelector(el) };
322
+ const ref = refOf(el);
323
+ if (ref) entry.ref = ref;
192
324
  const roleAttr = el.getAttribute('role');
193
325
  if (roleAttr) entry.role = roleAttr;
194
326
  return entry;
@@ -221,44 +353,46 @@ export function buildFindJs(opts) {
221
353
  ' but none with role "' + opts.role + '"' +
222
354
  (closest ? ' — closest: ' + describeForError(closest) + ' "' + snippetOf(closest) + '"' : '');
223
355
  result.near_misses = ranked.map(toNearMiss);
224
- return result;
356
+ return withDocId(result);
225
357
  }
226
358
  const ranked = rankCandidates(broadAll);
227
359
  if (ranked.length > 0) result.near_misses = ranked.map(toNearMiss);
228
- return result;
360
+ return withDocId(result);
229
361
  }
230
362
  const ranked = rankCandidates(all);
231
363
  if (ranked.length > 0) result.near_misses = ranked.map(toNearMiss);
232
- return result;
364
+ return withDocId(result);
233
365
  }
234
366
  if (matches.length > 1) {
235
- return {
367
+ return withDocId({
236
368
  matched: false,
237
369
  reason: 'multiple-matches',
238
- candidates: matches.slice(0, opts.candidateLimit).map((el) => ({
239
- selector: genSelector(el),
240
- text: shortText(el),
241
- tag: el.tagName.toLowerCase(),
242
- })),
243
- };
370
+ candidates: matches.slice(0, opts.candidateLimit).map((el) => {
371
+ const candidate = { selector: genSelector(el) };
372
+ const ref = refOf(el);
373
+ if (ref) candidate.ref = ref;
374
+ candidate.text = shortText(el);
375
+ candidate.tag = el.tagName.toLowerCase();
376
+ return candidate;
377
+ }),
378
+ });
244
379
  }
245
380
  const el = matches[0];
246
381
  const center = viewportCenterOf(el);
247
382
  const selInfo = genSelectorInfo(el);
248
- const result = {
249
- matched: true,
250
- selector: selInfo.selector,
251
- coords: {
252
- x: Math.round(center.x),
253
- y: Math.round(center.y),
254
- },
255
- tag: el.tagName.toLowerCase(),
256
- text: shortText(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),
257
389
  };
390
+ result.tag = el.tagName.toLowerCase();
391
+ result.text = shortText(el);
258
392
  if (selInfo.ambiguous) result.ambiguous = true;
259
393
  const frame = frameSelectorFor(el);
260
394
  if (frame) result.frame = frame;
261
- return result;
395
+ return withDocId(result);
262
396
  })()
263
397
  `;
264
398
  }
@@ -323,6 +457,26 @@ export function buildFocusJs(opts) {
323
457
  return true;
324
458
  })()`;
325
459
  }
460
+ export function buildWaitForSelectorJs(selector, condition) {
461
+ const optsJson = JSON.stringify({ selector, condition });
462
+ return `
463
+ (() => {
464
+ ${DEEP_QUERY_JS}
465
+ const opts = ${optsJson};
466
+ document.querySelector(opts.selector);
467
+ const el = deepQueryFirst(opts.selector);
468
+ if (opts.condition === 'attached') return Boolean(el);
469
+ if (opts.condition === 'detached') return !el;
470
+ const wantHidden = opts.condition === 'hidden';
471
+ if (!el) return wantHidden;
472
+ const view = (el.ownerDocument && el.ownerDocument.defaultView) || window;
473
+ const s = view.getComputedStyle(el);
474
+ if (s.display === 'none' || s.visibility === 'hidden' || s.opacity === '0') return wantHidden;
475
+ const r = el.getBoundingClientRect();
476
+ const shown = r.width > 0 && r.height > 0;
477
+ return wantHidden ? !shown : shown;
478
+ })()`;
479
+ }
326
480
  export function buildElementRectJs(opts) {
327
481
  const optsJson = JSON.stringify({ selector: opts.selector });
328
482
  return `
@@ -603,12 +757,16 @@ export function buildExpectProbeJs(opts) {
603
757
  * `focused` beyond `<body>`, no `dialogs` when none are open, and no
604
758
  * `scroll` when the page is at its default (0,0) position — token-lean by
605
759
  * construction, matching the omit-falsy convention the other builders use.
760
+ *
761
+ * With the page observer installed (see `PAGE_OBSERVER_JS`), `focused`
762
+ * carries `ref` right after `selector` and the result carries `doc_id`.
606
763
  */
607
764
  export function buildStateJs() {
608
765
  return `
609
766
  (() => {
610
767
  ${DEEP_QUERY_JS}
611
768
  ${DOM_HELPERS_JS}
769
+ ${PAGE_OBSERVER_JS}
612
770
  function deepActiveElement() {
613
771
  let el = document.activeElement;
614
772
  let guard = 0;
@@ -669,7 +827,10 @@ export function buildStateJs() {
669
827
  const active = deepActiveElement();
670
828
  if (active && active !== document.body && active.tagName) {
671
829
  const selInfo = genSelectorInfo(active);
672
- const focused = { selector: selInfo.selector, tag: active.tagName.toLowerCase() };
830
+ const focused = { selector: selInfo.selector };
831
+ const ref = refOf(active);
832
+ if (ref) focused.ref = ref;
833
+ focused.tag = active.tagName.toLowerCase();
673
834
  if (selInfo.ambiguous) focused.ambiguous = true;
674
835
  result.focused = focused;
675
836
  }
@@ -689,7 +850,7 @@ export function buildStateJs() {
689
850
  const scrollY = Math.round(window.scrollY);
690
851
  if (scrollX !== 0 || scrollY !== 0) result.scroll = { x: scrollX, y: scrollY };
691
852
 
692
- return result;
853
+ return withDocId(result);
693
854
  })()
694
855
  `;
695
856
  }
@@ -1 +1 @@
1
- {"version":3,"file":"builders.js","sourceRoot":"","sources":["../../src/inpage/builders.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"builders.js","sourceRoot":"","sources":["../../src/inpage/builders.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * The page observer: a persistent, coalescing MutationObserver that runs in
3
+ * the page's MAIN world (spec v4 §5.1–§5.3). The host installs it once per
4
+ * tab with `Runtime.addBinding` + `Page.addScriptToEvaluateOnNewDocument`
5
+ * (`runImmediately: true`), so Chrome re-runs it in every new document and
6
+ * in every same-process frame before page scripts. It pushes one summary
7
+ * per flush through the binding and keeps a bounded ring of summaries so an
8
+ * action window (mark → changes) can be read back exactly.
9
+ *
10
+ * Lab findings this source honours (`packages/plan/lab/2026-11-06-live-page-model/`):
11
+ * - flushes are scheduled with `setTimeout`, never `requestAnimationFrame`
12
+ * (rAF never fires in a hidden tab);
13
+ * - the instance is versioned: an older instance is torn down and replaced,
14
+ * an equal or newer one is left alone — never "if present, return";
15
+ * - one observer per root (an observer on `document` alone misses shadow
16
+ * roots), with `Element.prototype.attachShadow` patched to catch every
17
+ * root created later, open or closed.
18
+ *
19
+ * Verbatim sibling: `packages/server/src/cdp/inpage/observer.ts` (the server
20
+ * publishes standalone and cannot import this package). Everything below this
21
+ * header must stay byte-identical — `cdp/drift.test.ts` enforces it and
22
+ * `cdp/builders-parity.test.ts` checks the built output.
23
+ */
24
+ /** Bump whenever the in-page protocol (global shape, messages, methods) changes. */
25
+ export declare const OBSERVER_VERSION = 2;
26
+ /** Name of the `Runtime.addBinding` function the observer pushes messages through. */
27
+ export declare const OBSERVER_BINDING = "__blPageObserverPush";
28
+ /** Well-known global that holds the installed observer instance. */
29
+ export declare const OBSERVER_GLOBAL = "__blPageObserver";
30
+ export interface ObserverInstallOpts {
31
+ /** Binding name to push through; defaults to `OBSERVER_BINDING`. */
32
+ binding?: string;
33
+ /** Instance version; defaults to `OBSERVER_VERSION`. */
34
+ version?: number;
35
+ /** Max target descriptors per summary (default 20, clamped to 1..100). */
36
+ descriptor_cap?: number;
37
+ /** Summaries kept for mark/changes read-back (default 64, clamped to 1..1024). */
38
+ ring_size?: number;
39
+ }
40
+ /**
41
+ * Build the observer install source. Valid both as a
42
+ * `Page.addScriptToEvaluateOnNewDocument` source and as a one-off
43
+ * `Runtime.evaluate` expression, which evaluates to
44
+ * `{ ok: true, installed, version, doc_id, coverage }`.
45
+ *
46
+ * The instance also owns element identity (spec v4 §5.6): `refFor(el)` issues
47
+ * `e<N>` (monotonic per document instance, from `e1`) the first time an
48
+ * element is seen and reuses it while the node lives; `resolveRef(ref)`
49
+ * returns the element while it is connected, else `null`. Refs are only valid
50
+ * for the `doc_id` that issued them. Neither method throws. `nextSnapshotId()`
51
+ * issues `<doc_id>:s<N>` (N monotonic per document instance, from 1) for each
52
+ * `snapshot` result and returns `null` once the instance is torn down; the
53
+ * `doc_id` prefix lets a stale id from another document be rejected.
54
+ * `recordSnapshot(id, record)` keeps the last 4 snapshot records per document
55
+ * instance (oldest evicted first) for `snapshot({ since })`; `getSnapshot(id)`
56
+ * returns the record or `null` when unknown or evicted. The record is opaque
57
+ * here. Both are inert after teardown, which clears the store.
58
+ */
59
+ export declare function buildObserverInstallJs(opts?: ObserverInstallOpts): string;
60
+ /**
61
+ * Build the action-mark expression. Forces a flush (including records still
62
+ * queued in the observers) so the mark is exact, then baselines URL and focus.
63
+ * Evaluates to `{ ok: true, doc_id, mark, url, has_focus_baseline: true }` or
64
+ * `{ ok: false, reason: 'observer-unavailable' }`.
65
+ */
66
+ export declare function buildObserverMarkJs(): string;
67
+ /**
68
+ * Build the "changes since mark" expression. Forces a flush, then aggregates
69
+ * the ring summaries with `seq > mark`. `docId`, when given, must match the
70
+ * current document. Evaluates to the aggregated changes (see
71
+ * `SettleChanges` in `@browser-link/shared`) plus `ok`, `doc_id`, `since`,
72
+ * and `url_changed` / `focus_moved` only when they differ from the mark's
73
+ * baseline; or `{ ok: false, reason: 'observer-unavailable' | 'doc-changed' }`.
74
+ */
75
+ export declare function buildObserverChangesJs(mark: number, docId?: string): string;
76
+ /**
77
+ * Build the ref-resolve expression, for tests and debugging. Evaluates to
78
+ * `{ ok: true, found: true, tag }` when `ref` names an element of the current
79
+ * document that is still connected, `{ ok: true, found: false }` when it is
80
+ * unknown or stale, or `{ ok: false, reason: 'observer-unavailable' }`.
81
+ */
82
+ export declare function buildObserverRefResolveJs(ref: string): string;
83
+ /**
84
+ * Build the teardown expression: disconnects every observer, restores the
85
+ * `attachShadow` and `history` patches when they are still the observer's,
86
+ * and removes the global. Idempotent; evaluates to `{ ok: true, removed }`.
87
+ */
88
+ export declare function buildObserverTeardownJs(): string;