@jobshimo/browser-link 0.34.1 → 0.35.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 +9 -6
- package/dist/agent-instructions/content.js.map +1 -1
- package/dist/bridge/events.d.ts +1 -1
- package/dist/bridge/events.js +8 -1
- package/dist/bridge/events.js.map +1 -1
- package/dist/bridge/ws-bridge.d.ts +17 -0
- package/dist/bridge/ws-bridge.js +34 -0
- package/dist/bridge/ws-bridge.js.map +1 -1
- package/dist/cdp/inpage/builders.d.ts +18 -0
- package/dist/cdp/inpage/builders.js +21 -1
- package/dist/cdp/inpage/builders.js.map +1 -1
- package/dist/cdp/inpage/observer.d.ts +57 -0
- package/dist/cdp/inpage/observer.js +405 -0
- package/dist/cdp/inpage/observer.js.map +1 -0
- package/dist/cdp/observer-host.d.ts +137 -0
- package/dist/cdp/observer-host.js +188 -0
- package/dist/cdp/observer-host.js.map +1 -0
- package/dist/cdp/transport.js +160 -25
- package/dist/cdp/transport.js.map +1 -1
- package/dist/extension/background.js +182 -33
- package/dist/extension/background.js.map +1 -1
- package/dist/extension/inpage/builders.d.ts +18 -0
- package/dist/extension/inpage/builders.js +20 -0
- package/dist/extension/inpage/builders.js.map +1 -1
- package/dist/extension/inpage/observer.d.ts +68 -0
- package/dist/extension/inpage/observer.js +416 -0
- package/dist/extension/inpage/observer.js.map +1 -0
- package/dist/extension/manifest.json +1 -1
- package/dist/extension/observer-host.d.ts +84 -0
- package/dist/extension/observer-host.js +172 -0
- package/dist/extension/observer-host.js.map +1 -0
- package/dist/tools/browser-definitions.js +3 -3
- package/dist/tools/browser-definitions.js.map +1 -1
- package/dist/tools/server-instructions.js +9 -5
- package/dist/tools/server-instructions.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Independent copy of `packages/extension/src/inpage/observer.ts` — see
|
|
3
|
+
* `./deep-query.ts`'s header comment for why this is a verbatim duplicate
|
|
4
|
+
* rather than a cross-package import.
|
|
5
|
+
*
|
|
6
|
+
* The page observer: a persistent, coalescing MutationObserver that runs in
|
|
7
|
+
* the page's MAIN world (spec v4 §5.1–§5.3). cdp-direct installs it with the
|
|
8
|
+
* same `Runtime.addBinding` + `Page.addScriptToEvaluateOnNewDocument` pair as
|
|
9
|
+
* the extension. Everything below this header must stay byte-identical to the
|
|
10
|
+
* extension original — `cdp/drift.test.ts` enforces it and
|
|
11
|
+
* `cdp/builders-parity.test.ts` checks the built output.
|
|
12
|
+
*/
|
|
13
|
+
/** Bump whenever the in-page protocol (global shape, messages, methods) changes. */
|
|
14
|
+
export const OBSERVER_VERSION = 1;
|
|
15
|
+
/** Name of the `Runtime.addBinding` function the observer pushes messages through. */
|
|
16
|
+
export const OBSERVER_BINDING = '__blPageObserverPush';
|
|
17
|
+
/** Well-known global that holds the installed observer instance. */
|
|
18
|
+
export const OBSERVER_GLOBAL = '__blPageObserver';
|
|
19
|
+
const DEFAULT_DESCRIPTOR_CAP = 20;
|
|
20
|
+
const MAX_DESCRIPTOR_CAP = 100;
|
|
21
|
+
const DEFAULT_RING_SIZE = 64;
|
|
22
|
+
const MAX_RING_SIZE = 1024;
|
|
23
|
+
function clampInt(value, fallback, min, max) {
|
|
24
|
+
if (typeof value !== 'number' || !Number.isFinite(value))
|
|
25
|
+
return fallback;
|
|
26
|
+
return Math.min(max, Math.max(min, Math.floor(value)));
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Build the observer install source. Valid both as a
|
|
30
|
+
* `Page.addScriptToEvaluateOnNewDocument` source and as a one-off
|
|
31
|
+
* `Runtime.evaluate` expression, which evaluates to
|
|
32
|
+
* `{ ok: true, installed, version, doc_id, coverage }`.
|
|
33
|
+
*/
|
|
34
|
+
export function buildObserverInstallJs(opts = {}) {
|
|
35
|
+
const optsJson = JSON.stringify({
|
|
36
|
+
key: OBSERVER_GLOBAL,
|
|
37
|
+
binding: typeof opts.binding === 'string' && opts.binding.length > 0 ? opts.binding : OBSERVER_BINDING,
|
|
38
|
+
version: clampInt(opts.version, OBSERVER_VERSION, 0, Number.MAX_SAFE_INTEGER),
|
|
39
|
+
cap: clampInt(opts.descriptor_cap, DEFAULT_DESCRIPTOR_CAP, 1, MAX_DESCRIPTOR_CAP),
|
|
40
|
+
ringSize: clampInt(opts.ring_size, DEFAULT_RING_SIZE, 1, MAX_RING_SIZE),
|
|
41
|
+
});
|
|
42
|
+
return `
|
|
43
|
+
(() => {
|
|
44
|
+
const opts = ${optsJson};
|
|
45
|
+
const existing = window[opts.key];
|
|
46
|
+
if (existing && typeof existing.version === 'number' && existing.version >= opts.version) {
|
|
47
|
+
return { ok: true, installed: false, version: existing.version, doc_id: existing.doc_id, coverage: existing.coverage };
|
|
48
|
+
}
|
|
49
|
+
if (existing && typeof existing.teardown === 'function') {
|
|
50
|
+
try { existing.teardown(); } catch (e) {}
|
|
51
|
+
}
|
|
52
|
+
let isFrame = true;
|
|
53
|
+
try { isFrame = window !== window.top; } catch (e) {}
|
|
54
|
+
const zeroKinds = () => ({ childList: 0, attributes: 0, characterData: 0 });
|
|
55
|
+
const g = {
|
|
56
|
+
version: opts.version,
|
|
57
|
+
doc_id: Math.random().toString(36).slice(2, 10) + Date.now().toString(36),
|
|
58
|
+
seq: 0,
|
|
59
|
+
ring: [],
|
|
60
|
+
pending: new Map(),
|
|
61
|
+
pendingRecords: 0,
|
|
62
|
+
pendingKinds: zeroKinds(),
|
|
63
|
+
pendingRoots: { document: false, shadow: false },
|
|
64
|
+
t0: 0,
|
|
65
|
+
timer: null,
|
|
66
|
+
rescan: null,
|
|
67
|
+
roots: 0,
|
|
68
|
+
coverage: 'full',
|
|
69
|
+
baselines: new Map(),
|
|
70
|
+
dead: false,
|
|
71
|
+
};
|
|
72
|
+
const observers = [];
|
|
73
|
+
const observed = new WeakSet();
|
|
74
|
+
const round = (n) => Math.round(n * 10) / 10;
|
|
75
|
+
|
|
76
|
+
function push(message) {
|
|
77
|
+
const fn = window[opts.binding];
|
|
78
|
+
if (typeof fn !== 'function') return;
|
|
79
|
+
try { fn(JSON.stringify(message)); } catch (e) {}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function fold(records, kind) {
|
|
83
|
+
for (const rec of records) {
|
|
84
|
+
if (g.pendingRecords === 0) g.t0 = performance.now();
|
|
85
|
+
g.pendingRecords++;
|
|
86
|
+
g.pendingKinds[rec.type]++;
|
|
87
|
+
g.pendingRoots[kind] = true;
|
|
88
|
+
const target = rec.type === 'characterData' ? rec.target.parentNode : rec.target;
|
|
89
|
+
if (!target) continue;
|
|
90
|
+
g.pending.set(target, (g.pending.get(target) || 0) + 1);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function drain() {
|
|
95
|
+
for (const o of observers) {
|
|
96
|
+
const recs = o.mo.takeRecords();
|
|
97
|
+
if (recs.length) fold(recs, o.kind);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function segment(el) {
|
|
102
|
+
const tag = el.localName || String(el.nodeName).toLowerCase();
|
|
103
|
+
return el.id ? tag + '#' + el.id : tag;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function pathOf(node) {
|
|
107
|
+
const parts = [];
|
|
108
|
+
let n = node;
|
|
109
|
+
let segments = 0;
|
|
110
|
+
while (n && segments < 6) {
|
|
111
|
+
if (n.nodeType === 1) {
|
|
112
|
+
if (n === document.documentElement) break;
|
|
113
|
+
parts.unshift(segment(n));
|
|
114
|
+
segments++;
|
|
115
|
+
n = n.parentNode;
|
|
116
|
+
} else if (n.nodeType === 11 && n.host) {
|
|
117
|
+
parts.unshift('#shadow');
|
|
118
|
+
n = n.host;
|
|
119
|
+
} else {
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return parts.join('>');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function describe(node) {
|
|
127
|
+
if (node.nodeType === 11) {
|
|
128
|
+
return { tag: '#shadow-root', path: node.host ? pathOf(node.host) + '>#shadow' : '#shadow' };
|
|
129
|
+
}
|
|
130
|
+
if (node.nodeType === 9) return { tag: '#document', path: '#document' };
|
|
131
|
+
const d = { tag: node.localName || String(node.nodeName).toLowerCase() };
|
|
132
|
+
if (typeof node.id === 'string' && node.id) d.id = node.id;
|
|
133
|
+
const role = typeof node.getAttribute === 'function' ? node.getAttribute('role') : null;
|
|
134
|
+
if (role) d.role = role;
|
|
135
|
+
d.path = pathOf(node);
|
|
136
|
+
return d;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function flush() {
|
|
140
|
+
if (g.timer !== null) {
|
|
141
|
+
clearTimeout(g.timer);
|
|
142
|
+
g.timer = null;
|
|
143
|
+
}
|
|
144
|
+
drain();
|
|
145
|
+
if (g.pendingRecords === 0) return null;
|
|
146
|
+
const entries = Array.from(g.pending.entries()).sort((a, b) => b[1] - a[1]);
|
|
147
|
+
const top = entries.slice(0, opts.cap);
|
|
148
|
+
const summary = {
|
|
149
|
+
type: 'summary',
|
|
150
|
+
doc_id: g.doc_id,
|
|
151
|
+
seq: ++g.seq,
|
|
152
|
+
t0: round(g.t0),
|
|
153
|
+
t1: round(performance.now()),
|
|
154
|
+
records: g.pendingRecords,
|
|
155
|
+
targets: entries.length,
|
|
156
|
+
kinds: g.pendingKinds,
|
|
157
|
+
roots: { document: g.pendingRoots.document, shadow: g.pendingRoots.shadow, frame: isFrame },
|
|
158
|
+
descriptors: top.map((e) => describe(e[0])),
|
|
159
|
+
overflow: entries.length - top.length,
|
|
160
|
+
};
|
|
161
|
+
g.ring.push({ summary: summary, counts: top.map((e) => e[1]) });
|
|
162
|
+
while (g.ring.length > opts.ringSize) g.ring.shift();
|
|
163
|
+
g.pending = new Map();
|
|
164
|
+
g.pendingRecords = 0;
|
|
165
|
+
g.pendingKinds = zeroKinds();
|
|
166
|
+
g.pendingRoots = { document: false, shadow: false };
|
|
167
|
+
push(summary);
|
|
168
|
+
return summary;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function onTimer() {
|
|
172
|
+
g.timer = null;
|
|
173
|
+
try { flush(); } catch (e) {}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function onRecords(kind) {
|
|
177
|
+
return (records) => {
|
|
178
|
+
if (g.dead) return;
|
|
179
|
+
try {
|
|
180
|
+
fold(records, kind);
|
|
181
|
+
if (g.pendingRecords > 0 && g.timer === null) g.timer = setTimeout(onTimer, 16);
|
|
182
|
+
} catch (e) {}
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function observe(root, kind) {
|
|
187
|
+
if (!root || observed.has(root)) return;
|
|
188
|
+
observed.add(root);
|
|
189
|
+
try {
|
|
190
|
+
const mo = new MutationObserver(onRecords(kind));
|
|
191
|
+
mo.observe(root, { subtree: true, childList: true, attributes: true, characterData: true });
|
|
192
|
+
observers.push({ mo: mo, kind: kind });
|
|
193
|
+
g.roots++;
|
|
194
|
+
} catch (e) {}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function walk(root) {
|
|
198
|
+
let all;
|
|
199
|
+
try { all = root.querySelectorAll('*'); } catch (e) { return; }
|
|
200
|
+
for (const el of all) {
|
|
201
|
+
const sr = el.shadowRoot;
|
|
202
|
+
if (sr) {
|
|
203
|
+
observe(sr, 'shadow');
|
|
204
|
+
walk(sr);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
observe(document, 'document');
|
|
210
|
+
walk(document);
|
|
211
|
+
|
|
212
|
+
const proto = Element.prototype;
|
|
213
|
+
const originalAttach = proto.attachShadow;
|
|
214
|
+
let patchedAttach = null;
|
|
215
|
+
if (typeof originalAttach === 'function') {
|
|
216
|
+
try {
|
|
217
|
+
const candidate = function attachShadow() {
|
|
218
|
+
const root = originalAttach.apply(this, arguments);
|
|
219
|
+
if (!g.dead) {
|
|
220
|
+
try { observe(root, 'shadow'); } catch (e) {}
|
|
221
|
+
}
|
|
222
|
+
return root;
|
|
223
|
+
};
|
|
224
|
+
proto.attachShadow = candidate;
|
|
225
|
+
if (proto.attachShadow !== candidate) throw new Error('attachShadow not patchable');
|
|
226
|
+
patchedAttach = candidate;
|
|
227
|
+
} catch (e) {
|
|
228
|
+
g.coverage = 'partial';
|
|
229
|
+
g.rescan = setInterval(() => {
|
|
230
|
+
try { walk(document); } catch (err) {}
|
|
231
|
+
}, 2000);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function onNav() {
|
|
236
|
+
if (g.dead) return;
|
|
237
|
+
push({ type: 'nav', doc_id: g.doc_id, url: location.href });
|
|
238
|
+
}
|
|
239
|
+
const hist = window.history;
|
|
240
|
+
const historyPatches = [];
|
|
241
|
+
for (const name of ['pushState', 'replaceState']) {
|
|
242
|
+
const original = hist[name];
|
|
243
|
+
if (typeof original !== 'function') continue;
|
|
244
|
+
const hadOwn = Object.prototype.hasOwnProperty.call(hist, name);
|
|
245
|
+
const wrapped = function () {
|
|
246
|
+
const result = original.apply(this, arguments);
|
|
247
|
+
try { onNav(); } catch (e) {}
|
|
248
|
+
return result;
|
|
249
|
+
};
|
|
250
|
+
try {
|
|
251
|
+
hist[name] = wrapped;
|
|
252
|
+
historyPatches.push({ name: name, original: original, wrapped: wrapped, hadOwn: hadOwn });
|
|
253
|
+
} catch (e) {}
|
|
254
|
+
}
|
|
255
|
+
window.addEventListener('popstate', onNav);
|
|
256
|
+
window.addEventListener('hashchange', onNav);
|
|
257
|
+
|
|
258
|
+
g.flush = flush;
|
|
259
|
+
|
|
260
|
+
g.mark = function () {
|
|
261
|
+
flush();
|
|
262
|
+
const mark = g.seq;
|
|
263
|
+
g.baselines.delete(mark);
|
|
264
|
+
g.baselines.set(mark, { url: location.href, active: document.activeElement });
|
|
265
|
+
while (g.baselines.size > 16) g.baselines.delete(g.baselines.keys().next().value);
|
|
266
|
+
return { ok: true, doc_id: g.doc_id, mark: mark, url: location.href, has_focus_baseline: true };
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
g.changes = function (mark, docId) {
|
|
270
|
+
if (docId && docId !== g.doc_id) return { ok: false, reason: 'doc-changed' };
|
|
271
|
+
const base = g.baselines.get(mark);
|
|
272
|
+
if (!base) return { ok: false, reason: 'doc-changed' };
|
|
273
|
+
flush();
|
|
274
|
+
const items = g.ring.filter((it) => it.summary.seq > mark);
|
|
275
|
+
const kinds = zeroKinds();
|
|
276
|
+
const roots = { document: false, shadow: false, frame: isFrame };
|
|
277
|
+
const merged = new Map();
|
|
278
|
+
let records = 0;
|
|
279
|
+
let targets = 0;
|
|
280
|
+
let overflow = 0;
|
|
281
|
+
for (const it of items) {
|
|
282
|
+
const s = it.summary;
|
|
283
|
+
records += s.records;
|
|
284
|
+
targets += s.targets;
|
|
285
|
+
overflow += s.overflow;
|
|
286
|
+
kinds.childList += s.kinds.childList;
|
|
287
|
+
kinds.attributes += s.kinds.attributes;
|
|
288
|
+
kinds.characterData += s.kinds.characterData;
|
|
289
|
+
roots.document = roots.document || s.roots.document;
|
|
290
|
+
roots.shadow = roots.shadow || s.roots.shadow;
|
|
291
|
+
s.descriptors.forEach((d, i) => {
|
|
292
|
+
const key = d.tag + '\\u0000' + (d.id || '') + '\\u0000' + d.path;
|
|
293
|
+
const m = merged.get(key);
|
|
294
|
+
if (m) m.n += it.counts[i];
|
|
295
|
+
else merged.set(key, { d: d, n: it.counts[i] });
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
const sorted = Array.from(merged.values()).sort((a, b) => b.n - a.n);
|
|
299
|
+
const descriptors = sorted.slice(0, opts.cap).map((m) => m.d);
|
|
300
|
+
overflow += sorted.length - descriptors.length;
|
|
301
|
+
const out = {
|
|
302
|
+
ok: true,
|
|
303
|
+
doc_id: g.doc_id,
|
|
304
|
+
since: mark,
|
|
305
|
+
records: records,
|
|
306
|
+
targets: targets,
|
|
307
|
+
kinds: kinds,
|
|
308
|
+
roots: roots,
|
|
309
|
+
descriptors: descriptors,
|
|
310
|
+
overflow: overflow,
|
|
311
|
+
};
|
|
312
|
+
if (g.seq > mark && (items.length === 0 || items[0].summary.seq !== mark + 1)) out.truncated = true;
|
|
313
|
+
if (location.href !== base.url) out.url_changed = location.href;
|
|
314
|
+
if (document.activeElement !== base.active) out.focus_moved = true;
|
|
315
|
+
return out;
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
g.teardown = function () {
|
|
319
|
+
if (g.dead) return;
|
|
320
|
+
g.dead = true;
|
|
321
|
+
if (g.timer !== null) clearTimeout(g.timer);
|
|
322
|
+
if (g.rescan !== null) clearInterval(g.rescan);
|
|
323
|
+
for (const o of observers) {
|
|
324
|
+
try { o.mo.disconnect(); } catch (e) {}
|
|
325
|
+
}
|
|
326
|
+
observers.length = 0;
|
|
327
|
+
if (patchedAttach && proto.attachShadow === patchedAttach) {
|
|
328
|
+
try { proto.attachShadow = originalAttach; } catch (e) {}
|
|
329
|
+
}
|
|
330
|
+
for (const p of historyPatches) {
|
|
331
|
+
if (hist[p.name] !== p.wrapped) continue;
|
|
332
|
+
try {
|
|
333
|
+
if (p.hadOwn) hist[p.name] = p.original;
|
|
334
|
+
else delete hist[p.name];
|
|
335
|
+
} catch (e) {}
|
|
336
|
+
}
|
|
337
|
+
window.removeEventListener('popstate', onNav);
|
|
338
|
+
window.removeEventListener('hashchange', onNav);
|
|
339
|
+
g.baselines.clear();
|
|
340
|
+
if (window[opts.key] === g) {
|
|
341
|
+
try { delete window[opts.key]; } catch (e) {}
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
Object.defineProperty(window, opts.key, { value: g, configurable: true, writable: true, enumerable: false });
|
|
346
|
+
push({ type: 'doc_start', doc_id: g.doc_id, url: location.href, version: g.version, coverage: g.coverage });
|
|
347
|
+
return { ok: true, installed: true, version: g.version, doc_id: g.doc_id, coverage: g.coverage };
|
|
348
|
+
})()`;
|
|
349
|
+
}
|
|
350
|
+
const UNAVAILABLE = "{ ok: false, reason: 'observer-unavailable' }";
|
|
351
|
+
function callInstance(call) {
|
|
352
|
+
return `
|
|
353
|
+
(() => {
|
|
354
|
+
const g = window[${JSON.stringify(OBSERVER_GLOBAL)}];
|
|
355
|
+
if (!g || g.dead || typeof g.doc_id !== 'string') return ${UNAVAILABLE};
|
|
356
|
+
try {
|
|
357
|
+
return ${call};
|
|
358
|
+
} catch (e) {
|
|
359
|
+
return ${UNAVAILABLE};
|
|
360
|
+
}
|
|
361
|
+
})()`;
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Build the action-mark expression. Forces a flush (including records still
|
|
365
|
+
* queued in the observers) so the mark is exact, then baselines URL and focus.
|
|
366
|
+
* Evaluates to `{ ok: true, doc_id, mark, url, has_focus_baseline: true }` or
|
|
367
|
+
* `{ ok: false, reason: 'observer-unavailable' }`.
|
|
368
|
+
*/
|
|
369
|
+
export function buildObserverMarkJs() {
|
|
370
|
+
return callInstance('g.mark()');
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Build the "changes since mark" expression. Forces a flush, then aggregates
|
|
374
|
+
* the ring summaries with `seq > mark`. `docId`, when given, must match the
|
|
375
|
+
* current document. Evaluates to the aggregated changes (see
|
|
376
|
+
* `SettleChanges` in `@browser-link/shared`) plus `ok`, `doc_id`, `since`,
|
|
377
|
+
* and `url_changed` / `focus_moved` only when they differ from the mark's
|
|
378
|
+
* baseline; or `{ ok: false, reason: 'observer-unavailable' | 'doc-changed' }`.
|
|
379
|
+
*/
|
|
380
|
+
export function buildObserverChangesJs(mark, docId) {
|
|
381
|
+
const markJson = JSON.stringify(clampInt(mark, 0, 0, Number.MAX_SAFE_INTEGER));
|
|
382
|
+
const docJson = typeof docId === 'string' && docId.length > 0 ? JSON.stringify(docId) : 'null';
|
|
383
|
+
return callInstance(`g.changes(${markJson}, ${docJson})`);
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Build the teardown expression: disconnects every observer, restores the
|
|
387
|
+
* `attachShadow` and `history` patches when they are still the observer's,
|
|
388
|
+
* and removes the global. Idempotent; evaluates to `{ ok: true, removed }`.
|
|
389
|
+
*/
|
|
390
|
+
export function buildObserverTeardownJs() {
|
|
391
|
+
return `
|
|
392
|
+
(() => {
|
|
393
|
+
const key = ${JSON.stringify(OBSERVER_GLOBAL)};
|
|
394
|
+
const g = window[key];
|
|
395
|
+
if (!g) return { ok: true, removed: false };
|
|
396
|
+
if (typeof g.teardown === 'function') {
|
|
397
|
+
try { g.teardown(); } catch (e) {}
|
|
398
|
+
}
|
|
399
|
+
if (window[key] === g) {
|
|
400
|
+
try { delete window[key]; } catch (e) {}
|
|
401
|
+
}
|
|
402
|
+
return { ok: true, removed: true };
|
|
403
|
+
})()`;
|
|
404
|
+
}
|
|
405
|
+
//# sourceMappingURL=observer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observer.js","sourceRoot":"","sources":["../../../src/cdp/inpage/observer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/** Per-kind MutationRecord counts reported by the page observer. */
|
|
2
|
+
interface PageObserverKinds {
|
|
3
|
+
childList: number;
|
|
4
|
+
attributes: number;
|
|
5
|
+
characterData: number;
|
|
6
|
+
}
|
|
7
|
+
/** Which kinds of root the counted records came from. `frame` is true when
|
|
8
|
+
* the observer runs in a child frame (`window !== window.top`). */
|
|
9
|
+
interface PageObserverRoots {
|
|
10
|
+
document: boolean;
|
|
11
|
+
shadow: boolean;
|
|
12
|
+
frame: boolean;
|
|
13
|
+
}
|
|
14
|
+
/** One mutated target, as described by the page observer. `path` is a short
|
|
15
|
+
* structural path such as `body>div#app>ul` (at most six element segments,
|
|
16
|
+
* no indexes), with a `#shadow` segment at each shadow-root boundary. A
|
|
17
|
+
* shadow root that was itself the mutation target is reported with tag
|
|
18
|
+
* `#shadow-root`. */
|
|
19
|
+
interface PageObserverDescriptor {
|
|
20
|
+
tag: string;
|
|
21
|
+
id?: string;
|
|
22
|
+
role?: string;
|
|
23
|
+
path: string;
|
|
24
|
+
}
|
|
25
|
+
/** One coalesced flush of the page observer: every MutationRecord seen
|
|
26
|
+
* since the previous flush, folded per target element. */
|
|
27
|
+
interface PageObserverSummary {
|
|
28
|
+
type: 'summary';
|
|
29
|
+
doc_id: string;
|
|
30
|
+
/** Monotonic per document, starting at 1. */
|
|
31
|
+
seq: number;
|
|
32
|
+
/** `performance.now()` at the first record and at the flush. */
|
|
33
|
+
t0: number;
|
|
34
|
+
t1: number;
|
|
35
|
+
records: number;
|
|
36
|
+
/** Distinct target elements in this flush. */
|
|
37
|
+
targets: number;
|
|
38
|
+
kinds: PageObserverKinds;
|
|
39
|
+
roots: PageObserverRoots;
|
|
40
|
+
/** Up to the descriptor cap, ordered by record count (highest first). */
|
|
41
|
+
descriptors: PageObserverDescriptor[];
|
|
42
|
+
/** Targets beyond the descriptor cap. */
|
|
43
|
+
overflow: number;
|
|
44
|
+
}
|
|
45
|
+
/** Pushed once when the page observer installs in a document. */
|
|
46
|
+
interface PageObserverDocStart {
|
|
47
|
+
type: 'doc_start';
|
|
48
|
+
doc_id: string;
|
|
49
|
+
url: string;
|
|
50
|
+
version: number;
|
|
51
|
+
/** `partial` when `attachShadow` could not be patched and the observer
|
|
52
|
+
* falls back to a periodic rescan for open shadow roots. */
|
|
53
|
+
coverage: 'full' | 'partial';
|
|
54
|
+
}
|
|
55
|
+
/** Pushed on `pushState`, `replaceState`, `popstate` and `hashchange`. */
|
|
56
|
+
interface PageObserverNav {
|
|
57
|
+
type: 'nav';
|
|
58
|
+
doc_id: string;
|
|
59
|
+
url: string;
|
|
60
|
+
}
|
|
61
|
+
/** Every message the page observer pushes through its binding (JSON). */
|
|
62
|
+
type PageObserverMessage = PageObserverSummary | PageObserverDocStart | PageObserverNav;
|
|
63
|
+
/** Summaries kept in the host mirror (the page keeps its own, larger ring). */
|
|
64
|
+
export declare const OBSERVER_RECENT_CAP = 32;
|
|
65
|
+
/** Per-tab host state for the page observer of the tab's top document. */
|
|
66
|
+
export interface ObserverMirror {
|
|
67
|
+
/** True once the install commands succeeded on this tab. */
|
|
68
|
+
installed: boolean;
|
|
69
|
+
doc_id?: string;
|
|
70
|
+
url?: string;
|
|
71
|
+
coverage?: 'full' | 'partial';
|
|
72
|
+
last_seq: number;
|
|
73
|
+
recent: PageObserverSummary[];
|
|
74
|
+
}
|
|
75
|
+
export type ObserverMessageOutcome = {
|
|
76
|
+
kind: 'doc_start';
|
|
77
|
+
url: string;
|
|
78
|
+
} | {
|
|
79
|
+
kind: 'nav';
|
|
80
|
+
url: string;
|
|
81
|
+
} | {
|
|
82
|
+
kind: 'summary';
|
|
83
|
+
} | {
|
|
84
|
+
kind: 'ignored';
|
|
85
|
+
};
|
|
86
|
+
/** Reason attached as `settle.observer` when `changes` could not be read. */
|
|
87
|
+
export type ObserverChangesFailure = 'observer-unavailable' | 'doc-changed' | 'changes-error';
|
|
88
|
+
/** The settle result as the action callers carry it (`settleSafely`). */
|
|
89
|
+
export type SettleResultLike = Record<string, unknown>;
|
|
90
|
+
export declare function createObserverMirror(): ObserverMirror;
|
|
91
|
+
/**
|
|
92
|
+
* Parse one binding payload. The binding is a page-visible global, so the
|
|
93
|
+
* payload is untrusted: anything that is not valid JSON in one of the three
|
|
94
|
+
* observer message shapes yields `null`.
|
|
95
|
+
*/
|
|
96
|
+
export declare function parseObserverMessage(payload: string): PageObserverMessage | null;
|
|
97
|
+
/**
|
|
98
|
+
* Fold one observer message into the mirror. `doc_start` and `nav` are
|
|
99
|
+
* assumed to come from the top document — the caller confirms that first
|
|
100
|
+
* with `TOP_DOCUMENT_PROBE_JS` / `isTopDocumentMessage`, because the install
|
|
101
|
+
* script also runs in same-process child frames. Summaries from a child
|
|
102
|
+
* frame (`roots.frame`) or from a document other than the mirrored one are
|
|
103
|
+
* ignored. A `nav` that does not change the URL (e.g. a state-only
|
|
104
|
+
* `replaceState`) is ignored so it does not emit a navigation event.
|
|
105
|
+
*/
|
|
106
|
+
export declare function applyObserverMessage(mirror: ObserverMirror, message: PageObserverMessage): ObserverMessageOutcome;
|
|
107
|
+
/**
|
|
108
|
+
* Expression evaluated in the tab's top document (default `Runtime.evaluate`
|
|
109
|
+
* context) to tell whether a `doc_start` / `nav` came from it: evaluates to
|
|
110
|
+
* `{ doc_id }` of the observer installed there, or `{ doc_id: null }`.
|
|
111
|
+
*/
|
|
112
|
+
export declare const TOP_DOCUMENT_PROBE_JS: string;
|
|
113
|
+
/** True when the top-document probe reports the message's `doc_id`. */
|
|
114
|
+
export declare function isTopDocumentMessage(probe: unknown, docId: string): boolean;
|
|
115
|
+
/** The part of an action mark (`buildObserverMarkJs`) the changes read needs. */
|
|
116
|
+
export interface ObserverMark {
|
|
117
|
+
mark: number;
|
|
118
|
+
doc_id: string;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Validate the result of the mark evaluation. `null` means there is no
|
|
122
|
+
* usable mark (observer missing, `ok: false`, or a malformed value); the
|
|
123
|
+
* caller then skips the changes read and reports the failure instead.
|
|
124
|
+
*/
|
|
125
|
+
export declare function readObserverMark(value: unknown): ObserverMark | null;
|
|
126
|
+
/**
|
|
127
|
+
* Fold the observer's "changes since mark" into a settle result (spec v4
|
|
128
|
+
* §5.4). With valid changes: attach `changes` (the `SettleChanges` shape),
|
|
129
|
+
* recompute `mutation_count` from the observer's records, and replace
|
|
130
|
+
* `url_changed` / `focus_moved` with the values baselined at the action
|
|
131
|
+
* mark (present only when they changed). Otherwise keep every settle field
|
|
132
|
+
* as `settleSafely` produced it and add `changes: null` plus
|
|
133
|
+
* `observer: <reason>`. A `null` settle (settle disabled) stays `null`.
|
|
134
|
+
* Returns a new object; never throws.
|
|
135
|
+
*/
|
|
136
|
+
export declare function mergeSettleChanges(settle: SettleResultLike | null, changes: unknown): SettleResultLike | null;
|
|
137
|
+
export {};
|