@page-agent/page-controller 1.6.3 → 1.7.1

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.
@@ -20,7 +20,10 @@ export declare interface BrowserState {
20
20
  declare function cleanUpHighlights(): void;
21
21
 
22
22
  /**
23
- * Simulate a click on the element
23
+ * Simulate a full click following W3C Pointer Events + UI Events spec order:
24
+ * pointerover/enter → mouseover/enter → pointerdown → mousedown → [focus] →
25
+ * pointerup → mouseup → click
26
+ *
24
27
  * @private Internal method, subject to change at any time.
25
28
  */
26
29
  export declare function clickElement(element: HTMLElement): Promise<void>;
@@ -45,6 +48,11 @@ declare interface DomConfig {
45
48
  includeAttributes?: string[];
46
49
  highlightOpacity?: number;
47
50
  highlightLabelOpacity?: number;
51
+ /**
52
+ * Preserve semantic landmark tags in dehydrated output even if not interactive
53
+ * @note maybe confusing for LLM combining with page scrolling, use with caution
54
+ **/
55
+ keepSemanticTags?: boolean;
48
56
  }
49
57
 
50
58
  declare type DomNode = TextDomNode | ElementDomNode | InteractiveElementDomNode;
@@ -92,7 +100,7 @@ declare interface FlatDomTree {
92
100
  *
93
101
  * @todo 数据脱敏过滤器
94
102
  */
95
- declare function flatTreeToString(flatTree: FlatDomTree, includeAttributes?: string[]): string;
103
+ declare function flatTreeToString(flatTree: FlatDomTree, includeAttributes?: string[], keepSemanticTags?: boolean): string;
96
104
 
97
105
  declare const getAllTextTillNextClickableElement: (node: TreeNode, maxDepth?: number) => string;
98
106
 
@@ -253,20 +261,14 @@ export declare interface PageControllerConfig extends dom.DomConfig {
253
261
 
254
262
  declare function resolveViewportExpansion(viewportExpansion?: number): number;
255
263
 
256
- /**
257
- * @private Internal method, subject to change at any time.
258
- */
259
- export declare function scrollHorizontally(right: boolean, scroll_amount: number, element?: HTMLElement | null): Promise<string>;
264
+ export declare function scrollHorizontally(scroll_amount: number, element?: HTMLElement | null): Promise<string>;
260
265
 
261
266
  /**
262
267
  * @private Internal method, subject to change at any time.
263
268
  */
264
269
  export declare function scrollIntoViewIfNeeded(element: Element): Promise<void>;
265
270
 
266
- /**
267
- * @private Internal method, subject to change at any time.
268
- */
269
- export declare function scrollVertically(down: boolean, scroll_amount: number, element?: HTMLElement | null): Promise<string>;
271
+ export declare function scrollVertically(scroll_amount: number, element?: HTMLElement | null): Promise<string>;
270
272
 
271
273
  /**
272
274
  * @todo browser-use version is very complex and supports menu tags, need to follow up
@@ -192,8 +192,9 @@ const cursorStyles = {
192
192
  cursorRipple,
193
193
  clicking
194
194
  };
195
- const _SimulatorMask = class _SimulatorMask {
195
+ const _SimulatorMask = class _SimulatorMask extends EventTarget {
196
196
  constructor() {
197
+ super();
197
198
  __privateAdd(this, _SimulatorMask_instances);
198
199
  __publicField(this, "shown", false);
199
200
  __publicField(this, "wrapper", document.createElement("div"));
@@ -249,12 +250,28 @@ const _SimulatorMask = class _SimulatorMask {
249
250
  __privateMethod(this, _SimulatorMask_instances, createCursor_fn).call(this);
250
251
  document.body.appendChild(this.wrapper);
251
252
  __privateMethod(this, _SimulatorMask_instances, moveCursorToTarget_fn).call(this);
252
- window.addEventListener("PageAgent::MovePointerTo", (event) => {
253
+ const movePointerToListener = /* @__PURE__ */ __name((event) => {
253
254
  const { x, y } = event.detail;
254
255
  this.setCursorPosition(x, y);
255
- });
256
- window.addEventListener("PageAgent::ClickPointer", (event) => {
256
+ }, "movePointerToListener");
257
+ const clickPointerListener = /* @__PURE__ */ __name(() => {
257
258
  this.triggerClickAnimation();
259
+ }, "clickPointerListener");
260
+ const enablePassThroughListener = /* @__PURE__ */ __name(() => {
261
+ this.wrapper.style.pointerEvents = "none";
262
+ }, "enablePassThroughListener");
263
+ const disablePassThroughListener = /* @__PURE__ */ __name(() => {
264
+ this.wrapper.style.pointerEvents = "auto";
265
+ }, "disablePassThroughListener");
266
+ window.addEventListener("PageAgent::MovePointerTo", movePointerToListener);
267
+ window.addEventListener("PageAgent::ClickPointer", clickPointerListener);
268
+ window.addEventListener("PageAgent::EnablePassThrough", enablePassThroughListener);
269
+ window.addEventListener("PageAgent::DisablePassThrough", disablePassThroughListener);
270
+ this.addEventListener("dispose", () => {
271
+ window.removeEventListener("PageAgent::MovePointerTo", movePointerToListener);
272
+ window.removeEventListener("PageAgent::ClickPointer", clickPointerListener);
273
+ window.removeEventListener("PageAgent::EnablePassThrough", enablePassThroughListener);
274
+ window.removeEventListener("PageAgent::DisablePassThrough", disablePassThroughListener);
258
275
  });
259
276
  }
260
277
  setCursorPosition(x, y) {
@@ -290,8 +307,10 @@ const _SimulatorMask = class _SimulatorMask {
290
307
  }, 800);
291
308
  }
292
309
  dispose() {
310
+ console.log("dispose SimulatorMask");
293
311
  this.motion?.dispose();
294
312
  this.wrapper.remove();
313
+ this.dispatchEvent(new Event("dispose"));
295
314
  }
296
315
  };
