@wenathlan/extension 1.1.31 → 1.1.32

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,14 +1,339 @@
1
1
  "use strict";
2
2
  (() => {
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"]);
7
+ var allowedactions = /* @__PURE__ */ new Set([...sensitiveactions, ...interactionactions, ...readactions]);
8
+ function parseoptions(step) {
9
+ if (step.options === void 0) return {};
10
+ let parsed;
11
+ try {
12
+ parsed = JSON.parse(step.options);
13
+ } catch {
14
+ throw new Error("Step options must be a JSON object.");
15
+ }
16
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("Step options must be a JSON object.");
17
+ return parsed;
18
+ }
19
+
20
+ // extension/pageactions.ts
21
+ function events(target) {
22
+ target.dispatchEvent(new Event("input", { bubbles: true }));
23
+ target.dispatchEvent(new Event("change", { bubbles: true }));
24
+ }
25
+ function modifiers(options) {
26
+ return Array.isArray(options.modifiers) ? options.modifiers.filter((item) => typeof item === "string") : [];
27
+ }
28
+ function keyevent(type, key, mods) {
29
+ const code = key.length === 1 ? `Key${key.toUpperCase()}` : key;
30
+ 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") });
31
+ }
32
+ function fieldlike(target) {
33
+ return target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement ? target : null;
34
+ }
35
+ function stringify(value) {
36
+ try {
37
+ return JSON.parse(JSON.stringify(value)) ?? null;
38
+ } catch {
39
+ return String(value);
40
+ }
41
+ }
42
+ function runpageaction(step, target) {
43
+ const options = (() => {
44
+ try {
45
+ return parseoptions(step);
46
+ } catch {
47
+ return {};
48
+ }
49
+ })();
50
+ switch (step.kind) {
51
+ case "presskey": {
52
+ const receiver = target instanceof HTMLElement ? target : document.activeElement instanceof HTMLElement ? document.activeElement : document.body;
53
+ const key = step.value ?? "";
54
+ 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));
58
+ return { ok: true, summary: `Key ${key} delivered with ${mods.length} modifier${mods.length === 1 ? "" : "s"}.` };
59
+ }
60
+ case "clickdeep": {
61
+ if (!(target instanceof HTMLElement)) return { ok: false, summary: "Action target is no longer available." };
62
+ target.dispatchEvent(new PointerEvent("pointerdown", { bubbles: true, cancelable: true, composed: true }));
63
+ target.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, cancelable: true }));
64
+ target.dispatchEvent(new PointerEvent("pointerup", { bubbles: true, cancelable: true, composed: true }));
65
+ target.dispatchEvent(new MouseEvent("mouseup", { bubbles: true, cancelable: true }));
66
+ target.click();
67
+ return { ok: true, summary: "Full pointer click sequence delivered." };
68
+ }
69
+ case "rightclick": {
70
+ if (!(target instanceof HTMLElement)) return { ok: false, summary: "Action target is no longer available." };
71
+ const init = { bubbles: true, cancelable: true, button: 2, buttons: 2 };
72
+ target.dispatchEvent(new PointerEvent("pointerdown", { ...init, composed: true }));
73
+ target.dispatchEvent(new MouseEvent("mousedown", init));
74
+ target.dispatchEvent(new MouseEvent("contextmenu", init));
75
+ return { ok: true, summary: "Context menu events delivered." };
76
+ }
77
+ case "doubleclick": {
78
+ if (!(target instanceof HTMLElement)) return { ok: false, summary: "Action target is no longer available." };
79
+ target.click();
80
+ target.click();
81
+ target.dispatchEvent(new MouseEvent("dblclick", { bubbles: true, cancelable: true, detail: 2 }));
82
+ return { ok: true, summary: "Double click sequence delivered." };
83
+ }
84
+ case "drag": {
85
+ if (!(target instanceof HTMLElement)) return { ok: false, summary: "Drag source is no longer available." };
86
+ const destination = document.querySelector(step.value ?? "");
87
+ if (!destination) return { ok: false, summary: "Drag destination is no longer available." };
88
+ const transfer = new DataTransfer();
89
+ if (typeof options.data === "string") transfer.setData("text/plain", options.data);
90
+ target.dispatchEvent(new DragEvent("dragstart", { bubbles: true, cancelable: true, dataTransfer: transfer }));
91
+ destination.dispatchEvent(new DragEvent("dragenter", { bubbles: true, cancelable: true, dataTransfer: transfer }));
92
+ destination.dispatchEvent(new DragEvent("dragover", { bubbles: true, cancelable: true, dataTransfer: transfer }));
93
+ destination.dispatchEvent(new DragEvent("drop", { bubbles: true, cancelable: true, dataTransfer: transfer }));
94
+ target.dispatchEvent(new DragEvent("dragend", { bubbles: true, cancelable: true, dataTransfer: transfer }));
95
+ return { ok: true, summary: "Drag and drop sequence delivered." };
96
+ }
97
+ case "drop": {
98
+ if (!(target instanceof HTMLElement)) return { ok: false, summary: "Drop zone is no longer available." };
99
+ const transfer = new DataTransfer();
100
+ transfer.setData("text/plain", step.value ?? "");
101
+ target.dispatchEvent(new DragEvent("dragenter", { bubbles: true, cancelable: true, dataTransfer: transfer }));
102
+ target.dispatchEvent(new DragEvent("dragover", { bubbles: true, cancelable: true, dataTransfer: transfer }));
103
+ target.dispatchEvent(new DragEvent("drop", { bubbles: true, cancelable: true, dataTransfer: transfer }));
104
+ return { ok: true, summary: "Drop payload delivered." };
105
+ }
106
+ case "upload": {
107
+ if (!(target instanceof HTMLInputElement) || target.type !== "file") return { ok: false, summary: "Target is not a file input." };
108
+ const transfer = new DataTransfer();
109
+ transfer.items.add(new File([typeof options.content === "string" ? options.content : ""], step.value ?? "upload", { type: typeof options.type === "string" ? options.type : "text/plain" }));
110
+ target.files = transfer.files;
111
+ events(target);
112
+ return { ok: true, summary: `Uploaded ${step.value ?? "file"} into the reviewed input.` };
113
+ }
114
+ case "clear": {
115
+ const field = fieldlike(target);
116
+ if (!field) return { ok: false, summary: "Target cannot hold a value." };
117
+ field.value = "";
118
+ events(field);
119
+ return { ok: true, summary: "Field cleared." };
120
+ }
121
+ case "check":
122
+ case "uncheck":
123
+ case "toggle": {
124
+ if (!(target instanceof HTMLInputElement) || target.type !== "checkbox" && target.type !== "radio") return { ok: false, summary: "Target is not a checkbox or radio control." };
125
+ if (step.kind === "uncheck" && target.type === "radio") return { ok: false, summary: "A radio control cannot be unchecked." };
126
+ target.checked = step.kind === "toggle" ? !target.checked : step.kind === "check";
127
+ events(target);
128
+ return { ok: true, summary: `Control is now ${target.checked ? "checked" : "unchecked"}.` };
129
+ }
130
+ case "submit": {
131
+ const form = target instanceof HTMLFormElement ? target : target instanceof HTMLElement ? target.closest("form") : null;
132
+ if (!form) return { ok: false, summary: "No form owns the reviewed target." };
133
+ try {
134
+ form.requestSubmit(target instanceof HTMLFormElement ? void 0 : target);
135
+ } catch {
136
+ form.submit();
137
+ }
138
+ return { ok: true, summary: "Form submission requested." };
139
+ }
140
+ case "setattribute": {
141
+ if (!target) return { ok: false, summary: "Action target is no longer available." };
142
+ const name = typeof options.name === "string" ? options.name : "";
143
+ target.setAttribute(name, typeof options.value === "string" ? options.value : "");
144
+ return { ok: true, summary: `Attribute ${name} set.` };
145
+ }
146
+ case "removeattribute": {
147
+ if (!target) return { ok: false, summary: "Action target is no longer available." };
148
+ const name = step.value ?? "";
149
+ target.removeAttribute(name);
150
+ return { ok: true, summary: `Attribute ${name} removed.` };
151
+ }
152
+ case "writestorage": {
153
+ try {
154
+ localStorage.setItem(typeof options.key === "string" ? options.key : "", typeof options.value === "string" ? options.value : "");
155
+ return { ok: true, summary: `Local storage entry ${String(options.key)} written.` };
156
+ } catch (error) {
157
+ return { ok: false, summary: `Local storage refused the write: ${error instanceof Error ? error.message : String(error)}` };
158
+ }
159
+ }
160
+ case "evaluate": {
161
+ try {
162
+ let outcome;
163
+ try {
164
+ outcome = new Function(`"use strict"; return (${step.value ?? "undefined"});`)();
165
+ } catch {
166
+ outcome = new Function(`"use strict"; ${step.value ?? ""}`)();
167
+ }
168
+ return { ok: true, summary: `Reviewed expression returned ${outcome === void 0 ? "no value" : "a value"}.`, details: { result: stringify(outcome) } };
169
+ } catch (error) {
170
+ return { ok: false, summary: `Reviewed expression failed: ${error instanceof Error ? error.message : String(error)}` };
171
+ }
172
+ }
173
+ case "fullscreen": {
174
+ const element = target instanceof HTMLElement ? target : document.documentElement;
175
+ return document.fullscreenElement === element ? document.exitFullscreen().then(() => ({ ok: true, summary: "Fullscreen state cleared." })) : element.requestFullscreen().then(() => ({ ok: true, summary: "Fullscreen state entered." })).catch((error) => ({ ok: false, summary: `Fullscreen was refused: ${error instanceof Error ? error.message : String(error)}` }));
176
+ }
177
+ default:
178
+ return { ok: false, summary: "Unsupported page action." };
179
+ }
180
+ }
181
+
182
+ // extension/pagereads.ts
183
+ var highlightid = "devthinkactionhighlight";
184
+ function clearhighlight() {
185
+ document.getElementById(highlightid)?.remove();
186
+ }
187
+ function highlighttarget(target) {
188
+ clearhighlight();
189
+ const rect = target.getBoundingClientRect();
190
+ const overlay = document.createElement("div");
191
+ overlay.id = highlightid;
192
+ overlay.setAttribute("aria-hidden", "true");
193
+ 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 #2f9e44", borderRadius: "6px", pointerEvents: "none", zIndex: "2147483647", boxSizing: "border-box" });
194
+ document.documentElement.append(overlay);
195
+ window.setTimeout(clearhighlight, 5e3);
196
+ return { ok: true, summary: "Target outlined for five seconds." };
197
+ }
198
+ function poll(predicate, description, timeout) {
199
+ return new Promise((resolve) => {
200
+ const started = Date.now();
201
+ const check = () => {
202
+ if (predicate()) {
203
+ resolve({ ok: true, summary: `${description} is now present on the page.` });
204
+ return;
205
+ }
206
+ if (timeout > 0 && Date.now() - started >= timeout) {
207
+ resolve({ ok: false, summary: `${description} did not appear within ${timeout} milliseconds.` });
208
+ return;
209
+ }
210
+ window.setTimeout(check, 100);
211
+ };
212
+ check();
213
+ });
214
+ }
215
+ function formstate() {
216
+ return [...document.querySelectorAll("input, textarea, select")].map((element) => ({
217
+ type: element.getAttribute("type") ?? element.tagName.toLowerCase(),
218
+ name: element.getAttribute("name") ?? "",
219
+ value: element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement ? element.value : "",
220
+ ...element instanceof HTMLInputElement && (element.type === "checkbox" || element.type === "radio") ? { checked: element.checked } : {}
221
+ }));
222
+ }
223
+ function runpageread(step, target) {
224
+ const options = (() => {
225
+ try {
226
+ return parseoptions(step);
227
+ } catch {
228
+ return {};
229
+ }
230
+ })();
231
+ switch (step.kind) {
232
+ case "highlight": {
233
+ if (!target) return { ok: false, summary: "Highlight target is no longer available." };
234
+ return highlighttarget(target);
235
+ }
236
+ case "readattribute": {
237
+ if (!target) return { ok: false, summary: "Read target is no longer available." };
238
+ const value = target.getAttribute(step.value ?? "");
239
+ return value === null ? { ok: false, summary: `Attribute ${step.value} is absent.` } : { ok: true, summary: `Attribute ${step.value} read.`, details: { value } };
240
+ }
241
+ case "readstyle": {
242
+ if (!target) return { ok: false, summary: "Read target is no longer available." };
243
+ const computed = getComputedStyle(target);
244
+ const styles = {};
245
+ for (let index = 0; index < computed.length; index += 1) {
246
+ const property = computed.item(index);
247
+ styles[property] = computed.getPropertyValue(property);
248
+ }
249
+ return { ok: true, summary: `Read ${computed.length} computed style properties.`, details: { styles } };
250
+ }
251
+ case "readgeometry": {
252
+ if (!target) return { ok: false, summary: "Read target is no longer available." };
253
+ const rect = target.getBoundingClientRect();
254
+ const geometry = { x: rect.x, y: rect.y, width: rect.width, height: rect.height, top: rect.top, right: rect.right, bottom: rect.bottom, left: rect.left };
255
+ return { ok: true, summary: "Target geometry read.", details: { geometry } };
256
+ }
257
+ case "readvalue": {
258
+ if (!(target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement)) return { ok: false, summary: "Target does not hold a form value." };
259
+ return { ok: true, summary: "Form value read.", details: { value: target.value } };
260
+ }
261
+ case "readtext": {
262
+ if (!target) return { ok: false, summary: "Read target is no longer available." };
263
+ const text = target.textContent ?? "";
264
+ return { ok: true, summary: `Read ${text.length} characters of text.`, details: { text } };
265
+ }
266
+ case "readhtml": {
267
+ if (!target) return { ok: false, summary: "Read target is no longer available." };
268
+ return { ok: true, summary: "Target markup read.", details: { html: target.outerHTML } };
269
+ }
270
+ case "countelements": {
271
+ const count = document.querySelectorAll(step.target ?? "").length;
272
+ return { ok: true, summary: `Selector matches ${count} element${count === 1 ? "" : "s"}.`, details: { count } };
273
+ }
274
+ case "readtable": {
275
+ if (!(target instanceof HTMLTableElement)) return { ok: false, summary: "Read target is not a table element." };
276
+ const rows = [...target.querySelectorAll("tr")].map((row) => [...row.querySelectorAll("th, td")].map((cell) => cell.textContent?.trim() ?? ""));
277
+ const headers = rows[0] ?? [];
278
+ const body = rows.slice(1);
279
+ 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
+ }
281
+ case "readlinks": {
282
+ const links = [...document.querySelectorAll("a[href]")].map((element) => ({ text: element.textContent?.trim() ?? "", href: element.getAttribute("href") ?? "" }));
283
+ return { ok: true, summary: `Read ${links.length} link${links.length === 1 ? "" : "s"}.`, details: { links } };
284
+ }
285
+ case "readimages": {
286
+ const images = [...document.querySelectorAll("img")].map((element) => ({ src: element.getAttribute("src") ?? "", alt: element.getAttribute("alt") ?? "" }));
287
+ return { ok: true, summary: `Read ${images.length} image${images.length === 1 ? "" : "s"}.`, details: { images } };
288
+ }
289
+ case "readmeta": {
290
+ const meta = [...document.querySelectorAll("meta")].map((element) => ({ name: element.getAttribute("name") ?? "", property: element.getAttribute("property") ?? "", content: element.getAttribute("content") ?? "" }));
291
+ return { ok: true, summary: `Read ${meta.length} meta entr${meta.length === 1 ? "y" : "ies"}.`, details: { meta } };
292
+ }
293
+ case "readforms": {
294
+ const forms = formstate();
295
+ return { ok: true, summary: `Read ${forms.length} form control${forms.length === 1 ? "" : "s"}.`, details: { forms } };
296
+ }
297
+ case "readstorage": {
298
+ try {
299
+ if (step.value) {
300
+ const value = localStorage.getItem(step.value);
301
+ return { ok: true, summary: `Read local storage entry ${step.value}.`, details: { value } };
302
+ }
303
+ const entries = {};
304
+ for (let index = 0; index < localStorage.length; index += 1) {
305
+ const key = localStorage.key(index);
306
+ if (key !== null) entries[key] = localStorage.getItem(key);
307
+ }
308
+ return { ok: true, summary: `Read ${Object.keys(entries).length} local storage entr${Object.keys(entries).length === 1 ? "y" : "ies"}.`, details: { entries } };
309
+ } catch (error) {
310
+ return { ok: false, summary: `Local storage refused the read: ${error instanceof Error ? error.message : String(error)}` };
311
+ }
312
+ }
313
+ case "waitfor": {
314
+ const selector2 = step.target ?? "";
315
+ const timeout = typeof options.timeout === "number" ? options.timeout : 0;
316
+ return poll(() => Boolean(document.querySelector(selector2)), `Selector ${selector2}`, timeout);
317
+ }
318
+ case "waittext": {
319
+ const text = step.value ?? "";
320
+ const timeout = typeof options.timeout === "number" ? options.timeout : 0;
321
+ return poll(() => (document.body?.innerText ?? "").includes(text), `Text ${text}`, timeout);
322
+ }
323
+ default:
324
+ return { ok: false, summary: "Unsupported page read." };
325
+ }
326
+ }
327
+
3
328
  // extension/pagebridge.ts
