@wenathlan/extension 1.1.32 → 1.1.34

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.
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
  (() => {
3
3
  // policy.ts
4
- var sensitiveactions = /* @__PURE__ */ new Set(["click", "type", "navigate", "select", "presskey", "drag", "drop", "upload", "clear", "check", "uncheck", "toggle", "submit", "reload", "back", "forward", "writestorage", "setattribute", "removeattribute", "evaluate", "tabcreate", "tabactivate", "tabclose", "tabreload", "windowcreate", "windowclose", "windowresize", "downloadfile"]);
5
- var interactionactions = /* @__PURE__ */ new Set(["focus", "scroll", "hover", "clickdeep", "rightclick", "doubleclick", "scrollpage", "scrollby", "scrollend", "scrolltop", "fullscreen", "zoomset"]);
6
- var readactions = /* @__PURE__ */ new Set(["observe", "inspect", "extract", "wait", "waitfor", "waittext", "readattribute", "readstyle", "readgeometry", "readvalue", "readtext", "readhtml", "countelements", "readtable", "readlinks", "readimages", "readmeta", "readforms", "readstorage", "highlight", "tablist", "windowlist", "tabsnapshot"]);
4
+ var sensitiveactions = /* @__PURE__ */ new Set(["click", "type", "navigate", "select", "presskey", "drag", "drop", "upload", "clear", "check", "uncheck", "toggle", "submit", "reload", "back", "forward", "writestorage", "setattribute", "removeattribute", "evaluate", "tabcreate", "tabactivate", "tabclose", "tabreload", "windowcreate", "windowclose", "windowresize", "downloadfile", "clickpoint", "shiftclick", "dismissdialog", "enterframe", "typetime", "appendtext", "setvalue", "typeedit", "keyhold", "keyrelease", "submitsearch", "selectmulti", "chooseradio", "setslider", "setdate", "setcolor"]);
5
+ var interactionactions = /* @__PURE__ */ new Set(["focus", "scroll", "hover", "clickdeep", "rightclick", "doubleclick", "scrollpage", "scrollby", "scrollend", "scrolltop", "fullscreen", "zoomset", "movepointer", "clicktext", "clickaria", "clickname", "expanddetails", "pierceshadow", "retryaction"]);
6
+ var readactions = /* @__PURE__ */ new Set(["observe", "inspect", "extract", "wait", "waitfor", "waittext", "readattribute", "readstyle", "readgeometry", "readvalue", "readtext", "readhtml", "countelements", "readtable", "readlinks", "readimages", "readmeta", "readforms", "readstorage", "highlight", "tablist", "windowlist", "tabsnapshot", "mapclicks", "verifyvisible", "verifyenabled", "resolvexpath", "a11ytree", "readvisible", "readertree", "detectlists", "detecttables", "readjson", "watchmutate", "waitquiet", "watchbanner", "detectinfinitescroll", "detectvirtual", "detectlazy", "readscrollpos", "readlang", "readoutline", "countpages", "listshadow", "listframes", "classifypage", "fingerprintsection", "diffsnapshots", "readselection", "watchfocus", "detectsticky", "detectscrolllock", "readopengraph", "detectlanguage", "deriveselector"]);
7
7
  var allowedactions = /* @__PURE__ */ new Set([...sensitiveactions, ...interactionactions, ...readactions]);
8
8
  function parseoptions(step) {
9
9
  if (step.options === void 0) return {};
@@ -16,6 +16,10 @@
16
16
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("Step options must be a JSON object.");
17
17
  return parsed;
18
18
  }
19
+ function resolutionverdict(count) {
20
+ if (!Number.isFinite(count) || count <= 0) return "absent";
21
+ return count === 1 ? "resolved" : "ambiguous";
22
+ }
19
23
 
20
24
  // extension/pageactions.ts
21
25
  function events(target) {
@@ -49,12 +53,12 @@
49
53
  })();
50
54
  switch (step.kind) {
51
55
  case "presskey": {
52
- const receiver = target instanceof HTMLElement ? target : document.activeElement instanceof HTMLElement ? document.activeElement : document.body;
56
+ const receiver2 = target instanceof HTMLElement ? target : document.activeElement instanceof HTMLElement ? document.activeElement : document.body;
53
57
  const key = step.value ?? "";
54
58
  const mods = modifiers(options);
55
- receiver.dispatchEvent(keyevent("keydown", key, mods));
56
- receiver.dispatchEvent(keyevent("keypress", key, mods));
57
- receiver.dispatchEvent(keyevent("keyup", key, mods));
59
+ receiver2.dispatchEvent(keyevent("keydown", key, mods));
60
+ receiver2.dispatchEvent(keyevent("keypress", key, mods));
61
+ receiver2.dispatchEvent(keyevent("keyup", key, mods));
58
62
  return { ok: true, summary: `Key ${key} delivered with ${mods.length} modifier${mods.length === 1 ? "" : "s"}.` };
59
63
  }
60
64
  case "clickdeep": {
@@ -179,6 +183,361 @@
179
183
  }
180
184
  }
181
185
 
186
+ // extension/pagexpath.ts
187
+ function owntext(element) {
188
+ let combined = "";
189
+ for (const node of element.childNodes) if (node.nodeType === Node.TEXT_NODE) combined += node.textContent ?? "";
190
+ return combined.replace(/\s+/g, " ").trim();
191
+ }
192
+ function attributesof(element) {
193
+ const attributes = {};
194
+ for (const attribute of [...element.attributes]) attributes[attribute.name] = attribute.value;
195
+ return attributes;
196
+ }
197
+ function wrap(element) {
198
+ return { tag: element.tagName.toLowerCase(), attributes: attributesof(element), text: owntext(element), children: [...element.children].map(wrap), element };
199
+ }
200
+ function buildxtree(root) {
201
+ return { tag: "#document", attributes: {}, text: "", children: root.documentElement ? [wrap(root.documentElement)] : [] };
202
+ }
203
+ function parsepredicate(raw) {
204
+ const body = raw.trim();
205
+ let match = /^@([\w-]+)$/.exec(body);
206
+ if (match) return { kind: "attr", name: match[1] };
207
+ match = /^@([\w-]+)\s*=\s*['"]([^'"]*)['"]$/.exec(body);
208
+ if (match) return { kind: "attr", name: match[1], value: match[2] };
209
+ match = /^contains\(\s*@([\w-]+)\s*,\s*['"]([^'"]*)['"]\s*\)$/.exec(body);
210
+ if (match) return { kind: "attr", name: match[1], value: match[2], contains: true };
211
+ match = /^text\(\)\s*=\s*['"]([^'"]*)['"]$/.exec(body);
212
+ if (match) return { kind: "text", value: match[1] };
213
+ match = /^contains\(\s*text\(\)\s*,\s*['"]([^'"]*)['"]\s*\)$/.exec(body);
214
+ if (match) return { kind: "text", value: match[1], contains: true };
215
+ match = /^(\d+)$/.exec(body);
216
+ if (match) return { kind: "position", index: Number.parseInt(match[1], 10) };
217
+ return null;
218
+ }
219
+ function parsexpath(expression) {
220
+ const trimmed = expression.trim();
221
+ if (!trimmed.startsWith("/")) throw new Error("The reviewed xpath expression must start with a slash.");
222
+ const steps = [];
223
+ let index = 0;
224
+ while (index < trimmed.length) {
225
+ if (trimmed[index] !== "/") throw new Error("The reviewed xpath expression contains an unsupported segment.");
226
+ let slashes = 0;
227
+ while (index < trimmed.length && trimmed[index] === "/") {
228
+ slashes += 1;
229
+ index += 1;
230
+ }
231
+ const start = index;
232
+ let quote = "";
233
+ while (index < trimmed.length) {
234
+ const character = trimmed[index];
235
+ if (quote) {
236
+ if (character === quote) quote = "";
237
+ } else if (character === "'" || character === '"') quote = character;
238
+ else if (character === "/") break;
239
+ index += 1;
240
+ }
241
+ const body = trimmed.slice(start, index);
242
+ if (!body) throw new Error("The reviewed xpath expression contains an empty step.");
243
+ const parsed = /^(\*|[a-zA-Z][\w-]*)((?:\[[^\]]*\])*)$/.exec(body);
244
+ if (!parsed) throw new Error(`The reviewed xpath step ${body} is not supported.`);
245
+ const predicates = [];
246
+ const pattern = /\[([^\]]*)\]/g;
247
+ let predicate;
248
+ while ((predicate = pattern.exec(parsed[2] ?? "")) !== null) {
249
+ const parsedpredicate = parsepredicate(predicate[1]);
250
+ if (!parsedpredicate) throw new Error(`The reviewed xpath predicate [${predicate[1]}] is not supported.`);
251
+ predicates.push(parsedpredicate);
252
+ }
253
+ steps.push({ descendant: slashes > 1, tag: parsed[1].toLowerCase(), predicates });
254
+ }
255
+ return steps;
256
+ }
257
+ function descendants(node, includeself) {
258
+ const result = includeself ? [node] : [];
259
+ for (const child of node.children) {
260
+ result.push(child);
261
+ result.push(...descendants(child, false));
262
+ }
263
+ return result;
264
+ }
265
+ function applypredicates(nodes, predicates) {
266
+ let result = nodes;
267
+ for (const predicate of predicates) {
268
+ if (predicate.kind === "position") {
269
+ const entry = result[predicate.index - 1];
270
+ result = entry ? [entry] : [];
271
+ continue;
272
+ }
273
+ result = result.filter((node) => {
274
+ if (predicate.kind === "attr") {
275
+ const value = node.attributes[predicate.name];
276
+ if (value === void 0) return false;
277
+ if (predicate.value === void 0) return true;
278
+ return predicate.contains ? value.includes(predicate.value) : value === predicate.value;
279
+ }
280
+ return predicate.contains ? node.text.includes(predicate.value) : node.text === predicate.value;
281
+ });
282
+ }
283
+ return result;
284
+ }
285
+ function evaluatexpath(root, expression) {
286
+ const steps = parsexpath(expression);
287
+ let current = [root];
288
+ let first = true;
289
+ for (const step of steps) {
290
+ let matched = [];
291
+ for (const node of current) {
292
+ const pool = step.descendant ? descendants(node, first) : node.children;
293
+ matched = matched.concat(pool.filter((candidate) => candidate.tag === step.tag || step.tag === "*"));
294
+ }
295
+ current = applypredicates(matched, step.predicates);
296
+ first = false;
297
+ }
298
+ return current;
299
+ }
300
+
301
+ // extension/pageresolve.ts
302
+ function clean(value) {
303
+ return value.replace(/\s+/g, " ").trim();
304
+ }
305
+ function cssescape(value) {
306
+ return typeof CSS !== "undefined" && typeof CSS.escape === "function" ? CSS.escape(value) : value.replace(/[^a-zA-Z0-9_-]/g, "\\$&");
307
+ }
308
+ function elementlabel(element) {
309
+ const aria = element.getAttribute("aria-label");
310
+ let linked = "";
311
+ const labelledby = element.getAttribute("aria-labelledby");
312
+ if (labelledby) {
313
+ try {
314
+ const owner = element.ownerDocument?.getElementById(labelledby);
315
+ if (owner) linked = owner.textContent ?? "";
316
+ } catch {
317
+ }
318
+ }
319
+ let forlabel = "";
320
+ if (element.id) {
321
+ try {
322
+ const label = element.ownerDocument?.querySelector(`label[for="${cssescape(element.id)}"]`);
323
+ if (label instanceof HTMLElement) forlabel = label.textContent ?? "";
324
+ } catch {
325
+ }
326
+ }
327
+ return clean(aria || linked || forlabel || element.getAttribute("title") || element.textContent || "");
328
+ }
329
+ function implicitrole(element) {
330
+ const tag = element.tagName.toLowerCase();
331
+ if (tag === "button") return "button";
332
+ if (tag === "a" && element.getAttribute("href")) return "link";
333
+ if (tag === "select") return "combobox";
334
+ if (tag === "textarea") return "textbox";
335
+ if (tag === "details") return "group";
336
+ if (tag === "input") {
337
+ const type = element.getAttribute("type") ?? "text";
338
+ if (type === "checkbox") return "checkbox";
339
+ if (type === "radio") return "radio";
340
+ if (type === "button" || type === "submit" || type === "reset") return "button";
341
+ if (type === "range") return "slider";
342
+ return "textbox";
343
+ }
344
+ return "";
345
+ }
346
+ function elementselector(element) {
347
+ if (element.id) return `#${cssescape(element.id)}`;
348
+ const role = element.getAttribute("role");
349
+ const name = element.getAttribute("name");
350
+ if (role && name) return `[role="${cssescape(role)}"][name="${cssescape(name)}"]`;
351
+ if (name) return `${element.tagName.toLowerCase()}[name="${cssescape(name)}"]`;
352
+ const tag = element.tagName.toLowerCase();
353
+ const parent = element.parentElement;
354
+ if (!parent) return tag;
355
+ const peers = [...parent.children].filter((node) => node.tagName === element.tagName);
356
+ return `${tag}:nth-of-type(${peers.indexOf(element) + 1})`;
357
+ }
358
+ function owntext2(element) {
359
+ let combined = "";
360
+ for (const node of element.childNodes) if (node.nodeType === Node.TEXT_NODE) combined += node.textContent ?? "";
361
+ return clean(combined);
362
+ }
363
+ function summarize(element) {
364
+ return {
365
+ tag: element.tagName.toLowerCase(),
366
+ id: element.id,
367
+ role: element.getAttribute("role")?.toLowerCase() || implicitrole(element),
368
+ name: element.getAttribute("name") ?? "",
369
+ label: elementlabel(element),
370
+ text: owntext2(element),
371
+ selector: elementselector(element),
372
+ element
373
+ };
374
+ }
375
+ var clickableselector = "a[href], button, input, textarea, select, summary, [role=button], [role=link], [role=combobox], [role=option], [role=checkbox], [role=radio], [role=switch], [role=tab]";
376
+ function collectclickable(root) {
377
+ return [...root.querySelectorAll(clickableselector)].map(summarize);
378
+ }
379
+ function collectcandidates(root) {
380
+ return [...root.querySelectorAll("*")].map(summarize);
381
+ }
382
+ function matchtext(candidates, text) {
383
+ const wanted = clean(text).toLowerCase();
384
+ if (!wanted) return [];
385
+ const exact = candidates.filter((candidate) => candidate.text.toLowerCase() === wanted || candidate.label.toLowerCase() === wanted);
386
+ if (exact.length > 0) return exact;
387
+ return candidates.filter((candidate) => candidate.text.toLowerCase().includes(wanted) || candidate.label.toLowerCase().includes(wanted));
388
+ }
389
+ function matcharia(candidates, role, name) {
390
+ const wantedrole = clean(role).toLowerCase();
391
+ const wantedname = clean(name).toLowerCase();
392
+ if (!wantedrole || !wantedname) return [];
393
+ return candidates.filter((candidate) => candidate.role.toLowerCase() === wantedrole && (candidate.label.toLowerCase() === wantedname || candidate.name.toLowerCase() === wantedname));
394
+ }
395
+ function matchname(candidates, name, clickable) {
396
+ const wanted = clean(name).toLowerCase();
397
+ if (!wanted) return [];
398
+ const matches = candidates.filter((candidate) => candidate.label.toLowerCase() === wanted || candidate.name.toLowerCase() === wanted);
399
+ if (matches.length > 1 && clickable) {
400
+ const interactive = matches.filter(clickable);
401
+ if (interactive.length === 1) return interactive;
402
+ }
403
+ return matches;
404
+ }
405
+ function matchindex(candidates, index) {
406
+ if (!Number.isInteger(index) || index < 1) return [];
407
+ const entry = candidates[index - 1];
408
+ return entry ? [entry] : [];
409
+ }
410
+ function buildclickablemap(candidates, version, builtat = 0) {
411
+ const entries = candidates.map((candidate, position) => ({ number: position + 1, selector: candidate.selector, role: candidate.role || candidate.tag, label: candidate.label, mode: "selector" }));
412
+ return { version, entries, builtat };
413
+ }
414
+ function describeframes(root) {
415
+ const frames = [...root.querySelectorAll("iframe")].map((frame) => {
416
+ let content = null;
417
+ try {
418
+ content = frame.contentDocument;
419
+ } catch {
420
+ content = null;
421
+ }
422
+ let sameorigin = false;
423
+ try {
424
+ sameorigin = content !== null && frame.contentWindow?.location.origin === location.origin;
425
+ } catch {
426
+ sameorigin = false;
427
+ }
428
+ return sameorigin && content ? { sameorigin: true, document: describeframes(content) } : { sameorigin: false };
429
+ });
430
+ return { frames, live: root };
431
+ }
432
+ function walkframepath(root, path) {
433
+ let current = root;
434
+ for (const index of path) {
435
+ if (!Number.isInteger(index) || index < 0) return { ok: false, reason: "The reviewed frame path contains an invalid frame index." };
436
+ const entry = current.frames[index];
437
+ if (!entry) return { ok: false, reason: `Frame ${index} of the reviewed frame path is absent.` };
438
+ if (!entry.sameorigin || !entry.document) return { ok: false, reason: `Frame ${index} of the reviewed frame path is cross origin and was refused.` };
439
+ current = entry.document;
440
+ }
441
+ return { ok: true, document: current };
442
+ }
443
+ function describescopes(root) {
444
+ const elements = [...root.querySelectorAll("*")];
445
+ const shadows = [];
446
+ for (const element of elements) {
447
+ const shadow = element.shadowRoot;
448
+ if (shadow) {
449
+ const nested = describescopes(shadow);
450
+ nested.host = summarize(element);
451
+ shadows.push(nested);
452
+ }
453
+ }
454
+ return { candidates: elements.map(summarize), shadows };
455
+ }
456
+ function queryshadowchain(root, selectors) {
457
+ let scope = root;
458
+ for (let position = 0; position < selectors.length; position += 1) {
459
+ const found = scope.querySelector(selectors[position]);
460
+ if (!found) return null;
461
+ if (position === selectors.length - 1) return found;
462
+ const shadow = found.shadowRoot;
463
+ if (!shadow) return null;
464
+ scope = shadow;
465
+ }
466
+ return null;
467
+ }
468
+ function queryscoped(root, selector) {
469
+ const direct = root.querySelector(selector);
470
+ if (direct) return direct;
471
+ for (const element of [...root.querySelectorAll("*")]) {
472
+ const shadow = element.shadowRoot;
473
+ if (shadow) {
474
+ const found = queryscoped(shadow, selector);
475
+ if (found) return found;
476
+ }
477
+ }
478
+ return null;
479
+ }
480
+ function parseoptionssafe(step) {
481
+ try {
482
+ return parseoptions(step);
483
+ } catch {
484
+ return {};
485
+ }
486
+ }
487
+ function targetsummary(mode, element) {
488
+ const rect = element.getBoundingClientRect();
489
+ return { mode, selector: elementselector(element), tag: element.tagName.toLowerCase(), label: elementlabel(element), geometry: { x: rect.x, y: rect.y, width: rect.width, height: rect.height } };
490
+ }
491
+ function singleresolution(mode, matches) {
492
+ const verdict = resolutionverdict(matches.length);
493
+ if (verdict === "resolved") {
494
+ const winner = matches[0];
495
+ if (winner && winner.element instanceof HTMLElement) return { status: "resolved", element: winner.element, target: targetsummary(mode, winner.element) };
496
+ return { status: "absent", mode };
497
+ }
498
+ if (verdict === "ambiguous") return { status: "ambiguous", mode, candidates: matches.slice(0, 8).map((candidate) => candidate.label || candidate.selector) };
499
+ return { status: "absent", mode };
500
+ }
501
+ function resolvetargetref(reference, root) {
502
+ const mode = reference.mode;
503
+ if (mode === "selector") {
504
+ const selector = typeof reference.selector === "string" ? reference.selector : "";
505
+ const element = selector ? root.querySelector(selector) : null;
506
+ return element instanceof HTMLElement ? { status: "resolved", element, target: targetsummary("selector", element) } : { status: "absent", mode: "selector" };
507
+ }
508
+ if (mode === "point") {
509
+ const x = Number(reference.x);
510
+ const y = Number(reference.y);
511
+ if (!Number.isFinite(x) || !Number.isFinite(y)) return { status: "absent", mode: "point" };
512
+ const element = root.elementFromPoint(x, y);
513
+ return element instanceof HTMLElement ? { status: "resolved", element, target: targetsummary("point", element) } : { status: "absent", mode: "point" };
514
+ }
515
+ if (mode === "xpath") {
516
+ const expression = typeof reference.xpath === "string" ? reference.xpath : "";
517
+ if (!expression) return { status: "absent", mode: "xpath" };
518
+ const matches = evaluatexpath(buildxtree(root), expression);
519
+ const first = matches[0];
520
+ return first?.element instanceof HTMLElement ? { status: "resolved", element: first.element, target: targetsummary("xpath", first.element) } : { status: "absent", mode: "xpath" };
521
+ }
522
+ if (mode === "index") {
523
+ const matches = matchindex(collectclickable(root), Number(reference.index));
524
+ return singleresolution("index", matches);
525
+ }
526
+ const candidates = collectcandidates(root);
527
+ if (mode === "text") return singleresolution("text", matchtext(candidates, typeof reference.text === "string" ? reference.text : ""));
528
+ if (mode === "aria") return singleresolution("aria", matcharia(candidates, typeof reference.role === "string" ? reference.role : "", typeof reference.name === "string" ? reference.name : ""));
529
+ if (mode === "name") return singleresolution("name", matchname(candidates, typeof reference.name === "string" ? reference.name : ""));
530
+ return { status: "absent" };
531
+ }
532
+ function resolvestep(step, root) {
533
+ const reference = parseoptionssafe(step).targetref;
534
+ if (reference && typeof reference === "object" && !Array.isArray(reference)) return resolvetargetref(reference, root);
535
+ if (!step.target?.trim()) return { status: "none" };
536
+ const element = root.querySelector(step.target);
537
+ if (element instanceof HTMLElement) return { status: "resolved", element, target: targetsummary("selector", element) };
538
+ return { status: "absent", mode: "selector" };
539
+ }
540
+
182
541
  // extension/pagereads.ts