297
316
  _cursor = new WeakMap();
@@ -341,4 +360,4 @@ let SimulatorMask = _SimulatorMask;
341
360
  export {
342
361
  SimulatorMask
343
362
  };
344
- //# sourceMappingURL=SimulatorMask-BHnQ6LmL.js.map
363
+ //# sourceMappingURL=SimulatorMask-CU7szDjy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SimulatorMask-CU7szDjy.js","sources":["../../src/mask/checkDarkMode.ts","../../src/mask/SimulatorMask.ts"],"sourcesContent":["/**\n * Checks for common dark mode CSS classes on the html or body elements.\n * @returns {boolean} - True if a common dark mode class is found.\n */\nfunction hasDarkModeClass() {\n\tconst DEFAULT_DARK_MODE_CLASSES = ['dark', 'dark-mode', 'theme-dark', 'night', 'night-mode']\n\n\tconst htmlElement = document.documentElement\n\tconst bodyElement = document.body || document.documentElement // can be null in some cases\n\n\t// Check class names on <html> and <body>\n\tfor (const className of DEFAULT_DARK_MODE_CLASSES) {\n\t\tif (htmlElement.classList.contains(className) || bodyElement?.classList.contains(className)) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\t// Some sites use data attributes\n\tconst darkThemeAttribute = htmlElement.getAttribute('data-theme')\n\tif (darkThemeAttribute?.toLowerCase().includes('dark')) {\n\t\treturn true\n\t}\n\n\treturn false\n}\n\n/**\n * Parses an RGB or RGBA color string and returns an object with r, g, b properties.\n * @param {string} colorString - e.g., \"rgb(34, 34, 34)\" or \"rgba(0, 0, 0, 0.5)\"\n * @returns {{r: number, g: number, b: number}|null}\n */\nfunction parseRgbColor(colorString: string) {\n\tconst rgbMatch = /rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)/.exec(colorString)\n\tif (!rgbMatch) {\n\t\treturn null // Not a valid rgb/rgba string\n\t}\n\treturn {\n\t\tr: parseInt(rgbMatch[1]),\n\t\tg: parseInt(rgbMatch[2]),\n\t\tb: parseInt(rgbMatch[3]),\n\t}\n}\n\n/**\n * Determines if a color is \"dark\" based on its calculated luminance.\n * @param {string} colorString - The CSS color string (e.g., \"rgb(50, 50, 50)\").\n * @param {number} threshold - A value between 0 and 255. Colors with luminance below this will be considered dark. Default is 128.\n * @returns {boolean} - True if the color is considered dark.\n */\nfunction isColorDark(colorString: string, threshold = 128) {\n\tif (!colorString || colorString === 'transparent' || colorString.startsWith('rgba(0, 0, 0, 0)')) {\n\t\treturn false // Transparent is not dark\n\t}\n\n\tconst rgb = parseRgbColor(colorString)\n\tif (!rgb) {\n\t\treturn false // Could not parse color\n\t}\n\n\t// Calculate perceived luminance using the standard formula\n\tconst luminance = 0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b\n\n\treturn luminance < threshold\n}\n\n/**\n * Checks the background color of the body element to determine if the page is dark.\n * @returns {boolean}\n */\nfunction isBackgroundDark() {\n\t// We check both <html> and <body> because some pages set the color on <html>\n\tconst htmlStyle = window.getComputedStyle(document.documentElement)\n\tconst bodyStyle = window.getComputedStyle(document.body || document.documentElement)\n\n\t// Get background colors\n\tconst htmlBgColor = htmlStyle.backgroundColor\n\tconst bodyBgColor = bodyStyle.backgroundColor\n\n\t// The body's background might be transparent, in which case we should\n\t// fall back to the html element's background.\n\tif (isColorDark(bodyBgColor)) {\n\t\treturn true\n\t} else if (bodyBgColor === 'transparent' || bodyBgColor.startsWith('rgba(0, 0, 0, 0)')) {\n\t\treturn isColorDark(htmlBgColor)\n\t}\n\n\treturn false\n}\n\n/**\n * A comprehensive function to determine if the page is currently in a dark theme.\n * It combines class checking and background color analysis.\n * @returns {boolean} - True if the page is likely dark.\n */\nexport function isPageDark() {\n\ttry {\n\t\t// Strategy 1: Check for common dark mode classes\n\t\tif (hasDarkModeClass()) {\n\t\t\treturn true\n\t\t}\n\n\t\t// Strategy 2: Analyze the computed background color\n\t\tif (isBackgroundDark()) {\n\t\t\treturn true\n\t\t}\n\n\t\t// @TODO add more checks here, e.g., analyzing text color,\n\t\t// or checking the background of major layout elements like <main> or #app.\n\n\t\treturn false\n\t} catch (error) {\n\t\tconsole.warn('Error determining if page is dark:', error)\n\t\treturn false\n\t}\n}\n","import { Motion } from 'ai-motion'\n\nimport { isPageDark } from './checkDarkMode'\n\nimport styles from './SimulatorMask.module.css'\nimport cursorStyles from './cursor.module.css'\n\nexport class SimulatorMask extends EventTarget {\n\tshown: boolean = false\n\twrapper = document.createElement('div')\n\tmotion: Motion | null = null\n\n\t#cursor = document.createElement('div')\n\n\t#currentCursorX = 0\n\t#currentCursorY = 0\n\n\t#targetCursorX = 0\n\t#targetCursorY = 0\n\n\tconstructor() {\n\t\tsuper()\n\n\t\tthis.wrapper.id = 'page-agent-runtime_simulator-mask'\n\t\tthis.wrapper.className = styles.wrapper\n\t\tthis.wrapper.setAttribute('data-browser-use-ignore', 'true')\n\t\tthis.wrapper.setAttribute('data-page-agent-ignore', 'true')\n\n\t\ttry {\n\t\t\tconst motion = new Motion({\n\t\t\t\tmode: isPageDark() ? 'dark' : 'light',\n\t\t\t\tstyles: { position: 'absolute', inset: '0' },\n\t\t\t})\n\t\t\tthis.motion = motion\n\t\t\tthis.wrapper.appendChild(motion.element)\n\t\t\tmotion.autoResize(this.wrapper)\n\t\t} catch (e) {\n\t\t\tconsole.warn('[SimulatorMask] Motion overlay unavailable:', e)\n\t\t}\n\n\t\t// Capture all mouse, keyboard, and wheel events\n\t\tthis.wrapper.addEventListener('click', (e) => {\n\t\t\te.stopPropagation()\n\t\t\te.preventDefault()\n\t\t})\n\t\tthis.wrapper.addEventListener('mousedown', (e) => {\n\t\t\te.stopPropagation()\n\t\t\te.preventDefault()\n\t\t})\n\t\tthis.wrapper.addEventListener('mouseup', (e) => {\n\t\t\te.stopPropagation()\n\t\t\te.preventDefault()\n\t\t})\n\t\tthis.wrapper.addEventListener('mousemove', (e) => {\n\t\t\te.stopPropagation()\n\t\t\te.preventDefault()\n\t\t})\n\t\tthis.wrapper.addEventListener('wheel', (e) => {\n\t\t\te.stopPropagation()\n\t\t\te.preventDefault()\n\t\t})\n\t\tthis.wrapper.addEventListener('keydown', (e) => {\n\t\t\te.stopPropagation()\n\t\t\te.preventDefault()\n\t\t})\n\t\tthis.wrapper.addEventListener('keyup', (e) => {\n\t\t\te.stopPropagation()\n\t\t\te.preventDefault()\n\t\t})\n\n\t\t// Create AI cursor\n\t\tthis.#createCursor()\n\t\t// this.show()\n\n\t\tdocument.body.appendChild(this.wrapper)\n\n\t\tthis.#moveCursorToTarget()\n\n\t\t// global events\n\t\t// @note Mask should be isolated from the rest of the code.\n\t\t// Global events are easier to manage and cleanup.\n\n\t\tconst movePointerToListener = (event: Event) => {\n\t\t\tconst { x, y } = (event as CustomEvent).detail\n\t\t\tthis.setCursorPosition(x, y)\n\t\t}\n\t\tconst clickPointerListener = () => {\n\t\t\tthis.triggerClickAnimation()\n\t\t}\n\t\tconst enablePassThroughListener = () => {\n\t\t\tthis.wrapper.style.pointerEvents = 'none'\n\t\t}\n\t\tconst disablePassThroughListener = () => {\n\t\t\tthis.wrapper.style.pointerEvents = 'auto'\n\t\t}\n\n\t\twindow.addEventListener('PageAgent::MovePointerTo', movePointerToListener)\n\t\twindow.addEventListener('PageAgent::ClickPointer', clickPointerListener)\n\t\twindow.addEventListener('PageAgent::EnablePassThrough', enablePassThroughListener)\n\t\twindow.addEventListener('PageAgent::DisablePassThrough', disablePassThroughListener)\n\n\t\tthis.addEventListener('dispose', () => {\n\t\t\twindow.removeEventListener('PageAgent::MovePointerTo', movePointerToListener)\n\t\t\twindow.removeEventListener('PageAgent::ClickPointer', clickPointerListener)\n\t\t\twindow.removeEventListener('PageAgent::EnablePassThrough', enablePassThroughListener)\n\t\t\twindow.removeEventListener('PageAgent::DisablePassThrough', disablePassThroughListener)\n\t\t})\n\t}\n\n\t#createCursor() {\n\t\tthis.#cursor.className = cursorStyles.cursor\n\n\t\t// Create ripple effect container\n\t\tconst rippleContainer = document.createElement('div')\n\t\trippleContainer.className = cursorStyles.cursorRipple\n\t\tthis.#cursor.appendChild(rippleContainer)\n\n\t\t// Create filling layer\n\t\tconst fillingLayer = document.createElement('div')\n\t\tfillingLayer.className = cursorStyles.cursorFilling\n\t\tthis.#cursor.appendChild(fillingLayer)\n\n\t\t// Create border layer\n\t\tconst borderLayer = document.createElement('div')\n\t\tborderLayer.className = cursorStyles.cursorBorder\n\t\tthis.#cursor.appendChild(borderLayer)\n\n\t\tthis.wrapper.appendChild(this.#cursor)\n\t}\n\n\t#moveCursorToTarget() {\n\t\tconst newX = this.#currentCursorX + (this.#targetCursorX - this.#currentCursorX) * 0.2\n\t\tconst newY = this.#currentCursorY + (this.#targetCursorY - this.#currentCursorY) * 0.2\n\n\t\tconst xDistance = Math.abs(newX - this.#targetCursorX)\n\t\tif (xDistance > 0) {\n\t\t\tif (xDistance < 2) {\n\t\t\t\tthis.#currentCursorX = this.#targetCursorX\n\t\t\t} else {\n\t\t\t\tthis.#currentCursorX = newX\n\t\t\t}\n\t\t\tthis.#cursor.style.left = `${this.#currentCursorX}px`\n\t\t}\n\n\t\tconst yDistance = Math.abs(newY - this.#targetCursorY)\n\t\tif (yDistance > 0) {\n\t\t\tif (yDistance < 2) {\n\t\t\t\tthis.#currentCursorY = this.#targetCursorY\n\t\t\t} else {\n\t\t\t\tthis.#currentCursorY = newY\n\t\t\t}\n\t\t\tthis.#cursor.style.top = `${this.#currentCursorY}px`\n\t\t}\n\n\t\trequestAnimationFrame(() => this.#moveCursorToTarget())\n\t}\n\n\tsetCursorPosition(x: number, y: number) {\n\t\tthis.#targetCursorX = x\n\t\tthis.#targetCursorY = y\n\t}\n\n\ttriggerClickAnimation() {\n\t\tthis.#cursor.classList.remove(cursorStyles.clicking)\n\t\t// Force reflow to restart animation\n\t\tvoid this.#cursor.offsetHeight\n\t\tthis.#cursor.classList.add(cursorStyles.clicking)\n\t}\n\n\tshow() {\n\t\tif (this.shown) return\n\n\t\tthis.shown = true\n\t\tthis.motion?.start()\n\t\tthis.motion?.fadeIn()\n\n\t\tthis.wrapper.classList.add(styles.visible)\n\n\t\t// Initialize cursor position\n\t\tthis.#currentCursorX = window.innerWidth / 2\n\t\tthis.#currentCursorY = window.innerHeight / 2\n\t\tthis.#targetCursorX = this.#currentCursorX\n\t\tthis.#targetCursorY = this.#currentCursorY\n\t\tthis.#cursor.style.left = `${this.#currentCursorX}px`\n\t\tthis.#cursor.style.top = `${this.#currentCursorY}px`\n\t}\n\n\thide() {\n\t\tif (!this.shown) return\n\n\t\tthis.shown = false\n\t\tthis.motion?.fadeOut()\n\t\tthis.motion?.pause()\n\n\t\tthis.#cursor.classList.remove(cursorStyles.clicking)\n\n\t\tsetTimeout(() => {\n\t\t\tthis.wrapper.classList.remove(styles.visible)\n\t\t}, 800) // Match the animation duration\n\t}\n\n\tdispose() {\n\t\tconsole.log('dispose SimulatorMask')\n\t\tthis.motion?.dispose()\n\t\tthis.wrapper.remove()\n\t\tthis.dispatchEvent(new Event('dispose'))\n\t}\n}\n"],"names":[],"mappings":"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"}
@@ -35,15 +35,28 @@ async function waitFor(seconds) {
35
35
  await new Promise((resolve) => setTimeout(resolve, seconds * 1e3));
36
36
  }
