@wenathlan/extension 1.1.32 → 1.1.33

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"]);
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,348 @@
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 queryshadowchain(root, selectors) {
444
+ let scope = root;
445
+ for (let position = 0; position < selectors.length; position += 1) {
446
+ const found = scope.querySelector(selectors[position]);
447
+ if (!found) return null;
448
+ if (position === selectors.length - 1) return found;
449
+ const shadow = found.shadowRoot;
450
+ if (!shadow) return null;
451
+ scope = shadow;
452
+ }
453
+ return null;
454
+ }
455
+ function queryscoped(root, selector) {
456
+ const direct = root.querySelector(selector);
457
+ if (direct) return direct;
458
+ for (const element of [...root.querySelectorAll("*")]) {
459
+ const shadow = element.shadowRoot;
460
+ if (shadow) {
461
+ const found = queryscoped(shadow, selector);
462
+ if (found) return found;
463
+ }
464
+ }
465
+ return null;
466
+ }
467
+ function parseoptionssafe(step) {
468
+ try {
469
+ return parseoptions(step);
470
+ } catch {
471
+ return {};
472
+ }
473
+ }
474
+ function targetsummary(mode, element) {
475
+ const rect = element.getBoundingClientRect();
476
+ 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 } };
477
+ }
478
+ function singleresolution(mode, matches) {
479
+ const verdict = resolutionverdict(matches.length);
480
+ if (verdict === "resolved") {
481
+ const winner = matches[0];
482
+ if (winner && winner.element instanceof HTMLElement) return { status: "resolved", element: winner.element, target: targetsummary(mode, winner.element) };
483
+ return { status: "absent", mode };
484
+ }
485
+ if (verdict === "ambiguous") return { status: "ambiguous", mode, candidates: matches.slice(0, 8).map((candidate) => candidate.label || candidate.selector) };
486
+ return { status: "absent", mode };
487
+ }
488
+ function resolvetargetref(reference, root) {
489
+ const mode = reference.mode;
490
+ if (mode === "selector") {
491
+ const selector = typeof reference.selector === "string" ? reference.selector : "";
492
+ const element = selector ? root.querySelector(selector) : null;
493
+ return element instanceof HTMLElement ? { status: "resolved", element, target: targetsummary("selector", element) } : { status: "absent", mode: "selector" };
494
+ }
495
+ if (mode === "point") {
496
+ const x = Number(reference.x);
497
+ const y = Number(reference.y);
498
+ if (!Number.isFinite(x) || !Number.isFinite(y)) return { status: "absent", mode: "point" };
499
+ const element = root.elementFromPoint(x, y);
500
+ return element instanceof HTMLElement ? { status: "resolved", element, target: targetsummary("point", element) } : { status: "absent", mode: "point" };
501
+ }
502
+ if (mode === "xpath") {
503
+ const expression = typeof reference.xpath === "string" ? reference.xpath : "";
504
+ if (!expression) return { status: "absent", mode: "xpath" };
505
+ const matches = evaluatexpath(buildxtree(root), expression);
506
+ const first = matches[0];
507
+ return first?.element instanceof HTMLElement ? { status: "resolved", element: first.element, target: targetsummary("xpath", first.element) } : { status: "absent", mode: "xpath" };
508
+ }
509
+ if (mode === "index") {
510
+ const matches = matchindex(collectclickable(root), Number(reference.index));
511
+ return singleresolution("index", matches);
512
+ }
513
+ const candidates = collectcandidates(root);
514
+ if (mode === "text") return singleresolution("text", matchtext(candidates, typeof reference.text === "string" ? reference.text : ""));
515
+ if (mode === "aria") return singleresolution("aria", matcharia(candidates, typeof reference.role === "string" ? reference.role : "", typeof reference.name === "string" ? reference.name : ""));
516
+ if (mode === "name") return singleresolution("name", matchname(candidates, typeof reference.name === "string" ? reference.name : ""));
517
+ return { status: "absent" };
518
+ }
519
+ function resolvestep(step, root) {
520
+ const reference = parseoptionssafe(step).targetref;
521
+ if (reference && typeof reference === "object" && !Array.isArray(reference)) return resolvetargetref(reference, root);
522
+ if (!step.target?.trim()) return { status: "none" };
523
+ const element = root.querySelector(step.target);
524
+ if (element instanceof HTMLElement) return { status: "resolved", element, target: targetsummary("selector", element) };
525
+ return { status: "absent", mode: "selector" };
526
+ }
527
+
182
528
  // extension/pagereads.ts
183
529
  var highlightid = "devthinkactionhighlight";
184
530
  function clearhighlight() {
@@ -195,7 +541,7 @@
195
541
  window.setTimeout(clearhighlight, 5e3);
196
542
  return { ok: true, summary: "Target outlined for five seconds." };
197
543
  }
