agent-zero/plugins/_browser/assets/browser-page-content.js
Alessandro d3c249cbdd Add Browser v1 explicit screenshot and form actions
Adds the explicit browser:screenshot action that writes JPEG/PNG files for vision_load, extends agent-callable Browser input actions, and documents the explicit vision workflow.

Adds the browser-forms on-demand skill and regression coverage for dispatch, runtime screenshot files, ref point resolution, upload path normalization, prompt discoverability, and label-wrapped form controls surfaced by the chat-driven E2E.
2026-05-05 15:54:13 +02:00

3963 lines
119 KiB
JavaScript

(() => {
const GLOBAL_KEY = "__spaceBrowserPageContent__";
const DOM_HELPER_KEY = "__spaceBrowserDomHelper__";
const VERSION = "11";
const BLOCK_TAGS = new Set([
"ADDRESS",
"ARTICLE",
"ASIDE",
"BLOCKQUOTE",
"BODY",
"DETAILS",
"DIV",
"DL",
"FIELDSET",
"FIGCAPTION",
"FIGURE",
"FOOTER",
"FORM",
"H1",
"H2",
"H3",
"H4",
"H5",
"H6",
"HEADER",
"HR",
"HTML",
"LI",
"MAIN",
"NAV",
"OL",
"P",
"PRE",
"SECTION",
"TABLE",
"TBODY",
"TD",
"TFOOT",
"TH",
"THEAD",
"TR",
"UL"
]);
const SKIP_TAGS = new Set([
"HEAD",
"LINK",
"META",
"NOSCRIPT",
"SCRIPT",
"STYLE",
"TEMPLATE"
]);
const INTERACTIVE_ROLES = new Set([
"button",
"checkbox",
"combobox",
"link",
"menuitem",
"menuitemcheckbox",
"menuitemradio",
"option",
"radio",
"searchbox",
"slider",
"spinbutton",
"switch",
"tab",
"textbox"
]);
const INTERACTIVE_EVENT_NAMES = new Set([
"auxclick",
"change",
"click",
"contextmenu",
"dblclick",
"input",
"keydown",
"keypress",
"keyup",
"mousedown",
"mouseup",
"pointerdown",
"pointerup",
"submit",
"touchend",
"touchstart"
]);
const INTERACTIVE_EVENT_PROPERTIES = [...INTERACTIVE_EVENT_NAMES]
.map((eventName) => `on${eventName}`);
if (globalThis[GLOBAL_KEY]?.version === VERSION) {
return;
}
const state = {
backend: "live",
captureId: 0,
capturedAt: 0,
captureOptions: {
includeLabelQuotes: false,
includeLinkUrls: false,
includeSemanticTags: true,
includeStateTags: true,
includeListIndentation: true,
includeListMarkers: false
},
entries: new Map()
};
function isElementNode(value) {
return Boolean(value && value.nodeType === 1);
}
function isTextNode(value) {
return Boolean(value && value.nodeType === 3);
}
function normalizeText(value) {
return String(value ?? "")
.replace(/\s+/gu, " ")
.trim();
}
function looksLikeSerializedHtmlText(value) {
const normalizedValue = normalizeText(value);
if (!normalizedValue || !normalizedValue.includes("<") || !normalizedValue.includes(">")) {
return false;
}
if (/<!(?:doctype|--)\b/iu.test(normalizedValue)) {
return true;
}
if (/<\/?(?:style|script)\b[\s\S]*?>/iu.test(normalizedValue)) {
return true;
}
const tagMatches = normalizedValue.match(/<\/?[a-z][^>]*>/giu) || [];
return tagMatches.length >= 3 && normalizedValue.length >= 80;
}
function looksLikeBrowserHelperMarkupText(value) {
const normalizedValue = normalizeText(value);
if (!normalizedValue) {
return false;
}
return /space-browser-(?:frame-document|shadow-root)/iu.test(normalizedValue)
|| /data-space-browser-(?:frame|node|status|frame-url|frame-title|frame-src)/iu.test(normalizedValue);
}
function looksLikeMinifiedScriptText(value) {
const normalizedValue = normalizeText(value);
if (!normalizedValue || normalizedValue.length < 400) {
return false;
}
const jsSignals = [
/\bfunction\b/u,
/\breturn\b/u,
/\bvar\b/u,
/\bnew\b/u,
/\bcase\b/u,
/\bswitch\b/u,
/\bwhile\b/u,
/\bfor\b/u,
/\b(?:localStorage|postMessage|document\.|window\.|parent\.)/u,
/\bthis\./u,
/(?:&&|\|\||>>>|!==|===)/u
].reduce((count, pattern) => count + (pattern.test(normalizedValue) ? 1 : 0), 0);
if (jsSignals < 4) {
return false;
}
const punctuationCount = (normalizedValue.match(/[{}[\]();=<>\\]/gu) || []).length;
return punctuationCount / normalizedValue.length >= 0.12;
}
function shouldDropReadableText(value) {
const normalizedValue = normalizeText(value);
if (!normalizedValue) {
return true;
}
return looksLikeBrowserHelperMarkupText(normalizedValue)
|| looksLikeSerializedHtmlText(normalizedValue)
|| looksLikeMinifiedScriptText(normalizedValue);
}
function normalizeAttributeText(value) {
return normalizeText(value).slice(0, 160);
}
function escapeMarkdownText(value) {
return String(value ?? "").replace(/([\\`*_{}\[\]()#+\-!|>])/gu, "\\$1");
}
function quoteText(value) {
return JSON.stringify(String(value ?? ""));
}
function truncateText(value, maxLength = 120) {
const normalizedValue = normalizeText(value);
if (normalizedValue.length <= maxLength) {
return normalizedValue;
}
return `${normalizedValue.slice(0, Math.max(0, maxLength - 1)).trimEnd()}...`;
}
function delayMs(timeoutMs) {
return new Promise((resolve) => {
globalThis.setTimeout(resolve, Math.max(0, Number(timeoutMs) || 0));
});
}
function parseCssColor(value) {
const normalizedValue = normalizeText(value);
if (!normalizedValue || normalizedValue === "transparent") {
return null;
}
const rgbMatch = normalizedValue.match(/^rgba?\(([^)]+)\)$/iu);
if (rgbMatch) {
const parts = rgbMatch[1]
.split(",")
.map((part) => Number.parseFloat(String(part || "").trim()))
.filter((part) => Number.isFinite(part));
if (parts.length >= 3) {
return {
r: Math.max(0, Math.min(255, parts[0])),
g: Math.max(0, Math.min(255, parts[1])),
b: Math.max(0, Math.min(255, parts[2])),
a: parts.length >= 4 ? Math.max(0, Math.min(1, parts[3])) : 1
};
}
}
const hexMatch = normalizedValue.match(/^#([\da-f]{3,8})$/iu);
if (!hexMatch) {
return null;
}
const hex = hexMatch[1];
if (hex.length === 3 || hex.length === 4) {
const [r, g, b, a = "f"] = hex.split("");
return {
r: Number.parseInt(`${r}${r}`, 16),
g: Number.parseInt(`${g}${g}`, 16),
b: Number.parseInt(`${b}${b}`, 16),
a: Number.parseInt(`${a}${a}`, 16) / 255
};
}
if (hex.length === 6 || hex.length === 8) {
return {
r: Number.parseInt(hex.slice(0, 2), 16),
g: Number.parseInt(hex.slice(2, 4), 16),
b: Number.parseInt(hex.slice(4, 6), 16),
a: hex.length === 8 ? Number.parseInt(hex.slice(6, 8), 16) / 255 : 1
};
}
return null;
}
function rgbToHsl(color) {
if (!color) {
return null;
}
const r = color.r / 255;
const g = color.g / 255;
const b = color.b / 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
const delta = max - min;
const lightness = (max + min) / 2;
let hue = 0;
let saturation = 0;
if (delta > 0) {
saturation = delta / (1 - Math.abs(2 * lightness - 1));
if (max === r) {
hue = 60 * (((g - b) / delta) % 6);
} else if (max === g) {
hue = 60 * (((b - r) / delta) + 2);
} else {
hue = 60 * (((r - g) / delta) + 4);
}
}
if (hue < 0) {
hue += 360;
}
return {
hue,
lightness,
saturation
};
}
function isTrustedHtmlRequirementError(error) {
return /TrustedHTML/iu.test(String(error?.message || error || ""));
}
function joinBlocks(blocks) {
return blocks
.map((block) => String(block || "").trim())
.filter(Boolean)
.join("\n\n")
.trim();
}
function cleanReadableMarkdown(value) {
const lines = String(value || "")
.replace(/<style\\?>[\s\S]*?<\/style\\?>/giu, "")
.replace(/<script\\?>[\s\S]*?<\/script\\?>/giu, "")
.replace(/<space\\-browser\\-(?:frame\\-document|shadow\\-root)\b[\s\S]*?<\/space\\-browser\\-(?:frame\\-document|shadow\\-root)>/giu, "")
.split("\n");
const filteredLines = [];
let insideCodeFence = false;
lines.forEach((line) => {
const trimmedLine = String(line || "").trim();
if (trimmedLine.startsWith("```")) {
insideCodeFence = !insideCodeFence;
filteredLines.push(line);
return;
}
if (!trimmedLine || insideCodeFence) {
filteredLines.push(line);
return;
}
if (shouldDropReadableText(trimmedLine)) {
return;
}
filteredLines.push(line);
});
return filteredLines
.join("\n")
.replace(/\n{3,}/gu, "\n\n")
.trim();
}
function joinInlineParts(parts) {
return String(parts
.map((part) => String(part || "").trim())
.filter(Boolean)
.join(" "))
.replace(/\s+([,.;!?])/gu, "$1")
.replace(/([([{\u201c])\s+/gu, "$1")
.replace(/\s+([\])}\u201d])/gu, "$1")
.replace(/\s*\n\s*/gu, "\n")
.replace(/[ \t]+\n/gu, "\n")
.replace(/\n{3,}/gu, "\n\n")
.trim();
}
function indentBlock(text, level = 1) {
const prefix = " ".repeat(Math.max(0, level));
return String(text || "")
.split("\n")
.map((line) => `${prefix}${line}`)
.join("\n");
}
function createNamedError(name, message, details = {}) {
const error = new Error(message);
error.name = name;
Object.assign(error, details);
return error;
}
function coerceSelectorList(payload) {
if (typeof payload === "string") {
return [payload];
}
if (Array.isArray(payload?.selectors)) {
return payload.selectors;
}
if (typeof payload?.selectors === "string") {
return [payload.selectors];
}
if (Array.isArray(payload?.selector)) {
return payload.selector;
}
if (typeof payload?.selector === "string") {
return [payload.selector];
}
if (Array.isArray(payload)) {
return payload;
}
return [];
}
function normalizeSelectorList(payload) {
return coerceSelectorList(payload)
.map((selector) => String(selector || "").trim())
.filter(Boolean);
}
function normalizeIncludeLinkUrls(payload) {
return payload?.includeLinkUrls === true;
}
function normalizeIncludeLabelQuotes(payload) {
return payload?.includeLabelQuotes === true;
}
function normalizeIncludeListIndentation(payload) {
return payload?.includeListIndentation !== false;
}
function normalizeIncludeListMarkers(payload) {
return payload?.includeListMarkers === true;
}
function normalizeIncludeStateTags(payload) {
return payload?.includeStateTags !== false;
}
function normalizeIncludeSemanticTags(payload) {
return payload?.includeSemanticTags !== false;
}
function formatSummaryValue(value, options = {}) {
const normalizedValue = normalizeText(value);
if (!normalizedValue) {
return "";
}
if (options.includeLabelQuotes === true) {
return quoteText(normalizedValue);
}
return escapeMarkdownText(normalizedValue);
}
function normalizeFrameChain(value) {
const rawFrameChain = Array.isArray(value)
? value
: typeof value === "string"
? value.split(">")
: [];
return rawFrameChain
.map((entry) => String(entry || "").trim())
.filter(Boolean);
}
function getDomHelper() {
const helper = globalThis[DOM_HELPER_KEY];
if (
helper
&& typeof helper.captureDocument === "function"
&& typeof helper.detailNode === "function"
&& typeof helper.clickNode === "function"
&& typeof helper.typeNode === "function"
&& typeof helper.submitNode === "function"
&& typeof helper.typeSubmitNode === "function"
&& typeof helper.scrollNode === "function"
) {
return helper;
}
return null;
}
function requireDomHelper(actionLabel) {
const helper = getDomHelper();
if (helper) {
return helper;
}
throw createNamedError(
"BrowserPageContentHelperUnavailableError",
`Browser page content cannot ${actionLabel} without the desktop DOM helper.`,
{
code: "browser_page_content_dom_helper_unavailable",
details: {
action: String(actionLabel || "resolve")
}
}
);
}
function normalizeReferenceId(value) {
if (typeof value === "number" && Number.isFinite(value)) {
return String(Math.trunc(value));
}
if (typeof value === "string") {
return value.trim();
}
if (value && typeof value === "object") {
return normalizeReferenceId(value.referenceId ?? value.ref ?? value.id);
}
return "";
}
function getTagName(element) {
return String(element?.tagName || "").toUpperCase();
}
function expandSlotNodes(node) {
if (!isElementNode(node) || getTagName(node) !== "SLOT" || typeof node.assignedNodes !== "function") {
return [node];
}
try {
const assignedNodes = [...(node.assignedNodes({ flatten: true }) || [])].filter(Boolean);
if (assignedNodes.length) {
return assignedNodes.flatMap((assignedNode) => expandSlotNodes(assignedNode));
}
} catch {
// Fall through to the slot's fallback children.
}
return [...(node.childNodes || [])].flatMap((childNode) => expandSlotNodes(childNode));
}
function getReadableChildNodes(element) {
const shadowRoot = element?.shadowRoot;
if (shadowRoot) {
const shadowNodes = [...(shadowRoot.childNodes || [])].flatMap((childNode) => expandSlotNodes(childNode));
if (shadowNodes.length) {
return shadowNodes;
}
}
return [...(element?.childNodes || [])].flatMap((childNode) => expandSlotNodes(childNode));
}
function getReadableElementChildren(element) {
return getReadableChildNodes(element).filter((childNode) => isElementNode(childNode));
}
function getReadableNodeText(node) {
if (isTextNode(node)) {
return node.textContent || "";
}
if (!isElementNode(node) || isHiddenElement(node)) {
return "";
}
return getReadableChildNodes(node)
.map((childNode) => getReadableNodeText(childNode))
.filter(Boolean)
.join(" ");
}
function querySelectorAllDeep(selector, root = globalThis.document) {
const results = [];
const seen = new Set();
const addResult = (element) => {
if (element && !seen.has(element)) {
seen.add(element);
results.push(element);
}
};
const visitRoot = (scope) => {
if (!scope || typeof scope.querySelectorAll !== "function") {
return;
}
[...(scope.querySelectorAll(selector) || [])].forEach(addResult);
[...(scope.querySelectorAll("*") || [])].forEach((element) => {
if (element.shadowRoot) {
visitRoot(element.shadowRoot);
}
});
};
visitRoot(root);
return results;
}
function getAttributeNamesSafe(element) {
try {
if (typeof element?.getAttributeNames === "function") {
return element.getAttributeNames();
}
return [...(element?.attributes || [])]
.map((attribute) => String(attribute?.name || "").trim())
.filter(Boolean);
} catch {
return [];
}
}
function normalizeInteractiveEventName(value) {
return String(value || "")
.trim()
.toLowerCase()
.split(/[.:]/u, 1)[0];
}
function isInteractiveEventName(value) {
return INTERACTIVE_EVENT_NAMES.has(normalizeInteractiveEventName(value));
}
function isInteractiveEventAttributeName(attributeName) {
const normalizedName = String(attributeName || "").trim().toLowerCase();
if (!normalizedName) {
return false;
}
if (normalizedName.startsWith("@")) {
return isInteractiveEventName(normalizedName.slice(1));
}
if (normalizedName.startsWith("x-on:") || normalizedName.startsWith("v-on:")) {
return isInteractiveEventName(normalizedName.slice(5));
}
if (normalizedName.startsWith("ng-")) {
return isInteractiveEventName(normalizedName.slice(3));
}
if (normalizedName.startsWith("on") && normalizedName.length > 2) {
return isInteractiveEventName(normalizedName.slice(2));
}
return false;
}
function hasHelperManagedNodeReference(element) {
return Boolean(normalizeAttributeText(element?.getAttribute?.("data-space-browser-node-id")));
}
function hasInteractiveEventHandlerAttribute(element) {
return getAttributeNamesSafe(element).some((attributeName) => {
return isInteractiveEventAttributeName(attributeName);
});
}
function hasInteractiveEventHandlerProperty(element) {
return INTERACTIVE_EVENT_PROPERTIES.some((propertyName) => {
return typeof element?.[propertyName] === "function";
});
}
function hasInteractiveEventHandler(element) {
return hasInteractiveEventHandlerAttribute(element) || hasInteractiveEventHandlerProperty(element);
}
function isStyleDeclarationHidden(styleValue) {
const normalizedStyleValue = String(styleValue || "")
.toLowerCase()
.replace(/\s+/gu, "");
if (!normalizedStyleValue) {
return false;
}
return /(?:^|;)display:none(?:;|$)/u.test(normalizedStyleValue)
|| /(?:^|;)visibility:hidden(?:;|$)/u.test(normalizedStyleValue)
|| /(?:^|;)visibility:collapse(?:;|$)/u.test(normalizedStyleValue)
|| /(?:^|;)content-visibility:hidden(?:;|$)/u.test(normalizedStyleValue)
|| /(?:^|;)opacity:0(?:\.0+)?(?:;|$)/u.test(normalizedStyleValue);
}
function isComputedStyleHidden(computedStyle) {
if (!computedStyle) {
return false;
}
const display = normalizeText(computedStyle.display).toLowerCase();
const visibility = normalizeText(computedStyle.visibility).toLowerCase();
const contentVisibility = normalizeText(computedStyle.contentVisibility).toLowerCase();
const opacity = Number(computedStyle.opacity || 1);
return display === "none"
|| visibility === "hidden"
|| visibility === "collapse"
|| contentVisibility === "hidden"
|| opacity <= 0;
}
function isEffectivelyHiddenByAncestor(element) {
let current = element;
while (isElementNode(current)) {
if (current.hidden || current.getAttribute?.("aria-hidden") === "true") {
return true;
}
if (isStyleDeclarationHidden(current.getAttribute?.("style"))) {
return true;
}
if (isComputedStyleHidden(getComputedStyleSafe(current))) {
return true;
}
current = current.parentElement;
}
return false;
}
function isHiddenElement(element) {
if (!isElementNode(element)) {
return true;
}
const tagName = getTagName(element);
if (SKIP_TAGS.has(tagName)) {
return true;
}
if (element.hidden || element.getAttribute?.("aria-hidden") === "true") {
return true;
}
if (tagName === "INPUT" && String(element.getAttribute?.("type") || "").toLowerCase() === "hidden") {
return true;
}
if (isStyleDeclarationHidden(element.getAttribute?.("style"))) {
return true;
}
const computedStyle = getComputedStyleSafe(element);
if (isComputedStyleHidden(computedStyle)) {
return true;
}
return isEffectivelyHiddenByAncestor(element.parentElement);
}
function isBlockElement(element) {
return BLOCK_TAGS.has(getTagName(element));
}
function isInteractiveElement(element) {
if (!isElementNode(element) || isHiddenElement(element)) {
return false;
}
if (hasHelperManagedNodeReference(element)) {
return true;
}
const tagName = getTagName(element);
if (tagName === "A" && element.hasAttribute?.("href")) {
return true;
}
if (tagName === "BUTTON" || tagName === "INPUT" || tagName === "SELECT" || tagName === "TEXTAREA" || tagName === "SUMMARY") {
return true;
}
if (String(element.getAttribute?.("contenteditable") || "").toLowerCase() === "true") {
return true;
}
const role = String(element.getAttribute?.("role") || "").trim().toLowerCase();
return INTERACTIVE_ROLES.has(role) || hasInteractiveEventHandler(element);
}
function isFileInputElement(element) {
return getTagName(element) === "INPUT"
&& String(element.getAttribute?.("type") || element.type || "").toLowerCase() === "file";
}
function getAssociatedLabelFileInput(labelElement) {
if (getTagName(labelElement) !== "LABEL") {
return null;
}
if (isFileInputElement(labelElement.control)) {
return labelElement.control;
}
const descendantInput = labelElement.querySelector?.("input[type='file']");
if (isFileInputElement(descendantInput)) {
return descendantInput;
}
const forId = normalizeAttributeText(labelElement.getAttribute?.("for"));
if (!forId) {
return null;
}
return isFileInputElement(labelElement.ownerDocument?.getElementById?.(forId))
? labelElement.ownerDocument.getElementById(forId)
: null;
}
function isFileInputLabel(element) {
if (getTagName(element) !== "LABEL" || isHiddenElement(element)) {
return false;
}
const input = getAssociatedLabelFileInput(element);
return Boolean(input && isHiddenElement(input));
}
function getComputedStyleSafe(element) {
try {
return globalThis.getComputedStyle?.(element) || null;
} catch {
return null;
}
}
function getElementRectSafe(element) {
try {
const rect = element?.getBoundingClientRect?.();
if (!rect) {
return null;
}
return {
height: Number(rect.height) || 0,
width: Number(rect.width) || 0,
x: Number(rect.x) || 0,
y: Number(rect.y) || 0
};
} catch {
return null;
}
}
function readSerializedTagList(element, attributeName) {
const rawValue = normalizeText(element?.getAttribute?.(attributeName));
if (!rawValue) {
return [];
}
return rawValue
.split(/\s+/u)
.map((part) => normalizeText(part))
.filter(Boolean);
}
function detectSemanticTone(element, computedStyle, metadata = {}) {
const opacity = Number(computedStyle?.opacity || 1);
const backgroundColor = parseCssColor(computedStyle?.backgroundColor || "");
const borderColor = parseCssColor(computedStyle?.borderTopColor || "");
const foregroundColor = parseCssColor(computedStyle?.color || "");
const isButtonLike = ["BUTTON", "INPUT", "SUMMARY"].includes(getTagName(element))
|| ["button", "tab", "menuitem"].includes(String(element?.getAttribute?.("role") || "").trim().toLowerCase());
if (metadata.disabled || metadata.blocked || opacity <= 0.58) {
return "muted";
}
const preferredColor = [backgroundColor, borderColor, foregroundColor]
.filter((color) => color && color.a > 0.15)
.map((color) => ({
color,
hsl: rgbToHsl(color)
}))
.find((entry) => entry.hsl && entry.hsl.saturation >= 0.2);
if (!preferredColor) {
return "";
}
const {
hue,
lightness,
saturation
} = preferredColor.hsl;
if (saturation < 0.2) {
return "";
}
if ((hue >= 345 || hue < 20) && lightness >= 0.18 && lightness <= 0.82) {
return "error";
}
if (hue >= 20 && hue < 65 && lightness >= 0.2 && lightness <= 0.9) {
return "warning";
}
if (hue >= 65 && hue < 170 && lightness >= 0.16 && lightness <= 0.84) {
return "success";
}
if (hue >= 170 && hue < 280 && lightness >= 0.14 && lightness <= 0.82) {
if (isButtonLike && backgroundColor?.a > 0.2) {
return "primary";
}
return "";
}
return "";
}
function collectElementStateMetadata(element, options = {}) {
if (!isElementNode(element)) {
return {
descriptorTags: [],
semanticTags: [],
stateTags: []
};
}
const computedStyle = getComputedStyleSafe(element);
const rect = getElementRectSafe(element);
const tagName = getTagName(element);
const ariaDisabled = String(element.getAttribute?.("aria-disabled") || "").trim().toLowerCase() === "true";
const ariaBusy = String(element.getAttribute?.("aria-busy") || "").trim().toLowerCase() === "true";
const ariaChecked = String(element.getAttribute?.("aria-checked") || "").trim().toLowerCase() === "true";
const ariaCurrent = normalizeText(element.getAttribute?.("aria-current"));
const ariaInvalid = String(element.getAttribute?.("aria-invalid") || "").trim().toLowerCase() === "true";
const ariaPressed = String(element.getAttribute?.("aria-pressed") || "").trim().toLowerCase() === "true";
const ariaReadonly = String(element.getAttribute?.("aria-readonly") || "").trim().toLowerCase() === "true";
const ariaRequired = String(element.getAttribute?.("aria-required") || "").trim().toLowerCase() === "true";
const ariaSelected = String(element.getAttribute?.("aria-selected") || "").trim().toLowerCase() === "true";
const helperStateTags = readSerializedTagList(element, "data-space-browser-state-tags");
const helperSemanticTags = readSerializedTagList(element, "data-space-browser-semantic-tags");
const closestInert = typeof element.closest === "function" ? element.closest("[inert]") : null;
const pointerEventsNone = normalizeText(computedStyle?.pointerEvents || "").toLowerCase() === "none";
const disabled = Boolean(element.disabled || ariaDisabled || closestInert || helperStateTags.includes("disabled"));
const blocked = !disabled && (pointerEventsNone || helperStateTags.includes("blocked"));
const checked = Boolean(element.checked || ariaChecked || helperStateTags.includes("checked"));
const selected = tagName === "OPTION"
? Boolean(element.selected)
: Boolean(ariaSelected || helperStateTags.includes("selected"));
const invalid = Boolean(ariaInvalid || helperStateTags.includes("invalid") || element.matches?.(":invalid"));
const readonly = Boolean(element.readOnly || ariaReadonly);
const required = Boolean(element.required || ariaRequired);
const expanded = String(element.getAttribute?.("aria-expanded") || "").trim().toLowerCase() === "true" || helperStateTags.includes("expanded");
const pressed = ariaPressed || helperStateTags.includes("pressed");
const busy = ariaBusy || helperStateTags.includes("busy");
const current = Boolean((ariaCurrent && ariaCurrent !== "false") || helperStateTags.includes("current"));
const zeroRect = Boolean(
rect
&& element.ownerDocument === globalThis.document
&& rect.width <= 1
&& rect.height <= 1
);
const opacity = Number(computedStyle?.opacity || 1);
const semanticTone = helperSemanticTags[0] || detectSemanticTone(element, computedStyle, {
blocked,
disabled
});
const stateTags = helperStateTags.length
? helperStateTags.slice()
: [
disabled ? "disabled" : "",
!disabled && (blocked || zeroRect) ? "blocked" : "",
checked ? "checked" : "",
selected && tagName !== "SELECT" ? "selected" : "",
invalid ? "invalid" : "",
expanded ? "expanded" : "",
pressed ? "pressed" : ""
].filter(Boolean);
const semanticTags = helperSemanticTags.length
? helperSemanticTags.slice(0, 1)
: (semanticTone ? [semanticTone] : []);
const descriptorTags = [
...(options.includeStateTags !== false ? stateTags : []),
...(options.includeSemanticTags !== false ? semanticTags : [])
];
return {
blocked,
busy,
checked,
current,
cursor: normalizeText(computedStyle?.cursor || "").toLowerCase(),
descriptorTags,
disabled,
expanded,
invalid,
opacity,
pointerEventsNone,
pressed,
readonly,
required,
selected,
semanticTags,
semanticTone,
stateTags,
visible: !isHiddenElement(element),
zeroRect
};
}
function getReferenceValueMetadata(element) {
const tagName = getTagName(element);
const helperLiveValue = normalizeText(element?.getAttribute?.("data-space-browser-live-value"));
const helperSelectedValue = normalizeText(element?.getAttribute?.("data-space-browser-selected-text"));
if (tagName === "INPUT") {
const inputType = String(element.getAttribute?.("type") || element.type || "text").toLowerCase();
if (inputType === "password") {
return "";
}
return truncateText(helperLiveValue || element.value || element.getAttribute?.("value") || "", 96);
}
if (tagName === "TEXTAREA") {
return truncateText(helperLiveValue || element.value || "", 96);
}
if (tagName === "SELECT") {
if (helperSelectedValue) {
return helperSelectedValue;
}
const selectedOptions = [...(element.selectedOptions || [])]
.map((option) => truncateText(option.textContent || option.label || option.value || "", 48))
.filter(Boolean);
return selectedOptions.join(" | ");
}
if (String(element.getAttribute?.("contenteditable") || "").toLowerCase() === "true") {
return truncateText(element.textContent || "", 96);
}
return "";
}
function collectMetaLines(doc = globalThis.document) {
const lines = [];
const title = normalizeAttributeText(doc?.title || "");
const description = normalizeAttributeText(
doc?.querySelector?.('meta[name="description"]')?.getAttribute?.("content") || ""
);
const url = String(globalThis.location?.href || "");
if (!title && !description && !url) {
return "";
}
lines.push("---");
if (title) {
lines.push(`title: ${quoteText(title)}`);
}
if (description) {
lines.push(`description: ${quoteText(description)}`);
}
if (url) {
lines.push(`url: ${quoteText(url)}`);
}
lines.push("---");
return lines.join("\n");
}
function summarizeUrl(value) {
const normalizedValue = String(value || "").trim();
if (!normalizedValue) {
return "";
}
try {
const url = new URL(normalizedValue, globalThis.location?.href || "http://localhost/");
if (url.origin === globalThis.location?.origin) {
const relative = `${url.pathname || "/"}${url.search || ""}${url.hash || ""}`;
return truncateText(relative || "/", 96);
}
return truncateText(`${url.hostname}${url.pathname || "/"}`, 96);
} catch {
return truncateText(normalizedValue, 96);
}
}
function getElementText(element) {
const readableText = normalizeText(getReadableNodeText(element));
return readableText || normalizeText(element?.textContent || "");
}
function isLabelableControlForText(element) {
return ["BUTTON", "INPUT", "METER", "OUTPUT", "PROGRESS", "SELECT", "TEXTAREA"].includes(getTagName(element));
}
function getLabelElementText(labelElement, controlElement = null) {
const collect = (node) => {
if (isTextNode(node)) {
return node.textContent || "";
}
if (!isElementNode(node) || isHiddenElement(node)) {
return "";
}
if (node !== labelElement && (node === controlElement || isLabelableControlForText(node))) {
return "";
}
return getReadableChildNodes(node)
.map((childNode) => collect(childNode))
.filter(Boolean)
.join(" ");
};
return normalizeText(collect(labelElement)) || getElementText(labelElement);
}
function collectLabelCandidates(element, options = {}) {
const includeAlt = options.includeAlt !== false;
const includeDescendantImageAlt = options.includeDescendantImageAlt !== false;
const includePlaceholder = options.includePlaceholder === true;
const includeText = options.includeText !== false;
const collectedLabels = [];
try {
if (Array.isArray(element?.labels) || typeof element?.labels?.forEach === "function") {
element.labels.forEach((labelElement) => {
const text = getLabelElementText(labelElement, element);
if (text) {
collectedLabels.push(text);
}
});
}
} catch {
// Ignore labels lookup failures from non-form elements.
}
[
element?.getAttribute?.("aria-label"),
element?.getAttribute?.("title")
].forEach((candidate) => {
const text = normalizeAttributeText(candidate);
if (text) {
collectedLabels.push(text);
}
});
if (includeAlt) {
const altText = normalizeAttributeText(element?.getAttribute?.("alt"));
if (altText) {
collectedLabels.push(altText);
}
}
if (includePlaceholder) {
const placeholderText = normalizeAttributeText(element?.getAttribute?.("placeholder"));
if (placeholderText) {
collectedLabels.push(placeholderText);
}
}
if (includeDescendantImageAlt) {
try {
[...(element?.querySelectorAll?.("img[alt], img[title]") || [])]
.slice(0, 3)
.forEach((mediaElement) => {
const text = normalizeAttributeText(
mediaElement.getAttribute?.("alt")
|| mediaElement.getAttribute?.("title")
);
if (text) {
collectedLabels.push(text);
}
});
} catch {
// Ignore descendant-media lookup failures.
}
}
if (includeText) {
const textContent = getElementText(element);
if (textContent) {
collectedLabels.push(textContent);
}
}
return [...new Set(collectedLabels.filter(Boolean))];
}
function getLabelText(element, options = {}) {
return collectLabelCandidates(element, options)[0] || "";
}
function serializeElementSnapshot(element) {
if (!isElementNode(element)) {
return "";
}
try {
if (typeof element.outerHTML === "string" && element.outerHTML) {
return element.outerHTML;
}
} catch {
// Fall through to XMLSerializer.
}
try {
if (typeof globalThis.XMLSerializer === "function") {
return new globalThis.XMLSerializer().serializeToString(element);
}
} catch {
// Ignore serialization errors.
}
return "";
}
function getReferenceKind(element) {
const tagName = getTagName(element);
const role = String(element.getAttribute?.("role") || "").trim().toLowerCase();
const inputType = String(element.getAttribute?.("type") || element.type || "text").toLowerCase();
if (tagName === "A" || role === "link") {
return "link";
}
if (tagName === "IMG") {
return "image";
}
if (tagName === "BUTTON" || ["button", "menuitem", "tab"].includes(role)) {
return "button";
}
if (tagName === "TEXTAREA") {
return "textarea";
}
if (tagName === "SELECT" || role === "combobox") {
return "select";
}
if (tagName === "SUMMARY") {
return "summary";
}
if (tagName === "INPUT") {
if (["button", "submit", "reset"].includes(inputType)) {
return "button";
}
if (inputType === "checkbox") {
return "checkbox";
}
if (inputType === "radio") {
return "radio";
}
return `input ${inputType || "text"}`;
}
if (tagName === "LABEL" && isFileInputLabel(element)) {
return "file input label";
}
if (String(element.getAttribute?.("contenteditable") || "").toLowerCase() === "true") {
return "editable";
}
if (role === "searchbox") {
return "input search";
}
if (role === "textbox") {
return "input text";
}
if (hasHelperManagedNodeReference(element) || hasInteractiveEventHandler(element)) {
return "button";
}
return role || tagName.toLowerCase();
}
function collectReferenceSummaryData(element, options = {}) {
const tagName = getTagName(element);
const role = String(element.getAttribute?.("role") || "").trim().toLowerCase();
const id = normalizeAttributeText(element.getAttribute?.("id"));
const name = normalizeAttributeText(element.getAttribute?.("name"));
const kind = getReferenceKind(element);
const stateMetadata = collectElementStateMetadata(element, options);
const formatValue = (value) => formatSummaryValue(value, options);
const includeLinkUrls = options.includeLinkUrls === true;
const parts = [];
const appendFallbackIdOrName = () => {
if (id) {
parts.push(`#${id}`);
return;
}
if (name) {
parts.push(`name=${formatValue(name)}`);
}
};
if (tagName === "A" || role === "link") {
const hrefSummary = summarizeUrl(element.getAttribute?.("href") || element.href || "");
const label = truncateText(getLabelText(element, {
includeAlt: false,
includeDescendantImageAlt: true,
includePlaceholder: false,
includeText: true
}), 120);
const displayLabel = label || hrefSummary;
if (displayLabel) {
parts.push(formatValue(displayLabel));
} else {
appendFallbackIdOrName();
}
if (includeLinkUrls) {
if (hrefSummary && hrefSummary !== displayLabel) {
parts.push(`-> ${hrefSummary}`);
}
}
} else if (tagName === "BUTTON" || ["button", "menuitem", "tab"].includes(role)) {
const label = truncateText(getLabelText(element, {
includeAlt: false,
includeDescendantImageAlt: true,
includePlaceholder: false,
includeText: true
}), 120);
if (label) {
parts.push(formatValue(label));
} else {
appendFallbackIdOrName();
}
} else if (tagName === "TEXTAREA" || role === "textbox" || role === "searchbox") {
const label = truncateText(getLabelText(element, {
includeAlt: false,
includeDescendantImageAlt: false,
includePlaceholder: false,
includeText: true
}), 120);
if (label) {
parts.push(formatValue(label));
}
const placeholder = normalizeAttributeText(element.getAttribute?.("placeholder"));
if (placeholder) {
parts.push(`placeholder=${formatValue(placeholder)}`);
} else if (!label) {
appendFallbackIdOrName();
}
} else if (tagName === "SELECT" || role === "combobox") {
const label = truncateText(getLabelText(element, {
includeAlt: false,
includeDescendantImageAlt: false,
includePlaceholder: false,
includeText: true
}), 120);
if (label) {
parts.push(formatValue(label));
} else {
appendFallbackIdOrName();
}
const selectedValue = getReferenceValueMetadata(element);
const selectedOptions = selectedValue
? [selectedValue]
: [...(element.selectedOptions || [])]
.map((option) => truncateText(option.textContent || "", 48))
.filter(Boolean);
if (selectedOptions.length) {
parts.push(`selected=${formatValue(selectedOptions.join(" | "))}`);
}
} else if (tagName === "SUMMARY") {
const label = truncateText(getLabelText(element, {
includeAlt: false,
includeDescendantImageAlt: true,
includePlaceholder: false,
includeText: true
}), 120);
if (label) {
parts.push(formatValue(label));
} else {
appendFallbackIdOrName();
}
} else if (tagName === "INPUT") {
const inputType = String(element.getAttribute?.("type") || element.type || "text").toLowerCase();
if (["button", "submit", "reset"].includes(inputType)) {
const label = truncateText(getLabelText(element, {
includeAlt: false,
includeDescendantImageAlt: false,
includePlaceholder: false,
includeText: false
}) || element.value || "", 120);
if (label) {
parts.push(formatValue(label));
} else {
appendFallbackIdOrName();
}
} else if (["checkbox", "radio"].includes(inputType)) {
const label = truncateText(getLabelText(element, {
includeAlt: false,
includeDescendantImageAlt: false,
includePlaceholder: false,
includeText: false
}), 120);
if (label) {
parts.push(formatValue(label));
} else {
appendFallbackIdOrName();
}
} else if (inputType === "file") {
const label = truncateText(getLabelText(element, {
includeAlt: false,
includeDescendantImageAlt: false,
includePlaceholder: false,
includeText: false
}), 120);
if (label) {
parts.push(formatValue(label));
} else {
appendFallbackIdOrName();
}
} else {
const label = truncateText(getLabelText(element, {
includeAlt: false,
includeDescendantImageAlt: false,
includePlaceholder: false,
includeText: false
}), 120);
if (label) {
parts.push(formatValue(label));
}
const placeholder = normalizeAttributeText(element.getAttribute?.("placeholder"));
const value = inputType === "password"
? ""
: getReferenceValueMetadata(element);
if (placeholder) {
parts.push(`placeholder=${formatValue(placeholder)}`);
}
if (value) {
parts.push(`value=${formatValue(value)}`);
}
if (!label && !placeholder && !value) {
appendFallbackIdOrName();
}
}
} else if (String(element.getAttribute?.("contenteditable") || "").toLowerCase() === "true") {
const label = truncateText(getLabelText(element, {
includeAlt: false,
includeDescendantImageAlt: false,
includePlaceholder: false,
includeText: true
}), 120);
if (label) {
parts.push(formatValue(label));
} else {
appendFallbackIdOrName();
}
} else if (tagName === "IMG") {
const srcSummary = summarizeUrl(element.currentSrc || element.getAttribute?.("src") || element.src || "");
const label = truncateText(getLabelText(element, {
includeAlt: true,
includeDescendantImageAlt: false,
includePlaceholder: false,
includeText: false
}), 120);
const displayLabel = label || srcSummary;
if (displayLabel) {
parts.push(formatValue(displayLabel));
} else {
appendFallbackIdOrName();
}
} else if (role) {
const label = truncateText(getLabelText(element, {
includeAlt: false,
includeDescendantImageAlt: true,
includePlaceholder: false,
includeText: true
}), 120);
if (label) {
parts.push(formatValue(label));
} else {
appendFallbackIdOrName();
}
} else {
const label = truncateText(getLabelText(element, {
includeAlt: false,
includeDescendantImageAlt: true,
includePlaceholder: false,
includeText: true
}), 120);
if (label) {
parts.push(formatValue(label));
} else {
appendFallbackIdOrName();
}
}
return {
descriptorTags: stateMetadata.descriptorTags.slice(),
kind,
semanticTags: stateMetadata.semanticTags.slice(),
state: stateMetadata,
summary: parts.filter(Boolean).join(" ")
};
}
function createReferenceEntry(element, referenceId, options = {}) {
const nodeId = normalizeAttributeText(element.getAttribute?.("data-space-browser-node-id"));
const frameId = normalizeAttributeText(element.getAttribute?.("data-space-browser-frame-id"));
const frameChain = normalizeFrameChain(element.getAttribute?.("data-space-browser-frame-chain"));
const helperBacked = Boolean(nodeId && frameChain.length);
const summaryData = collectReferenceSummaryData(element, options);
return {
connected: helperBacked ? true : element.isConnected !== false,
dom: serializeElementSnapshot(element),
descriptorTags: summaryData.descriptorTags,
element: helperBacked ? null : element,
frameChain,
frameId,
helperBacked,
id: normalizeAttributeText(element.getAttribute?.("id")),
name: normalizeAttributeText(element.getAttribute?.("name")),
nodeId,
referenceId,
kind: summaryData.kind,
semanticTags: summaryData.semanticTags,
state: summaryData.state,
summary: summaryData.summary,
tagName: getTagName(element)
};
}
function ensureReference(element, context) {
if (context.referenceIdsByElement.has(element)) {
return context.referenceIdsByElement.get(element);
}
const referenceId = String(context.nextReferenceId++);
const entry = createReferenceEntry(element, referenceId, context.options);
context.referenceIdsByElement.set(element, referenceId);
context.entries.set(referenceId, entry);
return referenceId;
}
function renderReference(element, context) {
const referenceId = ensureReference(element, context);
const entry = context.entries.get(referenceId);
const kind = normalizeText(entry?.kind || getTagName(element).toLowerCase());
const descriptorTags = Array.isArray(entry?.descriptorTags)
? entry.descriptorTags.map((tag) => normalizeText(tag)).filter(Boolean)
: [];
const summary = normalizeText(entry?.summary || "");
const descriptor = [...descriptorTags, kind, referenceId].filter(Boolean).join(" ");
return summary ? `[${descriptor}] ${summary}` : `[${descriptor}]`;
}
function isReferenceableElement(element) {
return isInteractiveElement(element) || getTagName(element) === "IMG" || isFileInputLabel(element);
}
function collectLabelControlElements(labelElement) {
const controls = [];
const seen = new Set();
const addControl = (element) => {
if (!isElementNode(element) || seen.has(element) || !isReferenceableElement(element)) {
return;
}
seen.add(element);
controls.push(element);
};
[
"input",
"textarea",
"select",
"button",
"summary",
"a[href]",
"[role]",
"[contenteditable='true']",
"[contenteditable='']"
].forEach((selector) => {
try {
[...(labelElement.querySelectorAll?.(selector) || [])].forEach(addControl);
} catch {
// Ignore unsupported selectors in unusual DOMs.
}
});
return controls;
}
function renderControlLabelReferences(labelElement, context) {
return collectLabelControlElements(labelElement)
.map((controlElement) => renderReference(controlElement, context))
.filter(Boolean)
.join("\n");
}
function renderInlineNode(node, context) {
if (isTextNode(node)) {
const textContent = normalizeText(node.textContent || "");
if (shouldDropReadableText(textContent)) {
return "";
}
return escapeMarkdownText(textContent);
}
if (!isElementNode(node) || isHiddenElement(node)) {
return "";
}
if (isReferenceableElement(node)) {
return renderReference(node, context);
}
const tagName = getTagName(node);
if (tagName === "LABEL" && (node.getAttribute?.("for") || node.querySelector?.("input, textarea, select, button"))) {
return renderControlLabelReferences(node, context);
}
if (tagName === "BR") {
return "\n";
}
if (tagName === "STRONG" || tagName === "B") {
const content = renderInlineChildren(node, context);
return content ? `**${content}**` : "";
}
if (tagName === "EM" || tagName === "I") {
const content = renderInlineChildren(node, context);
return content ? `*${content}*` : "";
}
if (tagName === "S" || tagName === "STRIKE" || tagName === "DEL") {
const content = renderInlineChildren(node, context);
return content ? `~~${content}~~` : "";
}
if (tagName === "CODE") {
const content = normalizeText(node.textContent || "");
return content ? `\`${content.replace(/`/gu, "\\`")}\`` : "";
}
return renderInlineChildren(node, context);
}
function renderInlineChildren(element, context) {
const parts = [];
getReadableChildNodes(element).forEach((childNode) => {
const renderedChild = renderInlineNode(childNode, context);
if (renderedChild) {
parts.push(renderedChild);
}
});
return joinInlineParts(parts);
}
function renderParagraph(element, context) {
return renderInlineChildren(element, context);
}
function renderHeading(element, context) {
const level = Math.min(6, Math.max(1, Number.parseInt(getTagName(element).slice(1), 10) || 1));
const content = renderInlineChildren(element, context);
return content ? `${"#".repeat(level)} ${content}` : "";
}
function renderCodeBlock(element) {
const content = String(element.textContent || "").trimEnd();
if (!content) {
return "";
}
return `\`\`\`\n${content.replace(/```/gu, "\\`\\`\\`")}\n\`\`\``;
}
function renderBlockquote(element, context) {
const content = renderBlockChildren(element, context);
if (!content) {
return "";
}
return content
.split("\n")
.map((line) => `> ${line}`)
.join("\n");
}
function renderListItem(element, context, depth, index, ordered) {
const includeListMarkers = context.options.includeListMarkers === true;
const includeListIndentation = context.options.includeListIndentation !== false;
const marker = includeListMarkers ? (ordered ? `${index + 1}.` : "-") : "";
const indentation = includeListIndentation ? " ".repeat(Math.max(0, depth)) : "";
const inlineParts = [];
const nestedBlocks = [];
getReadableChildNodes(element).forEach((childNode) => {
if (isElementNode(childNode) && (getTagName(childNode) === "UL" || getTagName(childNode) === "OL")) {
const nestedList = renderList(childNode, context, depth + 1);
if (nestedList) {
nestedBlocks.push(nestedList);
}
return;
}
const renderedChild = renderInlineNode(childNode, context);
if (renderedChild) {
inlineParts.push(renderedChild);
}
});
const head = joinInlineParts(inlineParts);
const linePrefix = marker ? `${indentation}${marker} ` : indentation;
const lines = [`${linePrefix}${head || "(empty)"}`];
nestedBlocks.forEach((nestedBlock) => {
lines.push(indentBlock(nestedBlock, includeListIndentation ? 1 : 0));
});
return lines.join("\n");
}
function renderList(element, context, depth = 0) {
const ordered = getTagName(element) === "OL";
return getReadableElementChildren(element)
.filter((child) => getTagName(child) === "LI" && !isHiddenElement(child))
.map((item, index) => renderListItem(item, context, depth, index, ordered))
.filter(Boolean)
.join("\n");
}
function renderTableCell(element, context) {
return renderInlineChildren(element, context);
}
function renderTable(element, context) {
const rows = [...element.querySelectorAll?.(":scope > thead > tr, :scope > tbody > tr, :scope > tr, :scope > tfoot > tr") || []]
.filter((row) => getTagName(row) === "TR");
if (!rows.length) {
return "";
}
const renderedRows = rows.map((row) => {
return [...row.children]
.filter((cell) => ["TD", "TH"].includes(getTagName(cell)) && !isHiddenElement(cell))
.map((cell) => renderTableCell(cell, context));
}).filter((cells) => cells.length);
if (!renderedRows.length) {
return "";
}
const columnCount = Math.max(...renderedRows.map((cells) => cells.length));
const normalizedRows = renderedRows.map((cells) => {
const nextCells = cells.slice();
while (nextCells.length < columnCount) {
nextCells.push("");
}
return nextCells;
});
const headerRow = normalizedRows[0];
const separatorRow = headerRow.map(() => "---");
const tableLines = [
`| ${headerRow.join(" | ")} |`,
`| ${separatorRow.join(" | ")} |`
];
normalizedRows.slice(1).forEach((row) => {
tableLines.push(`| ${row.join(" | ")} |`);
});
return tableLines.join("\n");
}
function renderGenericContainer(element, context) {
return renderBlockChildren(element, context);
}
function renderElementAsBlock(element, context) {
if (!isElementNode(element) || isHiddenElement(element)) {
return "";
}
if (isReferenceableElement(element)) {
return renderReference(element, context);
}
const tagName = getTagName(element);
if (tagName === "LABEL" && (element.getAttribute?.("for") || element.querySelector?.("input, textarea, select, button"))) {
return renderControlLabelReferences(element, context);
}
if (/^H[1-6]$/u.test(tagName)) {
return renderHeading(element, context);
}
if (tagName === "P") {
return renderParagraph(element, context);
}
if (tagName === "PRE") {
return renderCodeBlock(element);
}
if (tagName === "BLOCKQUOTE") {
return renderBlockquote(element, context);
}
if (tagName === "UL" || tagName === "OL") {
return renderList(element, context);
}
if (tagName === "TABLE") {
return renderTable(element, context);
}
if (tagName === "HR") {
return "---";
}
return renderGenericContainer(element, context);
}
function renderBlockChildren(element, context) {
const blocks = [];
const inlineParts = [];
const flushInlineParts = () => {
const inlineText = joinInlineParts(inlineParts.splice(0, inlineParts.length));
if (inlineText) {
blocks.push(inlineText);
}
};
getReadableChildNodes(element).forEach((childNode) => {
if (isTextNode(childNode)) {
const rawTextContent = normalizeText(childNode.textContent || "");
if (shouldDropReadableText(rawTextContent)) {
return;
}
const textContent = escapeMarkdownText(rawTextContent);
if (textContent) {
inlineParts.push(textContent);
}
return;
}
if (!isElementNode(childNode) || isHiddenElement(childNode)) {
return;
}
const renderedChild = renderElementAsBlock(childNode, context);
if (!renderedChild) {
return;
}
if (isBlockElement(childNode) || isReferenceableElement(childNode)) {
flushInlineParts();
blocks.push(renderedChild);
return;
}
inlineParts.push(renderedChild);
});
flushInlineParts();
return joinBlocks(blocks);
}
function createCaptureContext(payload = null) {
return {
entries: new Map(),
nextReferenceId: 1,
options: {
includeLabelQuotes: normalizeIncludeLabelQuotes(payload),
includeLinkUrls: normalizeIncludeLinkUrls(payload),
includeSemanticTags: normalizeIncludeSemanticTags(payload),
includeStateTags: normalizeIncludeStateTags(payload),
includeListIndentation: normalizeIncludeListIndentation(payload),
includeListMarkers: normalizeIncludeListMarkers(payload)
},
referenceIdsByElement: new WeakMap()
};
}
function resolveSelectorTargets(payload, doc = globalThis.document) {
const selectors = normalizeSelectorList(payload);
if (!selectors.length) {
return {
includeMetaData: true,
items: [
{
key: "document",
targets: [doc?.body || doc?.documentElement].filter(Boolean)
}
]
};
}
return {
includeMetaData: false,
items: selectors.map((selector) => {
let targets = [];
try {
targets = doc === globalThis.document
? querySelectorAllDeep(selector, doc)
: [...(doc?.querySelectorAll?.(selector) || [])];
} catch (error) {
throw createNamedError(
"BrowserPageContentSelectorError",
`Browser page content could not resolve selector "${selector}".`,
{
code: "browser_page_content_selector_error",
details: {
selector
},
cause: error
}
);
}
return {
key: selector,
targets
};
})
};
}
function parseSnapshotFragment(html, parser) {
return parser.parseFromString(
`<!DOCTYPE html><html><body>${String(html || "")}</body></html>`,
"text/html"
);
}
function renderSnapshotFragment(html, captureContext, parser) {
const parsedDocument = parseSnapshotFragment(html, parser);
const blocks = [];
const inlineParts = [];
const flushInlineParts = () => {
const inlineText = joinInlineParts(inlineParts.splice(0, inlineParts.length));
if (inlineText) {
blocks.push(inlineText);
}
};
parsedDocument.body.childNodes.forEach((childNode) => {
if (isTextNode(childNode)) {
const rawTextContent = normalizeText(childNode.textContent || "");
if (shouldDropReadableText(rawTextContent)) {
return;
}
const textContent = escapeMarkdownText(rawTextContent);
if (textContent) {
inlineParts.push(textContent);
}
return;
}
if (!isElementNode(childNode) || isHiddenElement(childNode)) {
return;
}
const renderedChild = renderElementAsBlock(childNode, captureContext);
if (!renderedChild) {
return;
}
if (isBlockElement(childNode) || isReferenceableElement(childNode)) {
flushInlineParts();
blocks.push(renderedChild);
return;
}
inlineParts.push(renderedChild);
});
flushInlineParts();
return cleanReadableMarkdown(joinBlocks(blocks));
}
function captureLive(payload = null) {
const captureContext = createCaptureContext(payload);
const resolvedTargets = resolveSelectorTargets(payload);
const snapshot = {};
resolvedTargets.items.forEach((item) => {
const blocks = [];
if (resolvedTargets.includeMetaData && item.key === "document") {
const meta = collectMetaLines(globalThis.document);
if (meta) {
blocks.push(meta);
}
}
item.targets.forEach((target) => {
const renderedTarget = renderElementAsBlock(target, captureContext);
if (renderedTarget) {
blocks.push(renderedTarget);
}
});
snapshot[item.key] = cleanReadableMarkdown(joinBlocks(blocks));
});
state.captureId += 1;
state.capturedAt = Date.now();
state.backend = "live";
state.captureOptions = { ...captureContext.options };
state.entries = captureContext.entries;
return snapshot;
}
async function captureWithDomHelper(payload = null) {
const helper = requireDomHelper("capture content");
const selectors = normalizeSelectorList(payload);
const helperPayload = {
snapshotMode: "content"
};
if (selectors.length) {
helperPayload.selectors = selectors;
}
const documentSnapshot = await helper.captureDocument({
...helperPayload
});
const snapshot = {};
const parser = new globalThis.DOMParser();
const captureContext = createCaptureContext(payload);
try {
if (selectors.length && documentSnapshot?.targets && typeof documentSnapshot.targets === "object") {
selectors.forEach((selector) => {
snapshot[selector] = renderSnapshotFragment(documentSnapshot.targets?.[selector] || "", captureContext, parser);
});
state.captureId += 1;
state.capturedAt = Date.now();
state.backend = "dom_helper";
state.captureOptions = { ...captureContext.options };
state.entries = captureContext.entries;
return snapshot;
}
const parsedDocument = parser.parseFromString(String(documentSnapshot?.html || ""), "text/html");
const resolvedTargets = resolveSelectorTargets(payload, parsedDocument);
resolvedTargets.items.forEach((item) => {
const blocks = [];
if (resolvedTargets.includeMetaData && item.key === "document") {
const meta = collectMetaLines(parsedDocument);
if (meta) {
blocks.push(meta);
}
}
item.targets.forEach((target) => {
const renderedTarget = renderElementAsBlock(target, captureContext);
if (renderedTarget) {
blocks.push(renderedTarget);
}
});
snapshot[item.key] = cleanReadableMarkdown(joinBlocks(blocks));
});
state.captureId += 1;
state.capturedAt = Date.now();
state.backend = "dom_helper";
state.captureOptions = { ...captureContext.options };
state.entries = captureContext.entries;
return snapshot;
} catch (error) {
if (!isTrustedHtmlRequirementError(error)) {
throw error;
}
return captureLive(payload);
}
}
async function capture(payload = null) {
if (getDomHelper()) {
return captureWithDomHelper(payload);
}
return captureLive(payload);
}
function detailLive(entry) {
const liveState = entry.connected && entry.element
? collectElementStateMetadata(entry.element, state.captureOptions)
: entry.state || collectElementStateMetadata(null);
return {
captureId: state.captureId,
capturedAt: state.capturedAt,
connected: entry.connected,
descriptorTags: liveState.descriptorTags,
dom: entry.connected ? serializeElementSnapshot(entry.element) || entry.dom : entry.dom,
referenceId: entry.referenceId,
semanticTags: liveState.semanticTags,
state: liveState,
summary: entry.summary,
tagName: entry.tagName
};
}
async function detail(referenceId) {
const entry = requireReferenceEntry(referenceId, {
actionLabel: "detail",
requireConnected: false
});
if (entry.helperBacked) {
const helper = requireDomHelper("resolve detail");
const resolvedDetail = await helper.detailNode(entry.frameChain, entry.nodeId);
return {
captureId: state.captureId,
capturedAt: state.capturedAt,
connected: resolvedDetail?.connected !== false,
descriptorTags: Array.isArray(resolvedDetail?.descriptorTags) ? resolvedDetail.descriptorTags : (entry.descriptorTags || []),
dom: String(resolvedDetail?.dom || entry.dom || ""),
frameChain: entry.frameChain.slice(),
frameId: entry.frameId,
nodeId: entry.nodeId,
referenceId: entry.referenceId,
semanticTags: Array.isArray(resolvedDetail?.semanticTags) ? resolvedDetail.semanticTags : (entry.semanticTags || []),
state: resolvedDetail?.state || entry.state || collectElementStateMetadata(null),
summary: entry.summary,
tagName: String(resolvedDetail?.tagName || entry.tagName || "")
};
}
return detailLive(entry);
}
function requireReferenceEntry(referenceId, options = {}) {
const normalizedReferenceId = normalizeReferenceId(referenceId);
if (!normalizedReferenceId) {
throw createNamedError(
"BrowserPageContentReferenceError",
"Browser page content requests require a reference id.",
{
code: "browser_page_content_reference_required",
details: {
action: String(options.actionLabel || "resolve")
}
}
);
}
if (!state.entries.size) {
throw createNamedError(
"BrowserPageContentReferenceError",
`Browser page content has no reference capture for "${normalizedReferenceId}".`,
{
code: "browser_page_content_reference_missing_capture",
details: {
action: String(options.actionLabel || "resolve"),
referenceId: normalizedReferenceId
}
}
);
}
const entry = state.entries.get(normalizedReferenceId);
if (!entry) {
throw createNamedError(
"BrowserPageContentReferenceError",
`Browser page content could not find reference "${normalizedReferenceId}".`,
{
code: "browser_page_content_reference_not_found",
details: {
action: String(options.actionLabel || "resolve"),
referenceId: normalizedReferenceId
}
}
);
}
refreshReferenceEntry(entry);
if (options.requireConnected !== false && !entry.connected) {
throw createNamedError(
"BrowserPageContentReferenceError",
`Browser page content reference "${normalizedReferenceId}" is no longer connected.`,
{
code: "browser_page_content_reference_disconnected",
details: {
action: String(options.actionLabel || "resolve"),
referenceId: normalizedReferenceId
}
}
);
}
return entry;
}
function computeStableSelector(el) {
if (!el || el.nodeType !== 1) return null;
const doc = el.ownerDocument || document;
if (el.id && /^[A-Za-z_][\w-]*$/.test(el.id)) {
const sel = "#" + (typeof CSS !== "undefined" && CSS.escape ? CSS.escape(el.id) : el.id);
try {
if (doc.querySelectorAll(sel).length === 1) return sel;
} catch (_) {}
}
const parts = [];
let node = el;
while (node && node.nodeType === 1 && node !== doc.documentElement) {
let part = node.tagName.toLowerCase();
if (node.id && /^[A-Za-z_][\w-]*$/.test(node.id)) {
const idSel = "#" + (typeof CSS !== "undefined" && CSS.escape ? CSS.escape(node.id) : node.id);
try {
if (doc.querySelectorAll(idSel).length === 1) {
parts.unshift(idSel);
break;
}
} catch (_) {}
}
const parent = node.parentElement;
if (parent) {
const sibs = parent.children;
let idx = 0;
let sameTag = 0;
for (let i = 0; i < sibs.length; i++) {
if (sibs[i].tagName === node.tagName) {
sameTag++;
if (sibs[i] === node) idx = sameTag;
}
}
if (sameTag > 1) part += ":nth-of-type(" + idx + ")";
}
parts.unshift(part);
node = parent;
}
const sel = parts.join(" > ");
if (!sel) return null;
try {
if (doc.querySelectorAll(sel).length === 1) return sel;
} catch (_) {}
return null;
}
function boundingBoxFor(referenceId) {
const entry = requireReferenceEntry(referenceId, {
actionLabel: "boundingBox",
requireConnected: false
});
if (entry.helperBacked || !entry.element) return null;
const el = entry.element;
if (typeof el.getBoundingClientRect !== "function") return null;
try {
el.scrollIntoView({ block: "center", inline: "center", behavior: "instant" });
} catch (_) {}
const r = el.getBoundingClientRect();
const selector = computeStableSelector(el);
const hasBox = r && r.width > 0 && r.height > 0;
if (!hasBox && !selector) return null;
return {
x: hasBox ? r.left : 0,
y: hasBox ? r.top : 0,
width: hasBox ? r.width : 0,
height: hasBox ? r.height : 0,
selector: selector || null
};
}
function pointFor(referenceId, offsets = {}) {
const entry = requireReferenceEntry(referenceId, {
actionLabel: "point",
requireConnected: true
});
if (entry.helperBacked || !entry.element) {
throw createNamedError(
"BrowserPageContentActionError",
`Browser page content cannot resolve point for helper-backed reference "${entry.referenceId}".`,
{
code: "browser_page_content_point_helper_backed"
}
);
}
const element = entry.element;
scrollElementIntoView(element);
const rect = getElementRectSafe(element);
if (!rect || rect.width <= 0 || rect.height <= 0) {
throw createNamedError(
"BrowserPageContentActionError",
`Browser page content reference "${entry.referenceId}" has no visible viewport box.`,
{
code: "browser_page_content_point_no_box"
}
);
}
const offsetX = Number(offsets?.offset_x ?? offsets?.offsetX ?? 0) || 0;
const offsetY = Number(offsets?.offset_y ?? offsets?.offsetY ?? 0) || 0;
const useOffsets = offsets?.useOffsets === true || offsetX !== 0 || offsetY !== 0;
return {
rect,
selector: computeStableSelector(element),
x: rect.x + (useOffsets ? offsetX : rect.width / 2),
y: rect.y + (useOffsets ? offsetY : rect.height / 2)
};
}
function normalizeActionValues(valueOrValues) {
if (Array.isArray(valueOrValues)) {
return valueOrValues.map((value) => String(value ?? ""));
}
if (valueOrValues === null || valueOrValues === undefined) {
return [];
}
return [String(valueOrValues)];
}
function optionMatchesValue(option, value) {
const normalizedValue = normalizeText(value);
const candidates = [
option?.value,
option?.label,
option?.textContent,
option?.getAttribute?.("aria-label"),
option?.getAttribute?.("data-value"),
option?.getAttribute?.("id")
].map((candidate) => normalizeText(candidate));
return candidates.some((candidate) => candidate === normalizedValue);
}
function findNativeSelectOption(selectElement, value) {
const options = [...(selectElement.options || [])];
return options.find((option) => optionMatchesValue(option, value)) || null;
}
function setNativeChecked(element, checked) {
const descriptor = Object.getOwnPropertyDescriptor(globalThis.HTMLInputElement?.prototype || {}, "checked");
if (typeof descriptor?.set === "function") {
descriptor.set.call(element, Boolean(checked));
} else {
element.checked = Boolean(checked);
}
}
async function selectNativeElement(entry, values) {
const element = entry.element;
const beforeSnapshot = captureActionEffectSnapshot(element);
const requestedValues = values.length ? values : [""];
const appliedValues = [];
const {
observedMutations
} = await withObservedActionWindow(beforeSnapshot.observationRoot, async () => {
scrollElementIntoView(element);
focusElement(element);
if (element.multiple) {
const matchedOptions = requestedValues.map((requestedValue) => {
const option = findNativeSelectOption(element, requestedValue);
if (!option) {
throw createNamedError(
"BrowserPageContentActionError",
`Browser page content could not find select option "${requestedValue}".`,
{
code: "browser_page_content_select_option_not_found"
}
);
}
return option;
});
const matchedSet = new Set(matchedOptions);
[...(element.options || [])].forEach((option) => {
option.selected = matchedSet.has(option);
});
matchedOptions.forEach((option) => appliedValues.push(option.value));
} else {
const option = findNativeSelectOption(element, requestedValues[0]);
if (!option) {
throw createNamedError(
"BrowserPageContentActionError",
`Browser page content could not find select option "${requestedValues[0]}".`,
{
code: "browser_page_content_select_option_not_found"
}
);
}
appliedValues.push(setNativeValue(element, option.value));
}
dispatchDomEvent(element, "input", "InputEvent", {
inputType: "insertReplacementText"
});
dispatchDomEvent(element, "change");
return appliedValues.slice();
});
refreshReferenceEntry(entry);
return buildActionResult(entry, {
...buildActionEffectResult(entry, beforeSnapshot, captureActionEffectSnapshot(element), observedMutations),
values: appliedValues.slice()
});
}
function ariaOptionMatchesValue(option, value) {
const normalizedValue = normalizeText(value);
const candidates = [
option?.getAttribute?.("aria-label"),
option?.getAttribute?.("data-value"),
option?.getAttribute?.("value"),
option?.getAttribute?.("id"),
getElementText(option)
].map((candidate) => normalizeText(candidate));
return candidates.some((candidate) => candidate === normalizedValue);
}
function visibleAriaOptions(root) {
const scope = isElementNode(root) && String(root.getAttribute?.("role") || "").trim().toLowerCase() === "listbox"
? root
: globalThis.document;
try {
return [...(scope.querySelectorAll?.("[role='option']") || [])]
.filter((option) => isElementNode(option) && !isHiddenElement(option));
} catch {
return [];
}
}
function findAriaOption(root, value) {
const matches = visibleAriaOptions(root).filter((option) => ariaOptionMatchesValue(option, value));
return matches.length === 1 ? matches[0] : null;
}
async function selectAriaElement(entry, values) {
const element = entry.element;
const role = String(element.getAttribute?.("role") || "").trim().toLowerCase();
if (!["combobox", "listbox"].includes(role)) {
throw createNamedError(
"BrowserPageContentActionError",
`Browser page content cannot select options on <${getTagName(element).toLowerCase()}>.`,
{
code: "browser_page_content_select_unsupported"
}
);
}
const beforeSnapshot = captureActionEffectSnapshot(element);
const requestedValues = values.length ? values : [""];
const appliedValues = [];
const {
observedMutations
} = await withObservedActionWindow(beforeSnapshot.observationRoot, async () => {
scrollElementIntoView(element);
focusElement(element);
if (role === "combobox") {
dispatchDomEvent(element, "mousedown", "MouseEvent", { button: 0 });
if (typeof element.click === "function") {
element.click();
} else {
dispatchDomEvent(element, "click", "MouseEvent", { button: 0 });
}
await delayMs(80);
}
for (const requestedValue of requestedValues) {
const option = findAriaOption(element, requestedValue);
if (!option) {
throw createNamedError(
"BrowserPageContentActionError",
`Browser page content could not safely find one ARIA option "${requestedValue}".`,
{
code: "browser_page_content_aria_option_not_found"
}
);
}
scrollElementIntoView(option);
dispatchDomEvent(option, "mousedown", "MouseEvent", { button: 0 });
if (typeof option.click === "function") {
option.click();
} else {
dispatchDomEvent(option, "click", "MouseEvent", { button: 0 });
}
appliedValues.push(requestedValue);
await delayMs(40);
}
});
refreshReferenceEntry(entry);
return buildActionResult(entry, {
...buildActionEffectResult(entry, beforeSnapshot, captureActionEffectSnapshot(element), observedMutations),
values: appliedValues.slice()
});
}
async function selectReference(referenceId, valueOrValues) {
const entry = requireReferenceEntry(referenceId, {
actionLabel: "select"
});
if (entry.helperBacked) {
throw createNamedError(
"BrowserPageContentActionError",
`Browser page content cannot select helper-backed reference "${entry.referenceId}".`,
{
code: "browser_page_content_select_helper_backed"
}
);
}
const element = entry.element;
const values = normalizeActionValues(valueOrValues);
if (getTagName(element) === "SELECT") {
return selectNativeElement(entry, values);
}
return selectAriaElement(entry, values);
}
function checkedStateForElement(element) {
const tagName = getTagName(element);
const role = String(element.getAttribute?.("role") || "").trim().toLowerCase();
if (tagName === "INPUT") {
const inputType = String(element.getAttribute?.("type") || element.type || "").toLowerCase();
if (["checkbox", "radio"].includes(inputType)) {
return Boolean(element.checked);
}
}
if (["checkbox", "radio", "switch", "menuitemcheckbox", "menuitemradio"].includes(role)) {
return String(element.getAttribute?.("aria-checked") || "").trim().toLowerCase() === "true";
}
if (role === "button" && element.hasAttribute?.("aria-pressed")) {
return String(element.getAttribute?.("aria-pressed") || "").trim().toLowerCase() === "true";
}
return null;
}
async function setCheckedReference(referenceId, checked = true) {
const entry = requireReferenceEntry(referenceId, {
actionLabel: "setChecked"
});
if (entry.helperBacked) {
throw createNamedError(
"BrowserPageContentActionError",
`Browser page content cannot set helper-backed reference "${entry.referenceId}".`,
{
code: "browser_page_content_checked_helper_backed"
}
);
}
const element = entry.element;
const beforeSnapshot = captureActionEffectSnapshot(element);
const desiredChecked = Boolean(checked);
const tagName = getTagName(element);
const role = String(element.getAttribute?.("role") || "").trim().toLowerCase();
const currentChecked = checkedStateForElement(element);
if (currentChecked === null) {
throw createNamedError(
"BrowserPageContentActionError",
`Browser page content cannot set checked state on <${tagName.toLowerCase()}>.`,
{
code: "browser_page_content_checked_unsupported"
}
);
}
const {
observedMutations
} = await withObservedActionWindow(beforeSnapshot.observationRoot, async () => {
scrollElementIntoView(element);
focusElement(element);
if (tagName === "INPUT") {
setNativeChecked(element, desiredChecked);
dispatchDomEvent(element, "input", "InputEvent", {
inputType: "insertReplacementText"
});
dispatchDomEvent(element, "change");
} else if (["checkbox", "radio", "switch", "menuitemcheckbox", "menuitemradio"].includes(role)) {
if (currentChecked !== desiredChecked) {
if (typeof element.click === "function") {
element.click();
} else {
dispatchDomEvent(element, "click", "MouseEvent", {
button: 0
});
}
await delayMs(40);
}
if (checkedStateForElement(element) !== desiredChecked) {
element.setAttribute("aria-checked", desiredChecked ? "true" : "false");
dispatchDomEvent(element, "input", "InputEvent", {
inputType: "insertReplacementText"
});
dispatchDomEvent(element, "change");
}
} else if (role === "button" && element.hasAttribute?.("aria-pressed")) {
if (currentChecked !== desiredChecked) {
if (typeof element.click === "function") {
element.click();
} else {
dispatchDomEvent(element, "click", "MouseEvent", {
button: 0
});
}
await delayMs(40);
}
if (checkedStateForElement(element) !== desiredChecked) {
element.setAttribute("aria-pressed", desiredChecked ? "true" : "false");
}
}
});
refreshReferenceEntry(entry);
return buildActionResult(entry, {
...buildActionEffectResult(entry, beforeSnapshot, captureActionEffectSnapshot(element), observedMutations),
checked: desiredChecked
});
}
function resolveFileInputElement(referenceId) {
const entry = requireReferenceEntry(referenceId, {
actionLabel: "fileInput"
});
if (entry.helperBacked) {
throw createNamedError(
"BrowserPageContentActionError",
`Browser page content cannot upload files through helper-backed reference "${entry.referenceId}".`,
{
code: "browser_page_content_file_input_helper_backed"
}
);
}
const element = entry.element;
const isFileInput = (candidate) => {
return getTagName(candidate) === "INPUT"
&& String(candidate.getAttribute?.("type") || candidate.type || "").toLowerCase() === "file";
};
if (isFileInput(element)) {
return element;
}
if (getTagName(element) === "LABEL") {
if (isFileInput(element.control)) {
return element.control;
}
const labelledInput = element.querySelector?.("input[type='file']");
if (isFileInput(labelledInput)) {
return labelledInput;
}
const forId = normalizeAttributeText(element.getAttribute?.("for"));
if (forId) {
const byId = element.ownerDocument?.getElementById?.(forId);
if (isFileInput(byId)) {
return byId;
}
}
}
const descendantInput = element.querySelector?.("input[type='file']");
if (isFileInput(descendantInput)) {
return descendantInput;
}
const closestLabel = element.closest?.("label");
if (closestLabel) {
if (isFileInput(closestLabel.control)) {
return closestLabel.control;
}
const labelledInput = closestLabel.querySelector?.("input[type='file']");
if (isFileInput(labelledInput)) {
return labelledInput;
}
}
return null;
}
function fileInputFor(referenceId) {
const input = resolveFileInputElement(referenceId);
if (!input) {
throw createNamedError(
"BrowserPageContentActionError",
`Browser page content reference "${normalizeReferenceId(referenceId)}" is not a file input or associated label.`,
{
code: "browser_page_content_file_input_not_found"
}
);
}
return {
accept: normalizeAttributeText(input.getAttribute?.("accept")),
multiple: Boolean(input.multiple),
name: normalizeAttributeText(input.getAttribute?.("name")),
selector: computeStableSelector(input),
tagName: getTagName(input),
type: String(input.getAttribute?.("type") || input.type || "").toLowerCase()
};
}
function fileInputElementFor(referenceId) {
return resolveFileInputElement(referenceId);
}
function refreshReferenceEntry(entry) {
if (!entry || entry.helperBacked || !entry.element) {
return entry;
}
entry.connected = entry.element.isConnected !== false;
if (entry.connected) {
entry.dom = serializeElementSnapshot(entry.element) || entry.dom;
entry.id = normalizeAttributeText(entry.element.getAttribute?.("id"));
entry.name = normalizeAttributeText(entry.element.getAttribute?.("name"));
const summaryData = collectReferenceSummaryData(entry.element, state.captureOptions);
entry.descriptorTags = summaryData.descriptorTags;
entry.kind = summaryData.kind;
entry.semanticTags = summaryData.semanticTags;
entry.state = summaryData.state;
entry.summary = summaryData.summary;
entry.tagName = getTagName(entry.element);
}
return entry;
}
function scrollElementIntoView(element) {
try {
element.scrollIntoView?.({
behavior: "auto",
block: "center",
inline: "center"
});
return true;
} catch {
return false;
}
}
function focusElement(element) {
try {
element.focus?.({
preventScroll: true
});
return true;
} catch {
try {
element.focus?.();
return true;
} catch {
return false;
}
}
}
function describeActiveElement(element) {
if (!isElementNode(element)) {
return "";
}
const tagName = getTagName(element).toLowerCase();
const id = normalizeAttributeText(element.getAttribute?.("id"));
const name = normalizeAttributeText(element.getAttribute?.("name"));
const label = truncateText(getLabelText(element, {
includeAlt: false,
includeDescendantImageAlt: true,
includePlaceholder: false,
includeText: false
}), 48);
return [tagName, id ? `#${id}` : "", name ? `name=${name}` : "", label].filter(Boolean).join(" ");
}
function getActionObservationRoot(element) {
if (!isElementNode(element)) {
return globalThis.document?.body || globalThis.document?.documentElement || null;
}
return element.closest?.("form, fieldset, dialog, [role='dialog'], [role='alert'], [role='status'], [aria-live], article, section, main, li, tr, td, th")
|| element.parentElement
|| element;
}
function getElementDirectText(element) {
if (!isElementNode(element)) {
return "";
}
return normalizeText(
[...(element.childNodes || [])]
.filter((node) => isTextNode(node))
.map((node) => node.textContent || "")
.join(" ")
);
}
function collectNearbyTextEntries(root, limit = 24) {
if (!isElementNode(root)) {
return [];
}
const entries = [];
const seen = new Set();
const acceptElement = (element) => {
if (!isElementNode(element) || isHiddenElement(element) || entries.length >= limit) {
return;
}
const role = normalizeText(element.getAttribute?.("role")).toLowerCase();
const directText = getElementDirectText(element);
const fallbackText = ["alert", "status"].includes(role) || element.hasAttribute?.("aria-live")
? getElementText(element)
: "";
const text = truncateText(directText || fallbackText, 220);
if (!text) {
return;
}
const key = `${role}|${text}`;
if (seen.has(key)) {
return;
}
seen.add(key);
const state = collectElementStateMetadata(element, {
includeSemanticTags: true,
includeStateTags: true
});
entries.push({
invalid: state.invalid === true,
role,
semanticTone: state.semanticTone || "",
text
});
};
acceptElement(root);
const walker = globalThis.document?.createTreeWalker?.(root, globalThis.NodeFilter?.SHOW_ELEMENT ?? 1);
if (!walker) {
return entries;
}
let currentNode = walker.nextNode();
while (currentNode && entries.length < limit) {
acceptElement(currentNode);
currentNode = walker.nextNode();
}
return entries;
}
function captureActionEffectSnapshot(element) {
const observationRoot = getActionObservationRoot(element);
return {
activeElement: describeActiveElement(globalThis.document?.activeElement),
observationRoot,
observationText: truncateText(getElementText(observationRoot), 2000),
targetDom: truncateText(serializeElementSnapshot(element), 2000),
targetState: collectElementStateMetadata(element, {
includeSemanticTags: true,
includeStateTags: true
}),
textEntries: collectNearbyTextEntries(observationRoot),
value: getReferenceValueMetadata(element)
};
}
async function waitForObservedActionWindow(observationRoot, {
quietMs = 40,
timeoutMs = 180
} = {}) {
const target = observationRoot?.ownerDocument?.body
|| observationRoot?.ownerDocument?.documentElement
|| globalThis.document?.body
|| globalThis.document?.documentElement;
if (!target || typeof globalThis.MutationObserver !== "function") {
await delayMs(timeoutMs);
return {
attributeNames: [],
mutationCount: 0
};
}
const attributeNames = new Set();
let lastMutationAt = 0;
let mutationCount = 0;
const observer = new globalThis.MutationObserver((mutations) => {
mutationCount += mutations.length;
lastMutationAt = Date.now();
mutations.forEach((mutation) => {
if (mutation.type === "attributes" && mutation.attributeName) {
attributeNames.add(String(mutation.attributeName));
}
});
});
try {
observer.observe(target, {
attributes: true,
characterData: true,
childList: true,
subtree: true
});
const startedAt = Date.now();
while (Date.now() - startedAt < timeoutMs) {
await delayMs(20);
if (mutationCount > 0 && Date.now() - lastMutationAt >= quietMs) {
break;
}
}
} finally {
observer.disconnect();
}
return {
attributeNames: [...attributeNames],
mutationCount
};
}
async function withObservedActionWindow(observationRoot, action, options = {}) {
const target = observationRoot?.ownerDocument?.body
|| observationRoot?.ownerDocument?.documentElement
|| globalThis.document?.body
|| globalThis.document?.documentElement;
if (!target || typeof globalThis.MutationObserver !== "function") {
const result = await action();
const observedMutations = await waitForObservedActionWindow(observationRoot, options);
return {
observedMutations,
result
};
}
const attributeNames = new Set();
let lastMutationAt = 0;
let mutationCount = 0;
const observer = new globalThis.MutationObserver((mutations) => {
mutationCount += mutations.length;
lastMutationAt = Date.now();
mutations.forEach((mutation) => {
if (mutation.type === "attributes" && mutation.attributeName) {
attributeNames.add(String(mutation.attributeName));
}
});
});
try {
observer.observe(target, {
attributes: true,
characterData: true,
childList: true,
subtree: true
});
const result = await action();
const quietMs = Math.max(0, Number(options.quietMs) || 40);
const timeoutMs = Math.max(0, Number(options.timeoutMs) || 180);
const startedAt = Date.now();
while (Date.now() - startedAt < timeoutMs) {
await delayMs(20);
if (mutationCount > 0 && Date.now() - lastMutationAt >= quietMs) {
break;
}
}
return {
observedMutations: {
attributeNames: [...attributeNames],
mutationCount
},
result
};
} finally {
observer.disconnect();
}
}
function compareDescriptorTags(beforeTags = [], afterTags = []) {
const beforeValue = beforeTags.filter(Boolean).join("|");
const afterValue = afterTags.filter(Boolean).join("|");
return beforeValue !== afterValue;
}
function buildActionEffectResult(entry, beforeSnapshot, afterSnapshot, observedMutations, extra = {}) {
const newTextEntries = afterSnapshot.textEntries.filter((entryData) => {
return !beforeSnapshot.textEntries.some((beforeEntry) => beforeEntry.text === entryData.text);
});
const validationEntries = newTextEntries.filter((entryData) => {
return entryData.invalid
|| ["alert", "status"].includes(entryData.role)
|| ["error", "warning"].includes(entryData.semanticTone);
});
const focusChanged = beforeSnapshot.activeElement !== afterSnapshot.activeElement;
const nearbyTextChanged = beforeSnapshot.observationText !== afterSnapshot.observationText;
const valueChanged = beforeSnapshot.value !== afterSnapshot.value;
const checkedChanged = beforeSnapshot.targetState.checked !== afterSnapshot.targetState.checked;
const selectedChanged = beforeSnapshot.targetState.selected !== afterSnapshot.targetState.selected;
const expandedChanged = beforeSnapshot.targetState.expanded !== afterSnapshot.targetState.expanded;
const pressedChanged = beforeSnapshot.targetState.pressed !== afterSnapshot.targetState.pressed;
const descriptorChanged = compareDescriptorTags(beforeSnapshot.targetState.descriptorTags, afterSnapshot.targetState.descriptorTags);
const targetDomChanged = beforeSnapshot.targetDom !== afterSnapshot.targetDom;
const domChanged = Boolean(observedMutations.mutationCount) || targetDomChanged || nearbyTextChanged;
const status = {
alertTextAdded: newTextEntries.some((entryData) => ["alert", "status"].includes(entryData.role)),
checkedChanged,
descriptorChanged,
domChanged,
expandedChanged,
focusChanged,
nearbyTextChanged,
pressedChanged,
reacted: false,
selectedChanged,
targetChanged: descriptorChanged || targetDomChanged || valueChanged || checkedChanged || selectedChanged || expandedChanged || pressedChanged,
targetDomChanged,
valueChanged,
validationTextAdded: validationEntries.length > 0
};
status.reacted = Object.entries(status).some(([key, value]) => key !== "reacted" && value === true);
status.noObservedEffect = !status.reacted;
return {
...extra,
descriptorTags: afterSnapshot.targetState.descriptorTags.slice(),
effect: {
mutationAttributes: observedMutations.attributeNames.slice(0, 8),
mutationCount: observedMutations.mutationCount,
newText: newTextEntries.map((entryData) => entryData.text).slice(0, 3),
semanticHints: [...new Set(newTextEntries.map((entryData) => entryData.semanticTone).filter(Boolean))].slice(0, 3),
validationText: validationEntries.map((entryData) => entryData.text).slice(0, 3)
},
semanticTags: afterSnapshot.targetState.semanticTags.slice(),
state: afterSnapshot.targetState,
status
};
}
function buildActionResult(entry, extra = {}) {
return {
captureId: state.captureId,
descriptorTags: Array.isArray(entry?.descriptorTags) ? entry.descriptorTags.slice() : [],
referenceId: entry.referenceId,
semanticTags: Array.isArray(entry?.semanticTags) ? entry.semanticTags.slice() : [],
state: entry.state || collectElementStateMetadata(entry.element, state.captureOptions),
summary: entry.summary,
tagName: entry.tagName,
...extra
};
}
function buildHelperBackedActionResult(entry, helperResult, extra = {}) {
return {
captureId: state.captureId,
descriptorTags: Array.isArray(helperResult?.descriptorTags) ? helperResult.descriptorTags : (entry.descriptorTags || []),
frameChain: entry.frameChain.slice(),
frameId: entry.frameId,
nodeId: entry.nodeId,
referenceId: entry.referenceId,
semanticTags: Array.isArray(helperResult?.semanticTags) ? helperResult.semanticTags : (entry.semanticTags || []),
state: helperResult?.state || entry.state || collectElementStateMetadata(null),
summary: entry.summary,
tagName: String(helperResult?.tagName || entry.tagName || ""),
...extra
};
}
function mergeActionOutcomeResults(...results) {
const normalizedResults = results.filter(Boolean);
const mergedStatus = {};
const mergedEffect = {
mutationAttributes: [],
mutationCount: 0,
newText: [],
semanticHints: [],
validationText: []
};
normalizedResults.forEach((result) => {
Object.entries(result?.status || {}).forEach(([key, value]) => {
if (typeof value === "boolean") {
mergedStatus[key] = mergedStatus[key] === true || value === true;
}
});
if (Number.isFinite(result?.effect?.mutationCount)) {
mergedEffect.mutationCount += Number(result.effect.mutationCount);
}
["mutationAttributes", "newText", "semanticHints", "validationText"].forEach((key) => {
const values = Array.isArray(result?.effect?.[key]) ? result.effect[key] : [];
values.forEach((value) => {
if (value && !mergedEffect[key].includes(value)) {
mergedEffect[key].push(value);
}
});
});
});
mergedStatus.reacted = Object.entries(mergedStatus).some(([key, value]) => key !== "reacted" && key !== "noObservedEffect" && value === true);
mergedStatus.noObservedEffect = !mergedStatus.reacted;
return {
effect: mergedEffect,
status: mergedStatus
};
}
function dispatchDomEvent(target, eventName, EventType = "Event", options = {}) {
const EventConstructor = typeof globalThis[EventType] === "function"
? globalThis[EventType]
: globalThis.Event;
const event = new EventConstructor(eventName, {
bubbles: true,
cancelable: true,
composed: true,
...options
});
target.dispatchEvent(event);
return event;
}
function dispatchKeyboardEvent(target, eventName, options = {}) {
const KeyboardEventConstructor = typeof globalThis.KeyboardEvent === "function"
? globalThis.KeyboardEvent
: globalThis.Event;
const event = new KeyboardEventConstructor(eventName, {
bubbles: true,
cancelable: true,
composed: true,
code: "Enter",
key: "Enter",
...options
});
[
["charCode", Number(options.charCode ?? 0)],
["keyCode", Number(options.keyCode ?? 13)],
["which", Number(options.which ?? 13)]
].forEach(([propertyName, propertyValue]) => {
try {
if (typeof event[propertyName] !== "number") {
Object.defineProperty(event, propertyName, {
configurable: true,
enumerable: true,
value: propertyValue
});
}
} catch {
// Ignore read-only KeyboardEvent properties.
}
});
target.dispatchEvent(event);
return event;
}
function setNativeValue(element, nextValue) {
const tagName = getTagName(element);
const normalizedValue = String(nextValue ?? "");
if (tagName === "INPUT") {
const descriptor = Object.getOwnPropertyDescriptor(globalThis.HTMLInputElement?.prototype || {}, "value");
if (typeof descriptor?.set === "function") {
descriptor.set.call(element, normalizedValue);
} else {
element.value = normalizedValue;
}
return normalizedValue;
}
if (tagName === "TEXTAREA") {
const descriptor = Object.getOwnPropertyDescriptor(globalThis.HTMLTextAreaElement?.prototype || {}, "value");
if (typeof descriptor?.set === "function") {
descriptor.set.call(element, normalizedValue);
} else {
element.value = normalizedValue;
}
return normalizedValue;
}
if (tagName === "SELECT") {
const matchedOption = [...(element.options || [])].find((option) => {
return option.value === normalizedValue
|| normalizeText(option.textContent || "") === normalizeText(normalizedValue)
|| normalizeText(option.label || "") === normalizeText(normalizedValue);
});
const resolvedValue = matchedOption ? matchedOption.value : normalizedValue;
const descriptor = Object.getOwnPropertyDescriptor(globalThis.HTMLSelectElement?.prototype || {}, "value");
if (typeof descriptor?.set === "function") {
descriptor.set.call(element, resolvedValue);
} else {
element.value = resolvedValue;
}
return resolvedValue;
}
if (String(element.getAttribute?.("contenteditable") || "").toLowerCase() === "true") {
element.textContent = normalizedValue;
return normalizedValue;
}
throw createNamedError(
"BrowserPageContentActionError",
`Browser page content cannot type into <${getTagName(element).toLowerCase()}>.`,
{
code: "browser_page_content_type_unsupported"
}
);
}
async function updateElementValue(referenceId, value) {
const entry = requireReferenceEntry(referenceId, {
actionLabel: "type"
});
if (entry.helperBacked) {
const helper = requireDomHelper("type into reference");
const typedResult = await helper.typeNode(entry.frameChain, entry.nodeId, value);
return buildHelperBackedActionResult(entry, typedResult, {
effect: typedResult?.effect || {},
status: typedResult?.status || {},
value: typedResult?.value ?? String(value ?? "")
});
}
const element = entry.element;
const beforeSnapshot = captureActionEffectSnapshot(element);
const {
result: appliedValue,
observedMutations
} = await withObservedActionWindow(beforeSnapshot.observationRoot, async () => {
scrollElementIntoView(element);
focusElement(element);
const nextValue = setNativeValue(element, value);
if (typeof element.setSelectionRange === "function") {
try {
element.setSelectionRange(String(nextValue).length, String(nextValue).length);
} catch {
// Ignore selection errors for unsupported input types.
}
}
dispatchDomEvent(element, "beforeinput", "InputEvent", {
data: String(value ?? ""),
inputType: "insertText"
});
dispatchDomEvent(element, "input", "InputEvent", {
data: String(value ?? ""),
inputType: "insertText"
});
dispatchDomEvent(element, "change");
return nextValue;
});
refreshReferenceEntry(entry);
return buildActionResult(entry, {
...buildActionEffectResult(entry, beforeSnapshot, captureActionEffectSnapshot(element), observedMutations),
value: appliedValue
});
}
async function activateElement(referenceId) {
const entry = requireReferenceEntry(referenceId, {
actionLabel: "click"
});
if (entry.helperBacked) {
const helper = requireDomHelper("click reference");
const clickedResult = await helper.clickNode(entry.frameChain, entry.nodeId);
return buildHelperBackedActionResult(entry, clickedResult, {
effect: clickedResult?.effect || {},
status: clickedResult?.status || {}
});
}
const element = entry.element;
const beforeSnapshot = captureActionEffectSnapshot(element);
scrollElementIntoView(element);
focusElement(element);
if (beforeSnapshot.targetState.disabled) {
throw createNamedError(
"BrowserPageContentActionError",
`Browser page content reference "${entry.referenceId}" is disabled.`,
{
code: "browser_page_content_click_disabled"
}
);
}
const {
observedMutations
} = await withObservedActionWindow(beforeSnapshot.observationRoot, async () => {
if (typeof element.click === "function") {
element.click();
} else {
dispatchDomEvent(element, "click", "MouseEvent", {
button: 0
});
}
});
refreshReferenceEntry(entry);
return buildActionResult(entry, buildActionEffectResult(
entry,
beforeSnapshot,
captureActionEffectSnapshot(element),
observedMutations
));
}
async function submitElement(referenceId) {
const entry = requireReferenceEntry(referenceId, {
actionLabel: "submit"
});
if (entry.helperBacked) {
const helper = requireDomHelper("submit reference");
const submittedResult = await helper.submitNode(entry.frameChain, entry.nodeId);
return buildHelperBackedActionResult(entry, submittedResult, {
effect: submittedResult?.effect || {},
status: submittedResult?.status || {}
});
}
const element = entry.element;
const tagName = getTagName(element);
const beforeSnapshot = captureActionEffectSnapshot(element);
const {
observedMutations
} = await withObservedActionWindow(beforeSnapshot.observationRoot, async () => {
scrollElementIntoView(element);
focusElement(element);
if (tagName === "FORM") {
if (typeof element.requestSubmit === "function") {
element.requestSubmit();
} else {
const submitEvent = dispatchDomEvent(element, "submit");
if (!submitEvent.defaultPrevented) {
element.submit?.();
}
}
} else if (typeof element.form?.requestSubmit === "function") {
if (tagName === "BUTTON" || tagName === "INPUT") {
element.form.requestSubmit(element);
} else {
element.form.requestSubmit();
}
} else if (element.form) {
const submitEvent = dispatchDomEvent(element.form, "submit");
if (!submitEvent.defaultPrevented) {
element.form.submit?.();
}
} else if (typeof element.click === "function") {
element.click();
} else {
throw createNamedError(
"BrowserPageContentActionError",
`Browser page content cannot submit reference "${entry.referenceId}".`,
{
code: "browser_page_content_submit_unsupported"
}
);
}
});
refreshReferenceEntry(entry);
return buildActionResult(entry, buildActionEffectResult(
entry,
beforeSnapshot,
captureActionEffectSnapshot(element),
observedMutations
));
}
function shouldEnterSubmitForm(element) {
const tagName = getTagName(element);
if (tagName !== "INPUT") {
return false;
}
const inputType = String(element.getAttribute?.("type") || element.type || "text").toLowerCase();
return ![
"button",
"checkbox",
"color",
"file",
"hidden",
"image",
"radio",
"range",
"reset",
"submit"
].includes(inputType);
}
async function pressEnterElement(referenceId, actionLabel = "type_submit") {
const entry = requireReferenceEntry(referenceId, {
actionLabel
});
if (entry.helperBacked) {
const helper = requireDomHelper("press enter on reference");
const submittedResult = await helper.typeSubmitNode(entry.frameChain, entry.nodeId, "");
return buildHelperBackedActionResult(entry, submittedResult, {
effect: submittedResult?.effect || {},
status: submittedResult?.status || {}
});
}
const element = entry.element;
const beforeSnapshot = captureActionEffectSnapshot(element);
const {
observedMutations
} = await withObservedActionWindow(beforeSnapshot.observationRoot, async () => {
scrollElementIntoView(element);
focusElement(element);
const keydownEvent = dispatchKeyboardEvent(element, "keydown", {
charCode: 0,
keyCode: 13,
which: 13
});
const keypressEvent = dispatchKeyboardEvent(element, "keypress", {
charCode: 13,
keyCode: 13,
which: 13
});
const keyupEvent = dispatchKeyboardEvent(element, "keyup", {
charCode: 0,
keyCode: 13,
which: 13
});
if (
!keydownEvent.defaultPrevented
&& !keypressEvent.defaultPrevented
&& !keyupEvent.defaultPrevented
&& shouldEnterSubmitForm(element)
) {
if (typeof element.form?.requestSubmit === "function") {
element.form.requestSubmit();
} else if (element.form) {
const submitEvent = dispatchDomEvent(element.form, "submit");
if (!submitEvent.defaultPrevented) {
element.form.submit?.();
}
}
}
});
refreshReferenceEntry(entry);
return buildActionResult(entry, buildActionEffectResult(
entry,
beforeSnapshot,
captureActionEffectSnapshot(element),
observedMutations
));
}
async function typeAndSubmit(referenceId, value) {
const entry = requireReferenceEntry(referenceId, {
actionLabel: "type_submit"
});
if (entry.helperBacked) {
const helper = requireDomHelper("type and submit reference");
const submittedResult = await helper.typeSubmitNode(entry.frameChain, entry.nodeId, value);
return buildHelperBackedActionResult(entry, submittedResult, {
effect: submittedResult?.effect || {},
status: submittedResult?.status || {},
value: submittedResult?.value ?? String(value ?? "")
});
}
const typed = await updateElementValue(referenceId, value);
const submitted = await pressEnterElement(referenceId);
const mergedOutcome = mergeActionOutcomeResults(typed, submitted);
return {
...submitted,
...mergedOutcome,
value: typed.value
};
}
async function scrollToReference(referenceId) {
const entry = requireReferenceEntry(referenceId, {
actionLabel: "scroll"
});
if (entry.helperBacked) {
const helper = requireDomHelper("scroll to reference");
const scrollResult = await helper.scrollNode(entry.frameChain, entry.nodeId);
return buildHelperBackedActionResult(entry, scrollResult, {
effect: scrollResult?.effect || {},
status: scrollResult?.status || {}
});
}
const beforeSnapshot = captureActionEffectSnapshot(entry.element);
scrollElementIntoView(entry.element);
focusElement(entry.element);
refreshReferenceEntry(entry);
const afterSnapshot = captureActionEffectSnapshot(entry.element);
const scrollEffect = buildActionEffectResult(entry, beforeSnapshot, afterSnapshot, {
attributeNames: [],
mutationCount: 0
});
return buildActionResult(entry, {
...scrollEffect,
status: {
...scrollEffect.status,
reacted: true,
noObservedEffect: false
}
});
}
function cssEscape(value) {
const rawValue = String(value || "");
if (!rawValue) {
return "";
}
if (typeof globalThis.CSS?.escape === "function") {
return globalThis.CSS.escape(rawValue);
}
return rawValue.replace(/[^a-zA-Z0-9_-]/gu, (character) => `\\${character}`);
}
function getClassSummary(element) {
try {
return [...(element?.classList || [])]
.map((className) => normalizeAttributeText(className))
.filter(Boolean)
.slice(0, 4)
.join(" ");
} catch {
return "";
}
}
function buildCssSelector(element) {
if (!isElementNode(element)) {
return "";
}
const id = normalizeAttributeText(element.getAttribute?.("id"));
if (id) {
return `#${cssEscape(id)}`;
}
const parts = [];
let current = element;
while (isElementNode(current) && current !== globalThis.document?.documentElement && parts.length < 6) {
const tagName = getTagName(current).toLowerCase();
if (!tagName) {
break;
}
let part = tagName;
const classes = getClassSummary(current)
.split(/\s+/u)
.filter(Boolean)
.slice(0, 2);
if (classes.length && !["body", "html"].includes(tagName)) {
part += classes.map((className) => `.${cssEscape(className)}`).join("");
}
const parent = current.parentElement;
if (parent) {
const siblings = [...parent.children].filter((sibling) => getTagName(sibling) === getTagName(current));
if (siblings.length > 1) {
part += `:nth-of-type(${siblings.indexOf(current) + 1})`;
}
}
parts.unshift(part);
if (tagName === "body") {
break;
}
current = parent;
}
return parts.join(" > ");
}
function sanitizeAnnotationDom(value) {
return truncateText(
String(value || "")
.replace(/(<input\b(?=[^>]*\btype\s*=\s*(["'])?password\2?)[^>]*?)\s+value\s*=\s*(["'])[\s\S]*?\3/giu, "$1 value=\"[redacted]\"")
.replace(/\svalue\s*=\s*(["'])[\s\S]{0,600}?\1/giu, " value=\"[redacted]\"")
.replace(/\sdata-space-browser-live-value\s*=\s*(["'])[\s\S]{0,600}?\1/giu, "")
.replace(/\sdata-space-browser-selected-text\s*=\s*(["'])[\s\S]{0,600}?\1/giu, ""),
1200
);
}
function summarizeAnnotationElement(element) {
if (!isElementNode(element)) {
return null;
}
const summaryData = collectReferenceSummaryData(element, {
includeLabelQuotes: false,
includeLinkUrls: true,
includeSemanticTags: true,
includeStateTags: true
});
const rawDom = serializeElementSnapshot(element);
return {
classes: getClassSummary(element),
dom: sanitizeAnnotationDom(rawDom),
id: normalizeAttributeText(element.getAttribute?.("id")),
kind: summaryData.kind,
name: normalizeAttributeText(element.getAttribute?.("name")),
rect: getElementRectSafe(element),
role: normalizeAttributeText(element.getAttribute?.("role")).toLowerCase(),
selector: buildCssSelector(element),
semanticTags: Array.isArray(summaryData.semanticTags) ? summaryData.semanticTags.slice(0, 4) : [],
stateTags: Array.isArray(summaryData.state?.stateTags) ? summaryData.state.stateTags.slice(0, 8) : [],
summary: truncateText(summaryData.summary || getLabelText(element, {
includeAlt: true,
includeDescendantImageAlt: true,
includePlaceholder: true,
includeText: true
}), 240),
tagName: getTagName(element)
};
}
function annotationViewport() {
return {
height: Math.max(0, Number(globalThis.innerHeight || globalThis.document?.documentElement?.clientHeight || 0)),
scrollX: Number(globalThis.scrollX || globalThis.pageXOffset || 0),
scrollY: Number(globalThis.scrollY || globalThis.pageYOffset || 0),
width: Math.max(0, Number(globalThis.innerWidth || globalThis.document?.documentElement?.clientWidth || 0))
};
}
function normalizeAnnotationPoint(payload = {}, viewport = annotationViewport()) {
const source = payload?.point && typeof payload.point === "object" ? payload.point : payload;
const width = Math.max(1, Number(viewport.width || 1));
const height = Math.max(1, Number(viewport.height || 1));
return {
x: Math.max(0, Math.min(width, Number(source?.x || 0))),
y: Math.max(0, Math.min(height, Number(source?.y || 0)))
};
}
function normalizeAnnotationRectPayload(payload = {}, viewport = annotationViewport()) {
const source = payload?.rect && typeof payload.rect === "object" ? payload.rect : payload;
const width = Math.max(1, Number(viewport.width || 1));
const height = Math.max(1, Number(viewport.height || 1));
const x = Math.max(0, Math.min(width, Number(source?.x || 0)));
const y = Math.max(0, Math.min(height, Number(source?.y || 0)));
return {
height: Math.max(1, Math.min(height - y, Number(source?.height || source?.h || 1))),
width: Math.max(1, Math.min(width - x, Number(source?.width || source?.w || 1))),
x,
y
};
}
function intersectRects(leftRect, rightRect) {
if (!leftRect || !rightRect) {
return null;
}
const x = Math.max(Number(leftRect.x || 0), Number(rightRect.x || 0));
const y = Math.max(Number(leftRect.y || 0), Number(rightRect.y || 0));
const right = Math.min(
Number(leftRect.x || 0) + Number(leftRect.width || 0),
Number(rightRect.x || 0) + Number(rightRect.width || 0)
);
const bottom = Math.min(
Number(leftRect.y || 0) + Number(leftRect.height || 0),
Number(rightRect.y || 0) + Number(rightRect.height || 0)
);
const width = right - x;
const height = bottom - y;
if (width <= 0 || height <= 0) {
return null;
}
return {
area: width * height,
height,
width,
x,
y
};
}
function deepElementFromPoint(x, y) {
let element = null;
try {
element = globalThis.document?.elementFromPoint?.(x, y) || null;
} catch {
return null;
}
let guard = 0;
while (isElementNode(element) && element.shadowRoot && guard < 8) {
guard += 1;
try {
const nestedElement = element.shadowRoot.elementFromPoint?.(x, y);
if (!nestedElement || nestedElement === element) {
break;
}
element = nestedElement;
} catch {
break;
}
}
return element;
}
function findAnnotationTarget(element) {
if (!isElementNode(element)) {
return null;
}
const selector = [
"a[href]",
"button",
"input",
"textarea",
"select",
"summary",
"[role]",
"img",
"label",
"form",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"p",
"li",
"td",
"th",
"article",
"section",
"nav",
"header",
"main",
"footer"
].join(",");
const target = element.closest?.(selector) || element;
return isElementNode(target) && !isHiddenElement(target) ? target : element;
}
function isMeaningfulAnnotationElement(element) {
if (!isElementNode(element) || isHiddenElement(element)) {
return false;
}
if (isInteractiveElement(element) || getTagName(element) === "IMG") {
return true;
}
const tagName = getTagName(element);
const role = normalizeAttributeText(element.getAttribute?.("role")).toLowerCase();
return Boolean(
role
|| /^H[1-6]$/u.test(tagName)
|| ["ARTICLE", "SECTION", "MAIN", "NAV", "HEADER", "FOOTER", "FORM", "LABEL", "P", "LI", "TD", "TH"].includes(tagName)
);
}
function collectIntersectingAnnotationElements(rect) {
const selector = [
"a[href]",
"button",
"input",
"textarea",
"select",
"summary",
"[role]",
"img",
"label",
"form",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"p",
"li",
"td",
"th",
"article",
"section",
"main",
"nav",
"header",
"footer"
].join(",");
let candidates = [];
try {
candidates = [...(globalThis.document?.querySelectorAll?.(selector) || [])];
} catch {
candidates = [];
}
const seen = new Set();
return candidates
.map((element) => {
if (!isMeaningfulAnnotationElement(element) || seen.has(element)) {
return null;
}
seen.add(element);
const elementRect = getElementRectSafe(element);
const intersection = intersectRects(rect, elementRect);
if (!intersection || intersection.area < 48) {
return null;
}
return {
element,
elementArea: Math.max(1, Number(elementRect.width || 0) * Number(elementRect.height || 0)),
intersection
};
})
.filter(Boolean)
.sort((left, right) => {
if (right.intersection.area !== left.intersection.area) {
return right.intersection.area - left.intersection.area;
}
return left.elementArea - right.elementArea;
})
.slice(0, 12)
.map((entry) => summarizeAnnotationElement(entry.element))
.filter(Boolean);
}
function annotate(payload = null) {
const request = payload && typeof payload === "object" ? payload : {};
const viewport = annotationViewport();
const kind = request.kind === "area" || request.rect ? "area" : "element";
if (kind === "area") {
const rect = normalizeAnnotationRectPayload(request, viewport);
const point = {
x: rect.x + rect.width / 2,
y: rect.y + rect.height / 2
};
const elements = collectIntersectingAnnotationElements(rect);
const fallbackElement = findAnnotationTarget(deepElementFromPoint(point.x, point.y));
const fallbackTarget = fallbackElement ? summarizeAnnotationElement(fallbackElement) : null;
return {
elements,
kind,
point,
rect,
status: elements.length || fallbackTarget ? "ok" : "empty",
target: elements[0] || fallbackTarget,
viewport
};
}
const point = normalizeAnnotationPoint(request, viewport);
const rawElement = deepElementFromPoint(point.x, point.y);
const targetElement = findAnnotationTarget(rawElement);
const target = targetElement ? summarizeAnnotationElement(targetElement) : null;
return {
kind,
point,
rect: target?.rect || {
height: 1,
width: 1,
x: point.x,
y: point.y
},
status: target ? "ok" : "empty",
target,
viewport
};
}
globalThis[GLOBAL_KEY] = {
click(referenceId) {
return activateElement(referenceId);
},
annotate,
capture,
clear() {
state.captureId = 0;
state.capturedAt = 0;
state.captureOptions = {
includeLabelQuotes: false,
includeLinkUrls: false,
includeSemanticTags: true,
includeStateTags: true,
includeListIndentation: true,
includeListMarkers: false
};
state.entries = new Map();
},
detail,
getState() {
return {
captureId: state.captureId,
capturedAt: state.capturedAt,
includeLabelQuotes: state.captureOptions.includeLabelQuotes === true,
includeLinkUrls: state.captureOptions.includeLinkUrls === true,
includeSemanticTags: state.captureOptions.includeSemanticTags !== false,
includeStateTags: state.captureOptions.includeStateTags !== false,
includeListIndentation: state.captureOptions.includeListIndentation !== false,
includeListMarkers: state.captureOptions.includeListMarkers === true,
referenceCount: state.entries.size
};
},
scroll(referenceId) {
return scrollToReference(referenceId);
},
submit(referenceId) {
return submitElement(referenceId);
},
type(referenceId, value) {
return updateElementValue(referenceId, value);
},
typeSubmit(referenceId, value) {
return typeAndSubmit(referenceId, value);
},
boundingBoxFor,
fileInputElementFor,
fileInputFor,
pointFor,
select(referenceId, valueOrValues) {
return selectReference(referenceId, valueOrValues);
},
setChecked(referenceId, checked) {
return setCheckedReference(referenceId, checked);
},
version: VERSION
};
})();