37
37
  __name(waitFor, "waitFor");
38
- async function movePointerToElement(element) {
39
- const rect = element.getBoundingClientRect();
38
+ async function movePointerToElement(element, x, y) {
40
39
  const offset = getIframeOffset(element);
41
- const x = rect.left + rect.width / 2 + offset.x;
42
- const y = rect.top + rect.height / 2 + offset.y;
43
- window.dispatchEvent(new CustomEvent("PageAgent::MovePointerTo", { detail: { x, y } }));
40
+ window.dispatchEvent(
41
+ new CustomEvent("PageAgent::MovePointerTo", {
42
+ detail: { x: x + offset.x, y: y + offset.y }
43
+ })
44
+ );
44
45
  await waitFor(0.3);
45
46
  }
46
47
  __name(movePointerToElement, "movePointerToElement");
48
+ async function clickPointer() {
49
+ window.dispatchEvent(new CustomEvent("PageAgent::ClickPointer"));
50
+ }
51
+ __name(clickPointer, "clickPointer");
52
+ async function enablePassThrough() {
53
+ window.dispatchEvent(new CustomEvent("PageAgent::EnablePassThrough"));
54
+ }
55
+ __name(enablePassThrough, "enablePassThrough");
56
+ async function disablePassThrough() {
57
+ window.dispatchEvent(new CustomEvent("PageAgent::DisablePassThrough"));
58
+ }
59
+ __name(disablePassThrough, "disablePassThrough");
47
60
  function getElementByIndex(selectorMap, index) {
48
61
  const interactiveNode = selectorMap.get(index);
49
62
  if (!interactiveNode) {
@@ -62,13 +75,11 @@ __name(getElementByIndex, "getElementByIndex");
62
75
  let lastClickedElement = null;
63
76
  function blurLastClickedElement() {
64
77
  if (lastClickedElement) {
78
+ lastClickedElement.dispatchEvent(new PointerEvent("pointerout", { bubbles: true }));
79
+ lastClickedElement.dispatchEvent(new PointerEvent("pointerleave", { bubbles: false }));
80
+ lastClickedElement.dispatchEvent(new MouseEvent("mouseout", { bubbles: true }));
81
+ lastClickedElement.dispatchEvent(new MouseEvent("mouseleave", { bubbles: false }));
65
82
  lastClickedElement.blur();
66
- lastClickedElement.dispatchEvent(
67
- new MouseEvent("mouseout", { bubbles: true, cancelable: true })
68
- );
69
- lastClickedElement.dispatchEvent(
70
- new MouseEvent("mouseleave", { bubbles: false, cancelable: true })
71
- );
72
83
  lastClickedElement = null;
73
84
  }
74
85
  }
@@ -79,15 +90,35 @@ async function clickElement(element) {
79
90
  await scrollIntoViewIfNeeded(element);
80
91
  const frame = element.ownerDocument.defaultView?.frameElement;
81
92
  if (frame) await scrollIntoViewIfNeeded(frame);
82
- await movePointerToElement(element);
83
- window.dispatchEvent(new CustomEvent("PageAgent::ClickPointer"));
93
+ const rect = element.getBoundingClientRect();
94
+ const x = rect.left + rect.width / 2;
95
+ const y = rect.top + rect.height / 2;
96
+ await movePointerToElement(element, x, y);
97
+ await clickPointer();
84
98
  await waitFor(0.1);
85
- element.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true, cancelable: true }));
86
- element.dispatchEvent(new MouseEvent("mouseover", { bubbles: true, cancelable: true }));
87
- element.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, cancelable: true }));
88
- element.focus();
89
- element.dispatchEvent(new MouseEvent("mouseup", { bubbles: true, cancelable: true }));
90
- element.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
99
+ const doc = element.ownerDocument;
100
+ await enablePassThrough();
101
+ const hitTarget = doc.elementFromPoint(x, y);
102
+ await disablePassThrough();
103
+ const target = hitTarget instanceof HTMLElement && element.contains(hitTarget) ? hitTarget : element;
104
+ const pointerOpts = {
105
+ bubbles: true,
106
+ cancelable: true,
107
+ clientX: x,
108
+ clientY: y,
109
+ pointerType: "mouse"
110
+ };
111
+ const mouseOpts = { bubbles: true, cancelable: true, clientX: x, clientY: y, button: 0 };
112
+ target.dispatchEvent(new PointerEvent("pointerover", pointerOpts));
113
+ target.dispatchEvent(new PointerEvent("pointerenter", { ...pointerOpts, bubbles: false }));
114
+ target.dispatchEvent(new MouseEvent("mouseover", mouseOpts));
115
+ target.dispatchEvent(new MouseEvent("mouseenter", { ...mouseOpts, bubbles: false }));
116
+ target.dispatchEvent(new PointerEvent("pointerdown", pointerOpts));
117
+ target.dispatchEvent(new MouseEvent("mousedown", mouseOpts));
118
+ element.focus({ preventScroll: true });
119
+ target.dispatchEvent(new PointerEvent("pointerup", pointerOpts));
120
+ target.dispatchEvent(new MouseEvent("mouseup", mouseOpts));
121
+ target.click();
91
122
  await waitFor(0.2);