198
- function poll(predicate, description, timeout) {
544
+ function poll(root, predicate, description, timeout) {
199
545
  return new Promise((resolve) => {
200
546
  const started = Date.now();
201
547
  const check = () => {
@@ -212,15 +558,15 @@
212
558
  check();
213
559
  });
214
560
  }
215
- function formstate() {
216
- return [...document.querySelectorAll("input, textarea, select")].map((element) => ({
561
+ function formstate(root) {
562
+ return [...root.querySelectorAll("input, textarea, select")].map((element) => ({
217
563
  type: element.getAttribute("type") ?? element.tagName.toLowerCase(),
218
564
  name: element.getAttribute("name") ?? "",
219
565
  value: element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement ? element.value : "",
220
566
  ...element instanceof HTMLInputElement && (element.type === "checkbox" || element.type === "radio") ? { checked: element.checked } : {}
221
567
  }));
222
568
  }
223
- function runpageread(step, target) {
569
+ function runpageread(step, target, root = document) {
224
570
  const options = (() => {
225
571
  try {
226
572
  return parseoptions(step);
@@ -268,7 +614,7 @@
268
614
  return { ok: true, summary: "Target markup read.", details: { html: target.outerHTML } };
269
615
  }
270
616
  case "countelements": {
271
- const count = document.querySelectorAll(step.target ?? "").length;
617
+ const count = root.querySelectorAll(step.target ?? "").length;
272
618
  return { ok: true, summary: `Selector matches ${count} element${count === 1 ? "" : "s"}.`, details: { count } };
273
619
  }
274
620
  case "readtable": {
@@ -279,19 +625,19 @@
279
625
  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
626
  }
281
627
  case "readlinks": {
282
- const links = [...document.querySelectorAll("a[href]")].map((element) => ({ text: element.textContent?.trim() ?? "", href: element.getAttribute("href") ?? "" }));
628
+ const links = [...root.querySelectorAll("a[href]")].map((element) => ({ text: element.textContent?.trim() ?? "", href: element.getAttribute("href") ?? "" }));
283
629
  return { ok: true, summary: `Read ${links.length} link${links.length === 1 ? "" : "s"}.`, details: { links } };
284
630
  }
285
631
  case "readimages": {
286
- const images = [...document.querySelectorAll("img")].map((element) => ({ src: element.getAttribute("src") ?? "", alt: element.getAttribute("alt") ?? "" }));
632
+ const images = [...root.querySelectorAll("img")].map((element) => ({ src: element.getAttribute("src") ?? "", alt: element.getAttribute("alt") ?? "" }));
287
633
  return { ok: true, summary: `Read ${images.length} image${images.length === 1 ? "" : "s"}.`, details: { images } };
288
634
  }
289
635
  case "readmeta": {
290
- const meta = [...document.querySelectorAll("meta")].map((element) => ({ name: element.getAttribute("name") ?? "", property: element.getAttribute("property") ?? "", content: element.getAttribute("content") ?? "" }));
636
+ const meta = [...root.querySelectorAll("meta")].map((element) => ({ name: element.getAttribute("name") ?? "", property: element.getAttribute("property") ?? "", content: element.getAttribute("content") ?? "" }));
291
637
  return { ok: true, summary: `Read ${meta.length} meta entr${meta.length === 1 ? "y" : "ies"}.`, details: { meta } };
292
638
  }
293
639
  case "readforms": {
294
- const forms = formstate();
640
+ const forms = formstate(root);
295
641
  return { ok: true, summary: `Read ${forms.length} form control${forms.length === 1 ? "" : "s"}.`, details: { forms } };
296
642
  }
297
643
  case "readstorage": {
@@ -311,42 +657,486 @@
311
657
  }
312
658
  }
313
659
  case "waitfor": {
314
- const selector2 = step.target ?? "";
660
+ const selector = step.target ?? "";
315
661
  const timeout = typeof options.timeout === "number" ? options.timeout : 0;
316
- return poll(() => Boolean(document.querySelector(selector2)), `Selector ${selector2}`, timeout);
662
+ return poll(root, () => Boolean(root.querySelector(selector)), `Selector ${selector}`, timeout);
317
663
  }
318
664
  case "waittext": {
319
665
  const text = step.value ?? "";
320
666
  const timeout = typeof options.timeout === "number" ? options.timeout : 0;
321
- return poll(() => (document.body?.innerText ?? "").includes(text), `Text ${text}`, timeout);
667
+ return poll(root, () => (root.body?.innerText ?? "").includes(text), `Text ${text}`, timeout);
668
+ }
669
+ case "mapclicks": {
670
+ const candidates = collectclickable(root);
671
+ const map = buildclickablemap(candidates, 0, 0);
672
+ return { ok: true, summary: `Mapped ${map.entries.length} clickable element${map.entries.length === 1 ? "" : "s"}.`, details: { entries: map.entries } };
673
+ }
674
+ case "verifyvisible": {
675
+ if (!target) return { ok: false, summary: "Verify target is no longer available." };
676
+ const rect = target.getBoundingClientRect();
677
+ const rendered = rect.width > 0 && rect.height > 0;
678
+ 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 } } };
679
+ }
680
+ case "verifyenabled": {
681
+ if (!target) return { ok: false, summary: "Verify target is no longer available." };
682
+ const control = target;
683
+ const disabled = control.disabled === true || target.hasAttribute("disabled");
684
+ const readonly = control.readOnly === true || target.hasAttribute("readonly");
685
+ const enabled = !disabled && !readonly;
686
+ return { ok: enabled, summary: enabled ? "Target is enabled and writable." : disabled ? "Target is disabled." : "Target is readonly.", details: { enabled, disabled, readonly } };
687
+ }
688
+ case "resolvexpath": {
689
+ const reference = options.targetref;
690
+ const expression = typeof reference?.xpath === "string" ? reference.xpath : "";
691
+ if (!expression) return { ok: false, summary: "The reviewed xpath expression is absent." };
692
+ let matches = [];
693
+ try {
694
+ matches = evaluatexpath(buildxtree(root), expression);
695
+ } catch (error) {
696
+ return { ok: false, summary: `The reviewed xpath expression failed: ${error instanceof Error ? error.message : String(error)}` };
697
+ }
698
+ const summaries = matches.map((node) => ({ tag: node.tag, ...node.element ? { selector: elementselector(node.element), label: elementlabel(node.element) } : {} }));
699
+ 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
700
  }
323
701
  default:
324
702
  return { ok: false, summary: "Unsupported page read." };
325
703
  }
326
704
  }
327
705
 
328
- // extension/pagebridge.ts
329
- function clean(value) {
330
- return value.replace(/\s+/g, " ").trim();
706
+ // extension/pagecontrols.ts
707
+ function typetimeschedule(text, delay) {
708
+ return [...text].map((character, position) => ({ key: character, delay: position === 0 ? 0 : delay }));
331
709
  }
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 || "");
710
+ function appendvalue(current, addition) {
711
+ return current + addition;
337
712
  }
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})`;
713
+ function valueevents() {
714
+ return ["input", "change"];
715
+ }
716
+ function multichoices(values, options) {
717
+ const present = [];
718
+ const missing = [];
719
+ for (const value of values) {
720
+ const option = options.find((candidate) => candidate.value === value || candidate.label === value);
721
+ if (option) present.push(option.value);
722
+ else missing.push(value);
723
+ }
724
+ return { present, missing };
725
+ }
726
+ function radiochoice(inputs, choice) {
727
+ return inputs.findIndex((candidate) => candidate.value === choice || candidate.label === choice);
728
+ }
729
+ function slidervalue(requested, min, max, step) {
730
+ const lower = Math.min(min, max);
731
+ const upper = Math.max(min, max);
732
+ const clamped = Math.min(upper, Math.max(lower, requested));
733
+ if (!Number.isFinite(step) || step <= 0) return clamped;
734
+ return Math.round((clamped - lower) / step) * step + lower;
349
735
  }
736
+ function datevalue(requested) {
737
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(requested)) return null;
738
+ const parts = requested.split("-").map((part) => Number.parseInt(part, 10));
739
+ const year = parts[0];
740
+ const month = parts[1];
741
+ const day = parts[2];
742
+ if (!year || !month || !day || month < 1 || month > 12 || day < 1 || day > 31) return null;
743
+ return requested;
744
+ }
745
+ function colorvalue(requested) {
746
+ if (!/^#[0-9a-fA-F]{6}$/.test(requested)) return null;
747
+ return requested.toLowerCase();
748
+ }
749
+ function expandstate(open) {
750
+ return open ? { open: true, changed: false } : { open: true, changed: true };
751
+ }
752
+ function events2(target) {
753
+ target.dispatchEvent(new Event("input", { bubbles: true }));
754
+ target.dispatchEvent(new Event("change", { bubbles: true }));
755
+ }
756
+ function modifiers2(options) {
757
+ return Array.isArray(options.modifiers) ? options.modifiers.filter((item) => typeof item === "string") : [];
758
+ }
759
+ function keyevent2(type, key, mods) {
760
+ const code = key.length === 1 ? `Key${key.toUpperCase()}` : key;
761
+ 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") });
762
+ }
763
+ function fieldlike2(target) {
764
+ return target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement ? target : null;
765
+ }
766
+ function receiver(target) {
767
+ return target instanceof HTMLElement ? target : document.activeElement instanceof HTMLElement ? document.activeElement : document.body;
768
+ }
769
+ function wait(delay) {
770
+ return new Promise((resolve) => window.setTimeout(resolve, delay));
771
+ }
772
+ function focuswhenneeded(target, options) {
773
+ if (!(target instanceof HTMLElement)) return;
774
+ if (options.focus === false) return;
775
+ if (options.focus === true || document.activeElement !== target) target.focus();
776
+ }
777
+ function pollfor(predicate, description, timeout) {
778
+ return new Promise((resolve) => {
779
+ const started = Date.now();
780
+ const check = () => {
781
+ if (predicate()) {
782
+ resolve({ ok: true, summary: `${description} is now present on the page.` });
783
+ return;
784
+ }
785
+ if (timeout > 0 && Date.now() - started >= timeout) {
786
+ resolve({ ok: false, summary: `${description} did not appear within ${timeout} milliseconds.` });
787
+ return;
788
+ }
789
+ window.setTimeout(check, 100);
790
+ };
791
+ check();
792
+ });
793
+ }
794
+ function runpagecontrol(step, target, root = document) {
795
+ let options = {};
796
+ try {
797
+ options = parseoptions(step);
798
+ } catch {
799
+ options = {};
800
+ }
801
+ switch (step.kind) {
802
+ case "typetime": {
803
+ const field = fieldlike2(target);
804
+ if (!field) return { ok: false, summary: "Target cannot receive timed text." };
805
+ const text = step.value ?? "";
806
+ const delay = typeof options.delay === "number" && options.delay > 0 ? options.delay : 0;
807
+ focuswhenneeded(field, options);
808
+ const schedule = typetimeschedule(text, delay);
809
+ return (async () => {
810
+ for (const entry of schedule) {
811
+ await wait(entry.delay);
812
+ field.dispatchEvent(keyevent2("keydown", entry.key, []));
813
+ field.dispatchEvent(new KeyboardEvent("keypress", { key: entry.key, bubbles: true, cancelable: true }));
814
+ field.value = `${field.value}${entry.key}`;
815
+ field.dispatchEvent(new Event("input", { bubbles: true }));
816
+ }
817
+ field.dispatchEvent(new Event("change", { bubbles: true }));
818
+ return { ok: true, summary: `Typed ${text.length} character${text.length === 1 ? "" : "s"} with a per keystroke delay of ${delay} milliseconds.` };
819
+ })();
820
+ }
821
+ case "appendtext": {
822
+ const field = fieldlike2(target);
823
+ if (!field) return { ok: false, summary: "Target cannot hold a value." };
824
+ focuswhenneeded(field, options);
825
+ field.value = appendvalue(field.value, step.value ?? "");
826
+ events2(field);
827
+ return { ok: true, summary: "Reviewed text appended to the current field value." };
828
+ }
829
+ case "setvalue": {
830
+ const field = fieldlike2(target);
831
+ if (!field) return { ok: false, summary: "Target cannot hold a value." };
832
+ focuswhenneeded(field, options);
833
+ field.value = step.value ?? "";
834
+ events2(field);
835
+ return { ok: true, summary: `Field value set through the dom property with ${valueevents().join(" and ")} events.` };
836
+ }
837
+ case "typeedit": {
838
+ if (!(target instanceof HTMLElement) || !target.isContentEditable) return { ok: false, summary: "Target is not a content editable region." };
839
+ focuswhenneeded(target, options);
840
+ const text = step.value ?? "";
841
+ return (async () => {
842
+ for (const character of [...text]) {
843
+ target.dispatchEvent(new InputEvent("beforeinput", { bubbles: true, cancelable: true, data: character, inputType: "insertText" }));
844
+ target.append(document.createTextNode(character));
845
+ target.dispatchEvent(new InputEvent("input", { bubbles: true, data: character, inputType: "insertText" }));
846
+ }
847
+ return { ok: true, summary: `Typed ${text.length} character${text.length === 1 ? "" : "s"} into the content editable region.` };
848
+ })();
849
+ }
850
+ case "keyhold": {
851
+ const key = step.value ?? "";
852
+ const mods = modifiers2(options);
853
+ receiver(target).dispatchEvent(keyevent2("keydown", key, mods));
854
+ const holdid = typeof options.holdid === "string" && options.holdid ? options.holdid : "";
855
+ return { ok: true, summary: `Key ${key} pressed and held${holdid ? ` under hold id ${holdid}` : ""}.`, details: { ...holdid ? { holdid } : {}, modifiers: mods } };
856
+ }
857
+ case "keyrelease": {
858
+ const key = step.value ?? "";
859
+ const mods = modifiers2(options);
860
+ receiver(target).dispatchEvent(keyevent2("keyup", key, mods));
861
+ return { ok: true, summary: `Key ${key} released.`, details: { modifiers: mods } };
862
+ }
863
+ case "submitsearch": {
864
+ const field = fieldlike2(target);
865
+ if (!field) return { ok: false, summary: "Target is not a search field." };
866
+ const results = typeof options.results === "string" ? options.results : "";
867
+ const timeout = typeof options.timeout === "number" ? options.timeout : 0;
868
+ focuswhenneeded(field, options);
869
+ field.dispatchEvent(keyevent2("keydown", "Enter", []));
870
+ field.dispatchEvent(new KeyboardEvent("keypress", { key: "Enter", bubbles: true, cancelable: true }));
871
+ field.dispatchEvent(keyevent2("keyup", "Enter", []));
872
+ return pollfor(() => Boolean(document.querySelector(results)), `Results region ${results}`, timeout);
873
+ }
874
+ case "selectmulti": {
875
+ if (!(target instanceof HTMLSelectElement) || !target.multiple) return { ok: false, summary: "Target is not a multi select control." };
876
+ const choices = [...target.options].map((option) => ({ value: option.value, label: clean(option.textContent || option.value) }));
877
+ const requested = Array.isArray(options.values) ? options.values.filter((item) => typeof item === "string") : [];
878
+ const outcome = multichoices(requested, choices);
879
+ 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.` };
880
+ for (const option of target.options) option.selected = outcome.present.includes(option.value);
881
+ events2(target);
882
+ return { ok: true, summary: `Selected ${outcome.present.length} reviewed option${outcome.present.length === 1 ? "" : "s"} in the multi select control.`, details: { selected: outcome.present } };
883
+ }
884
+ case "chooseradio": {
885
+ const radios = target instanceof HTMLInputElement && target.type === "radio" ? [...root.querySelectorAll(`input[type=radio][name="${CSS.escape(target.name)}"]`)] : target ? [...target.querySelectorAll("input[type=radio]")] : [];
886
+ if (radios.length === 0) return { ok: false, summary: "No radio group owns the reviewed target." };
887
+ const inputs = radios.map((radio) => ({ value: radio.value, label: radio.labels && radio.labels.length > 0 ? clean(radio.labels[0]?.textContent || "") || radio.value : radio.value }));
888
+ const index = radiochoice(inputs, step.value ?? "");
889
+ const chosen = radios[index];
890
+ if (!chosen) return { ok: false, summary: "The reviewed radio option is not part of the group." };
891
+ chosen.checked = true;
892
+ events2(chosen);
893
+ return { ok: true, summary: `Picked reviewed radio option ${step.value}.`, details: { value: chosen.value } };
894
+ }
895
+ case "setslider": {
896
+ if (!(target instanceof HTMLInputElement) || target.type !== "range") return { ok: false, summary: "Target is not a range slider." };
897
+ const requested = Number(step.value);
898
+ if (!Number.isFinite(requested)) return { ok: false, summary: "The reviewed slider value is not a number." };
899
+ focuswhenneeded(target, options);
900
+ const value = slidervalue(requested, Number(target.min), Number(target.max), Number(target.step));
901
+ target.value = String(value);
902
+ events2(target);
903
+ return { ok: true, summary: `Slider dragged to the reviewed value ${value}.`, details: { value } };
904
+ }
905
+ case "setdate": {
906
+ if (!(target instanceof HTMLInputElement) || target.type !== "date") return { ok: false, summary: "Target is not a date input." };
907
+ const value = datevalue(step.value ?? "");
908
+ if (value === null) return { ok: false, summary: "The reviewed date is invalid." };
909
+ focuswhenneeded(target, options);
910
+ target.value = value;
911
+ events2(target);
912
+ return { ok: true, summary: `Date input set to ${value}.`, details: { value } };
913
+ }
914
+ case "setcolor": {
915
+ if (!(target instanceof HTMLInputElement) || target.type !== "color") return { ok: false, summary: "Target is not a color input." };
916
+ const value = colorvalue(step.value ?? "");
917
+ if (value === null) return { ok: false, summary: "The reviewed color is invalid." };
918
+ focuswhenneeded(target, options);
919
+ target.value = value;
920
+ events2(target);
921
+ return { ok: true, summary: `Color input set to ${value}.`, details: { value } };
922
+ }
923
+ case "expanddetails": {
924
+ const details = target instanceof HTMLElement ? target.closest("details") : null;
925
+ if (!details) return { ok: false, summary: "Target is not inside a details section." };
926
+ const outcome = expandstate(details.open);
927
+ details.open = outcome.open;
928
+ return { ok: true, summary: outcome.changed ? "Collapsed details section opened." : "Details section was already open.", details: { changed: outcome.changed } };
929
+ }
930
+ default:
931
+ return { ok: false, summary: "Unsupported control action." };
932
+ }
933
+ }
934
+
935
+ // extension/pagepointer.ts
936
+ var basecadence = 16;
937
+ function distance(a, b) {
938
+ return Math.hypot(b.x - a.x, b.y - a.y);
939
+ }
940
+ function ease(easing, progress) {
941
+ if (easing === "easeinout") return progress * progress * (3 - 2 * progress);
942
+ return progress;
943
+ }
944
+ function ispointref(value) {
945
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
946
+ const point = value;
947
+ return typeof point.x === "number" && Number.isFinite(point.x) && typeof point.y === "number" && Number.isFinite(point.y);
948
+ }
949
+ function pathhops(path, profile, random = Math.random, cadence = basecadence) {
950
+ const easing = profile?.easing === "easeinout" ? "easeinout" : "linear";
951
+ const peak = typeof profile?.peak === "number" && profile.peak > 0 ? profile.peak : void 0;
952
+ const jitter = typeof profile?.jitter === "number" && profile.jitter > 0 ? profile.jitter : 0;
953
+ const points = [path.start, ...path.waypoints ?? [], path.end];
954
+ const lengths = [];
955
+ let total = 0;
956
+ for (let index = 1; index < points.length; index += 1) {
957
+ const length = distance(points[index - 1], points[index]);
958
+ lengths.push(length);
959
+ total += length;
960
+ }
961
+ const reviewedduration = typeof path.duration === "number" && Number.isFinite(path.duration) && path.duration > 0 ? path.duration : void 0;
962
+ const duration = reviewedduration ?? (peak !== void 0 && total > 0 ? total / peak * 1e3 : 300);
963
+ const hops = [];
964
+ let previous = points[0];
965
+ for (let index = 1; index < points.length; index += 1) {
966
+ const from = points[index - 1];
967
+ const to = points[index];
968
+ const length = lengths[index - 1] ?? 0;
969
+ if (total <= 0 || length <= 0) {
970
+ hops.push({ x: to.x, y: to.y, delay: 0 });
971
+ previous = to;
972
+ continue;
973
+ }
974
+ const segmentduration = duration * length / total;
975
+ const count = Math.max(1, Math.ceil(segmentduration / Math.max(1, cadence)));
976
+ for (let hop = 1; hop <= count; hop += 1) {
977
+ const progress = hop / count;
978
+ const eased = ease(easing, progress);
979
+ const position = { x: from.x + (to.x - from.x) * eased, y: from.y + (to.y - from.y) * eased };
980
+ const step = distance(previous, position);
981
+ const base = segmentduration / count;
982
+ const capped = peak !== void 0 ? Math.max(base, step / peak * 1e3) : base;
983
+ hops.push({ x: position.x, y: position.y, delay: Math.max(0, capped + (jitter > 0 ? random() * jitter : 0)) });
984
+ previous = position;
985
+ }
986
+ }
987
+ return hops;
988
+ }
989
+ function clickplan(x, y, modifiers3) {
990
+ const shift = modifiers3.includes("shift");
991
+ const pointer = (type) => ({ type, eventkind: "pointer", x, y, shift });
992
+ const mouse = (type) => ({ type, eventkind: "mouse", x, y, shift });
993
+ return [pointer("pointerover"), pointer("pointermove"), pointer("pointerdown"), mouse("mousedown"), pointer("pointerup"), mouse("mouseup"), mouse("click")];
994
+ }
995
+ function dispatchplanned(element, event) {
996
+ const init = { bubbles: true, cancelable: true, composed: true, clientX: event.x, clientY: event.y, shiftKey: event.shift };
997
+ if (event.eventkind === "pointer") element.dispatchEvent(new PointerEvent(event.type, init));
998
+ else element.dispatchEvent(new MouseEvent(event.type, init));
999
+ }
1000
+ function dispatchclick(element, modifiers3 = []) {
1001
+ const rect = element.getBoundingClientRect();
1002
+ const x = rect.left + rect.width / 2;
1003
+ const y = rect.top + rect.height / 2;
1004
+ for (const event of clickplan(x, y, modifiers3)) dispatchplanned(element, event);
1005
+ }
1006
+ function ensurevisible(element) {
1007
+ try {
1008
+ element.scrollIntoView({ block: "center", inline: "nearest", behavior: "auto" });
1009
+ } catch {
1010
+ }
1011
+ }
1012
+ function settle(delay) {
1013
+ return new Promise((resolve) => window.setTimeout(resolve, delay));
1014
+ }
1015
+ function dispatchmove(x, y) {
1016
+ const element = document.elementFromPoint(x, y);
1017
+ const receiver2 = element ?? document.documentElement;
1018
+ receiver2.dispatchEvent(new PointerEvent("pointermove", { bubbles: true, cancelable: true, composed: true, clientX: x, clientY: y }));
1019
+ }
1020
+ async function travel(path, profile) {
1021
+ const hops = pathhops(path, profile);
1022
+ const startelement = document.elementFromPoint(path.start.x, path.start.y) ?? document.documentElement;
1023
+ startelement.dispatchEvent(new PointerEvent("pointerover", { bubbles: true, cancelable: true, composed: true, clientX: path.start.x, clientY: path.start.y }));
1024
+ for (const hop of hops) {
1025
+ await settle(hop.delay);
1026
+ dispatchmove(hop.x, hop.y);
1027
+ }
1028
+ const endelement = document.elementFromPoint(path.end.x, path.end.y) ?? document.documentElement;
1029
+ endelement.dispatchEvent(new PointerEvent("pointerout", { bubbles: true, cancelable: true, composed: true, clientX: path.end.x, clientY: path.end.y }));
1030
+ return { ok: true, summary: `Pointer traveled ${hops.length} hop${hops.length === 1 ? "" : "s"} to the reviewed end point.` };
1031
+ }
1032
+ function runpointerstep(step, resolution) {
1033
+ let options = {};
1034
+ try {
1035
+ options = parseoptions(step);
1036
+ } catch {
1037
+ options = {};
1038
+ }
1039
+ if (step.kind === "movepointer") {
1040
+ const path = options.pointpath;
1041
+ if (!path || !ispointref(path.start) || !ispointref(path.end)) return { ok: false, summary: "The reviewed pointer path is absent." };
1042
+ const waypoints = Array.isArray(path.waypoints) && path.waypoints.every((item) => ispointref(item)) ? path.waypoints : void 0;
1043
+ const fullpath = { start: path.start, end: path.end, ...waypoints ? { waypoints } : {}, ...typeof path.duration === "number" && Number.isFinite(path.duration) ? { duration: path.duration } : {} };
1044
+ return travel(fullpath, options.speedprofile);
1045
+ }
1046
+ if (step.kind === "clickpoint") {
1047
+ const reference = options.targetref;
1048
+ const x = Number(reference?.x);
1049
+ const y = Number(reference?.y);
1050
+ if (!Number.isFinite(x) || !Number.isFinite(y)) return { ok: false, summary: "The reviewed click coordinates are absent." };
1051
+ const element = document.elementFromPoint(x, y);
1052
+ if (!(element instanceof HTMLElement)) return { ok: false, summary: "No element is rendered at the reviewed coordinates." };
1053
+ ensurevisible(element);
1054
+ for (const event of clickplan(x, y, [])) dispatchplanned(element, event);
1055
+ return { ok: true, summary: `Clicked the element at the reviewed coordinates ${x},${y}.` };
1056
+ }
1057
+ if (step.kind === "shiftclick") {
1058
+ 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 } };
1059
+ if (resolution.status !== "resolved") return { ok: false, summary: "Action target is no longer available." };
1060
+ ensurevisible(resolution.element);
1061
+ dispatchclick(resolution.element, ["shift"]);
1062
+ return { ok: true, summary: `Shift click delivered to ${resolution.target.label || resolution.target.tag}.`, details: { mode: resolution.target.mode, resolvedtarget: resolution.target } };
1063
+ }
1064
+ return { ok: false, summary: "Unsupported pointer action." };
1065
+ }
1066
+
1067
+ // extension/pageinteract.ts
1068
+ function optionsof(step) {
1069
+ try {
1070
+ return parseoptions(step);
1071
+ } catch {
1072
+ return {};
1073
+ }
1074
+ }
1075
+ function innerstep(step) {
1076
+ const options = optionsof(step);
1077
+ const kind = options.kind;
1078
+ if (typeof kind !== "string" || !kind.trim()) return null;
1079
+ const inneroptions = options.options;
1080
+ return {
1081
+ id: `${step.id}inner`,
1082
+ kind,
1083
+ summary: step.summary,
1084
+ risk: step.risk,
1085
+ ...typeof options.target === "string" ? { target: options.target } : {},
1086
+ ...typeof options.value === "string" ? { value: options.value } : {},
1087
+ ...inneroptions && typeof inneroptions === "object" && !Array.isArray(inneroptions) ? { options: JSON.stringify(inneroptions) } : {}
1088
+ };
1089
+ }
1090
+ function clickresolved(stepkind, resolution) {
1091
+ 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 } };
1092
+ if (resolution.status !== "resolved") return { ok: false, summary: "The reviewed target is no longer available." };
1093
+ ensurevisible(resolution.element);
1094
+ dispatchclick(resolution.element);
1095
+ 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 } };
1096
+ }
1097
+ function runinteractstep(step, expectedorigin, dispatch) {
1098
+ if (step.kind === "clicktext" || step.kind === "clickaria" || step.kind === "clickname") {
1099
+ return clickresolved(step.kind, resolvestep(step, document));
1100
+ }
1101
+ if (step.kind === "pierceshadow") {
1102
+ const options = optionsof(step);
1103
+ const shadow = Array.isArray(options.shadow) ? options.shadow.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
1104
+ const element = shadow.length > 0 ? queryshadowchain(document, shadow) : queryscoped(document, step.target ?? "");
1105
+ if (!(element instanceof HTMLElement)) return { ok: false, summary: "The reviewed shadow target is not available." };
1106
+ ensurevisible(element);
1107
+ dispatchclick(element);
1108
+ const summary = targetsummary("selector", element);
1109
+ 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 } };
1110
+ }
1111
+ if (step.kind === "enterframe") {
1112
+ const options = optionsof(step);
1113
+ const path = Array.isArray(options.framepath) ? options.framepath.filter((item) => typeof item === "number" && Number.isInteger(item) && item >= 0) : [];
1114
+ const walk = walkframepath(describeframes(document), path);
1115
+ if (!walk.ok) return { ok: false, summary: walk.reason };
1116
+ const framedocument = walk.document.live;
1117
+ if (!framedocument) return { ok: false, summary: "The reviewed frame document is not available." };
1118
+ const inner = innerstep(step);
1119
+ if (!inner) return { ok: false, summary: "The reviewed inner step is absent." };
1120
+ return dispatch(inner, expectedorigin, framedocument);
1121
+ }
1122
+ return { ok: false, summary: "Unsupported interaction action." };
1123
+ }
1124
+
1125
+ // extension/pagedialogs.ts
1126
+ function harvestdialoglog(root) {
1127
+ const raw = root.documentElement.dataset.devthinkdialoglog;
1128
+ if (!raw) return [];
1129
+ delete root.documentElement.dataset.devthinkdialoglog;
1130
+ try {
1131
+ const parsed = JSON.parse(raw);
1132
+ if (!Array.isArray(parsed)) return [];
1133
+ return parsed.filter((item) => Boolean(item) && typeof item === "object" && typeof item.dialog === "string");
1134
+ } catch {
1135
+ return [];
1136
+ }
1137
+ }
1138
+
1139
+ // extension/pagebridge.ts
350
1140
  function stepoptions(step) {
351
1141
  if (!step.options) return {};
352
1142
  try {
@@ -360,11 +1150,13 @@
360
1150
  function clearpreview() {
361
1151
  document.getElementById(previewid)?.remove();
362
1152
  }
363
- function previewtarget(targetselector, expectedorigin) {
1153
+ function previewtarget(step, expectedorigin) {
364
1154
  if (location.origin !== expectedorigin) return { ok: false, summary: "Page origin changed before preview." };
365
1155
  clearpreview();
366
- const target = document.querySelector(targetselector);
367
- if (!(target instanceof HTMLElement)) return { ok: false, summary: "Reviewed target is no longer available." };
1156
+ const resolution = resolvestep(step, document);
1157
+ if (resolution.status === "ambiguous") return { ok: false, summary: `The reviewed ${resolution.mode} reference matched ${resolution.candidates.length} elements: ${resolution.candidates.join("; ")}.`, candidates: resolution.candidates };
1158
+ if (resolution.status !== "resolved") return { ok: false, summary: "Reviewed target is no longer available." };
1159
+ const target = resolution.element;
368
1160
  const rect = target.getBoundingClientRect();
369
1161
  if (rect.width <= 0 || rect.height <= 0) return { ok: false, summary: "Reviewed target is not currently visible." };
370
1162
  const overlay = document.createElement("div");
@@ -373,13 +1165,13 @@
373
1165
  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
1166
  document.documentElement.append(overlay);
375
1167
  window.setTimeout(clearpreview, 5e3);
376
- return { ok: true, summary: `Previewing ${label(target) || target.tagName.toLowerCase()} for five seconds.` };
1168
+ return { ok: true, summary: `Previewing ${elementlabel(target) || target.tagName.toLowerCase()} for five seconds.`, resolvedtarget: resolution.target };
377
1169
  }
378
1170
  function capturesnapshot() {
379
1171
  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);
1172
+ 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
1173
  const forms = [...document.querySelectorAll("input, textarea, select")].map((element) => ({
382
- label: label(element),
1174
+ label: elementlabel(element),
383
1175
  type: element.getAttribute("type") || element.tagName.toLowerCase(),
384
1176
  name: element.getAttribute("name") || "",
385
1177
  ...element instanceof HTMLSelectElement ? { options: [...element.options].map((option) => clean(option.textContent || option.value)) } : {}
@@ -387,29 +1179,32 @@
387
1179
  const text = clean(document.body?.innerText || "");
388
1180
  return { schemaversion: 2, url: location.href, title: clean(document.title), textpreview: text, textlength: document.body?.innerText.length ?? 0, forms, interactive, capturedat: Date.now() };
389
1181
  }
390
- function extractcontent(targetselector) {
1182
+ function readdialogs() {
1183
+ return harvestdialoglog(document);
1184
+ }
1185
+ function extractcontent(targetselector, root) {
391
1186
  if (!targetselector) {
392
- const links = [...document.querySelectorAll("a[href]")].map((element) => {
1187
+ const links = [...root.querySelectorAll("a[href]")].map((element) => {
393
1188
  const href = element instanceof HTMLAnchorElement ? element.getAttribute("href") ?? "" : "";
394
1189
  return { text: element.textContent?.trim() ?? "", href };
395
1190
  });
396
1191
  return { ok: true, summary: `Extracted ${links.length} link entries.`, details: { links } };
397
1192
  }
398
- const target = document.querySelector(targetselector);
1193
+ const target = root.querySelector(targetselector);
399
1194
  if (!target) return { ok: false, summary: "Extraction target is no longer available." };
400
1195
  const text = target.textContent ?? "";
401
1196
  return { ok: true, summary: `Extracted ${text.length} characters of content.`, details: { text } };
402
1197
  }
403
1198
  function scrolltarget(target) {
404
1199
  target.scrollIntoView({ block: "center", inline: "nearest", behavior: "auto" });
405
- return { ok: true, summary: `Scrolled ${label(target) || target.tagName.toLowerCase()} into view.` };
1200
+ return { ok: true, summary: `Scrolled ${elementlabel(target) || target.tagName.toLowerCase()} into view.` };
406
1201
  }
407
1202
  function hovertarget(target) {
408
1203
  for (const type of ["pointerover", "mouseover", "pointerenter"]) {
409
1204
  target.dispatchEvent(new PointerEvent(type, { bubbles: type !== "pointerenter", cancelable: true, composed: true }));
410
1205
  }
411
1206
  target.dispatchEvent(new MouseEvent("mouseenter", { bubbles: false, cancelable: true }));
412
- return { ok: true, summary: `Hover events delivered to ${label(target) || target.tagName.toLowerCase()}.` };
1207
+ return { ok: true, summary: `Hover events delivered to ${elementlabel(target) || target.tagName.toLowerCase()}.` };
413
1208
  }
414
1209
  function selectoption(target, value) {
415
1210
  if (!(target instanceof HTMLSelectElement)) return { ok: false, summary: "Target is not a select element." };
@@ -420,9 +1215,12 @@
420
1215
  target.dispatchEvent(new Event("change", { bubbles: true }));
421
1216
  return { ok: true, summary: `Selected ${clean(option.textContent || option.value)}.` };
422
1217
  }
423
- var readkinds = /* @__PURE__ */ new Set(["readattribute", "readstyle", "readgeometry", "readvalue", "readtext", "readhtml", "countelements", "readtable", "readlinks", "readimages", "readmeta", "readforms", "readstorage", "waitfor", "waittext", "highlight"]);
1218
+ 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
1219
  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) {
1220
+ var controlkinds = /* @__PURE__ */ new Set(["typetime", "appendtext", "setvalue", "typeedit", "keyhold", "keyrelease", "submitsearch", "selectmulti", "chooseradio", "setslider", "setdate", "setcolor", "expanddetails"]);
1221
+ var interactkinds = /* @__PURE__ */ new Set(["clicktext", "clickaria", "clickname", "pierceshadow", "enterframe"]);
1222
+ var pointerkinds = /* @__PURE__ */ new Set(["movepointer", "clickpoint", "shiftclick"]);
1223
+ async function performstep(step, expectedorigin, rootdocument = document) {
426
1224
  if (location.origin !== expectedorigin) return { ok: false, summary: "Page origin changed before action." };
427
1225
  if (step.kind === "observe") return { ok: true, summary: "Observation completed." };
428
1226
  if (step.kind === "wait") {
@@ -430,7 +1228,7 @@
430
1228
  const duration = Number.isFinite(requested) && requested > 0 ? requested : 0;
431
1229
  return new Promise((resolve) => window.setTimeout(() => resolve({ ok: true, summary: `Reviewed wait of ${duration} milliseconds completed.` }), duration));
432
1230
  }
433
- if (step.kind === "extract") return extractcontent(step.target);
1231
+ if (step.kind === "extract") return extractcontent(step.target, rootdocument);
434
1232
  if (step.kind === "navigate") {
435
1233
  if (!step.value || new URL(step.value).origin !== expectedorigin) return { ok: false, summary: "Navigation target is outside the approved origin." };
436
1234
  location.assign(step.value);
@@ -461,39 +1259,49 @@
461
1259
  window.scrollTo(0, 0);
462
1260
  return { ok: true, summary: "Window scrolled to the page top." };
463
1261
  }
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 } });
1262
+ const resolution = resolvestep(step, rootdocument);
1263
+ if (resolution.status === "ambiguous") {
1264
+ return { ok: false, summary: `The reviewed ${resolution.mode} reference matched ${resolution.candidates.length} elements: ${resolution.candidates.join("; ")}.`, details: { mode: resolution.mode, candidates: resolution.candidates } };
1265
+ }
1266
+ const element = resolution.status === "resolved" ? resolution.element : null;
1267
+ let result;
1268
+ if (readkinds.has(step.kind)) result = runpageread(step, element, rootdocument);
1269
+ else if (controlkinds.has(step.kind)) result = runpagecontrol(step, element, rootdocument);
1270
+ else if (interactkinds.has(step.kind)) return await runinteractstep(step, expectedorigin, performstep);
1271
+ else if (pointerkinds.has(step.kind)) result = runpointerstep(step, resolution);
1272
+ else if (mutatingkinds.has(step.kind)) result = runpageaction(step, element);
1273
+ else {
1274
+ if (!element) return { ok: false, summary: "Action target is no longer available." };
1275
+ if (step.kind === "scrollby") {
1276
+ const options = stepoptions(step);
1277
+ element.scrollBy({ left: typeof options.x === "number" ? options.x : 0, top: typeof options.y === "number" ? options.y : 600, behavior: "auto" });
1278
+ result = { ok: true, summary: "Container scrolled by the reviewed amounts." };
1279
+ } else if (step.kind === "focus") {
1280
+ element.focus();
1281
+ result = { ok: true, summary: "Target focused." };
1282
+ } else if (step.kind === "inspect") result = { ok: true, summary: `Target: ${elementlabel(element) || element.tagName.toLowerCase()}.` };
1283
+ else if (step.kind === "click") {
1284
+ element.click();
1285
+ result = { ok: true, summary: "Reviewed click completed." };
1286
+ } else if (step.kind === "scroll") result = scrolltarget(element);
1287
+ else if (step.kind === "hover") result = hovertarget(element);
1288
+ else if (step.kind === "select") result = selectoption(element, step.value ?? "");
1289
+ else if (step.kind === "type") {
1290
+ if (!(element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement)) return { ok: false, summary: "Target cannot receive text." };
1291
+ if (typeof step.value !== "string") return { ok: false, summary: "Approved text is absent." };
1292
+ element.focus();
1293
+ element.value = step.value;
1294
+ element.dispatchEvent(new Event("input", { bubbles: true }));
1295
+ element.dispatchEvent(new Event("change", { bubbles: true }));
1296
+ result = { ok: true, summary: "Approved text entered." };
1297
+ } else return { ok: false, summary: "Unsupported action." };
1298
+ }
1299
+ const output = await result;
1300
+ if (resolution.status === "resolved") {
1301
+ return { ...output, details: { ...output.details ?? {}, mode: resolution.target.mode, resolvedtarget: resolution.target } };
1302
+ }
1303
+ return output;
1304
+ }
1305
+ Object.assign(globalThis, { devthinkbridge: { capturesnapshot, previewtarget, performstep, readdialogs } });
498
1306
  })();
499
1307
  //# sourceMappingURL=pagebridge.js.map