4
- function bounded(value, length = 180) {
5
- return value.replace(/\s+/g, " ").trim().slice(0, length);
329
+ function clean(value) {
330
+ return value.replace(/\s+/g, " ").trim();
6
331
  }
7
332
  function label(element) {
8
333
  const aria = element.getAttribute("aria-label");
9
334
  const labelledby = element.getAttribute("aria-labelledby");
10
335
  const linked = labelledby ? document.getElementById(labelledby)?.textContent : "";
11
- return bounded(aria || linked || element.getAttribute("title") || element.textContent || "");
336
+ return clean(aria || linked || element.getAttribute("title") || element.textContent || "");
12
337
  }
13
338
  function selector(element) {
14
339
  if (element.id) return `#${CSS.escape(element.id)}`;
@@ -22,6 +347,15 @@
22
347
  const peers = [...parent.children].filter((node) => node.tagName === element.tagName);
23
348
  return `${tag}:nth-of-type(${peers.indexOf(element) + 1})`;
24
349
  }
350
+ function stepoptions(step) {
351
+ if (!step.options) return {};
352
+ try {
353
+ const parsed = JSON.parse(step.options);
354
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
355
+ } catch {
356
+ return {};
357
+ }
358
+ }
25
359
  var previewid = "devthinktargetpreview";
26
360
  function clearpreview() {
27
361
  document.getElementById(previewid)?.remove();
@@ -42,28 +376,29 @@
42
376
  return { ok: true, summary: `Previewing ${label(target) || target.tagName.toLowerCase()} for five seconds.` };
43
377
  }
44
378
  function capturesnapshot() {
45
- const candidates = [...document.querySelectorAll("a[href], button, input, textarea, select, [role=button], [role=link], [role=combobox], [role=option], details, summary")].slice(0, 80);
379
+ 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")];
46
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);
47
- const forms = [...document.querySelectorAll("input, textarea, select")].slice(0, 40).map((element) => ({
381
+ const forms = [...document.querySelectorAll("input, textarea, select")].map((element) => ({
48
382
  label: label(element),
49
383
  type: element.getAttribute("type") || element.tagName.toLowerCase(),
50
384
  name: element.getAttribute("name") || "",
51
- ...element instanceof HTMLSelectElement ? { options: [...element.options].slice(0, 12).map((option) => bounded(option.textContent || option.value, 60)) } : {}
385
+ ...element instanceof HTMLSelectElement ? { options: [...element.options].map((option) => clean(option.textContent || option.value)) } : {}
52
386
  }));
53
- const text = bounded(document.body?.innerText || "", 2e3);
54
- return { url: location.href, title: bounded(document.title, 180), textpreview: text, textlength: document.body?.innerText.length ?? 0, forms, interactive, capturedat: Date.now() };
387
+ 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() };
55
389
  }
56
390
  function extractcontent(targetselector) {
57
391
  if (!targetselector) {
58
- const links = [...document.querySelectorAll("a[href]")].slice(0, 25).map((element) => {
392
+ const links = [...document.querySelectorAll("a[href]")].map((element) => {
59
393
  const href = element instanceof HTMLAnchorElement ? element.getAttribute("href") ?? "" : "";
60
- return `${bounded(element.textContent || "")} (${bounded(href, 120)})`;
394
+ return { text: element.textContent?.trim() ?? "", href };
61
395
  });
62
- return { ok: true, summary: `Extracted ${links.length} bounded link entries: ${links.join("; ").slice(0, 180) || "no links present"}.` };
396
+ return { ok: true, summary: `Extracted ${links.length} link entries.`, details: { links } };
63
397
  }
64
398
  const target = document.querySelector(targetselector);
65
399
  if (!target) return { ok: false, summary: "Extraction target is no longer available." };
66
- return { ok: true, summary: `Extracted: ${bounded(target.textContent || "", 180) || "empty target"}.` };
400
+ const text = target.textContent ?? "";
401
+ return { ok: true, summary: `Extracted ${text.length} characters of content.`, details: { text } };
67
402
  }
68
403
  function scrolltarget(target) {
69
404
  target.scrollIntoView({ block: "center", inline: "nearest", behavior: "auto" });
@@ -83,15 +418,17 @@
83
418
  target.value = option.value;
84
419
  target.dispatchEvent(new Event("input", { bubbles: true }));
85
420
  target.dispatchEvent(new Event("change", { bubbles: true }));
86
- return { ok: true, summary: `Selected ${bounded(option.textContent || option.value, 60)}.` };
421
+ return { ok: true, summary: `Selected ${clean(option.textContent || option.value)}.` };
87
422
  }
423
+ var readkinds = /* @__PURE__ */ new Set(["readattribute", "readstyle", "readgeometry", "readvalue", "readtext", "readhtml", "countelements", "readtable", "readlinks", "readimages", "readmeta", "readforms", "readstorage", "waitfor", "waittext", "highlight"]);
424
+ var mutatingkinds = /* @__PURE__ */ new Set(["presskey", "clickdeep", "rightclick", "doubleclick", "drag", "drop", "upload", "clear", "check", "uncheck", "toggle", "submit", "setattribute", "removeattribute", "writestorage", "evaluate", "fullscreen"]);
88
425
  function performstep(step, expectedorigin) {
89
426
  if (location.origin !== expectedorigin) return { ok: false, summary: "Page origin changed before action." };
90
427
  if (step.kind === "observe") return { ok: true, summary: "Observation completed." };
91
428
  if (step.kind === "wait") {
92
429
  const requested = step.value ? Number.parseInt(step.value, 10) : 250;
93
- const duration = Number.isFinite(requested) && requested > 0 ? Math.min(requested, 1e4) : 0;
94
- return new Promise((resolve) => window.setTimeout(() => resolve({ ok: true, summary: `Bounded wait of ${duration} milliseconds completed.` }), duration));
430
+ const duration = Number.isFinite(requested) && requested > 0 ? requested : 0;
431
+ return new Promise((resolve) => window.setTimeout(() => resolve({ ok: true, summary: `Reviewed wait of ${duration} milliseconds completed.` }), duration));
95
432
  }
96
433
  if (step.kind === "extract") return extractcontent(step.target);
97
434
  if (step.kind === "navigate") {
@@ -99,9 +436,41 @@
99
436
  location.assign(step.value);
100
437
  return { ok: true, summary: "Navigation request sent." };
101
438
  }
439
+ if (step.kind === "reload") {
440
+ location.reload();
441
+ return { ok: true, summary: "Page reload requested." };
442
+ }
443
+ if (step.kind === "back") {
444
+ history.back();
445
+ return { ok: true, summary: "History back requested." };
446
+ }
447
+ if (step.kind === "forward") {
448
+ history.forward();
449
+ return { ok: true, summary: "History forward requested." };
450
+ }
451
+ if (step.kind === "scrollpage") {
452
+ const options = stepoptions(step);
453
+ window.scrollBy({ left: typeof options.x === "number" ? options.x : 0, top: typeof options.y === "number" ? options.y : 600, behavior: "auto" });
454
+ return { ok: true, summary: "Window scrolled by the reviewed amounts." };
455
+ }
456
+ if (step.kind === "scrollend") {
457
+ window.scrollTo(0, document.documentElement.scrollHeight);
458
+ return { ok: true, summary: "Window scrolled to the page end." };
459
+ }
460
+ if (step.kind === "scrolltop") {
461
+ window.scrollTo(0, 0);
462
+ return { ok: true, summary: "Window scrolled to the page top." };
463
+ }
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);
102
466
  if (!step.target) return { ok: false, summary: "Action target is absent." };
103
467
  const target = document.querySelector(step.target);
104
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
+ }
105
474
  if (step.kind === "focus") {
106
475
  target.focus();
107
476
  return { ok: true, summary: "Target focused." };