92
123
  }
93
124
  __name(clickElement, "clickElement");
@@ -177,7 +208,7 @@ async function scrollIntoViewIfNeeded(element) {
177
208
  }
178
209
  }
179
210
  __name(scrollIntoViewIfNeeded, "scrollIntoViewIfNeeded");
180
- async function scrollVertically(down, scroll_amount, element) {
211
+ async function scrollVertically(scroll_amount, element) {
181
212
  if (element) {
182
213
  const targetElement = element;
183
214
  let currentElement = targetElement;
@@ -188,7 +219,7 @@ async function scrollVertically(down, scroll_amount, element) {
188
219
  const dy2 = scroll_amount;
189
220
  while (currentElement && attempts < 10) {
190
221
  const computedStyle = window.getComputedStyle(currentElement);
191
- const hasScrollableY = /(auto|scroll|overlay)/.test(computedStyle.overflowY);
222
+ const hasScrollableY = /(auto|scroll|overlay)/.test(computedStyle.overflowY) || computedStyle.scrollbarWidth && computedStyle.scrollbarWidth !== "auto" || computedStyle.scrollbarGutter && computedStyle.scrollbarGutter !== "auto";
192
223
  const canScrollVertically = currentElement.scrollHeight > currentElement.clientHeight;
193
224
  if (hasScrollableY && canScrollVertically) {
194
225
  const beforeScroll = currentElement.scrollTop;
@@ -242,6 +273,8 @@ async function scrollVertically(down, scroll_amount, element) {
242
273
  if (reachedTop) return `✅ Scrolled page by ${scrolled}px. Reached the top of the page.`;
243
274
  return `✅ Scrolled page by ${scrolled}px.`;
244
275
  } else {
276
+ const warningMsg = `The document is not scrollable. Falling back to container scroll.`;
277
+ console.log(`[PageController] ${warningMsg}`);
245
278
  const scrollBefore = el.scrollTop;
246
279
  const scrollMax = el.scrollHeight - el.clientHeight;
247
280
  el.scrollBy({ top: dy, behavior: "smooth" });
@@ -249,19 +282,19 @@ async function scrollVertically(down, scroll_amount, element) {
249
282
  const scrollAfter = el.scrollTop;
250
283
  const scrolled = scrollAfter - scrollBefore;
251
284
  if (Math.abs(scrolled) < 1) {
252
- return dy > 0 ? `⚠️ Already at the bottom of container (${el.tagName}), cannot scroll down further.` : `⚠️ Already at the top of container (${el.tagName}), cannot scroll up further.`;
285
+ return dy > 0 ? `⚠️ ${warningMsg} Already at the bottom of container (${el.tagName}), cannot scroll down further.` : `⚠️ ${warningMsg} Already at the top of container (${el.tagName}), cannot scroll up further.`;
253
286
  }
254
287
  const reachedBottom = dy > 0 && scrollAfter >= scrollMax - 1;
255
288
  const reachedTop = dy < 0 && scrollAfter <= 1;
256
289
  if (reachedBottom)
257
- return `✅ Scrolled container (${el.tagName}) by ${scrolled}px. Reached the bottom.`;
290
+ return `✅ ${warningMsg} Scrolled container (${el.tagName}) by ${scrolled}px. Reached the bottom.`;
258
291
  if (reachedTop)
259
- return `✅ Scrolled container (${el.tagName}) by ${scrolled}px. Reached the top.`;
260
- return `✅ Scrolled container (${el.tagName}) by ${scrolled}px.`;
292
+ return `✅ ${warningMsg} Scrolled container (${el.tagName}) by ${scrolled}px. Reached the top.`;
293
+ return `✅ ${warningMsg} Scrolled container (${el.tagName}) by ${scrolled}px.`;
261
294
  }
262
295
  }
263
296
  __name(scrollVertically, "scrollVertically");
264
- async function scrollHorizontally(right, scroll_amount, element) {
297
+ async function scrollHorizontally(scroll_amount, element) {
265
298
  if (element) {
266
299
  const targetElement = element;
267
300
  let currentElement = targetElement;
@@ -269,10 +302,10 @@ async function scrollHorizontally(right, scroll_amount, element) {
269
302
  let scrolledElement = null;
270
303
  let scrollDelta = 0;
271
304
  let attempts = 0;
272
- const dx2 = right ? scroll_amount : -scroll_amount;
305
+ const dx2 = scroll_amount;
273
306
  while (currentElement && attempts < 10) {
274
307
  const computedStyle = window.getComputedStyle(currentElement);
275
- const hasScrollableX = /(auto|scroll|overlay)/.test(computedStyle.overflowX);
308
+ const hasScrollableX = /(auto|scroll|overlay)/.test(computedStyle.overflowX) || computedStyle.scrollbarWidth && computedStyle.scrollbarWidth !== "auto" || computedStyle.scrollbarGutter && computedStyle.scrollbarGutter !== "auto";
276
309
  const canScrollHorizontally = currentElement.scrollWidth > currentElement.clientWidth;
277
310
  if (hasScrollableX && canScrollHorizontally) {
278
311
  const beforeScroll = currentElement.scrollLeft;
@@ -305,7 +338,7 @@ async function scrollHorizontally(right, scroll_amount, element) {
305
338
  return `No horizontally scrollable container found for element (${targetElement.tagName})`;
306
339
  }
307
340
  }
308
- const dx = right ? scroll_amount : -scroll_amount;
341
+ const dx = scroll_amount;
309
342
  const bigEnough = /* @__PURE__ */ __name((el2) => el2.clientWidth >= window.innerWidth * 0.5, "bigEnough");
310
343
  const canScroll = /* @__PURE__ */ __name((el2) => el2 && /(auto|scroll|overlay)/.test(getComputedStyle(el2).overflowX) && el2.scrollWidth > el2.clientWidth && bigEnough(el2), "canScroll");
311
344
  let el = document.activeElement;
@@ -327,6 +360,8 @@ async function scrollHorizontally(right, scroll_amount, element) {
327
360
  if (reachedLeft) return `✅ Scrolled page by ${scrolled}px. Reached the left edge of the page.`;
328
361
  return `✅ Scrolled page horizontally by ${scrolled}px.`;
329
362
  } else {
363
+ const warningMsg = `The document is not scrollable. Falling back to container scroll.`;
364
+ console.log(`[PageController] ${warningMsg}`);
330
365
  const scrollBefore = el.scrollLeft;
331
366
  const scrollMax = el.scrollWidth - el.clientWidth;
332
367
  el.scrollBy({ left: dx, behavior: "smooth" });
@@ -334,15 +369,15 @@ async function scrollHorizontally(right, scroll_amount, element) {
334
369
  const scrollAfter = el.scrollLeft;
335
370
  const scrolled = scrollAfter - scrollBefore;
336
371
  if (Math.abs(scrolled) < 1) {
337
- return dx > 0 ? `⚠️ Already at the right edge of container (${el.tagName}), cannot scroll right further.` : `⚠️ Already at the left edge of container (${el.tagName}), cannot scroll left further.`;
372
+ return dx > 0 ? `⚠️ ${warningMsg} Already at the right edge of container (${el.tagName}), cannot scroll right further.` : `⚠️ ${warningMsg} Already at the left edge of container (${el.tagName}), cannot scroll left further.`;
338
373
  }
339
374
  const reachedRight = dx > 0 && scrollAfter >= scrollMax - 1;
340
375
  const reachedLeft = dx < 0 && scrollAfter <= 1;
341
376
  if (reachedRight)
342
- return `✅ Scrolled container (${el.tagName}) by ${scrolled}px. Reached the right edge.`;
377
+ return `✅ ${warningMsg} Scrolled container (${el.tagName}) by ${scrolled}px. Reached the right edge.`;
343
378
  if (reachedLeft)
344
- return `✅ Scrolled container (${el.tagName}) by ${scrolled}px. Reached the left edge.`;
345
- return `✅ Scrolled container (${el.tagName}) horizontally by ${scrolled}px.`;
379
+ return `✅ ${warningMsg} Scrolled container (${el.tagName}) by ${scrolled}px. Reached the left edge.`;
380
+ return `✅ ${warningMsg} Scrolled container (${el.tagName}) horizontally by ${scrolled}px.`;
346
381
  }
347
382
  }
348
383
  __name(scrollHorizontally, "scrollHorizontally");
@@ -599,9 +634,10 @@ const domTree = /* @__PURE__ */ __name((args = {
599
634
  }
600
635
  const overflowX = style.overflowX;
601
636
  const overflowY = style.overflowY;
637
+ const hasScrollbarSignal = style.scrollbarWidth && style.scrollbarWidth !== "auto" || style.scrollbarGutter && style.scrollbarGutter !== "auto";
602
638
  const scrollableX = overflowX === "auto" || overflowX === "scroll";
603
639
  const scrollableY = overflowY === "auto" || overflowY === "scroll";
604
- if (!scrollableX && !scrollableY) {
640
+ if (!scrollableX && !scrollableY && !hasScrollbarSignal) {
605
641
  return null;
606
642
  }
607
643
  const scrollWidth = element.scrollWidth - element.clientWidth;
@@ -610,10 +646,10 @@ const domTree = /* @__PURE__ */ __name((args = {
610
646
  if (scrollWidth < threshold && scrollHeight < threshold) {
611
647
  return null;
612
648
  }
613
- if (!scrollableY && scrollWidth < threshold) {
649
+ if (!scrollableY && !hasScrollbarSignal && scrollWidth < threshold) {
614
650
  return null;
615
651
  }
616
- if (!scrollableX && scrollHeight < threshold) {
652
+ if (!scrollableX && !hasScrollbarSignal && scrollHeight < threshold) {
617
653
  return null;
618
654
  }
619
655
  const distanceToTop = element.scrollTop;
@@ -630,6 +666,7 @@ const domTree = /* @__PURE__ */ __name((args = {
630
666
  scrollable: true,
631
667
  scrollData
632
668
  });
669
+ console.log("scrollData!!!", scrollData);
633
670
  return scrollData;
634
671
  }
635
672
  __name(isScrollableElement, "isScrollableElement");
@@ -1064,6 +1101,28 @@ const domTree = /* @__PURE__ */ __name((args = {
1064
1101
  return false;
1065
1102
  }
1066
1103
  __name(isInExpandedViewport, "isInExpandedViewport");
1104
+ const INTERACTIVE_ARIA_ATTRS = [
1105
+ "aria-expanded",
1106
+ "aria-checked",
1107
+ "aria-selected",
1108
+ "aria-pressed",
1109
+ "aria-haspopup",
1110
+ "aria-controls",
1111
+ "aria-owns",
1112
+ "aria-activedescendant",
1113
+ "aria-valuenow",
1114
+ "aria-valuetext",
1115
+ "aria-valuemax",
1116
+ "aria-valuemin",
1117
+ "aria-autocomplete"
1118
+ ];
1119
+ function hasInteractiveAria(el) {
1120
+ for (let i = 0; i < INTERACTIVE_ARIA_ATTRS.length; i++) {
1121
+ if (el.hasAttribute(INTERACTIVE_ARIA_ATTRS[i])) return true;
1122
+ }
1123
+ return false;
1124
+ }
1125
+ __name(hasInteractiveAria, "hasInteractiveAria");
1067
1126
  function isInteractiveCandidate(element) {
1068
1127
  if (!element || element.nodeType !== Node.ELEMENT_NODE) return false;
1069
1128
  const tagName = element.tagName.toLowerCase();
@@ -1078,7 +1137,7 @@ const domTree = /* @__PURE__ */ __name((args = {
1078
1137
  "label"
1079
1138
  ]);
1080
1139
  if (interactiveElements.has(tagName)) return true;
1081
- const hasQuickInteractiveAttr = element.hasAttribute("onclick") || element.hasAttribute("role") || element.hasAttribute("tabindex") || element.hasAttribute("aria-") || element.hasAttribute("data-action") || element.getAttribute("contenteditable") === "true";
1140
+ const hasQuickInteractiveAttr = element.hasAttribute("onclick") || element.hasAttribute("role") || element.hasAttribute("tabindex") || hasInteractiveAria(element) || element.hasAttribute("data-action") || element.getAttribute("contenteditable") === "true";
1082
1141
  return hasQuickInteractiveAttr;
1083
1142
  }
1084
1143
  __name(isInteractiveCandidate, "isInteractiveCandidate");
@@ -1091,9 +1150,10 @@ const domTree = /* @__PURE__ */ __name((args = {
1091
1150
  "summary",
1092
1151
  "details",
1093
1152
  "label",
1094
- "option"
1153
+ "option",
1154
+ "li"
1095
1155
  ]);
1096
- const INTERACTIVE_ROLES = /* @__PURE__ */ new Set([
1156
+ const DISTINCT_INTERACTIVE_ROLES = /* @__PURE__ */ new Set([
1097
1157
  "button",
1098
1158
  "link",
1099
1159
  "menuitem",
@@ -1109,6 +1169,9 @@ const domTree = /* @__PURE__ */ __name((args = {
1109
1169
  "searchbox",
1110
1170
  "textbox",
1111
1171
  "listbox",
1172
+ "listitem",
1173
+ "treeitem",
1174
+ "row",
1112
1175
  "option",
1113
1176
  "scrollbar"
1114
1177
  ]);
@@ -1139,7 +1202,7 @@ const domTree = /* @__PURE__ */ __name((args = {
1139
1202
  if (DISTINCT_INTERACTIVE_TAGS.has(tagName)) {
1140
1203
  return true;
1141
1204
  }
1142
- if (role && INTERACTIVE_ROLES.has(role)) {
1205
+ if (role && DISTINCT_INTERACTIVE_ROLES.has(role)) {
1143
1206
  return true;
1144
1207
  }
1145
1208
  if (element.isContentEditable || element.getAttribute("contenteditable") === "true") {
@@ -1151,6 +1214,9 @@ const domTree = /* @__PURE__ */ __name((args = {
1151
1214
  if (element.hasAttribute("onclick") || typeof element.onclick === "function") {
1152
1215
  return true;
1153
1216
  }
1217
+ if (hasInteractiveAria(element)) {
1218
+ return true;
1219
+ }
1154
1220
  try {
1155
1221
  const getEventListenersForNode = element?.ownerDocument?.defaultView?.getEventListenersForNode || window.getEventListenersForNode;
1156
1222
  if (typeof getEventListenersForNode === "function") {
@@ -1194,6 +1260,9 @@ const domTree = /* @__PURE__ */ __name((args = {
1194
1260
  if (isHeuristicallyInteractive(element)) {
1195
1261
  return true;
1196
1262
  }
1263
+ if (extraData.get(element)?.scrollable) {
1264
+ return true;
1265
+ }
1197
1266
  return false;
1198
1267
  }
1199
1268
  __name(isElementDistinctInteraction, "isElementDistinctInteraction");
@@ -1384,6 +1453,17 @@ function resolveViewportExpansion(viewportExpansion) {
1384
1453
  return viewportExpansion ?? DEFAULT_VIEWPORT_EXPANSION;
1385
1454
  }
1386
1455
  __name(resolveViewportExpansion, "resolveViewportExpansion");
1456
+ const SEMANTIC_TAGS = /* @__PURE__ */ new Set([
1457
+ "nav",
1458
+ "menu",
1459
+ // 'main',
1460
+ "header",
1461
+ "footer",
1462
+ "aside",
1463
+ // 'article',
1464
+ // 'form',
1465
+ "dialog"
1466
+ ]);
1387
1467
  const newElementsCache = /* @__PURE__ */ new WeakMap();
1388
1468
  function getFlatTree(config) {
1389
1469
  const viewportExpansion = resolveViewportExpansion(config.viewportExpansion);
@@ -1458,7 +1538,7 @@ function matchAttributes(attrs, patterns) {
1458
1538
  return result2;
1459
1539
  }
1460
1540
  __name(matchAttributes, "matchAttributes");
1461
- function flatTreeToString(flatTree, includeAttributes) {
1541
+ function flatTreeToString(flatTree, includeAttributes = [], keepSemanticTags = false) {
1462
1542
  const DEFAULT_INCLUDE_ATTRIBUTES = [
1463
1543
  "title",
1464
1544
  "type",
@@ -1485,7 +1565,7 @@ function flatTreeToString(flatTree, includeAttributes) {
1485
1565
  // content editable
1486
1566
  "contenteditable"
1487
1567
  ];
1488
- const includeAttrs = [...includeAttributes || [], ...DEFAULT_INCLUDE_ATTRIBUTES];
1568
+ const includeAttrs = [...includeAttributes, ...DEFAULT_INCLUDE_ATTRIBUTES];
1489
1569
  const capTextLength = /* @__PURE__ */ __name((text, maxLength) => {
1490
1570
  if (text.length > maxLength) {
1491
1571
  return text.substring(0, maxLength) + "...";
@@ -1554,6 +1634,7 @@ function flatTreeToString(flatTree, includeAttributes) {
1554
1634
  let nextDepth = depth;
1555
1635
  const depthStr = " ".repeat(depth);
1556
1636
  if (node.type === "element") {
1637
+ const isSemantic = keepSemanticTags && node.tagName && SEMANTIC_TAGS.has(node.tagName);
1557
1638
  if (node.highlightIndex !== void 0) {
1558
1639
  nextDepth += 1;
1559
1640
  const text = getAllTextTillNextClickableElement(node);
@@ -1621,9 +1702,22 @@ function flatTreeToString(flatTree, includeAttributes) {
1621
1702
  line += " />";
1622
1703
  result22.push(line);
1623
1704
  }
1705
+ const emitSemantic = isSemantic && node.highlightIndex === void 0;
1706
+ const mark = emitSemantic ? result22.length : -1;
1707
+ if (emitSemantic) {
1708
+ result22.push(`${depthStr}<${node.tagName}>`);
1709
+ nextDepth += 1;
1710
+ }
1624
1711
  for (const child of node.children) {
1625
1712
  processNode(child, nextDepth, result22);
1626
1713
  }
1714
+ if (emitSemantic) {
1715
+ if (result22.length === mark + 1) {
1716
+ result22.pop();
1717
+ } else {
1718
+ result22.push(`${depthStr}</${node.tagName}>`);
1719
+ }
1720
+ }
1627
1721
  } else if (node.type === "text") {
1628
1722
  if (hasParentWithHighlightIndex(node)) {
1629
1723
  return;
@@ -1794,7 +1888,7 @@ const _PageController = class _PageController extends EventTarget {
1794
1888
  initMask() {
1795
1889
  if (this.maskReady !== null) return;
1796
1890
  this.maskReady = (async () => {
1797
- const { SimulatorMask } = await import("./SimulatorMask-BHnQ6LmL.js");
1891
+ const { SimulatorMask } = await import("./SimulatorMask-CU7szDjy.js");
1798
1892
  this.mask = new SimulatorMask();
1799
1893
  })();
1800
1894
  }
@@ -1858,7 +1952,11 @@ ${scrollHintAbove}`;
1858
1952
  ...this.config,
1859
1953
  interactiveBlacklist: blacklist
1860
1954
  });
1861
- this.simplifiedHTML = flatTreeToString(this.flatTree, this.config.includeAttributes);
1955
+ this.simplifiedHTML = flatTreeToString(
1956
+ this.flatTree,
1957
+ this.config.includeAttributes,
1958
+ this.config.keepSemanticTags
1959
+ );
1862
1960
  this.selectorMap.clear();
1863
1961
  this.selectorMap = getSelectorMap(this.flatTree);
1864
1962
  this.elementTextMap.clear();
@@ -1874,6 +1972,7 @@ ${scrollHintAbove}`;
1874
1972
  * Clean up all element highlights
1875
1973
  */
1876
1974
  async cleanUpHighlights() {
1975
+ console.log("[PageController] cleanUpHighlights");
1877
1976
  cleanUpHighlights();
1878
1977
  }
1879
1978
  // ======= Element Actions =======
@@ -1959,9 +2058,9 @@ ${scrollHintAbove}`;
1959
2058
  try {
1960
2059
  const { down, numPages, pixels, index } = options;
1961
2060
  this.assertIndexed();
1962
- const scrollAmount = pixels ?? numPages * (down ? 1 : -1) * window.innerHeight;
2061
+ const scrollAmount = (pixels ?? numPages * window.innerHeight) * (down ? 1 : -1);
1963
2062
  const element = index !== void 0 ? getElementByIndex(this.selectorMap, index) : null;
1964
- const message = await scrollVertically(down, scrollAmount, element);
2063
+ const message = await scrollVertically(scrollAmount, element);
1965
2064
  return {
1966
2065
  success: true,
1967
2066
  message
@@ -1982,7 +2081,7 @@ ${scrollHintAbove}`;
1982
2081
  this.assertIndexed();
1983
2082
  const scrollAmount = pixels * (right ? 1 : -1);
1984
2083
  const element = index !== void 0 ? getElementByIndex(this.selectorMap, index) : null;
1985
- const message = await scrollHorizontally(right, scrollAmount, element);
2084
+ const message = await scrollHorizontally(scrollAmount, element);
1986
2085
  return {
1987
2086
  success: true,
1988
2087
  message