183
542
  var highlightid = "devthinkactionhighlight";
184
543
  function clearhighlight() {
@@ -195,7 +554,7 @@
195
554
  window.setTimeout(clearhighlight, 5e3);
196
555
  return { ok: true, summary: "Target outlined for five seconds." };
197
556
  }
198
- function poll(predicate, description, timeout) {
557
+ function poll(root, predicate, description, timeout) {
199
558
  return new Promise((resolve) => {
200
559
  const started = Date.now();
201
560
  const check = () => {
@@ -212,15 +571,15 @@
212
571
  check();
213
572
  });
214
573
  }
215
- function formstate() {
216
- return [...document.querySelectorAll("input, textarea, select")].map((element) => ({
574
+ function formstate(root) {
575
+ return [...root.querySelectorAll("input, textarea, select")].map((element) => ({
217
576
  type: element.getAttribute("type") ?? element.tagName.toLowerCase(),
218
577
  name: element.getAttribute("name") ?? "",
219
578
  value: element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement ? element.value : "",
220
579
  ...element instanceof HTMLInputElement && (element.type === "checkbox" || element.type === "radio") ? { checked: element.checked } : {}
221
580
  }));
222
581
  }
223
- function runpageread(step, target) {
582
+ function runpageread(step, target, root = document) {
224
583
  const options = (() => {
225
584
  try {
226
585
  return parseoptions(step);
@@ -268,7 +627,7 @@
268
627
  return { ok: true, summary: "Target markup read.", details: { html: target.outerHTML } };
269
628
  }
270
629
  case "countelements": {
271
- const count = document.querySelectorAll(step.target ?? "").length;
630
+ const count = root.querySelectorAll(step.target ?? "").length;
272
631
  return { ok: true, summary: `Selector matches ${count} element${count === 1 ? "" : "s"}.`, details: { count } };
273
632
  }
274
633
  case "readtable": {
@@ -279,19 +638,19 @@
279
638
  return { ok: true, summary: `Read table with ${headers.length} column${headers.length === 1 ? "" : "s"} and ${body.length} row${body.length === 1 ? "" : "s"}.`, details: { headers, rows: body } };
280
639
  }
281
640
  case "readlinks": {
282
- const links = [...document.querySelectorAll("a[href]")].map((element) => ({ text: element.textContent?.trim() ?? "", href: element.getAttribute("href") ?? "" }));
641
+ const links = [...root.querySelectorAll("a[href]")].map((element) => ({ text: element.textContent?.trim() ?? "", href: element.getAttribute("href") ?? "" }));
283
642
  return { ok: true, summary: `Read ${links.length} link${links.length === 1 ? "" : "s"}.`, details: { links } };
284
643
  }
285
644
  case "readimages": {
286
- const images = [...document.querySelectorAll("img")].map((element) => ({ src: element.getAttribute("src") ?? "", alt: element.getAttribute("alt") ?? "" }));
645
+ const images = [...root.querySelectorAll("img")].map((element) => ({ src: element.getAttribute("src") ?? "", alt: element.getAttribute("alt") ?? "" }));
287
646
  return { ok: true, summary: `Read ${images.length} image${images.length === 1 ? "" : "s"}.`, details: { images } };
288
647
  }
289
648
  case "readmeta": {
290
- const meta = [...document.querySelectorAll("meta")].map((element) => ({ name: element.getAttribute("name") ?? "", property: element.getAttribute("property") ?? "", content: element.getAttribute("content") ?? "" }));
649
+ const meta = [...root.querySelectorAll("meta")].map((element) => ({ name: element.getAttribute("name") ?? "", property: element.getAttribute("property") ?? "", content: element.getAttribute("content") ?? "" }));
291
650
  return { ok: true, summary: `Read ${meta.length} meta entr${meta.length === 1 ? "y" : "ies"}.`, details: { meta } };
292
651
  }
293
652
  case "readforms": {
294
- const forms = formstate();
653
+ const forms = formstate(root);
295
654
  return { ok: true, summary: `Read ${forms.length} form control${forms.length === 1 ? "" : "s"}.`, details: { forms } };
296
655
  }
297
656
  case "readstorage": {
@@ -311,43 +670,1379 @@
311
670
  }
312
671
  }
313
672
  case "waitfor": {
314
- const selector2 = step.target ?? "";
673
+ const selector = step.target ?? "";
315
674
  const timeout = typeof options.timeout === "number" ? options.timeout : 0;
316
- return poll(() => Boolean(document.querySelector(selector2)), `Selector ${selector2}`, timeout);
675
+ return poll(root, () => Boolean(root.querySelector(selector)), `Selector ${selector}`, timeout);
317
676
  }
318
677
  case "waittext": {
319
678
  const text = step.value ?? "";
320
679
  const timeout = typeof options.timeout === "number" ? options.timeout : 0;
321
- return poll(() => (document.body?.innerText ?? "").includes(text), `Text ${text}`, timeout);
680
+ return poll(root, () => (root.body?.innerText ?? "").includes(text), `Text ${text}`, timeout);
681
+ }
682
+ case "mapclicks": {
683
+ const candidates = collectclickable(root);
684
+ const map = buildclickablemap(candidates, 0, 0);
685
+ return { ok: true, summary: `Mapped ${map.entries.length} clickable element${map.entries.length === 1 ? "" : "s"}.`, details: { entries: map.entries } };
686
+ }
687
+ case "verifyvisible": {
688
+ if (!target) return { ok: false, summary: "Verify target is no longer available." };
689
+ const rect = target.getBoundingClientRect();
690
+ const rendered = rect.width > 0 && rect.height > 0;
691
+ return { ok: rendered, summary: rendered ? `Target is rendered at ${Math.round(rect.x)},${Math.round(rect.y)} with size ${Math.round(rect.width)}x${Math.round(rect.height)}.` : "Target is not rendered.", details: { visible: rendered, geometry: { x: rect.x, y: rect.y, width: rect.width, height: rect.height } } };
692
+ }
693
+ case "verifyenabled": {
694
+ if (!target) return { ok: false, summary: "Verify target is no longer available." };
695
+ const control = target;
696
+ const disabled = control.disabled === true || target.hasAttribute("disabled");
697
+ const readonly = control.readOnly === true || target.hasAttribute("readonly");
698
+ const enabled = !disabled && !readonly;
699
+ return { ok: enabled, summary: enabled ? "Target is enabled and writable." : disabled ? "Target is disabled." : "Target is readonly.", details: { enabled, disabled, readonly } };
700
+ }
701
+ case "resolvexpath": {
702
+ const reference = options.targetref;
703
+ const expression = typeof reference?.xpath === "string" ? reference.xpath : "";
704
+ if (!expression) return { ok: false, summary: "The reviewed xpath expression is absent." };
705
+ let matches = [];
706
+ try {
707
+ matches = evaluatexpath(buildxtree(root), expression);
708
+ } catch (error) {
709
+ return { ok: false, summary: `The reviewed xpath expression failed: ${error instanceof Error ? error.message : String(error)}` };
710
+ }
711
+ const summaries = matches.map((node) => ({ tag: node.tag, ...node.element ? { selector: elementselector(node.element), label: elementlabel(node.element) } : {} }));
712
+ return { ok: matches.length > 0, summary: matches.length > 0 ? `Resolved ${matches.length} element${matches.length === 1 ? "" : "s"} for the reviewed xpath.` : "The reviewed xpath matched no elements.", details: { mode: "xpath", matches: summaries } };
322
713
  }
323
714
  default:
324
715
  return { ok: false, summary: "Unsupported page read." };
325
716
  }
326
717
  }
327
718
 
328
- // extension/pagebridge.ts
329
- function clean(value) {
330
- return value.replace(/\s+/g, " ").trim();
719
+ // extension/pagecontrols.ts
720
+ function typetimeschedule(text, delay) {
721
+ return [...text].map((character, position) => ({ key: character, delay: position === 0 ? 0 : delay }));
331
722
  }
332
- function label(element) {
333
- const aria = element.getAttribute("aria-label");
334
- const labelledby = element.getAttribute("aria-labelledby");
335
- const linked = labelledby ? document.getElementById(labelledby)?.textContent : "";
336
- return clean(aria || linked || element.getAttribute("title") || element.textContent || "");
723
+ function appendvalue(current, addition) {
724
+ return current + addition;
337
725
  }
338
- function selector(element) {
339
- if (element.id) return `#${CSS.escape(element.id)}`;
340
- const role = element.getAttribute("role");
341
- const name = element.getAttribute("name");
342
- if (role && name) return `[role="${CSS.escape(role)}"][name="${CSS.escape(name)}"]`;
343
- if (name) return `${element.tagName.toLowerCase()}[name="${CSS.escape(name)}"]`;
344
- const tag = element.tagName.toLowerCase();
345
- const parent = element.parentElement;
346
- if (!parent) return tag;
347
- const peers = [...parent.children].filter((node) => node.tagName === element.tagName);
348
- return `${tag}:nth-of-type(${peers.indexOf(element) + 1})`;
726
+ function valueevents() {
727
+ return ["input", "change"];
728
+ }
729
+ function multichoices(values, options) {
730
+ const present = [];
731
+ const missing = [];
732
+ for (const value of values) {
733
+ const option = options.find((candidate) => candidate.value === value || candidate.label === value);
734
+ if (option) present.push(option.value);
735
+ else missing.push(value);
736
+ }
737
+ return { present, missing };
738
+ }
739
+ function radiochoice(inputs, choice) {
740
+ return inputs.findIndex((candidate) => candidate.value === choice || candidate.label === choice);
741
+ }
742
+ function slidervalue(requested, min, max, step) {
743
+ const lower = Math.min(min, max);
744
+ const upper = Math.max(min, max);
745
+ const clamped = Math.min(upper, Math.max(lower, requested));
746
+ if (!Number.isFinite(step) || step <= 0) return clamped;
747
+ return Math.round((clamped - lower) / step) * step + lower;
748
+ }
749
+ function datevalue(requested) {
750
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(requested)) return null;
751
+ const parts = requested.split("-").map((part) => Number.parseInt(part, 10));
752
+ const year = parts[0];
753
+ const month = parts[1];
754
+ const day = parts[2];
755
+ if (!year || !month || !day || month < 1 || month > 12 || day < 1 || day > 31) return null;
756
+ return requested;
757
+ }
758
+ function colorvalue(requested) {
759
+ if (!/^#[0-9a-fA-F]{6}$/.test(requested)) return null;
760
+ return requested.toLowerCase();
761
+ }
762
+ function expandstate(open) {
763
+ return open ? { open: true, changed: false } : { open: true, changed: true };
764
+ }
765
+ function events2(target) {
766
+ target.dispatchEvent(new Event("input", { bubbles: true }));
767
+ target.dispatchEvent(new Event("change", { bubbles: true }));
768
+ }
769
+ function modifiers2(options) {
770
+ return Array.isArray(options.modifiers) ? options.modifiers.filter((item) => typeof item === "string") : [];
771
+ }
772
+ function keyevent2(type, key, mods) {
773
+ const code = key.length === 1 ? `Key${key.toUpperCase()}` : key;
774
+ return new KeyboardEvent(type, { key, code, bubbles: true, cancelable: true, composed: true, ctrlKey: mods.includes("ctrl"), shiftKey: mods.includes("shift"), altKey: mods.includes("alt"), metaKey: mods.includes("meta") });
775
+ }
776
+ function fieldlike2(target) {
777
+ return target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement ? target : null;
778
+ }
779
+ function receiver(target) {
780
+ return target instanceof HTMLElement ? target : document.activeElement instanceof HTMLElement ? document.activeElement : document.body;
781
+ }
782
+ function wait(delay) {
783
+ return new Promise((resolve) => window.setTimeout(resolve, delay));
784
+ }
785
+ function focuswhenneeded(target, options) {
786
+ if (!(target instanceof HTMLElement)) return;
787
+ if (options.focus === false) return;
788
+ if (options.focus === true || document.activeElement !== target) target.focus();
789
+ }
790
+ function pollfor(predicate, description, timeout) {
791
+ return new Promise((resolve) => {
792
+ const started = Date.now();
793
+ const check = () => {
794
+ if (predicate()) {
795
+ resolve({ ok: true, summary: `${description} is now present on the page.` });
796
+ return;
797
+ }
798
+ if (timeout > 0 && Date.now() - started >= timeout) {
799
+ resolve({ ok: false, summary: `${description} did not appear within ${timeout} milliseconds.` });
800
+ return;
801
+ }
802
+ window.setTimeout(check, 100);
803
+ };
804
+ check();
805
+ });
806
+ }
807
+ function runpagecontrol(step, target, root = document) {
808
+ let options = {};
809
+ try {
810
+ options = parseoptions(step);
811
+ } catch {
812
+ options = {};
813
+ }
814
+ switch (step.kind) {
815
+ case "typetime": {
816
+ const field = fieldlike2(target);
817
+ if (!field) return { ok: false, summary: "Target cannot receive timed text." };
818
+ const text = step.value ?? "";
819
+ const delay = typeof options.delay === "number" && options.delay > 0 ? options.delay : 0;
820
+ focuswhenneeded(field, options);
821
+ const schedule = typetimeschedule(text, delay);
822
+ return (async () => {
823
+ for (const entry of schedule) {
824
+ await wait(entry.delay);
825
+ field.dispatchEvent(keyevent2("keydown", entry.key, []));
826
+ field.dispatchEvent(new KeyboardEvent("keypress", { key: entry.key, bubbles: true, cancelable: true }));
827
+ field.value = `${field.value}${entry.key}`;
828
+ field.dispatchEvent(new Event("input", { bubbles: true }));
829
+ }
830
+ field.dispatchEvent(new Event("change", { bubbles: true }));
831
+ return { ok: true, summary: `Typed ${text.length} character${text.length === 1 ? "" : "s"} with a per keystroke delay of ${delay} milliseconds.` };
832
+ })();
833
+ }
834
+ case "appendtext": {
835
+ const field = fieldlike2(target);
836
+ if (!field) return { ok: false, summary: "Target cannot hold a value." };
837
+ focuswhenneeded(field, options);
838
+ field.value = appendvalue(field.value, step.value ?? "");
839
+ events2(field);
840
+ return { ok: true, summary: "Reviewed text appended to the current field value." };
841
+ }
842
+ case "setvalue": {
843
+ const field = fieldlike2(target);
844
+ if (!field) return { ok: false, summary: "Target cannot hold a value." };
845
+ focuswhenneeded(field, options);
846
+ field.value = step.value ?? "";
847
+ events2(field);
848
+ return { ok: true, summary: `Field value set through the dom property with ${valueevents().join(" and ")} events.` };
849
+ }
850
+ case "typeedit": {
851
+ if (!(target instanceof HTMLElement) || !target.isContentEditable) return { ok: false, summary: "Target is not a content editable region." };
852
+ focuswhenneeded(target, options);
853
+ const text = step.value ?? "";
854
+ return (async () => {
855
+ for (const character of [...text]) {
856
+ target.dispatchEvent(new InputEvent("beforeinput", { bubbles: true, cancelable: true, data: character, inputType: "insertText" }));
857
+ target.append(document.createTextNode(character));
858
+ target.dispatchEvent(new InputEvent("input", { bubbles: true, data: character, inputType: "insertText" }));
859
+ }
860
+ return { ok: true, summary: `Typed ${text.length} character${text.length === 1 ? "" : "s"} into the content editable region.` };
861
+ })();
862
+ }
863
+ case "keyhold": {
864
+ const key = step.value ?? "";
865
+ const mods = modifiers2(options);
866
+ receiver(target).dispatchEvent(keyevent2("keydown", key, mods));
867
+ const holdid = typeof options.holdid === "string" && options.holdid ? options.holdid : "";
868
+ return { ok: true, summary: `Key ${key} pressed and held${holdid ? ` under hold id ${holdid}` : ""}.`, details: { ...holdid ? { holdid } : {}, modifiers: mods } };
869
+ }
870
+ case "keyrelease": {
871
+ const key = step.value ?? "";
872
+ const mods = modifiers2(options);
873
+ receiver(target).dispatchEvent(keyevent2("keyup", key, mods));
874
+ return { ok: true, summary: `Key ${key} released.`, details: { modifiers: mods } };
875
+ }
876
+ case "submitsearch": {
877
+ const field = fieldlike2(target);
878
+ if (!field) return { ok: false, summary: "Target is not a search field." };
879
+ const results = typeof options.results === "string" ? options.results : "";
880
+ const timeout = typeof options.timeout === "number" ? options.timeout : 0;
881
+ focuswhenneeded(field, options);
882
+ field.dispatchEvent(keyevent2("keydown", "Enter", []));
883
+ field.dispatchEvent(new KeyboardEvent("keypress", { key: "Enter", bubbles: true, cancelable: true }));
884
+ field.dispatchEvent(keyevent2("keyup", "Enter", []));
885
+ return pollfor(() => Boolean(document.querySelector(results)), `Results region ${results}`, timeout);
886
+ }
887
+ case "selectmulti": {
888
+ if (!(target instanceof HTMLSelectElement) || !target.multiple) return { ok: false, summary: "Target is not a multi select control." };
889
+ const choices = [...target.options].map((option) => ({ value: option.value, label: clean(option.textContent || option.value) }));
890
+ const requested = Array.isArray(options.values) ? options.values.filter((item) => typeof item === "string") : [];
891
+ const outcome = multichoices(requested, choices);
892
+ if (outcome.missing.length > 0) return { ok: false, summary: `Reviewed option${outcome.missing.length === 1 ? "" : "s"} ${outcome.missing.join(", ")} ${outcome.missing.length === 1 ? "is" : "are"} not part of the select control.` };
893
+ for (const option of target.options) option.selected = outcome.present.includes(option.value);
894
+ events2(target);
895
+ return { ok: true, summary: `Selected ${outcome.present.length} reviewed option${outcome.present.length === 1 ? "" : "s"} in the multi select control.`, details: { selected: outcome.present } };
896
+ }
897
+ case "chooseradio": {
898
+ const radios = target instanceof HTMLInputElement && target.type === "radio" ? [...root.querySelectorAll(`input[type=radio][name="${CSS.escape(target.name)}"]`)] : target ? [...target.querySelectorAll("input[type=radio]")] : [];
899
+ if (radios.length === 0) return { ok: false, summary: "No radio group owns the reviewed target." };
900
+ const inputs = radios.map((radio) => ({ value: radio.value, label: radio.labels && radio.labels.length > 0 ? clean(radio.labels[0]?.textContent || "") || radio.value : radio.value }));
901
+ const index = radiochoice(inputs, step.value ?? "");
902
+ const chosen = radios[index];
903
+ if (!chosen) return { ok: false, summary: "The reviewed radio option is not part of the group." };
904
+ chosen.checked = true;
905
+ events2(chosen);
906
+ return { ok: true, summary: `Picked reviewed radio option ${step.value}.`, details: { value: chosen.value } };
907
+ }
908
+ case "setslider": {
909
+ if (!(target instanceof HTMLInputElement) || target.type !== "range") return { ok: false, summary: "Target is not a range slider." };
910
+ const requested = Number(step.value);
911
+ if (!Number.isFinite(requested)) return { ok: false, summary: "The reviewed slider value is not a number." };
912
+ focuswhenneeded(target, options);
913
+ const value = slidervalue(requested, Number(target.min), Number(target.max), Number(target.step));
914
+ target.value = String(value);
915
+ events2(target);
916
+ return { ok: true, summary: `Slider dragged to the reviewed value ${value}.`, details: { value } };
917
+ }
918
+ case "setdate": {
919
+ if (!(target instanceof HTMLInputElement) || target.type !== "date") return { ok: false, summary: "Target is not a date input." };
920
+ const value = datevalue(step.value ?? "");
921
+ if (value === null) return { ok: false, summary: "The reviewed date is invalid." };
922
+ focuswhenneeded(target, options);
923
+ target.value = value;
924
+ events2(target);
925
+ return { ok: true, summary: `Date input set to ${value}.`, details: { value } };
926
+ }
927
+ case "setcolor": {
928
+ if (!(target instanceof HTMLInputElement) || target.type !== "color") return { ok: false, summary: "Target is not a color input." };
929
+ const value = colorvalue(step.value ?? "");
930
+ if (value === null) return { ok: false, summary: "The reviewed color is invalid." };
931
+ focuswhenneeded(target, options);
932
+ target.value = value;
933
+ events2(target);
934
+ return { ok: true, summary: `Color input set to ${value}.`, details: { value } };
935
+ }
936
+ case "expanddetails": {
937
+ const details = target instanceof HTMLElement ? target.closest("details") : null;
938
+ if (!details) return { ok: false, summary: "Target is not inside a details section." };
939
+ const outcome = expandstate(details.open);
940
+ details.open = outcome.open;
941
+ return { ok: true, summary: outcome.changed ? "Collapsed details section opened." : "Details section was already open.", details: { changed: outcome.changed } };
942
+ }
943
+ default:
944
+ return { ok: false, summary: "Unsupported control action." };
945
+ }
946
+ }
947
+
948
+ // extension/pagepointer.ts
949
+ var basecadence = 16;
950
+ function distance(a, b) {
951
+ return Math.hypot(b.x - a.x, b.y - a.y);
952
+ }
953
+ function ease(easing, progress) {
954
+ if (easing === "easeinout") return progress * progress * (3 - 2 * progress);
955
+ return progress;
956
+ }
957
+ function ispointref(value) {
958
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
959
+ const point = value;
960
+ return typeof point.x === "number" && Number.isFinite(point.x) && typeof point.y === "number" && Number.isFinite(point.y);
961
+ }
962
+ function pathhops(path, profile, random = Math.random, cadence = basecadence) {
963
+ const easing = profile?.easing === "easeinout" ? "easeinout" : "linear";
964
+ const peak = typeof profile?.peak === "number" && profile.peak > 0 ? profile.peak : void 0;
965
+ const jitter = typeof profile?.jitter === "number" && profile.jitter > 0 ? profile.jitter : 0;
966
+ const points = [path.start, ...path.waypoints ?? [], path.end];
967
+ const lengths = [];
968
+ let total = 0;
969
+ for (let index = 1; index < points.length; index += 1) {
970
+ const length = distance(points[index - 1], points[index]);
971
+ lengths.push(length);
972
+ total += length;
973
+ }
974
+ const reviewedduration = typeof path.duration === "number" && Number.isFinite(path.duration) && path.duration > 0 ? path.duration : void 0;
975
+ const duration = reviewedduration ?? (peak !== void 0 && total > 0 ? total / peak * 1e3 : 300);
976
+ const hops = [];
977
+ let previous = points[0];
978
+ for (let index = 1; index < points.length; index += 1) {
979
+ const from = points[index - 1];
980
+ const to = points[index];
981
+ const length = lengths[index - 1] ?? 0;
982
+ if (total <= 0 || length <= 0) {
983
+ hops.push({ x: to.x, y: to.y, delay: 0 });
984
+ previous = to;
985
+ continue;
986
+ }
987
+ const segmentduration = duration * length / total;
988
+ const count = Math.max(1, Math.ceil(segmentduration / Math.max(1, cadence)));
989
+ for (let hop = 1; hop <= count; hop += 1) {
990
+ const progress = hop / count;
991
+ const eased = ease(easing, progress);
992
+ const position = { x: from.x + (to.x - from.x) * eased, y: from.y + (to.y - from.y) * eased };
993
+ const step = distance(previous, position);
994
+ const base = segmentduration / count;
995
+ const capped = peak !== void 0 ? Math.max(base, step / peak * 1e3) : base;
996
+ hops.push({ x: position.x, y: position.y, delay: Math.max(0, capped + (jitter > 0 ? random() * jitter : 0)) });
997
+ previous = position;
998
+ }
999
+ }
1000
+ return hops;
1001
+ }
1002
+ function clickplan(x, y, modifiers3) {
1003
+ const shift = modifiers3.includes("shift");
1004
+ const pointer = (type) => ({ type, eventkind: "pointer", x, y, shift });
1005
+ const mouse = (type) => ({ type, eventkind: "mouse", x, y, shift });
1006
+ return [pointer("pointerover"), pointer("pointermove"), pointer("pointerdown"), mouse("mousedown"), pointer("pointerup"), mouse("mouseup"), mouse("click")];
1007
+ }
1008
+ function dispatchplanned(element, event) {
1009
+ const init = { bubbles: true, cancelable: true, composed: true, clientX: event.x, clientY: event.y, shiftKey: event.shift };
1010
+ if (event.eventkind === "pointer") element.dispatchEvent(new PointerEvent(event.type, init));
1011
+ else element.dispatchEvent(new MouseEvent(event.type, init));
1012
+ }
1013
+ function dispatchclick(element, modifiers3 = []) {
1014
+ const rect = element.getBoundingClientRect();
1015
+ const x = rect.left + rect.width / 2;
1016
+ const y = rect.top + rect.height / 2;
1017
+ for (const event of clickplan(x, y, modifiers3)) dispatchplanned(element, event);
1018
+ }
1019
+ function ensurevisible(element) {
1020
+ try {
1021
+ element.scrollIntoView({ block: "center", inline: "nearest", behavior: "auto" });
1022
+ } catch {
1023
+ }
1024
+ }
1025
+ function settle(delay) {
1026
+ return new Promise((resolve) => window.setTimeout(resolve, delay));
1027
+ }
1028
+ function dispatchmove(x, y) {
1029
+ const element = document.elementFromPoint(x, y);
1030
+ const receiver2 = element ?? document.documentElement;
1031
+ receiver2.dispatchEvent(new PointerEvent("pointermove", { bubbles: true, cancelable: true, composed: true, clientX: x, clientY: y }));
1032
+ }
1033
+ async function travel(path, profile) {
1034
+ const hops = pathhops(path, profile);
1035
+ const startelement = document.elementFromPoint(path.start.x, path.start.y) ?? document.documentElement;
1036
+ startelement.dispatchEvent(new PointerEvent("pointerover", { bubbles: true, cancelable: true, composed: true, clientX: path.start.x, clientY: path.start.y }));
1037
+ for (const hop of hops) {
1038
+ await settle(hop.delay);
1039
+ dispatchmove(hop.x, hop.y);
1040
+ }
1041
+ const endelement = document.elementFromPoint(path.end.x, path.end.y) ?? document.documentElement;
1042
+ endelement.dispatchEvent(new PointerEvent("pointerout", { bubbles: true, cancelable: true, composed: true, clientX: path.end.x, clientY: path.end.y }));
1043
+ return { ok: true, summary: `Pointer traveled ${hops.length} hop${hops.length === 1 ? "" : "s"} to the reviewed end point.` };
1044
+ }
1045
+ function runpointerstep(step, resolution) {
1046
+ let options = {};
1047
+ try {
1048
+ options = parseoptions(step);
1049
+ } catch {
1050
+ options = {};
1051
+ }
1052
+ if (step.kind === "movepointer") {
1053
+ const path = options.pointpath;
1054
+ if (!path || !ispointref(path.start) || !ispointref(path.end)) return { ok: false, summary: "The reviewed pointer path is absent." };
1055
+ const waypoints = Array.isArray(path.waypoints) && path.waypoints.every((item) => ispointref(item)) ? path.waypoints : void 0;
1056
+ const fullpath = { start: path.start, end: path.end, ...waypoints ? { waypoints } : {}, ...typeof path.duration === "number" && Number.isFinite(path.duration) ? { duration: path.duration } : {} };
1057
+ return travel(fullpath, options.speedprofile);
1058
+ }
1059
+ if (step.kind === "clickpoint") {
1060
+ const reference = options.targetref;
1061
+ const x = Number(reference?.x);
1062
+ const y = Number(reference?.y);
1063
+ if (!Number.isFinite(x) || !Number.isFinite(y)) return { ok: false, summary: "The reviewed click coordinates are absent." };
1064
+ const element = document.elementFromPoint(x, y);
1065
+ if (!(element instanceof HTMLElement)) return { ok: false, summary: "No element is rendered at the reviewed coordinates." };
1066
+ ensurevisible(element);
1067
+ for (const event of clickplan(x, y, [])) dispatchplanned(element, event);
1068
+ return { ok: true, summary: `Clicked the element at the reviewed coordinates ${x},${y}.` };
1069
+ }
1070
+ if (step.kind === "shiftclick") {
1071
+ if (resolution.status === "ambiguous") return { ok: false, summary: `The reviewed reference matched ${resolution.candidates.length} elements; choose one candidate.`, details: { mode: resolution.mode, candidates: resolution.candidates } };
1072
+ if (resolution.status !== "resolved") return { ok: false, summary: "Action target is no longer available." };
1073
+ ensurevisible(resolution.element);
1074
+ dispatchclick(resolution.element, ["shift"]);
1075
+ return { ok: true, summary: `Shift click delivered to ${resolution.target.label || resolution.target.tag}.`, details: { mode: resolution.target.mode, resolvedtarget: resolution.target } };
1076
+ }
1077
+ return { ok: false, summary: "Unsupported pointer action." };
1078
+ }
1079
+
1080
+ // extension/pageinteract.ts
1081
+ function optionsof(step) {
1082
+ try {
1083
+ return parseoptions(step);
1084
+ } catch {
1085
+ return {};
1086
+ }
1087
+ }
1088
+ function innerstep(step) {
1089
+ const options = optionsof(step);
1090
+ const kind = options.kind;
1091
+ if (typeof kind !== "string" || !kind.trim()) return null;
1092
+ const inneroptions = options.options;
1093
+ return {
1094
+ id: `${step.id}inner`,
1095
+ kind,
1096
+ summary: step.summary,
1097
+ risk: step.risk,
1098
+ ...typeof options.target === "string" ? { target: options.target } : {},
1099
+ ...typeof options.value === "string" ? { value: options.value } : {},
1100
+ ...inneroptions && typeof inneroptions === "object" && !Array.isArray(inneroptions) ? { options: JSON.stringify(inneroptions) } : {}
1101
+ };
1102
+ }
1103
+ function clickresolved(stepkind, resolution) {
1104
+ if (resolution.status === "ambiguous") return { ok: false, summary: `The reviewed ${resolution.mode} reference matched ${resolution.candidates.length} elements: ${resolution.candidates.join("; ")}.`, details: { mode: resolution.mode, candidates: resolution.candidates } };
1105
+ if (resolution.status !== "resolved") return { ok: false, summary: "The reviewed target is no longer available." };
1106
+ ensurevisible(resolution.element);
1107
+ dispatchclick(resolution.element);
1108
+ return { ok: true, summary: `Clicked ${resolution.target.label || resolution.target.tag} resolved by ${stepkind} ${resolution.target.mode} mode.`, details: { mode: resolution.target.mode, resolvedtarget: resolution.target } };
1109
+ }
1110
+ function runinteractstep(step, expectedorigin, dispatch) {
1111
+ if (step.kind === "clicktext" || step.kind === "clickaria" || step.kind === "clickname") {
1112
+ return clickresolved(step.kind, resolvestep(step, document));
1113
+ }
1114
+ if (step.kind === "pierceshadow") {
1115
+ const options = optionsof(step);
1116
+ const shadow = Array.isArray(options.shadow) ? options.shadow.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
1117
+ const element = shadow.length > 0 ? queryshadowchain(document, shadow) : queryscoped(document, step.target ?? "");
1118
+ if (!(element instanceof HTMLElement)) return { ok: false, summary: "The reviewed shadow target is not available." };
1119
+ ensurevisible(element);
1120
+ dispatchclick(element);
1121
+ const summary = targetsummary("selector", element);
1122
+ return { ok: true, summary: `Clicked ${summary.label || summary.tag} resolved through ${shadow.length > 0 ? "the reviewed shadow path" : "open shadow roots"}.`, details: { mode: "selector", resolvedtarget: summary } };
1123
+ }
1124
+ if (step.kind === "enterframe") {
1125
+ const options = optionsof(step);
1126
+ const path = Array.isArray(options.framepath) ? options.framepath.filter((item) => typeof item === "number" && Number.isInteger(item) && item >= 0) : [];
1127
+ const walk = walkframepath(describeframes(document), path);
1128
+ if (!walk.ok) return { ok: false, summary: walk.reason };
1129
+ const framedocument = walk.document.live;
1130
+ if (!framedocument) return { ok: false, summary: "The reviewed frame document is not available." };
1131
+ const inner = innerstep(step);
1132
+ if (!inner) return { ok: false, summary: "The reviewed inner step is absent." };
1133
+ return dispatch(inner, expectedorigin, framedocument);
1134
+ }
1135
+ return { ok: false, summary: "Unsupported interaction action." };
1136
+ }
1137
+
1138
+ // extension/pagedialogs.ts
1139
+ function harvestdialoglog(root) {
1140
+ const raw = root.documentElement.dataset.devthinkdialoglog;
1141
+ if (!raw) return [];
1142
+ delete root.documentElement.dataset.devthinkdialoglog;
1143
+ try {
1144
+ const parsed = JSON.parse(raw);
1145
+ if (!Array.isArray(parsed)) return [];
1146
+ return parsed.filter((item) => Boolean(item) && typeof item === "object" && typeof item.dialog === "string");
1147
+ } catch {
1148
+ return [];
1149
+ }
1150
+ }
1151
+
1152
+ // extension/pageobserve.ts
1153
+ var maxframedepth = 4;
1154
+ function elementstates(element) {
1155
+ const states = [];
1156
+ if (element.hasAttribute("disabled") || element.getAttribute("aria-disabled") === "true") states.push("disabled");
1157
+ if (element instanceof HTMLInputElement && (element.type === "checkbox" || element.type === "radio") && element.checked) states.push("checked");
1158
+ const expanded = element.getAttribute("aria-expanded");
1159
+ if (expanded !== null) states.push(`expanded ${expanded}`);
1160
+ if (element.getAttribute("aria-selected") === "true") states.push("selected");
1161
+ if (element.hasAttribute("required") || element.getAttribute("aria-required") === "true") states.push("required");
1162
+ if (element.hasAttribute("readonly") || element.getAttribute("aria-readonly") === "true") states.push("readonly");
1163
+ if (element.getAttribute("aria-hidden") === "true") states.push("hidden");
1164
+ return states;
1165
+ }
1166
+ function elementvalue(element) {
1167
+ if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement) return element.value;
1168
+ return "";
1169
+ }
1170
+ function elementhidden(element) {
1171
+ if (element instanceof HTMLInputElement && element.type === "hidden") return true;
1172
+ if (element.hasAttribute("hidden") || element.getAttribute("aria-hidden") === "true") return true;
1173
+ try {
1174
+ const style = element.ownerDocument?.defaultView?.getComputedStyle(element);
1175
+ if (style && (style.display === "none" || style.visibility === "hidden")) return true;
1176
+ } catch {
1177
+ }
1178
+ return false;
1179
+ }
1180
+ function framenode(frame, depth) {
1181
+ let content = null;
1182
+ try {
1183
+ content = frame.contentDocument;
1184
+ } catch {
1185
+ content = null;
1186
+ }
1187
+ let sameorigin = false;
1188
+ try {
1189
+ sameorigin = content !== null && frame.contentWindow?.location.origin === location.origin;
1190
+ } catch {
1191
+ sameorigin = false;
1192
+ }
1193
+ const node = wrap2(frame, depth);
1194
+ if (sameorigin && content && depth < maxframedepth) node.children.push(...wrapchildren(content, depth + 1));
1195
+ return node;
1196
+ }
1197
+ function wrap2(element, depth) {
1198
+ const shadow = element.shadowRoot;
1199
+ const node = {
1200
+ tag: element.tagName.toLowerCase(),
1201
+ selector: elementselector(element),
1202
+ id: element.id,
1203
+ classes: [...element.classList],
1204
+ role: element.getAttribute("role")?.toLowerCase() || implicitrole(element),
1205
+ name: elementlabel(element),
1206
+ text: owntext2(element),
1207
+ value: elementvalue(element),
1208
+ states: elementstates(element),
1209
+ hidden: elementhidden(element),
1210
+ children: [],
1211
+ element
1212
+ };
1213
+ if (shadow) node.children.push(...wrapchildren(shadow, depth));
1214
+ if (element instanceof HTMLIFrameElement) return framenode(element, depth);
1215
+ node.children.push(...wrapchildren(element, depth));
1216
+ return node;
1217
+ }
1218
+ function wrapchildren(scope, depth) {
1219
+ return [...scope.querySelectorAll(":scope > *")].map((child) => wrap2(child, depth));
1220
+ }
1221
+ function buildpagetree(scope) {
1222
+ const root = {
1223
+ tag: "#document",
1224
+ selector: "",
1225
+ id: "",
1226
+ classes: [],
1227
+ role: "document",
1228
+ name: "",
1229
+ text: "",
1230
+ value: "",
1231
+ states: [],
1232
+ hidden: false,
1233
+ children: []
1234
+ };
1235
+ if (scope instanceof Document) {
1236
+ root.children = scope.documentElement ? [wrap2(scope.documentElement, 0)] : [];
1237
+ } else {
1238
+ root.children = [...wrapchildren(scope, 0)];
1239
+ }
1240
+ return root;
1241
+ }
1242
+ function countnodes(node) {
1243
+ return 1 + node.children.reduce((total, child) => total + countnodes(child), 0);
1244
+ }
1245
+ function builda11ytree(node) {
1246
+ const children = node.children.filter((child) => !child.hidden).map(builda11ytree);
1247
+ return {
1248
+ role: node.role || "generic",
1249
+ name: node.name,
1250
+ states: node.states,
1251
+ ...node.value ? { value: node.value } : {},
1252
+ childcount: children.length,
1253
+ children
1254
+ };
1255
+ }
1256
+ function visibleentries(node) {
1257
+ if (node.hidden) return [];
1258
+ const entries = node.text ? [{ selector: node.selector || node.tag, text: node.text }] : [];
1259
+ for (const child of node.children) entries.push(...visibleentries(child));
1260
+ return entries;
1261
+ }
1262
+ function visibletext(node) {
1263
+ return visibleentries(node).map((entry) => entry.text).join(" ");
1264
+ }
1265
+ function nodetextlength(node) {
1266
+ return node.text.length + node.children.reduce((total, child) => total + nodetextlength(child), 0);
1267
+ }
1268
+ function nodelinktext(node) {
1269
+ const own = node.tag === "a" ? node.text.length : 0;
1270
+ return own + node.children.reduce((total, child) => total + nodelinktext(child), 0);
1271
+ }
1272
+ function wordsin(text) {
1273
+ return text.split(/\s+/).filter(Boolean).length;
1274
+ }
1275
+ function findbyline(node) {
1276
+ const markers = ["byline", "author"];
1277
+ const direct = node.classes.some((item) => markers.some((marker) => item.toLowerCase().includes(marker))) || markers.some((marker) => node.id.toLowerCase().includes(marker));
1278
+ if (direct && node.text) return node.text;
1279
+ for (const child of node.children) {
1280
+ const found = findbyline(child);
1281
+ if (found) return found;
1282
+ }
1283
+ return "";
1284
+ }
1285
+ function findheading(node, tags) {
1286
+ if (tags.includes(node.tag) && node.text) return node.text;
1287
+ for (const child of node.children) {
1288
+ const found = findheading(child, tags);
1289
+ if (found) return found;
1290
+ }
1291
+ return "";
1292
+ }
1293
+ function buildreader(root, title) {
1294
+ let best;
1295
+ let bestscore = 0;
1296
+ const walk = (node) => {
1297
+ if (node.tag !== "#document") {
1298
+ const length = nodetextlength(node);
1299
+ const links = nodelinktext(node);
1300
+ const score = length * (1 - (length > 0 ? links / length : 0));
1301
+ if (score > bestscore) {
1302
+ bestscore = score;
1303
+ best = node;
1304
+ }
1305
+ }
1306
+ for (const child of node.children) walk(child);
1307
+ };
1308
+ walk(root);
1309
+ const article = best ?? root;
1310
+ const blocks = article.children.filter((child) => !child.hidden && child.text).map((child) => ({ kind: child.tag, text: child.text, words: wordsin(child.text) }));
1311
+ const ownblock = article.text ? [{ kind: article.tag, text: article.text, words: wordsin(article.text) }] : [];
1312
+ const allblocks = [...ownblock, ...blocks];
1313
+ return {
1314
+ title: findheading(article, ["h1"]) || findheading(root, ["h1"]) || title,
1315
+ byline: findbyline(article) || findbyline(root),
1316
+ blocks: allblocks,
1317
+ words: allblocks.reduce((total, block) => total + block.words, 0),
1318
+ characters: allblocks.reduce((total, block) => total + block.text.length, 0)
1319
+ };
1320
+ }
1321
+ function pageoutline(root, title) {
1322
+ const headings = [];
1323
+ const walk = (node) => {
1324
+ const level = /^h([1-6])$/.exec(node.tag);
1325
+ if (level && node.text) headings.push({ level: Number.parseInt(level[1], 10), text: node.text });
1326
+ for (const child of node.children) walk(child);
1327
+ };
1328
+ walk(root);
1329
+ return { title: findheading(root, ["h1"]) || title, headings };
1330
+ }
1331
+ function captureselection(root) {
1332
+ const selection = root.getSelection?.() ?? null;
1333
+ const text = selection ? clean(selection.toString()) : "";
1334
+ return { text, length: text.length };
1335
+ }
1336
+ function opengraphfields(meta, jsonld) {
1337
+ const graph = {};
1338
+ for (const entry of meta) {
1339
+ if (entry.property.startsWith("og:") && entry.content) graph[entry.property] = entry.content;
1340
+ }
1341
+ const structured = [];
1342
+ let refused = 0;
1343
+ for (const raw of jsonld) {
1344
+ try {
1345
+ structured.push(JSON.parse(raw));
1346
+ } catch {
1347
+ refused += 1;
1348
+ }
1349
+ }
1350
+ return { graph, structured, refused };
1351
+ }
1352
+ var stopwords = {
1353
+ en: ["the", "is", "at", "which", "on", "and", "of", "to", "in", "that", "it", "with"],
1354
+ pt: ["de", "que", "n\xE3o", "uma", "para", "com", "por", "mais", "como", "p\xE1gina", "este", "voc\xEA"],
1355
+ es: ["que", "el", "las", "los", "por", "una", "para", "con", "como", "p\xE1gina", "m\xE1s", "este"],
1356
+ fr: ["le", "les", "des", "que", "pour", "dans", "est", "sur", "avec", "page", "plus", "cette"],
1357
+ de: ["der", "die", "und", "das", "ist", "von", "mit", "f\xFCr", "auf", "den", "nicht", "seite"],
1358
+ it: ["che", "il", "la", "per", "una", "del", "sono", "non", "con", "pagina", "pi\xF9", "questo"],
1359
+ nl: ["het", "een", "en", "van", "is", "dat", "op", "te", "voor", "met", "niet", "pagina"]
1360
+ };
1361
+ function detecttextlanguage(text) {
1362
+ const words = text.toLowerCase().split(/[^a-zà-ÿ]+/).filter(Boolean);
1363
+ if (words.length === 0) return "";
1364
+ let best = "";
1365
+ let bestscore = 0;
1366
+ for (const [language, dictionary] of Object.entries(stopwords)) {
1367
+ const score = words.filter((word) => dictionary.includes(word)).length;
1368
+ if (score > bestscore) {
1369
+ bestscore = score;
1370
+ best = language;
1371
+ }
1372
+ }
1373
+ return best;
1374
+ }
1375
+ function taglanguage(text) {
1376
+ return { text, language: detecttextlanguage(text) };
1377
+ }
1378
+ function documentlanguage(signals) {
1379
+ if (signals.lang.trim()) return { language: signals.lang.trim(), source: "document" };
1380
+ if (signals.meta.trim()) return { language: signals.meta.trim(), source: "meta" };
1381
+ return { language: detecttextlanguage(signals.text), source: "content" };
1382
+ }
1383
+ function shadowpaths(scope) {
1384
+ const paths = [];
1385
+ const walk = (tree, prefix) => {
1386
+ for (const shadow of tree.shadows) {
1387
+ if (!shadow.host) continue;
1388
+ const path = prefix ? `${prefix} > ${shadow.host.selector}` : shadow.host.selector;
1389
+ paths.push(path);
1390
+ walk(shadow, path);
1391
+ }
1392
+ };
1393
+ walk(scope, "");
1394
+ return paths;
1395
+ }
1396
+ function framelist(root) {
1397
+ return [...root.querySelectorAll("iframe")].map((frame, index) => {
1398
+ let origin = "";
1399
+ try {
1400
+ origin = frame.contentWindow?.location.origin ?? "";
1401
+ } catch {
1402
+ origin = "";
1403
+ }
1404
+ const rect = frame.getBoundingClientRect();
1405
+ return { index, origin, sameorigin: origin !== "" && origin === location.origin, width: Math.round(rect.width), height: Math.round(rect.height) };
1406
+ });
349
1407
  }
350
1408
  function stepoptions(step) {
1409
+ try {
1410
+ return parseoptions(step);
1411
+ } catch {
1412
+ return {};
1413
+ }
1414
+ }
1415
+ function runpageobservation(step, target, root = document) {
1416
+ switch (step.kind) {
1417
+ case "a11ytree": {
1418
+ const tree = builda11ytree(buildpagetree(root));
1419
+ const count = countnodes(tree);
1420
+ return { ok: true, summary: `Captured the accessibility tree with ${count} node${count === 1 ? "" : "s"}.`, details: { tree, nodecount: count } };
1421
+ }
1422
+ case "readvisible": {
1423
+ const scope = target ?? root;
1424
+ const tree = buildpagetree(scope);
1425
+ const entries = visibleentries(tree);
1426
+ return { ok: true, summary: `Read the rendered text of ${entries.length} visible element${entries.length === 1 ? "" : "s"}.`, details: { entries, text: visibletext(tree) } };
1427
+ }
1428
+ case "readertree": {
1429
+ const article = buildreader(buildpagetree(root), root.title);
1430
+ return { ok: true, summary: `Extracted the reader view with ${article.blocks.length} block${article.blocks.length === 1 ? "" : "s"} and ${article.words} words.`, details: { article } };
1431
+ }
1432
+ case "readoutline": {
1433
+ const outline = pageoutline(buildpagetree(root), root.title);
1434
+ return { ok: true, summary: `Read the outline with ${outline.headings.length} heading${outline.headings.length === 1 ? "" : "s"}.`, details: { title: outline.title, headings: outline.headings } };
1435
+ }
1436
+ case "readselection": {
1437
+ const selection = captureselection(root);
1438
+ return { ok: true, summary: selection.text ? `Read ${selection.length} characters of the current selection.` : "No text is currently selected.", details: { text: selection.text, length: selection.length } };
1439
+ }
1440
+ case "readopengraph": {
1441
+ const meta = [...root.querySelectorAll("meta")].map((element) => ({ property: element.getAttribute("property") ?? "", name: element.getAttribute("name") ?? "", content: element.getAttribute("content") ?? "" }));
1442
+ const jsonld = [...root.querySelectorAll('script[type="application/ld+json"]')].map((element) => element.textContent ?? "");
1443
+ const fields = opengraphfields(meta, jsonld);
1444
+ return { ok: true, summary: `Read ${Object.keys(fields.graph).length} open graph entr${Object.keys(fields.graph).length === 1 ? "y" : "ies"} and ${fields.structured.length} structured payload${fields.structured.length === 1 ? "" : "s"}${fields.refused > 0 ? `; ${fields.refused} malformed payload${fields.refused === 1 ? " was" : "s were"} refused` : ""}.`, details: { graph: fields.graph, structured: fields.structured, refused: fields.refused } };
1445
+ }
1446
+ case "readlang": {
1447
+ const metatag = root.querySelector('meta[http-equiv="content-language"]')?.getAttribute("content") ?? "";
1448
+ const outcome = documentlanguage({ lang: root.documentElement?.getAttribute("lang") ?? "", meta: metatag, text: root.body?.innerText ?? "" });
1449
+ return { ok: true, summary: `Detected page language ${outcome.language || "unknown"} from the ${outcome.source} signal.`, details: { language: outcome.language, source: outcome.source } };
1450
+ }
1451
+ case "detectlanguage": {
1452
+ const options = stepoptions(step);
1453
+ const text = typeof options.text === "string" && options.text ? options.text : target?.textContent ?? root.body?.innerText ?? "";
1454
+ const routed = taglanguage(clean(text));
1455
+ return { ok: routed.language !== "", summary: routed.language ? `Detected language ${routed.language} for the extracted text.` : "The extracted text language is undetermined.", details: { language: routed.language, routed } };
1456
+ }
1457
+ case "listshadow": {
1458
+ const paths = shadowpaths(describescopes(root));
1459
+ return { ok: true, summary: `Listed ${paths.length} open shadow root${paths.length === 1 ? "" : "s"}.`, details: { shadows: paths } };
1460
+ }
1461
+ case "listframes": {
1462
+ const frames = framelist(root);
1463
+ return { ok: true, summary: `Listed ${frames.length} iframe${frames.length === 1 ? "" : "s"}.`, details: { frames } };
1464
+ }
1465
+ default:
1466
+ return { ok: false, summary: "Unsupported page observation." };
1467
+ }
1468
+ }
1469
+
1470
+ // extension/pagedetect.ts
1471
+ function detectlistpatterns(samples) {
1472
+ const patterns = [];
1473
+ for (const sample of samples) {
1474
+ const groups = /* @__PURE__ */ new Map();
1475
+ for (const child of sample.children) {
1476
+ const key = `${child.tag}|${child.classes}`;
1477
+ const group = groups.get(key) ?? [];
1478
+ group.push(child);
1479
+ groups.set(key, group);
1480
+ }
1481
+ for (const [key, group] of groups) {
1482
+ if (group.length < 2) continue;
1483
+ if (!group.some((item) => item.text)) continue;
1484
+ const [tag, classes] = key.split("|");
1485
+ const classpart = (classes ?? "").split(" ").filter(Boolean).map((name) => `.${name}`).join("");
1486
+ patterns.push({ container: sample.container, itemselector: `${tag}${classpart}`, repeat: group.length, samples: group.map((item) => item.text).filter(Boolean) });
1487
+ }
1488
+ }
1489
+ return patterns;
1490
+ }
1491
+ function normalizetable(rows, caption) {
1492
+ const firstheader = rows.find((row) => row.header);
1493
+ const headers = firstheader?.cells ?? [];
1494
+ const body = firstheader ? rows.filter((row) => row !== firstheader) : rows;
1495
+ const width = rows.reduce((largest, row) => Math.max(largest, row.cells.length), 0);
1496
+ const columns = [];
1497
+ for (let index = 0; index < width; index += 1) {
1498
+ const label = headers[index] ?? `column ${index + 1}`;
1499
+ const cells = body.filter((row) => Boolean((row.cells[index] ?? "").trim())).length;
1500
+ columns.push({ label, cells });
1501
+ }
1502
+ return { headers, columns, rows: body.length, caption };
1503
+ }
1504
+ function paginationestimate(entries) {
1505
+ const pages = [];
1506
+ let current = 0;
1507
+ for (const entry of entries) {
1508
+ const parsed = /^\d+$/.exec(entry.text.trim());
1509
+ if (parsed) {
1510
+ const page = Number.parseInt(parsed[0], 10);
1511
+ pages.push(page);
1512
+ if (entry.current) current = page;
1513
+ }
1514
+ }
1515
+ const total = Math.max(0, ...pages, current);
1516
+ return { current, total, links: entries.length, pages };
1517
+ }
1518
+ function infinitescrollranges(ranges) {
1519
+ return ranges.filter((range) => range.scrollheight > range.clientheight && range.triggers.length > 0).map((range) => ({ selector: range.selector, scrollrange: range.scrollheight - range.clientheight, triggers: range.triggers }));
1520
+ }
1521
+ function virtualizedcontainers(containers) {
1522
+ const results = [];
1523
+ for (const container of containers) {
1524
+ const first = container.rows[0];
1525
+ if (!first || container.rows.length < 2 || first.height <= 0) continue;
1526
+ if (!container.rows.every((row) => row.height === first.height)) continue;
1527
+ if (container.scrollheight <= container.rows.length * first.height) continue;
1528
+ results.push({ selector: container.selector, rendered: container.rows.length, estimated: Math.floor(container.scrollheight / first.height) });
1529
+ }
1530
+ return results;
1531
+ }
1532
+ function lazysurvey(images) {
1533
+ const lazy = [];
1534
+ const placeholders = [];
1535
+ for (const image of images) {
1536
+ if (image.loading === "lazy") lazy.push({ selector: image.selector, reason: "loading attribute" });
1537
+ else if (image.datasrc) lazy.push({ selector: image.selector, reason: "deferred source" });
1538
+ if (!image.src) placeholders.push({ selector: image.selector, reason: "empty source" });
1539
+ else if (image.src.startsWith("data:")) placeholders.push({ selector: image.selector, reason: "inline data placeholder" });
1540
+ }
1541
+ return { lazy, placeholders };
1542
+ }
1543
+ function overlaygeometry(elements, viewport) {
1544
+ const area = viewport.width * viewport.height;
1545
+ return elements.filter((element) => (element.position === "sticky" || element.position === "fixed") && element.top <= 0 && element.height > 0).map((element) => {
1546
+ const coverage = area > 0 ? element.height * element.width / area : 0;
1547
+ return { selector: element.selector, position: element.position, coverage: Math.round(coverage * 1e3) / 1e3, hides: coverage >= overlaythreshold };
1548
+ });
1549
+ }
1550
+ function scrolllockstate(signals) {
1551
+ const reasons = [];
1552
+ if (signals.bodyoverflow.includes("hidden") || signals.htmloverflow.includes("hidden")) reasons.push("overflow hidden");
1553
+ if (signals.bodyposition === "fixed") reasons.push("fixed body");
1554
+ if (signals.modal) reasons.push("modal open");
1555
+ return { locked: reasons.length > 0, reasons, scrollable: signals.scrollable };
1556
+ }
1557
+ var consentkeywords = ["cookie", "consent", "gdpr", "lgpd", "privacy", "ccpa"];
1558
+ function bannermatches(candidates, at) {
1559
+ const reports = [];
1560
+ for (const candidate of candidates) {
1561
+ const haystack = `${candidate.id} ${candidate.classes.join(" ")} ${candidate.text}`.toLowerCase();
1562
+ const keyword = consentkeywords.find((word) => haystack.includes(word));
1563
+ if (!keyword) continue;
1564
+ if (!candidate.text && candidate.controls.length === 0) continue;
1565
+ reports.push({ kind: keyword, selector: candidate.selector, text: candidate.text.slice(0, 200), controls: candidate.controls, at });
1566
+ }
1567
+ return reports;
1568
+ }
1569
+ function classifytemplate(signals) {
1570
+ if (signals.password) return "login";
1571
+ if (signals.paragraphs >= 3) return "article";
1572
+ if (signals.tables > 0) return "table";
1573
+ if (signals.forms > 0 && signals.inputs > 0) return "form";
1574
+ if (signals.lists > 0) return "list";
1575
+ return "generic";
1576
+ }
1577
+ function sectionfingerprint(section) {
1578
+ const canonical = [section.tag, String(section.children), String(section.textlength), ...Object.keys(section.attributes).sort().map((key) => `${key}=${section.attributes[key] ?? ""}`)].join("|");
1579
+ let hash = 5381;
1580
+ for (let index = 0; index < canonical.length; index += 1) hash = (hash << 5) + hash + canonical.charCodeAt(index) >>> 0;
1581
+ return `fp${hash.toString(16)}`;
1582
+ }
1583
+ function scrollreport(window2, containers) {
1584
+ const range = Math.max(0, window2.scrollheight - window2.clientheight);
1585
+ return {
1586
+ window: { x: window2.scrollx, y: window2.scrolly, attop: window2.scrolly <= 0, atbottom: window2.scrolly >= range, height: window2.scrollheight },
1587
+ containers: containers.map((container) => {
1588
+ const containerrange = Math.max(0, container.scrollheight - container.clientheight);
1589
+ return { selector: container.selector, scrolltop: container.scrolltop, scrollleft: container.scrollleft, scrollrange: containerrange, atbottom: container.scrolltop >= containerrange };
1590
+ })
1591
+ };
1592
+ }
1593
+ var loadmorepattern = /(load more|show more|see more|ver mais|carregar mais|load older|afficher plus|mehr anzeigen)/i;
1594
+ var paginationtext = /^(next|prev|previous|last|first|next page|previous page|»|«|›|‹|\d+)$/i;
1595
+ var overlaythreshold = 0.25;
1596
+ function signatureof(element) {
1597
+ return `${element.tagName.toLowerCase()}|${[...element.classList].sort().join(" ")}`;
1598
+ }
1599
+ function collectsiblings(root) {
1600
+ const samples = [];
1601
+ for (const element of [...root.querySelectorAll("*")]) {
1602
+ const children = [...element.children];
1603
+ if (children.length < 2) continue;
1604
+ const counts = /* @__PURE__ */ new Map();
1605
+ for (const child of children) {
1606
+ const key = signatureof(child);
1607
+ counts.set(key, (counts.get(key) ?? 0) + 1);
1608
+ }
1609
+ if (![...counts.values()].some((count) => count >= 2)) continue;
1610
+ samples.push({
1611
+ container: elementselector(element),
1612
+ children: children.map((child) => ({ tag: child.tagName.toLowerCase(), classes: [...child.classList].sort().join(" "), text: clean(child.textContent ?? ""), selector: elementselector(child) }))
1613
+ });
1614
+ }
1615
+ return samples;
1616
+ }
1617
+ function collecttables(root) {
1618
+ return [...root.querySelectorAll("table")].map((table) => ({
1619
+ selector: elementselector(table),
1620
+ rows: [...table.querySelectorAll("tr")].map((row) => ({ cells: [...row.querySelectorAll("th, td")].map((cell) => clean(cell.textContent ?? "")), header: Boolean(row.querySelector("th")) })),
1621
+ caption: clean(table.querySelector("caption")?.textContent ?? "")
1622
+ }));
1623
+ }
1624
+ function collectpagination(root) {
1625
+ const entries = [];
1626
+ for (const element of [...root.querySelectorAll("a[href], button, [role=button], [role=link], li, span")]) {
1627
+ const text = clean(element.textContent ?? "");
1628
+ if (!text || !paginationtext.test(text)) continue;
1629
+ if (!element.closest("nav, footer, [class*=pag i], [id*=pag i]")) continue;
1630
+ const current = element.getAttribute("aria-current") === "page" || [...element.classList].some((name) => /current|active|selecionado/i.test(name));
1631
+ entries.push({ text, selector: elementselector(element), current });
1632
+ }
1633
+ return entries;
1634
+ }
1635
+ function collecttriggers(scope) {
1636
+ const triggers = [];
1637
+ for (const element of [...scope.querySelectorAll("button, a[href], [role=button], [class*=loading i], [class*=sentinel i], [class*=spinner i]")]) {
1638
+ const label = clean(element.getAttribute("aria-label") ?? element.textContent ?? "");
1639
+ if (loadmorepattern.test(label)) triggers.push(elementselector(element));
1640
+ }
1641
+ return triggers;
1642
+ }
1643
+ function collectscrollranges(root) {
1644
+ const ranges = [];
1645
+ const scrolling = root.scrollingElement ?? root.documentElement;
1646
+ const viewheight = root.defaultView?.innerHeight ?? 0;
1647
+ if (scrolling && scrolling.scrollHeight > viewheight) ranges.push({ selector: "window", scrollheight: scrolling.scrollHeight, clientheight: viewheight, triggers: collecttriggers(root) });
1648
+ for (const element of [...root.querySelectorAll("*")]) {
1649
+ if (!(element instanceof HTMLElement)) continue;
1650
+ if (element.scrollHeight <= element.clientHeight) continue;
1651
+ ranges.push({ selector: elementselector(element), scrollheight: element.scrollHeight, clientheight: element.clientHeight, triggers: collecttriggers(element) });
1652
+ }
1653
+ return ranges;
1654
+ }
1655
+ function collectvirtual(root) {
1656
+ const containers = [];
1657
+ for (const element of [...root.querySelectorAll("*")]) {
1658
+ const children = [...element.children];
1659
+ const first = children[0];
1660
+ if (!first || children.length < 2) continue;
1661
+ if (!children.every((child) => signatureof(child) === signatureof(first))) continue;
1662
+ const heights = children.map((child) => child.getBoundingClientRect().height);
1663
+ if (!heights.every((height) => height > 0 && height === heights[0])) continue;
1664
+ containers.push({ selector: elementselector(element), scrollheight: element.scrollHeight, rows: children.map((child) => ({ selector: elementselector(child), height: child.getBoundingClientRect().height, classes: [...child.classList].join(" ") })) });
1665
+ }
1666
+ return containers;
1667
+ }
1668
+ function collectimages(root) {
1669
+ return [...root.querySelectorAll("img")].map((image) => ({
1670
+ selector: elementselector(image),
1671
+ src: image.getAttribute("src") ?? "",
1672
+ datasrc: image.getAttribute("data-src") ?? image.getAttribute("data-original") ?? "",
1673
+ loading: image.getAttribute("loading") ?? "",
1674
+ width: image.naturalWidth,
1675
+ height: image.naturalHeight
1676
+ }));
1677
+ }
1678
+ function collectoverlays(root) {
1679
+ const elements = [];
1680
+ for (const element of [...root.querySelectorAll("*")]) {
1681
+ if (!(element instanceof HTMLElement)) continue;
1682
+ const view = element.ownerDocument.defaultView;
1683
+ const position = view ? view.getComputedStyle(element).position : "";
1684
+ if (position !== "sticky" && position !== "fixed") continue;
1685
+ const rect = element.getBoundingClientRect();
1686
+ elements.push({ selector: elementselector(element), position, top: rect.top, height: rect.height, width: rect.width });
1687
+ }
1688
+ return elements;
1689
+ }
1690
+ function collectlocksignals(root) {
1691
+ const view = root.defaultView;
1692
+ const bodystyle = root.body ? view ? view.getComputedStyle(root.body) : void 0 : void 0;
1693
+ const htmlstyle = view ? view.getComputedStyle(root.documentElement) : void 0;
1694
+ return {
1695
+ bodyoverflow: bodystyle?.overflow ?? "",
1696
+ htmloverflow: htmlstyle?.overflow ?? "",
1697
+ bodyposition: bodystyle?.position ?? "",
1698
+ modal: Boolean(root.querySelector("dialog[open], [aria-modal=true]")),
1699
+ scrollable: root.documentElement.scrollHeight > root.documentElement.clientHeight
1700
+ };
1701
+ }
1702
+ var bannerselector = '[id*="cookie" i], [class*="cookie" i], [id*="consent" i], [class*="consent" i], [id*="gdpr" i], [class*="gdpr" i], [id*="privacy" i], [class*="privacy" i], [id*="banner" i], [class*="banner" i], dialog, [role="dialog"], [aria-modal="true"]';
1703
+ function collectbannercandidates(root) {
1704
+ const found = [...root.querySelectorAll(bannerselector)];
1705
+ return found.filter((element) => !found.some((other) => other !== element && other.contains(element))).map((element) => ({
1706
+ selector: elementselector(element),
1707
+ id: element.id,
1708
+ classes: [...element.classList],
1709
+ text: clean(element.textContent ?? "").slice(0, 200),
1710
+ controls: [...element.querySelectorAll("button, a[href], [role=button]")].map((control) => clean(control.getAttribute("aria-label") ?? control.textContent ?? "")).filter(Boolean)
1711
+ }));
1712
+ }
1713
+ function runpagedetection(step, target, root = document) {
1714
+ switch (step.kind) {
1715
+ case "detectlists": {
1716
+ const patterns = detectlistpatterns(collectsiblings(root));
1717
+ return { ok: true, summary: `Detected ${patterns.length} repeated list${patterns.length === 1 ? "" : "s"}.`, details: { lists: patterns } };
1718
+ }
1719
+ case "detecttables": {
1720
+ const tables = collecttables(root).map((entry) => {
1721
+ const shape = normalizetable(entry.rows, entry.caption);
1722
+ return { selector: entry.selector, headers: shape.headers, columns: shape.columns, rows: shape.rows, caption: shape.caption };
1723
+ });
1724
+ return { ok: true, summary: `Detected ${tables.length} data table${tables.length === 1 ? "" : "s"}.`, details: { tables } };
1725
+ }
1726
+ case "countpages": {
1727
+ const estimate = paginationestimate(collectpagination(root));
1728
+ return { ok: true, summary: `Counted ${estimate.links} pagination entr${estimate.links === 1 ? "y" : "ies"} and estimated ${estimate.total} total page${estimate.total === 1 ? "" : "s"}.`, details: { current: estimate.current, total: estimate.total, links: estimate.links, pages: estimate.pages } };
1729
+ }
1730
+ case "detectinfinitescroll": {
1731
+ const containers = infinitescrollranges(collectscrollranges(root));
1732
+ return { ok: true, summary: `Detected ${containers.length} infinite scroll container${containers.length === 1 ? "" : "s"}.`, details: { containers } };
1733
+ }
1734
+ case "detectvirtual": {
1735
+ const containers = virtualizedcontainers(collectvirtual(root));
1736
+ return { ok: true, summary: `Detected ${containers.length} virtualized list${containers.length === 1 ? "" : "s"}.`, details: { containers } };
1737
+ }
1738
+ case "detectlazy": {
1739
+ const survey = lazysurvey(collectimages(root));
1740
+ return { ok: true, summary: `Detected ${survey.lazy.length} lazy image${survey.lazy.length === 1 ? "" : "s"} and ${survey.placeholders.length} placeholder${survey.placeholders.length === 1 ? "" : "s"}.`, details: { lazy: survey.lazy, placeholders: survey.placeholders } };
1741
+ }
1742
+ case "detectsticky": {
1743
+ const overlays = overlaygeometry(collectoverlays(root), { width: root.defaultView?.innerWidth ?? 0, height: root.defaultView?.innerHeight ?? 0 });
1744
+ return { ok: true, summary: `Detected ${overlays.length} sticky or fixed overlay${overlays.length === 1 ? "" : "s"}.`, details: { overlays } };
1745
+ }
1746
+ case "detectscrolllock": {
1747
+ const lock = scrolllockstate(collectlocksignals(root));
1748
+ return { ok: true, summary: lock.locked ? `Scroll is locked: ${lock.reasons.join(", ")}.` : "Scroll is not locked.", details: { locked: lock.locked, reasons: lock.reasons, scrollable: lock.scrollable } };
1749
+ }
1750
+ case "classifypage": {
1751
+ const signals = {
1752
+ paragraphs: root.querySelectorAll("p").length,
1753
+ headings: root.querySelectorAll("h1, h2, h3, h4, h5, h6").length,
1754
+ lists: root.querySelectorAll("ul, ol").length,
1755
+ tables: root.querySelectorAll("table").length,
1756
+ forms: root.querySelectorAll("form").length,
1757
+ inputs: root.querySelectorAll("input, textarea, select").length,
1758
+ password: Boolean(root.querySelector("input[type=password]"))
1759
+ };
1760
+ const template = classifytemplate(signals);
1761
+ const fingerprint = sectionfingerprint({ tag: "body", attributes: {}, children: root.body?.children.length ?? 0, textlength: (root.body?.innerText ?? "").length });
1762
+ return { ok: true, summary: `Classified the page template as ${template}.`, details: { template, fingerprint } };
1763
+ }
1764
+ case "fingerprintsection": {
1765
+ if (!target) return { ok: false, summary: "Fingerprint target is no longer available." };
1766
+ const attributes = {};
1767
+ for (const attribute of [...target.attributes]) attributes[attribute.name] = attribute.value;
1768
+ const fingerprint = sectionfingerprint({ tag: target.tagName.toLowerCase(), attributes, children: target.children.length, textlength: (target.textContent ?? "").length });
1769
+ return { ok: true, summary: `Computed section fingerprint ${fingerprint}.`, details: { fingerprint, section: elementselector(target) } };
1770
+ }
1771
+ case "readscrollpos": {
1772
+ const report = scrollreport(
1773
+ { scrollx: root.defaultView?.scrollX ?? 0, scrolly: root.defaultView?.scrollY ?? 0, scrollheight: root.documentElement.scrollHeight, clientheight: root.defaultView?.innerHeight ?? 0 },
1774
+ [...root.querySelectorAll("*")].filter((element) => element instanceof HTMLElement && element.scrollHeight > element.clientHeight).map((element) => ({ selector: elementselector(element), scrolltop: element.scrollTop, scrollleft: element.scrollLeft, scrollheight: element.scrollHeight, clientheight: element.clientHeight }))
1775
+ );
1776
+ return { ok: true, summary: `Read the scroll position at ${Math.round(report.window.x)},${Math.round(report.window.y)} with ${report.containers.length} scrollable container${report.containers.length === 1 ? "" : "s"}.`, details: { scroll: report } };
1777
+ }
1778
+ default:
1779
+ return { ok: false, summary: "Unsupported page detection." };
1780
+ }
1781
+ }
1782
+
1783
+ // extension/pagewatch.ts
1784
+ var defaultpoll = 250;
1785
+ function parsewatchoptions(step, fallbackid) {
1786
+ let options = {};
1787
+ try {
1788
+ options = parseoptions(step);
1789
+ } catch {
1790
+ options = {};
1791
+ }
1792
+ const scopes = Array.isArray(options.scopes) ? options.scopes.filter((item) => typeof item === "string" && item.trim().length > 0) : void 0;
1793
+ const events3 = Array.isArray(options.events) ? options.events.filter((item) => typeof item === "string" && item.trim().length > 0) : void 0;
1794
+ const lifetime = typeof options.lifetime === "number" && Number.isFinite(options.lifetime) && options.lifetime > 0 ? options.lifetime : 0;
1795
+ return {
1796
+ watchid: typeof options.watchid === "string" && options.watchid.trim() ? options.watchid : fallbackid,
1797
+ ...scopes ? { scopes } : {},
1798
+ ...events3 ? { events: events3 } : {},
1799
+ lifetime,
1800
+ poll: typeof options.poll === "number" && Number.isFinite(options.poll) && options.poll >= 0 ? options.poll : defaultpoll
1801
+ };
1802
+ }
1803
+ function batchmutations(records, windowms) {
1804
+ const batches = [];
1805
+ let current = [];
1806
+ let opened = -1;
1807
+ for (const record of records) {
1808
+ if (current.length === 0 || windowms > 0 && record.at - opened >= windowms) {
1809
+ if (current.length > 0) batches.push(current);
1810
+ current = [record];
1811
+ opened = record.at;
1812
+ } else current.push(record);
1813
+ }
1814
+ if (current.length > 0) batches.push(current);
1815
+ return batches;
1816
+ }
1817
+ function quietfor(entries, now) {
1818
+ let last = 0;
1819
+ for (const entry of entries) if (entry.responseend > last) last = entry.responseend;
1820
+ return Math.max(0, now - last);
1821
+ }
1822
+ function quietresolution(samples, idle, timeout) {
1823
+ const start = samples[0]?.at ?? 0;
1824
+ const last = samples[samples.length - 1];
1825
+ const waited = Math.max(0, (last?.at ?? 0) - start);
1826
+ const reached = samples.find((sample) => sample.quietfor >= idle);
1827
+ if (reached) return { ok: true, quietfor: reached.quietfor, waited: reached.at - start, samples: samples.length };
1828
+ return { ok: false, quietfor: last?.quietfor ?? 0, waited, samples: samples.length };
1829
+ }
1830
+ function nodehash(summary) {
1831
+ const canonical = [summary.tag, summary.text, ...Object.keys(summary.attributes).sort().map((key) => `${key}=${summary.attributes[key] ?? ""}`)].join("|");
1832
+ let hash = 5381;
1833
+ for (let index = 0; index < canonical.length; index += 1) hash = (hash << 5) + hash + canonical.charCodeAt(index) >>> 0;
1834
+ return hash.toString(16);
1835
+ }
1836
+ function diffsummaries(base, target) {
1837
+ const basemap = new Map(base.map((node) => [node.selector, node]));
1838
+ const targetmap = new Map(target.map((node) => [node.selector, node]));
1839
+ const added = [];
1840
+ const removed = [];
1841
+ const changed = [];
1842
+ for (const [selector, node] of targetmap) {
1843
+ const previous = basemap.get(selector);
1844
+ if (!previous) {
1845
+ added.push({ kind: "added", selector, summary: node.text || node.tag });
1846
+ continue;
1847
+ }
1848
+ if (nodehash(previous) !== nodehash(node)) changed.push({ kind: "changed", selector, summary: `${previous.text || previous.tag} became ${node.text || node.tag}` });
1849
+ }
1850
+ for (const [selector, node] of basemap) {
1851
+ if (!targetmap.has(selector)) removed.push({ kind: "removed", selector, summary: node.text || node.tag });
1852
+ }
1853
+ return { added, removed, changed };
1854
+ }
1855
+ function scanjson(scripts) {
1856
+ const states = [];
1857
+ let refused = 0;
1858
+ for (const script of scripts) {
1859
+ if (script.src) continue;
1860
+ const content = script.content.trim();
1861
+ if (!(script.type.includes("json") || content.startsWith("{") || content.startsWith("["))) continue;
1862
+ try {
1863
+ states.push({ scripturl: script.src, rootpath: script.id, payload: JSON.parse(content) });
1864
+ } catch {
1865
+ refused += 1;
1866
+ }
1867
+ }
1868
+ return { states, refused };
1869
+ }
1870
+ function rankselectors(shape) {
1871
+ const candidates = [];
1872
+ if (shape.id) candidates.push({ selector: `#${shape.id}`, strategy: "id", score: 100 });
1873
+ for (const [name, value] of Object.entries(shape.attributes)) {
1874
+ if (!value) continue;
1875
+ if (name === "name" || name.startsWith("data-") || name.startsWith("aria-")) candidates.push({ selector: `${shape.tag}[${name}="${value}"]`, strategy: "attribute", score: 80 });
1876
+ }
1877
+ if (shape.text) candidates.push({ selector: shape.text, strategy: "text", score: 60 });
1878
+ if (shape.index > 0) candidates.push({ selector: `${shape.tag}:nth-of-type(${shape.index})`, strategy: "structural", score: 40 });
1879
+ return candidates.sort((left, right) => right.score - left.score);
1880
+ }
1881
+ function wait2(ms) {
1882
+ return new Promise((resolve) => window.setTimeout(resolve, ms));
1883
+ }
1884
+ function quietruleof(step) {
1885
+ let options = {};
1886
+ try {
1887
+ options = parseoptions(step);
1888
+ } catch {
1889
+ options = {};
1890
+ }
1891
+ const rule = options.quietrule;
1892
+ if (!rule || typeof rule !== "object" || Array.isArray(rule)) return { idle: 0 };
1893
+ const quiet = rule;
1894
+ return {
1895
+ idle: typeof quiet.idle === "number" && Number.isFinite(quiet.idle) && quiet.idle > 0 ? quiet.idle : 0,
1896
+ ...typeof quiet.poll === "number" && Number.isFinite(quiet.poll) && quiet.poll >= 0 ? { poll: quiet.poll } : {},
1897
+ ...typeof quiet.timeout === "number" && Number.isFinite(quiet.timeout) && quiet.timeout >= 0 ? { timeout: quiet.timeout } : {}
1898
+ };
1899
+ }
1900
+ async function watchmutations(step, root) {
1901
+ const options = parsewatchoptions(step, step.id);
1902
+ if (options.lifetime <= 0) return { ok: false, summary: "The reviewed mutation watch lifetime is absent." };
1903
+ const roots = options.scopes ? options.scopes.flatMap((selector) => [...root.querySelectorAll(selector)]) : [root];
1904
+ if (roots.length === 0) return { ok: false, summary: "The reviewed watch scopes match no elements." };
1905
+ const allowed = options.events;
1906
+ const collected = [];
1907
+ const observer = new MutationObserver((records) => {
1908
+ for (const record of records) {
1909
+ if (allowed && !allowed.includes(record.type)) continue;
1910
+ const target = record.target instanceof Element ? record.target : null;
1911
+ collected.push({ watchid: options.watchid, event: record.type, targetpath: target ? elementselector(target) : "#text", at: Date.now() });
1912
+ }
1913
+ });
1914
+ for (const scope of roots) observer.observe(scope, { childList: true, attributes: true, characterData: true, subtree: true });
1915
+ await wait2(options.lifetime);
1916
+ observer.disconnect();
1917
+ const batches = batchmutations(collected, options.poll);
1918
+ return { ok: true, summary: `Watched ${collected.length} mutation${collected.length === 1 ? "" : "s"} in ${batches.length} batch${batches.length === 1 ? "" : "es"} for the reviewed lifetime of ${options.lifetime} milliseconds.`, details: { events: collected, batches: batches.length, watchid: options.watchid, lifetime: options.lifetime, scopes: options.scopes ?? [] } };
1919
+ }
1920
+ async function watchfocus(step, root) {
1921
+ const options = parsewatchoptions(step, step.id);
1922
+ if (options.lifetime <= 0) return { ok: false, summary: "The reviewed focus watch lifetime is absent." };
1923
+ const collected = [];
1924
+ const record = (kind) => (event) => {
1925
+ const target = event.target instanceof Element ? event.target : null;
1926
+ collected.push({ watchid: options.watchid, kind, targetpath: target ? elementselector(target) : "#document", at: Date.now() });
1927
+ };
1928
+ const onfocus = record("focus");
1929
+ const onblur = record("blur");
1930
+ root.addEventListener("focusin", onfocus, true);
1931
+ root.addEventListener("focusout", onblur, true);
1932
+ await wait2(options.lifetime);
1933
+ root.removeEventListener("focusin", onfocus, true);
1934
+ root.removeEventListener("focusout", onblur, true);
1935
+ return { ok: true, summary: `Watched ${collected.length} focus change${collected.length === 1 ? "" : "s"} for the reviewed lifetime of ${options.lifetime} milliseconds.`, details: { events: collected, watchid: options.watchid, lifetime: options.lifetime } };
1936
+ }
1937
+ async function watchbanners(step, root) {
1938
+ const options = parsewatchoptions(step, step.id);
1939
+ if (options.lifetime <= 0) return { ok: false, summary: "The reviewed banner watch lifetime is absent." };
1940
+ const started = Date.now();
1941
+ const seen = /* @__PURE__ */ new Map();
1942
+ while (Date.now() - started < options.lifetime) {
1943
+ const at = Date.now();
1944
+ for (const report of bannermatches(collectbannercandidates(root), at)) {
1945
+ if (!seen.has(report.selector)) seen.set(report.selector, report);
1946
+ }
1947
+ await wait2(options.poll);
1948
+ }
1949
+ const reports = [...seen.values()];
1950
+ return { ok: true, summary: `Watched for consent banners for the reviewed lifetime of ${options.lifetime} milliseconds and observed ${reports.length} banner${reports.length === 1 ? "" : "s"}.`, details: { banners: reports, watchid: options.watchid, lifetime: options.lifetime } };
1951
+ }
1952
+ async function waitquiet(step) {
1953
+ const rule = quietruleof(step);
1954
+ if (rule.idle <= 0) return { ok: false, summary: "The reviewed quiet idle threshold is absent." };
1955
+ const poll2 = rule.poll ?? 100;
1956
+ const timeout = rule.timeout ?? 0;
1957
+ const started = performance.now();
1958
+ const samples = [];
1959
+ for (; ; ) {
1960
+ const now = performance.now();
1961
+ const entries = performance.getEntriesByType("resource").map((entry) => ({ responseend: entry.responseEnd }));
1962
+ samples.push({ at: now - started, quietfor: quietfor(entries, now) });
1963
+ const latest = samples[samples.length - 1];
1964
+ if (latest && latest.quietfor >= rule.idle) break;
1965
+ if (timeout > 0 && now - started >= timeout) break;
1966
+ await wait2(poll2);
1967
+ }
1968
+ const outcome = quietresolution(samples, rule.idle, timeout);
1969
+ return {
1970
+ ok: outcome.ok,
1971
+ summary: outcome.ok ? `The network stayed quiet for ${Math.round(outcome.quietfor)} milliseconds, meeting the reviewed idle threshold of ${rule.idle} milliseconds.` : `The network did not stay quiet for ${rule.idle} milliseconds${timeout > 0 ? ` within the reviewed timeout of ${timeout} milliseconds` : ""}.`,
1972
+ details: { samples, idle: rule.idle, timeout, waited: Math.round(outcome.waited) }
1973
+ };
1974
+ }
1975
+ function scriptsurfaces(target, root) {
1976
+ const elements = target ? [target] : [...root.querySelectorAll("script")];
1977
+ return elements.map((element) => ({ src: element.getAttribute("src") ?? "", type: element.getAttribute("type") ?? "", id: element.id, content: element.textContent ?? "" }));
1978
+ }
1979
+ function readjson(step, target, root) {
1980
+ const outcome = scanjson(scriptsurfaces(target, root));
1981
+ if (target && outcome.states.length === 0 && outcome.refused > 0) return { ok: false, summary: "The reviewed json payload is malformed and was refused." };
1982
+ return { ok: true, summary: `Extracted ${outcome.states.length} embedded json state${outcome.states.length === 1 ? "" : "s"}${outcome.refused > 0 ? ` and refused ${outcome.refused} malformed payload${outcome.refused === 1 ? "" : "s"}` : ""}.`, details: { states: outcome.states, refused: outcome.refused } };
1983
+ }
1984
+ function tonodesummaries(value) {
1985
+ if (!Array.isArray(value)) return null;
1986
+ const summaries = [];
1987
+ for (const entry of value) {
1988
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
1989
+ const candidate = entry;
1990
+ if (typeof candidate.selector !== "string") continue;
1991
+ const attributes = {};
1992
+ if (candidate.attributes && typeof candidate.attributes === "object" && !Array.isArray(candidate.attributes)) {
1993
+ for (const [key, item] of Object.entries(candidate.attributes)) if (typeof item === "string") attributes[key] = item;
1994
+ }
1995
+ summaries.push({ selector: candidate.selector, tag: typeof candidate.tag === "string" ? candidate.tag : "", text: typeof candidate.text === "string" ? candidate.text : "", attributes });
1996
+ }
1997
+ return summaries;
1998
+ }
1999
+ function diffsnapshots(step) {
2000
+ let options = {};
2001
+ try {
2002
+ options = parseoptions(step);
2003
+ } catch {
2004
+ options = {};
2005
+ }
2006
+ const base = tonodesummaries(options.base);
2007
+ const target = tonodesummaries(options.target);
2008
+ if (!base || !target) return { ok: false, summary: "Two stored observation versions must be reviewed before diffing." };
2009
+ const versions = Array.isArray(options.versions) && options.versions.length === 2 ? options.versions : [0, 0];
2010
+ const diff = diffsummaries(base, target);
2011
+ return { ok: true, summary: `Diffed observation versions ${versions[0] ?? 0} and ${versions[1] ?? 0}: ${diff.added.length} added, ${diff.removed.length} removed and ${diff.changed.length} changed node${diff.added.length + diff.removed.length + diff.changed.length === 1 ? "" : "s"}.`, details: { versions, added: diff.added, removed: diff.removed, changed: diff.changed } };
2012
+ }
2013
+ function deriveselector(target) {
2014
+ if (!(target instanceof Element)) return { ok: false, summary: "Derivation target is no longer available." };
2015
+ const attributes = {};
2016
+ for (const attribute of [...target.attributes]) attributes[attribute.name] = attribute.value;
2017
+ const parent = target.parentElement;
2018
+ const siblings = parent ? [...parent.children].filter((node) => node.tagName === target.tagName) : [target];
2019
+ const candidates = rankselectors({ id: target.id, tag: target.tagName.toLowerCase(), attributes, text: clean(target.textContent ?? "").slice(0, 80), index: siblings.indexOf(target) + 1, siblings: siblings.length });
2020
+ const best = candidates[0];
2021
+ return { ok: candidates.length > 0, summary: best ? `Derived ${candidates.length} selector candidate${candidates.length === 1 ? "" : "s"}; the most stable is ${best.selector} through the ${best.strategy} strategy with stability ${best.score}.` : "No selector candidate could be derived.", details: { candidates } };
2022
+ }
2023
+ function runpagewatch(step, target, root = document) {
2024
+ switch (step.kind) {
2025
+ case "watchmutate":
2026
+ return watchmutations(step, root);
2027
+ case "watchfocus":
2028
+ return watchfocus(step, root);
2029
+ case "watchbanner":
2030
+ return watchbanners(step, root);
2031
+ case "waitquiet":
2032
+ return waitquiet(step);
2033
+ case "readjson":
2034
+ return readjson(step, target, root);
2035
+ case "diffsnapshots":
2036
+ return diffsnapshots(step);
2037
+ case "deriveselector":
2038
+ return deriveselector(target);
2039
+ default:
2040
+ return { ok: false, summary: "Unsupported watched observation." };
2041
+ }
2042
+ }
2043
+
2044
+ // extension/pagebridge.ts
2045
+ function stepoptions2(step) {
351
2046
  if (!step.options) return {};
352
2047
  try {
353
2048
  const parsed = JSON.parse(step.options);
@@ -360,11 +2055,13 @@
360
2055
  function clearpreview() {
361
2056
  document.getElementById(previewid)?.remove();
362
2057
  }
363
- function previewtarget(targetselector, expectedorigin) {
2058
+ function previewtarget(step, expectedorigin) {
364
2059
  if (location.origin !== expectedorigin) return { ok: false, summary: "Page origin changed before preview." };
365
2060
  clearpreview();
366
- const target = document.querySelector(targetselector);
367
- if (!(target instanceof HTMLElement)) return { ok: false, summary: "Reviewed target is no longer available." };
2061
+ const resolution = resolvestep(step, document);
2062
+ if (resolution.status === "ambiguous") return { ok: false, summary: `The reviewed ${resolution.mode} reference matched ${resolution.candidates.length} elements: ${resolution.candidates.join("; ")}.`, candidates: resolution.candidates };
2063
+ if (resolution.status !== "resolved") return { ok: false, summary: "Reviewed target is no longer available." };
2064
+ const target = resolution.element;
368
2065
  const rect = target.getBoundingClientRect();
369
2066
  if (rect.width <= 0 || rect.height <= 0) return { ok: false, summary: "Reviewed target is not currently visible." };
370
2067
  const overlay = document.createElement("div");
@@ -373,43 +2070,65 @@
373
2070
  Object.assign(overlay.style, { position: "fixed", left: `${Math.max(0, rect.left - 3)}px`, top: `${Math.max(0, rect.top - 3)}px`, width: `${rect.width + 6}px`, height: `${rect.height + 6}px`, border: "3px solid #2f80ed", borderRadius: "6px", boxShadow: "0 0 0 3px rgba(47,128,237,.28)", pointerEvents: "none", zIndex: "2147483647", boxSizing: "border-box" });
374
2071
  document.documentElement.append(overlay);
375
2072
  window.setTimeout(clearpreview, 5e3);
376
- return { ok: true, summary: `Previewing ${label(target) || target.tagName.toLowerCase()} for five seconds.` };
2073
+ return { ok: true, summary: `Previewing ${elementlabel(target) || target.tagName.toLowerCase()} for five seconds.`, resolvedtarget: resolution.target };
377
2074
  }
378
2075
  function capturesnapshot() {
379
2076
  const candidates = [...document.querySelectorAll("a[href], button, input, textarea, select, [role=button], [role=link], [role=combobox], [role=option], [role=checkbox], [role=radio], [role=switch], [role=tab], details, summary")];
380
- const interactive = candidates.map((element) => ({ selector: selector(element), role: element.getAttribute("role") || element.tagName.toLowerCase(), label: label(element) })).filter((item) => item.label || item.role);
2077
+ const interactive = candidates.map((element) => ({ selector: elementselector(element), role: element.getAttribute("role") || element.tagName.toLowerCase(), label: elementlabel(element) })).filter((item) => item.label || item.role);
381
2078
  const forms = [...document.querySelectorAll("input, textarea, select")].map((element) => ({
382
- label: label(element),
2079
+ label: elementlabel(element),
383
2080
  type: element.getAttribute("type") || element.tagName.toLowerCase(),
384
2081
  name: element.getAttribute("name") || "",
385
2082
  ...element instanceof HTMLSelectElement ? { options: [...element.options].map((option) => clean(option.textContent || option.value)) } : {}
386
2083
  }));
387
2084
  const text = clean(document.body?.innerText || "");
388
- return { schemaversion: 2, url: location.href, title: clean(document.title), textpreview: text, textlength: document.body?.innerText.length ?? 0, forms, interactive, capturedat: Date.now() };
2085
+ const tree = buildpagetree(document);
2086
+ const tables = collecttables(document).map((entry) => {
2087
+ const shape = normalizetable(entry.rows, entry.caption);
2088
+ return { selector: entry.selector, headers: shape.headers, columns: shape.columns, rows: shape.rows, caption: shape.caption };
2089
+ });
2090
+ return {
2091
+ schemaversion: 3,
2092
+ url: location.href,
2093
+ title: clean(document.title),
2094
+ textpreview: text,
2095
+ textlength: document.body?.innerText.length ?? 0,
2096
+ forms,
2097
+ interactive,
2098
+ capturedat: Date.now(),
2099
+ mode: "passive",
2100
+ a11y: builda11ytree(tree),
2101
+ reader: buildreader(tree, clean(document.title)),
2102
+ listpattern: detectlistpatterns(collectsiblings(document)),
2103
+ tableshape: tables
2104
+ };
2105
+ }
2106
+ function readdialogs() {
2107
+ return harvestdialoglog(document);
389
2108
  }
390
- function extractcontent(targetselector) {
2109
+ function extractcontent(targetselector, root) {
391
2110
  if (!targetselector) {
392
- const links = [...document.querySelectorAll("a[href]")].map((element) => {
2111
+ const links = [...root.querySelectorAll("a[href]")].map((element) => {
393
2112
  const href = element instanceof HTMLAnchorElement ? element.getAttribute("href") ?? "" : "";
394
2113
  return { text: element.textContent?.trim() ?? "", href };
395
2114
  });
396
2115
  return { ok: true, summary: `Extracted ${links.length} link entries.`, details: { links } };
397
2116
  }
398
- const target = document.querySelector(targetselector);
2117
+ const target = root.querySelector(targetselector);
399
2118
  if (!target) return { ok: false, summary: "Extraction target is no longer available." };
400
2119
  const text = target.textContent ?? "";
401
2120
  return { ok: true, summary: `Extracted ${text.length} characters of content.`, details: { text } };
402
2121
  }
403
2122
  function scrolltarget(target) {
404
2123
  target.scrollIntoView({ block: "center", inline: "nearest", behavior: "auto" });
405
- return { ok: true, summary: `Scrolled ${label(target) || target.tagName.toLowerCase()} into view.` };
2124
+ return { ok: true, summary: `Scrolled ${elementlabel(target) || target.tagName.toLowerCase()} into view.` };
406
2125
  }
407
2126
  function hovertarget(target) {
408
2127
  for (const type of ["pointerover", "mouseover", "pointerenter"]) {
409
2128
  target.dispatchEvent(new PointerEvent(type, { bubbles: type !== "pointerenter", cancelable: true, composed: true }));
410
2129
  }
411
2130
  target.dispatchEvent(new MouseEvent("mouseenter", { bubbles: false, cancelable: true }));
412
- return { ok: true, summary: `Hover events delivered to ${label(target) || target.tagName.toLowerCase()}.` };
2131
+ return { ok: true, summary: `Hover events delivered to ${elementlabel(target) || target.tagName.toLowerCase()}.` };
413
2132
  }
414
2133
  function selectoption(target, value) {
415
2134
  if (!(target instanceof HTMLSelectElement)) return { ok: false, summary: "Target is not a select element." };
@@ -420,9 +2139,15 @@
420
2139
  target.dispatchEvent(new Event("change", { bubbles: true }));
421
2140
  return { ok: true, summary: `Selected ${clean(option.textContent || option.value)}.` };
422
2141
  }
423
- var readkinds = /* @__PURE__ */ new Set(["readattribute", "readstyle", "readgeometry", "readvalue", "readtext", "readhtml", "countelements", "readtable", "readlinks", "readimages", "readmeta", "readforms", "readstorage", "waitfor", "waittext", "highlight"]);
2142
+ var readkinds = /* @__PURE__ */ new Set(["readattribute", "readstyle", "readgeometry", "readvalue", "readtext", "readhtml", "countelements", "readtable", "readlinks", "readimages", "readmeta", "readforms", "readstorage", "waitfor", "waittext", "highlight", "mapclicks", "verifyvisible", "verifyenabled", "resolvexpath"]);
424
2143
  var mutatingkinds = /* @__PURE__ */ new Set(["presskey", "clickdeep", "rightclick", "doubleclick", "drag", "drop", "upload", "clear", "check", "uncheck", "toggle", "submit", "setattribute", "removeattribute", "writestorage", "evaluate", "fullscreen"]);
425
- function performstep(step, expectedorigin) {
2144
+ var controlkinds = /* @__PURE__ */ new Set(["typetime", "appendtext", "setvalue", "typeedit", "keyhold", "keyrelease", "submitsearch", "selectmulti", "chooseradio", "setslider", "setdate", "setcolor", "expanddetails"]);
2145
+ var interactkinds = /* @__PURE__ */ new Set(["clicktext", "clickaria", "clickname", "pierceshadow", "enterframe"]);
2146
+ var pointerkinds = /* @__PURE__ */ new Set(["movepointer", "clickpoint", "shiftclick"]);
2147
+ var observationkinds = /* @__PURE__ */ new Set(["a11ytree", "readvisible", "readertree", "readoutline", "readselection", "readopengraph", "readlang", "detectlanguage", "listshadow", "listframes"]);
2148
+ var detectionkinds = /* @__PURE__ */ new Set(["detectlists", "detecttables", "detectinfinitescroll", "detectvirtual", "detectlazy", "detectsticky", "detectscrolllock", "countpages", "classifypage", "fingerprintsection", "readscrollpos"]);
2149
+ var watchstepkinds = /* @__PURE__ */ new Set(["watchmutate", "watchbanner", "watchfocus", "waitquiet", "readjson", "diffsnapshots", "deriveselector"]);
2150
+ async function performstep(step, expectedorigin, rootdocument = document) {
426
2151
  if (location.origin !== expectedorigin) return { ok: false, summary: "Page origin changed before action." };
427
2152
  if (step.kind === "observe") return { ok: true, summary: "Observation completed." };
428
2153
  if (step.kind === "wait") {
@@ -430,7 +2155,7 @@
430
2155
  const duration = Number.isFinite(requested) && requested > 0 ? requested : 0;
431
2156
  return new Promise((resolve) => window.setTimeout(() => resolve({ ok: true, summary: `Reviewed wait of ${duration} milliseconds completed.` }), duration));
432
2157
  }
433
- if (step.kind === "extract") return extractcontent(step.target);
2158
+ if (step.kind === "extract") return extractcontent(step.target, rootdocument);
434
2159
  if (step.kind === "navigate") {
435
2160
  if (!step.value || new URL(step.value).origin !== expectedorigin) return { ok: false, summary: "Navigation target is outside the approved origin." };
436
2161
  location.assign(step.value);
@@ -449,7 +2174,7 @@
449
2174
  return { ok: true, summary: "History forward requested." };
450
2175
  }
451
2176
  if (step.kind === "scrollpage") {
452
- const options = stepoptions(step);
2177
+ const options = stepoptions2(step);
453
2178
  window.scrollBy({ left: typeof options.x === "number" ? options.x : 0, top: typeof options.y === "number" ? options.y : 600, behavior: "auto" });
454
2179
  return { ok: true, summary: "Window scrolled by the reviewed amounts." };
455
2180
  }
@@ -461,39 +2186,52 @@
461
2186
  window.scrollTo(0, 0);
462
2187
  return { ok: true, summary: "Window scrolled to the page top." };
463
2188
  }
464
- if (readkinds.has(step.kind)) return runpageread(step, step.target ? document.querySelector(step.target) : null);
465
- if (mutatingkinds.has(step.kind)) return runpageaction(step, step.target ? document.querySelector(step.target) : null);
466
- if (!step.target) return { ok: false, summary: "Action target is absent." };
467
- const target = document.querySelector(step.target);
468
- if (!(target instanceof HTMLElement)) return { ok: false, summary: "Action target is no longer available." };
469
- if (step.kind === "scrollby") {
470
- const options = stepoptions(step);
471
- target.scrollBy({ left: typeof options.x === "number" ? options.x : 0, top: typeof options.y === "number" ? options.y : 600, behavior: "auto" });
472
- return { ok: true, summary: "Container scrolled by the reviewed amounts." };
473
- }
474
- if (step.kind === "focus") {
475
- target.focus();
476
- return { ok: true, summary: "Target focused." };
477
- }
478
- if (step.kind === "inspect") return { ok: true, summary: `Target: ${label(target) || target.tagName.toLowerCase()}.` };
479
- if (step.kind === "click") {
480
- target.click();
481
- return { ok: true, summary: "Reviewed click completed." };
482
- }
483
- if (step.kind === "scroll") return scrolltarget(target);
484
- if (step.kind === "hover") return hovertarget(target);
485
- if (step.kind === "select") return selectoption(target, step.value ?? "");
486
- if (step.kind === "type") {
487
- if (!(target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement)) return { ok: false, summary: "Target cannot receive text." };
488
- if (typeof step.value !== "string") return { ok: false, summary: "Approved text is absent." };
489
- target.focus();
490
- target.value = step.value;
491
- target.dispatchEvent(new Event("input", { bubbles: true }));
492
- target.dispatchEvent(new Event("change", { bubbles: true }));
493
- return { ok: true, summary: "Approved text entered." };
494
- }
495
- return { ok: false, summary: "Unsupported action." };
496
- }
497
- Object.assign(globalThis, { devthinkbridge: { capturesnapshot, previewtarget, performstep } });
2189
+ const resolution = resolvestep(step, rootdocument);
2190
+ if (resolution.status === "ambiguous") {
2191
+ return { ok: false, summary: `The reviewed ${resolution.mode} reference matched ${resolution.candidates.length} elements: ${resolution.candidates.join("; ")}.`, details: { mode: resolution.mode, candidates: resolution.candidates } };
2192
+ }
2193
+ const element = resolution.status === "resolved" ? resolution.element : null;
2194
+ let result;
2195
+ if (readkinds.has(step.kind)) result = runpageread(step, element, rootdocument);
2196
+ else if (controlkinds.has(step.kind)) result = runpagecontrol(step, element, rootdocument);
2197
+ else if (interactkinds.has(step.kind)) return await runinteractstep(step, expectedorigin, performstep);
2198
+ else if (pointerkinds.has(step.kind)) result = runpointerstep(step, resolution);
2199
+ else if (observationkinds.has(step.kind)) result = runpageobservation(step, element, rootdocument);
2200
+ else if (detectionkinds.has(step.kind)) result = runpagedetection(step, element, rootdocument);
2201
+ else if (watchstepkinds.has(step.kind)) result = runpagewatch(step, element, rootdocument);
2202
+ else if (mutatingkinds.has(step.kind)) result = runpageaction(step, element);
2203
+ else {
2204
+ if (!element) return { ok: false, summary: "Action target is no longer available." };
2205
+ if (step.kind === "scrollby") {
2206
+ const options = stepoptions2(step);
2207
+ element.scrollBy({ left: typeof options.x === "number" ? options.x : 0, top: typeof options.y === "number" ? options.y : 600, behavior: "auto" });
2208
+ result = { ok: true, summary: "Container scrolled by the reviewed amounts." };
2209
+ } else if (step.kind === "focus") {
2210
+ element.focus();
2211
+ result = { ok: true, summary: "Target focused." };
2212
+ } else if (step.kind === "inspect") result = { ok: true, summary: `Target: ${elementlabel(element) || element.tagName.toLowerCase()}.` };
2213
+ else if (step.kind === "click") {
2214
+ element.click();
2215
+ result = { ok: true, summary: "Reviewed click completed." };
2216
+ } else if (step.kind === "scroll") result = scrolltarget(element);
2217
+ else if (step.kind === "hover") result = hovertarget(element);
2218
+ else if (step.kind === "select") result = selectoption(element, step.value ?? "");
2219
+ else if (step.kind === "type") {
2220
+ if (!(element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement)) return { ok: false, summary: "Target cannot receive text." };
2221
+ if (typeof step.value !== "string") return { ok: false, summary: "Approved text is absent." };
2222
+ element.focus();
2223
+ element.value = step.value;
2224
+ element.dispatchEvent(new Event("input", { bubbles: true }));
2225
+ element.dispatchEvent(new Event("change", { bubbles: true }));
2226
+ result = { ok: true, summary: "Approved text entered." };
2227
+ } else return { ok: false, summary: "Unsupported action." };
2228
+ }
2229
+ const output = await result;
2230
+ if (resolution.status === "resolved") {
2231
+ return { ...output, details: { ...output.details ?? {}, mode: resolution.target.mode, resolvedtarget: resolution.target } };
2232
+ }
2233
+ return output;
2234
+ }
2235
+ Object.assign(globalThis, { devthinkbridge: { capturesnapshot, previewtarget, performstep, readdialogs } });
498
2236
  })();
499
2237
  //# sourceMappingURL=pagebridge.js.map