@browserbasehq/stagehand
Version:
An AI web browsing framework focused on simplicity and extensibility.
1,348 lines (1,345 loc) • 1.46 MB
JavaScript
var __create = Object.create;
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
var __objRest = (source, exclude) => {
var target = {};
for (var prop in source)
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
target[prop] = source[prop];
if (source != null && __getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(source)) {
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
target[prop] = source[prop];
}
return target;
};
var __esm = (fn, res) => function __init() {
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var __async = (__this, __arguments, generator) => {
return new Promise((resolve3, reject) => {
var fulfilled = (value) => {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
};
var rejected = (value) => {
try {
step(generator.throw(value));
} catch (e) {
reject(e);
}
};
var step = (x) => x.done ? resolve3(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
step((generator = generator.apply(__this, __arguments)).next());
});
};
// lib/v3/logger.ts
function bindInstanceLogger(instanceId, logger) {
instanceLoggers.set(instanceId, logger);
}
function unbindInstanceLogger(instanceId) {
instanceLoggers.delete(instanceId);
}
function withInstanceLogContext(instanceId, fn) {
return logContext.run(instanceId, fn);
}
function v3Logger(line) {
var _a, _b, _c;
const id = logContext.getStore();
if (id) {
const fn = instanceLoggers.get(id);
if (fn) {
const enriched = __spreadProps(__spreadValues({}, line), {
auxiliary: __spreadValues({}, line.auxiliary || {})
});
try {
fn(enriched);
return;
} catch (e) {
}
}
}
const ts = (_a = line.timestamp) != null ? _a : (/* @__PURE__ */ new Date()).toISOString();
const lvl = (_b = line.level) != null ? _b : 1;
const levelStr = lvl === 0 ? "ERROR" : lvl === 2 ? "DEBUG" : "INFO";
let output = `[${ts}] ${levelStr}: ${line.message}`;
if (line.auxiliary) {
for (const [key, { value, type }] of Object.entries(line.auxiliary)) {
let formattedValue = value;
if (type === "object") {
try {
formattedValue = JSON.stringify(JSON.parse(value), null, 2).split("\n").map((line2, i) => i === 0 ? line2 : ` ${line2}`).join("\n");
} catch (e) {
formattedValue = value;
}
}
output += `
${key}: ${formattedValue}`;
}
}
if (lvl === 0) {
console.error(output);
} else if (lvl === 2) {
((_c = console.debug) != null ? _c : console.log)(output);
} else {
console.log(output);
}
}
var import_node_async_hooks, logContext, instanceLoggers;
var init_logger = __esm({
"lib/v3/logger.ts"() {
import_node_async_hooks = require("async_hooks");
logContext = new import_node_async_hooks.AsyncLocalStorage();
instanceLoggers = /* @__PURE__ */ new Map();
}
});
// lib/v3/understudy/executionContextRegistry.ts
var ExecutionContextRegistry, executionContexts;
var init_executionContextRegistry = __esm({
"lib/v3/understudy/executionContextRegistry.ts"() {
ExecutionContextRegistry = class {
constructor() {
this.byFrame = /* @__PURE__ */ new WeakMap();
this.byExec = /* @__PURE__ */ new WeakMap();
}
/** Wire listeners for this session. Call BEFORE Runtime.enable. */
attachSession(session) {
const onCreated = (evt) => {
var _a;
const aux = (_a = evt.context.auxData) != null ? _a : {};
if (aux.isDefault === true && typeof aux.frameId === "string") {
this.register(session, aux.frameId, evt.context.id);
}
};
const onDestroyed = (evt) => {
const rev = this.byExec.get(session);
const fwd = this.byFrame.get(session);
if (!rev || !fwd) return;
const frameId = rev.get(evt.executionContextId);
if (!frameId) return;
rev.delete(evt.executionContextId);
if (fwd.get(frameId) === evt.executionContextId) fwd.delete(frameId);
};
const onCleared = () => {
this.byFrame.delete(session);
this.byExec.delete(session);
};
session.on("Runtime.executionContextCreated", onCreated);
session.on("Runtime.executionContextDestroyed", onDestroyed);
session.on("Runtime.executionContextsCleared", onCleared);
}
getMainWorld(session, frameId) {
var _a, _b;
return (_b = (_a = this.byFrame.get(session)) == null ? void 0 : _a.get(frameId)) != null ? _b : null;
}
waitForMainWorld(session, frameId, timeoutMs = 800) {
return __async(this, null, function* () {
const cached = this.getMainWorld(session, frameId);
if (cached) return cached;
yield session.send("Runtime.enable").catch(() => {
});
const after = this.getMainWorld(session, frameId);
if (after) return after;
return yield new Promise((resolve3, reject) => {
let done = false;
const onCreated = (evt) => {
var _a;
const aux = (_a = evt.context.auxData) != null ? _a : {};
if (aux.isDefault === true && aux.frameId === frameId) {
this.register(session, frameId, evt.context.id);
if (!done) {
done = true;
clearTimeout(timer);
session.off("Runtime.executionContextCreated", onCreated);
resolve3(evt.context.id);
}
}
};
const timer = setTimeout(() => {
if (!done) {
done = true;
session.off("Runtime.executionContextCreated", onCreated);
reject(new Error(`main world not ready for frame ${frameId}`));
}
}, timeoutMs);
session.on("Runtime.executionContextCreated", onCreated);
});
});
}
register(session, frameId, ctxId) {
let fwd = this.byFrame.get(session);
if (!fwd) {
fwd = /* @__PURE__ */ new Map();
this.byFrame.set(session, fwd);
}
let rev = this.byExec.get(session);
if (!rev) {
rev = /* @__PURE__ */ new Map();
this.byExec.set(session, rev);
}
fwd.set(frameId, ctxId);
rev.set(ctxId, frameId);
}
};
executionContexts = new ExecutionContextRegistry();
}
});
// lib/v3/understudy/a11y/snapshot.ts
function resolveXpathForLocation(page, x, y) {
return __async(this, null, function* () {
var _a, _b, _c, _d, _e, _f, _g, _h;
const tree = page.getFullFrameTree();
const parentByFrame = /* @__PURE__ */ new Map();
(function index(n, parent) {
var _a2;
parentByFrame.set(n.frame.id, parent);
for (const c of (_a2 = n.childFrames) != null ? _a2 : []) index(c, n.frame.id);
})(tree, null);
const iframeChain = [];
let curFrameId = page.mainFrameId();
let curSession = page.getSessionForFrame(curFrameId);
let curX = x;
let curY = y;
for (let depth = 0; depth < 8; depth++) {
try {
yield curSession.send("DOM.enable").catch(() => {
});
let sx = 0;
let sy = 0;
try {
yield curSession.send("Runtime.enable").catch(() => {
});
const ctxId = yield executionContexts.waitForMainWorld(curSession, curFrameId).catch(() => {
});
const evalParams = ctxId ? {
contextId: ctxId,
expression: scrollOffsetsExpr(),
returnByValue: true
} : { expression: scrollOffsetsExpr(), returnByValue: true };
const { result } = yield curSession.send("Runtime.evaluate", evalParams);
sx = Number((_b = (_a = result == null ? void 0 : result.value) == null ? void 0 : _a.sx) != null ? _b : 0);
sy = Number((_d = (_c = result == null ? void 0 : result.value) == null ? void 0 : _c.sy) != null ? _d : 0);
} catch (e) {
}
const xi = Math.max(0, Math.floor(curX + sx));
const yi = Math.max(0, Math.floor(curY + sy));
let res;
try {
res = yield curSession.send("DOM.getNodeForLocation", {
x: xi,
y: yi,
includeUserAgentShadowDOM: false,
ignorePointerEventsNone: false
});
} catch (e) {
return null;
}
const be = res == null ? void 0 : res.backendNodeId;
const reportedFrameId = res == null ? void 0 : res.frameId;
if (typeof be === "number" && reportedFrameId && reportedFrameId !== curFrameId) {
const abs = yield buildAbsoluteXPathFromChain(
iframeChain,
curSession,
be
);
return abs ? { frameId: reportedFrameId, backendNodeId: be, absoluteXPath: abs } : null;
}
if (typeof be !== "number") return null;
let matchedChild;
for (const fid of listChildrenOf(parentByFrame, curFrameId)) {
try {
const { backendNodeId } = yield curSession.send("DOM.getFrameOwner", { frameId: fid });
if (backendNodeId === be) {
matchedChild = fid;
break;
}
} catch (e) {
}
}
if (!matchedChild) {
const abs = yield buildAbsoluteXPathFromChain(
iframeChain,
curSession,
be
);
return abs ? { frameId: curFrameId, backendNodeId: be, absoluteXPath: abs } : null;
}
iframeChain.push({
parentFrameId: curFrameId,
parentSession: curSession,
iframeBackendNodeId: be
});
let left = 0;
let top = 0;
try {
const { object } = yield curSession.send("DOM.resolveNode", { backendNodeId: be });
const objectId = object == null ? void 0 : object.objectId;
if (objectId) {
const { result } = yield curSession.send("Runtime.callFunctionOn", {
objectId,
functionDeclaration: "function(){ const r=this.getBoundingClientRect(); return {left:r.left, top:r.top}; }",
returnByValue: true
});
left = Number((_f = (_e = result == null ? void 0 : result.value) == null ? void 0 : _e.left) != null ? _f : 0);
top = Number((_h = (_g = result == null ? void 0 : result.value) == null ? void 0 : _g.top) != null ? _h : 0);
yield curSession.send("Runtime.releaseObject", { objectId }).catch(() => {
});
}
} catch (e) {
}
curX = Math.max(0, curX - left);
curY = Math.max(0, curY - top);
curFrameId = matchedChild;
curSession = page.getSessionForFrame(curFrameId);
} catch (e) {
return null;
}
}
return null;
});
}
function computeActiveElementXpath(page) {
return __async(this, null, function* () {
var _a;
const tree = page.getFullFrameTree();
const parentByFrame = /* @__PURE__ */ new Map();
(function index(n, parent) {
var _a2;
parentByFrame.set(n.frame.id, parent);
for (const c of (_a2 = n.childFrames) != null ? _a2 : []) index(c, n.frame.id);
})(tree, null);
const frames = page.listAllFrameIds();
let focusedFrameId = null;
for (const fid of frames) {
const sess = page.getSessionForFrame(fid);
try {
yield sess.send("Runtime.enable").catch(() => {
});
const ctxId = yield executionContexts.waitForMainWorld(sess, fid, 1e3).catch(() => {
});
const evalParams = ctxId ? {
contextId: ctxId,
expression: "document.hasFocus()===true",
returnByValue: true
} : { expression: "document.hasFocus()===true", returnByValue: true };
const { result } = yield sess.send(
"Runtime.evaluate",
evalParams
);
if ((result == null ? void 0 : result.value) === true) {
focusedFrameId = fid;
break;
}
} catch (e) {
}
}
if (!focusedFrameId) focusedFrameId = page.mainFrameId();
const focusedSession = page.getSessionForFrame(focusedFrameId);
let objectId;
try {
yield focusedSession.send("Runtime.enable").catch(() => {
});
const ctxId = yield executionContexts.waitForMainWorld(focusedSession, focusedFrameId, 1e3).catch(() => {
});
const expr = `(() => {
try {
function deepActive(doc) {
let el = doc.activeElement || null;
while (el && el.shadowRoot && el.shadowRoot.activeElement) {
el = el.shadowRoot.activeElement;
}
return el || null;
}
return deepActive(document);
} catch { return null; }
})()`;
const evalParams = ctxId ? { contextId: ctxId, expression: expr, returnByValue: false } : { expression: expr, returnByValue: false };
const { result } = yield focusedSession.send(
"Runtime.evaluate",
evalParams
);
objectId = result == null ? void 0 : result.objectId;
} catch (e) {
objectId = void 0;
}
if (!objectId) return null;
const leafXPath = yield (() => __async(null, null, function* () {
try {
const { result } = yield focusedSession.send("Runtime.callFunctionOn", {
objectId,
functionDeclaration: `function() {
try {
const node = this;
function sibIndex(n) {
let i = 1; const t = n.nodeType+':'+(n.nodeName||'').toLowerCase();
for (let p = n.previousSibling; p; p = p.previousSibling) {
const key = p.nodeType+':'+(p.nodeName||'').toLowerCase();
if (key === t) i++;
}
return i;
}
function step(n) {
if (!n) return '';
if (n.nodeType === Node.DOCUMENT_NODE) return '';
if (n.nodeType === Node.DOCUMENT_FRAGMENT_NODE) return '//';
if (n.nodeType === Node.TEXT_NODE) return 'text()[' + sibIndex(n) + ']';
if (n.nodeType === Node.COMMENT_NODE) return 'comment()[' + sibIndex(n) + ']';
const tag = (n.nodeName||'').toLowerCase();
const name = tag.includes(':') ? "*[name()='"+tag+"']" : tag;
return name + '[' + sibIndex(n) + ']';
}
const parts = [];
let cur = node;
while (cur) {
if (cur.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
parts.push('//');
cur = (cur && cur.host) ? cur.host : null;
continue;
}
const s = step(cur);
if (s) parts.push(s);
cur = cur.parentNode;
}
parts.reverse();
let out = '';
for (const s of parts) {
if (s === '//') out = out ? (out.endsWith('/') ? out + '/' : out + '//') : '//';
else out = out ? (out.endsWith('/') ? out + s : out + '/' + s) : '/' + s;
}
return out || '/';
} catch { return ''; }
}`,
returnByValue: true
});
try {
yield focusedSession.send("Runtime.releaseObject", { objectId });
} catch (e) {
}
const xp = (result == null ? void 0 : result.value) || "";
return typeof xp === "string" && xp ? xp : null;
} catch (e) {
try {
yield focusedSession.send("Runtime.releaseObject", { objectId });
} catch (e2) {
}
return null;
}
}))();
if (!leafXPath) return null;
let prefix = "";
let cur = focusedFrameId;
while (cur) {
const parent = (_a = parentByFrame.get(cur)) != null ? _a : null;
if (!parent) break;
const parentSess = page.getSessionForFrame(parent);
try {
const { backendNodeId } = yield parentSess.send("DOM.getFrameOwner", { frameId: cur });
if (typeof backendNodeId === "number") {
const xp = yield absoluteXPathForBackendNode(parentSess, backendNodeId);
if (xp) prefix = prefix ? prefixXPath(prefix, xp) : normalizeXPath(xp);
}
} catch (e) {
}
cur = parent;
}
return prefix ? prefixXPath(prefix, leafXPath) : normalizeXPath(leafXPath);
});
}
function scrollOffsetsExpr() {
return "({sx:(window.scrollX||window.pageXOffset||0),sy:(window.scrollY||window.pageYOffset||0)})";
}
function buildAbsoluteXPathFromChain(chain, leafSession, leafBackendNodeId) {
return __async(this, null, function* () {
let prefix = "";
for (const step of chain) {
const xp = yield absoluteXPathForBackendNode(
step.parentSession,
step.iframeBackendNodeId
);
if (!xp) continue;
prefix = prefix ? prefixXPath(prefix, xp) : normalizeXPath(xp);
}
const leaf = yield absoluteXPathForBackendNode(
leafSession,
leafBackendNodeId
);
if (!leaf) return prefix || "/";
return prefix ? prefixXPath(prefix, leaf) : normalizeXPath(leaf);
});
}
function absoluteXPathForBackendNode(session, backendNodeId) {
return __async(this, null, function* () {
try {
const { object } = yield session.send(
"DOM.resolveNode",
{ backendNodeId }
);
const objectId = object == null ? void 0 : object.objectId;
if (!objectId) return null;
const { result } = yield session.send(
"Runtime.callFunctionOn",
{
objectId,
functionDeclaration: `function() {
try {
const node = this;
function sibIndex(n) {
let i = 1; const t = n.nodeType+':'+(n.nodeName||'').toLowerCase();
for (let p = n.previousSibling; p; p = p.previousSibling) {
const key = p.nodeType+':'+(p.nodeName||'').toLowerCase();
if (key === t) i++;
}
return i;
}
function step(n) {
if (!n) return '';
if (n.nodeType === Node.DOCUMENT_NODE) return '';
if (n.nodeType === Node.DOCUMENT_FRAGMENT_NODE) return '//'; // ShadowRoot hop
if (n.nodeType === Node.TEXT_NODE) return 'text()[' + sibIndex(n) + ']';
if (n.nodeType === Node.COMMENT_NODE) return 'comment()[' + sibIndex(n) + ']';
const tag = (n.nodeName||'').toLowerCase();
const name = tag.includes(':') ? "*[name()='"+tag+"']" : tag;
return name + '[' + sibIndex(n) + ']';
}
const parts = [];
let cur = node;
while (cur) {
// Insert a marker before stepping out of a ShadowRoot
if (cur.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
parts.push('//');
cur = (cur && cur.host) ? cur.host : null;
continue;
}
const s = step(cur);
if (s) parts.push(s);
cur = cur.parentNode;
}
parts.reverse();
let out = '';
for (const s of parts) {
if (s === '//') out = out ? (out.endsWith('/') ? out + '/' : out + '//') : '//';
else out = out ? (out.endsWith('/') ? out + s : out + '/' + s) : '/' + s;
}
return out || '/';
} catch { return ''; }
}`,
returnByValue: true
}
);
yield session.send("Runtime.releaseObject", { objectId }).catch(() => {
});
return typeof (result == null ? void 0 : result.value) === "string" && result.value ? result.value : null;
} catch (e) {
return null;
}
});
}
function captureHybridSnapshot(page, options) {
return __async(this, null, function* () {
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
const pierce = (_a = options == null ? void 0 : options.pierceShadow) != null ? _a : true;
const rootId = page.mainFrameId();
const frameTree = page.asProtocolFrameTree(rootId);
const parentByFrame = /* @__PURE__ */ new Map();
(function index(n, parent) {
var _a2;
parentByFrame.set(n.frame.id, parent);
for (const c of (_a2 = n.childFrames) != null ? _a2 : []) index(c, n.frame.id);
})(frameTree, null);
const frames = page.listAllFrameIds();
const combinedXpathMap = {};
const combinedUrlMap = {};
const perFrameOutlines = [];
const perFrameMaps = /* @__PURE__ */ new Map();
const requestedFocus = (_b = options == null ? void 0 : options.focusSelector) == null ? void 0 : _b.trim();
if (requestedFocus) {
const logScopeFallback = () => {
var _a2;
v3Logger({
message: `Unable to narrow scope with selector. Falling back to using full DOM`,
level: 1,
auxiliary: {
arguments: {
value: `selector: ${(_a2 = options == null ? void 0 : options.focusSelector) == null ? void 0 : _a2.trim()}`,
type: "string"
}
}
});
};
try {
let targetFrameId;
let tailSelector;
let absPrefix2;
const looksLikeXPath = /^xpath=/i.test(requestedFocus) || requestedFocus.startsWith("/");
if (looksLikeXPath) {
const focus = normalizeXPath(requestedFocus);
const hit = yield resolveFocusFrameAndTail(
page,
focus,
parentByFrame,
rootId
);
targetFrameId = hit.targetFrameId;
tailSelector = hit.tailXPath || void 0;
absPrefix2 = hit.absPrefix;
} else {
const cssHit = yield resolveCssFocusFrameAndTail(
page,
requestedFocus,
parentByFrame,
rootId
);
targetFrameId = cssHit.targetFrameId;
tailSelector = cssHit.tailSelector || void 0;
absPrefix2 = cssHit.absPrefix;
}
const owningSess = ownerSession(page, targetFrameId);
const parentId = parentByFrame.get(targetFrameId);
const sameSessionAsParent = !!parentId && ownerSession(page, parentId) === ownerSession(page, targetFrameId);
const { tagNameMap, xpathMap, scrollableMap } = yield domMapsForSession(
owningSess,
targetFrameId,
pierce,
(fid, be) => `${page.getOrdinal(fid)}-${be}`,
/*attemptOwnerLookup=*/
sameSessionAsParent
);
const { outline, urlMap, scopeApplied } = yield a11yForFrame(
owningSess,
targetFrameId,
{
focusSelector: tailSelector || void 0,
tagNameMap,
experimental: (_c = options == null ? void 0 : options.experimental) != null ? _c : false,
scrollableMap,
encode: (backendNodeId) => `${page.getOrdinal(targetFrameId)}-${backendNodeId}`
}
);
const combinedXpathMap2 = {};
const abs = absPrefix2 != null ? absPrefix2 : "";
const isRoot = !abs || abs === "/";
if (isRoot) {
Object.assign(combinedXpathMap2, xpathMap);
} else {
for (const [encId, xp] of Object.entries(xpathMap)) {
combinedXpathMap2[encId] = prefixXPath(abs, xp);
}
}
const combinedUrlMap2 = __spreadValues({}, urlMap);
const snapshot = {
combinedTree: outline,
combinedXpathMap: combinedXpathMap2,
combinedUrlMap: combinedUrlMap2,
perFrame: [
{
frameId: targetFrameId,
outline,
xpathMap,
urlMap
}
]
};
if (scopeApplied) {
return snapshot;
}
logScopeFallback();
} catch (e) {
logScopeFallback();
}
}
function buildSessionDomIndex(session, pierce2) {
return __async(this, null, function* () {
var _a2, _b2;
yield session.send("DOM.enable").catch(() => {
});
const { root } = yield session.send(
"DOM.getDocument",
{ depth: -1, pierce: pierce2 }
);
const absByBe = /* @__PURE__ */ new Map();
const tagByBe = /* @__PURE__ */ new Map();
const scrollByBe = /* @__PURE__ */ new Map();
const docRootOf = /* @__PURE__ */ new Map();
const contentDocRootByIframe = /* @__PURE__ */ new Map();
const rootBe = root.backendNodeId;
const stack = [{ node: root, xp: "/", docRootBe: rootBe }];
while (stack.length) {
const { node, xp, docRootBe } = stack.pop();
if (node.backendNodeId) {
absByBe.set(node.backendNodeId, xp || "/");
tagByBe.set(node.backendNodeId, String(node.nodeName).toLowerCase());
if ((node == null ? void 0 : node.isScrollable) === true)
scrollByBe.set(node.backendNodeId, true);
docRootOf.set(node.backendNodeId, docRootBe);
}
const kids = (_a2 = node.children) != null ? _a2 : [];
if (kids.length) {
const segs = buildChildXPathSegments(kids);
for (let i = kids.length - 1; i >= 0; i--) {
const child = kids[i];
const step = segs[i];
stack.push({ node: child, xp: joinXPath(xp, step), docRootBe });
}
}
for (const sr of (_b2 = node.shadowRoots) != null ? _b2 : []) {
stack.push({ node: sr, xp: joinXPath(xp, "//"), docRootBe });
}
const cd = node.contentDocument;
if (cd && typeof cd.backendNodeId === "number") {
contentDocRootByIframe.set(node.backendNodeId, cd.backendNodeId);
stack.push({ node: cd, xp, docRootBe: cd.backendNodeId });
}
}
return {
rootBackend: rootBe,
absByBe,
tagByBe,
scrollByBe,
docRootOf,
contentDocRootByIframe
};
});
}
function relativizeXPath(baseAbs, nodeAbs) {
const base = normalizeXPath(baseAbs);
const abs = normalizeXPath(nodeAbs);
if (abs === base) return "/";
if (abs.startsWith(base)) {
const tail = abs.slice(base.length);
if (!tail) return "/";
return tail.startsWith("/") || tail.startsWith("//") ? tail : `/${tail}`;
}
if (base === "/") return abs;
return abs;
}
const sessionToIndex = /* @__PURE__ */ new Map();
const sessionById = /* @__PURE__ */ new Map();
for (const frameId of frames) {
const sess = ownerSession(page, frameId);
const sid = (_d = sess.id) != null ? _d : "root";
if (!sessionById.has(sid)) sessionById.set(sid, sess);
}
for (const [sid, sess] of sessionById.entries()) {
const idx = yield buildSessionDomIndex(sess, pierce);
sessionToIndex.set(sid, idx);
}
for (const frameId of frames) {
const sess = ownerSession(page, frameId);
const sid = (_e = sess.id) != null ? _e : "root";
let idx = sessionToIndex.get(sid);
if (!idx) {
idx = yield buildSessionDomIndex(sess, pierce);
sessionToIndex.set(sid, idx);
}
const parentId = parentByFrame.get(frameId);
const sameSessionAsParent = !!parentId && ownerSession(page, parentId) === sess;
let docRootBe = idx.rootBackend;
if (sameSessionAsParent) {
try {
const { backendNodeId } = yield sess.send(
"DOM.getFrameOwner",
{ frameId }
);
if (typeof backendNodeId === "number") {
const cdBe = idx.contentDocRootByIframe.get(backendNodeId);
if (typeof cdBe === "number") docRootBe = cdBe;
}
} catch (e) {
}
}
const tagNameMap = {};
const xpathMap = {};
const scrollableMap = {};
const enc = (be) => `${page.getOrdinal(frameId)}-${be}`;
const baseAbs = (_f = idx.absByBe.get(docRootBe)) != null ? _f : "/";
for (const [be, nodeAbs] of idx.absByBe.entries()) {
const nodeDocRoot = idx.docRootOf.get(be);
if (nodeDocRoot !== docRootBe) continue;
const rel = relativizeXPath(baseAbs, nodeAbs);
const key = enc(be);
xpathMap[key] = rel;
const tag = idx.tagByBe.get(be);
if (tag) tagNameMap[key] = tag;
if (idx.scrollByBe.get(be)) scrollableMap[key] = true;
}
const { outline, urlMap } = yield a11yForFrame(sess, frameId, {
experimental: (_g = options == null ? void 0 : options.experimental) != null ? _g : false,
tagNameMap,
scrollableMap,
encode: (backendNodeId) => `${page.getOrdinal(frameId)}-${backendNodeId}`
});
perFrameOutlines.push({ frameId, outline });
perFrameMaps.set(frameId, { tagNameMap, xpathMap, scrollableMap, urlMap });
}
const absPrefix = /* @__PURE__ */ new Map();
const iframeHostEncByChild = /* @__PURE__ */ new Map();
absPrefix.set(rootId, "");
const queue = [rootId];
while (queue.length) {
const parent = queue.shift();
const parentAbs = absPrefix.get(parent);
for (const child of frames) {
if (parentByFrame.get(child) !== parent) continue;
queue.push(child);
const parentSess = parentSession(page, parentByFrame, child);
const ownerBackendNodeId = yield (() => __async(null, null, function* () {
try {
const { backendNodeId } = yield parentSess.send("DOM.getFrameOwner", { frameId: child });
return backendNodeId;
} catch (e) {
return void 0;
}
}))();
if (!ownerBackendNodeId) {
absPrefix.set(child, parentAbs);
continue;
}
const parentDom = perFrameMaps.get(parent);
const iframeEnc = `${page.getOrdinal(parent)}-${ownerBackendNodeId}`;
const iframeXPath = parentDom == null ? void 0 : parentDom.xpathMap[iframeEnc];
const childAbs = iframeXPath ? prefixXPath(parentAbs || "/", iframeXPath) : parentAbs;
absPrefix.set(child, childAbs);
iframeHostEncByChild.set(child, iframeEnc);
}
}
for (const frameId of frames) {
const maps = perFrameMaps.get(frameId);
if (!maps) continue;
const abs = (_h = absPrefix.get(frameId)) != null ? _h : "";
const isRoot = abs === "" || abs === "/";
if (isRoot) {
Object.assign(combinedXpathMap, maps.xpathMap);
Object.assign(combinedUrlMap, maps.urlMap);
continue;
}
for (const [encId, xp] of Object.entries(maps.xpathMap)) {
combinedXpathMap[encId] = prefixXPath(abs, xp);
}
Object.assign(combinedUrlMap, maps.urlMap);
}
const idToTree = /* @__PURE__ */ new Map();
for (const { frameId, outline } of perFrameOutlines) {
const parentEnc = iframeHostEncByChild.get(frameId);
if (parentEnc) idToTree.set(parentEnc, outline);
}
const rootOutline = (_l = (_k = (_i = perFrameOutlines.find((o) => o.frameId === rootId)) == null ? void 0 : _i.outline) != null ? _k : (_j = perFrameOutlines[0]) == null ? void 0 : _j.outline) != null ? _l : "";
const combinedTree = injectSubtrees(rootOutline, idToTree);
return {
combinedTree,
combinedXpathMap,
combinedUrlMap,
perFrame: perFrameOutlines.map(({ frameId, outline }) => {
var _a2, _b2;
const maps = perFrameMaps.get(frameId);
return {
frameId,
outline,
xpathMap: (_a2 = maps == null ? void 0 : maps.xpathMap) != null ? _a2 : {},
urlMap: (_b2 = maps == null ? void 0 : maps.urlMap) != null ? _b2 : {}
};
})
};
});
}
function prefixXPath(parentAbs, child) {
const p = parentAbs === "/" ? "" : parentAbs.replace(/\/$/, "");
if (!child || child === "/") return p || "/";
if (child.startsWith("//"))
return p ? `${p}//${child.slice(2)}` : `//${child.slice(2)}`;
const c = child.replace(/^\//, "");
return p ? `${p}/${c}` : `/${c}`;
}
function normalizeXPath(x) {
if (!x) return "";
let s = x.trim().replace(/^xpath=/i, "");
if (!s.startsWith("/")) s = "/" + s;
if (s.length > 1 && s.endsWith("/")) s = s.slice(0, -1);
return s;
}
function parseXPathToSteps(path6) {
const s = path6.trim();
let i = 0;
const steps = [];
while (i < s.length) {
let axis = "child";
if (s.startsWith("//", i)) {
axis = "desc";
i += 2;
} else if (s[i] === "/") {
axis = "child";
i += 1;
}
const start = i;
while (i < s.length && s[i] !== "/") i++;
const raw = s.slice(start, i).trim();
if (!raw) continue;
const name = raw.replace(/\[\d+\]\s*$/u, "").toLowerCase();
steps.push({ axis, raw, name });
}
return steps;
}
function buildXPathFromSteps(steps) {
let out = "";
for (const st of steps) {
out += st.axis === "desc" ? "//" : "/";
out += st.raw;
}
return out || "/";
}
function resolveFocusFrameAndTail(page, absoluteXPath, parentByFrame, rootId) {
return __async(this, null, function* () {
const steps = parseXPathToSteps(absoluteXPath);
let ctxFrameId = rootId;
let buf = [];
let absPrefix = "";
const flushIntoChild = () => __async(null, null, function* () {
if (!buf.length) return;
const selectorForIframe = buildXPathFromSteps(buf);
const parentSess = page.getSessionForFrame(ctxFrameId);
const objectId = yield resolveObjectIdForXPath(
parentSess,
selectorForIframe,
ctxFrameId
);
if (!objectId) throw new Error("Failed to resolve iframe element by XPath");
try {
yield parentSess.send("DOM.enable").catch(() => {
});
const desc = yield parentSess.send(
"DOM.describeNode",
{ objectId }
);
const iframeBackendNodeId = desc.node.backendNodeId;
let childFrameId;
for (const fid of listChildrenOf(parentByFrame, ctxFrameId)) {
try {
const { backendNodeId } = yield parentSess.send("DOM.getFrameOwner", { frameId: fid });
if (backendNodeId === iframeBackendNodeId) {
childFrameId = fid;
break;
}
} catch (e) {
}
}
if (!childFrameId)
throw new Error("Could not map iframe to child frameId");
absPrefix = prefixXPath(absPrefix || "/", selectorForIframe);
ctxFrameId = childFrameId;
} finally {
yield parentSess.send("Runtime.releaseObject", { objectId }).catch(() => {
});
}
buf = [];
});
for (const st of steps) {
buf.push(st);
if (IFRAME_STEP_RE.test(st.name)) {
yield flushIntoChild();
}
}
const tailXPath = buildXPathFromSteps(buf);
return { targetFrameId: ctxFrameId, tailXPath, absPrefix };
});
}
function resolveCssFocusFrameAndTail(page, rawSelector, parentByFrame, rootId) {
return __async(this, null, function* () {
var _a;
const parts = rawSelector.split(">>").map((s) => s.trim()).filter(Boolean);
let ctxFrameId = rootId;
const absPrefix = "";
for (let i = 0; i < Math.max(0, parts.length - 1); i++) {
const parentSess = page.getSessionForFrame(ctxFrameId);
const objectId = yield resolveObjectIdForCss(
parentSess,
parts[i],
ctxFrameId
);
if (!objectId) throw new Error("Failed to resolve iframe via CSS hop");
try {
yield parentSess.send("DOM.enable").catch(() => {
});
const desc = yield parentSess.send(
"DOM.describeNode",
{ objectId }
);
const iframeBackendNodeId = desc.node.backendNodeId;
let childFrameId;
for (const fid of listChildrenOf(parentByFrame, ctxFrameId)) {
try {
const { backendNodeId } = yield parentSess.send("DOM.getFrameOwner", { frameId: fid });
if (backendNodeId === iframeBackendNodeId) {
childFrameId = fid;
break;
}
} catch (e) {
}
}
if (!childFrameId)
throw new Error("Could not map CSS iframe hop to child frameId");
ctxFrameId = childFrameId;
} finally {
yield parentSess.send("Runtime.releaseObject", { objectId }).catch(() => {
});
}
}
const tailSelector = (_a = parts[parts.length - 1]) != null ? _a : "*";
return { targetFrameId: ctxFrameId, tailSelector, absPrefix };
});
}
function listChildrenOf(parentByFrame, parentId) {
const out = [];
for (const [fid, p] of parentByFrame.entries()) {
if (p === parentId) out.push(fid);
}
return out;
}
function domMapsForSession(session, frameId, pierce, encode = (fid, be) => `${fid}-${be}`, attemptOwnerLookup = true) {
return __async(this, null, function* () {
var _a, _b;
yield session.send("DOM.enable").catch(() => {
});
const { root } = yield session.send(
"DOM.getDocument",
{ depth: -1, pierce }
);
let startNode = root;
if (attemptOwnerLookup) {
try {
const owner = yield session.send(
"DOM.getFrameOwner",
{ frameId }
);
const ownerBackendId = owner.backendNodeId;
if (typeof ownerBackendId === "number") {
const ownerEl = findNodeByBackendId(root, ownerBackendId);
if (ownerEl == null ? void 0 : ownerEl.contentDocument) {
startNode = ownerEl.contentDocument;
}
}
} catch (e) {
}
}
const tagNameMap = {};
const xpathMap = {};
const scrollableMap = {};
const stack = [{ node: startNode, xpath: "" }];
while (stack.length) {
const { node, xpath } = stack.pop();
if (node.backendNodeId) {
const encId = encode(frameId, node.backendNodeId);
tagNameMap[encId] = String(node.nodeName).toLowerCase();
xpathMap[encId] = xpath || "/";
const isScrollable = (node == null ? void 0 : node.isScrollable) === true;
if (isScrollable) scrollableMap[encId] = true;
}
const kids = (_a = node.children) != null ? _a : [];
if (kids.length) {
const segs = buildChildXPathSegments(kids);
for (let i = kids.length - 1; i >= 0; i--) {
const child = kids[i];
const step = segs[i];
stack.push({
node: child,
xpath: joinXPath(xpath, step)
});
}
}
for (const sr of (_b = node.shadowRoots) != null ? _b : []) {
stack.push({
node: sr,
xpath: joinXPath(xpath, "//")
});
}
}
return { tagNameMap, xpathMap, scrollableMap };
});
}
function buildChildXPathSegments(kids) {
var _a;
const segs = [];
const ctr = {};
for (const child of kids) {
const tag = String(child.nodeName).toLowerCase();
const key = `${child.nodeType}:${tag}`;
const idx = ctr[key] = ((_a = ctr[key]) != null ? _a : 0) + 1;
if (child.nodeType === 3) {
segs.push(`text()[${idx}]`);
} else if (child.nodeType === 8) {
segs.push(`comment()[${idx}]`);
} else {
segs.push(
tag.includes(":") ? `*[name()='${tag}'][${idx}]` : `${tag}[${idx}]`
);
}
}
return segs;
}
function joinXPath(base, step) {
if (step === "//") {
if (!base || base === "/") return "//";
return base.endsWith("/") ? `${base}/` : `${base}//`;
}
if (!base || base === "/") return step ? `/${step}` : "/";
if (base.endsWith("//")) return `${base}${step}`;
if (!step) return base;
return `${base}/${step}`;
}
function a11yForFrame(session, frameId, opts) {
return __async(this, null, function* () {
var _a, _b;
yield session.send("Accessibility.enable").catch(() => {
});
yield session.send("Runtime.enable").catch(() => {
});
yield session.send("DOM.enable").catch(() => {
});
let nodes = [];
try {
const params = frameId ? { frameId } : {};
({ nodes } = yield session.send("Accessibility.getFullAXTree", params));
} catch (e) {
const msg = String((_b = (_a = e == null ? void 0 : e.message) != null ? _a : e) != null ? _b : "");
const isFrameScopeError = msg.includes("Frame with the given") || msg.includes("does not belong to the target") || msg.includes("is not found");
if (!isFrameScopeError || !frameId) throw e;
({ nodes } = yield session.send("Accessibility.getFullAXTree"));
}
const urlMap = {};
for (const n of nodes) {
const be = n.backendDOMNodeId;
if (typeof be !== "number") continue;
const url = extractUrlFromAXNode(n);
if (!url) continue;
const enc = opts.encode(be);
urlMap[enc] = url;
}
let scopeApplied = false;
const nodesForOutline = yield (() => __async(null, null, function* () {
var _a2, _b2, _c;
const sel = (_a2 = opts.focusSelector) == null ? void 0 : _a2.trim();
if (!sel) return nodes;
try {
const looksLikeXPath = /^xpath=/i.test(sel) || sel.startsWith("/");
const objectId = looksLikeXPath ? yield resolveObjectIdForXPath(session, sel, frameId) : yield resolveObjectIdForCss(session, sel, frameId);
if (!objectId) return nodes;
const desc = yield session.send(
"DOM.describeNode",
{ objectId }
);
const be = (_b2 = desc.node) == null ? void 0 : _b2.backendNodeId;
if (typeof be !== "number") return nodes;
const target = nodes.find((n) => n.backendDOMNodeId === be);
if (!target) return nodes;
scopeApplied = true;
const keep = /* @__PURE__ */ new Set([target.nodeId]);
const queue = [target];
while (queue.length) {
const cur = queue.shift();
for (const id of (_c = cur.childIds) != null ? _c : []) {
if (keep.has(id)) continue;
keep.add(id);
const child = nodes.find((n) => n.nodeId === id);
if (child) queue.push(child);
}
}
return nodes.filter((n) => keep.has(n.nodeId)).map(
(n) => n.nodeId === target.nodeId ? __spreadProps(__spreadValues({}, n), { parentId: void 0 }) : n
);
} catch (e) {
return nodes;
}
}))();
const decorated = decorateRoles(nodesForOutline, opts);
const { tree } = yield buildHierarchicalTree(decorated, opts);
const simplified = tree.map((n) => formatTreeLine(n)).join("\n");
return { outline: simplified.trimEnd(), urlMap, scopeApplied };
});
}
function resolveObjectIdForXPath(session, xpath, frameId) {
return __async(this, null, function* () {
var _a;
let contextId;
try {
if (frameId) {
contextId = yield executionContexts.waitForMainWorld(session, frameId, 800).catch(
() => {
var _a2;
return (_a2 = executionContexts.getMainWorld(session, frameId)) != null ? _a2 : void 0;
}
);
}
} catch (e) {
contextId = void 0;
}
const expr = `(() => {
const xp = ${JSON.stringify(xpath)};
try {
if (window.__stagehandV3__ && typeof window.__stagehandV3__.resolveSimpleXPath === 'function') {
return window.__stagehandV3__.resolveSimpleXPath(xp);
}
} catch {}
try {
const res = document.evaluate(xp, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
return res.singleNodeValue;
} catch { return null; }
})()`;
const { result, exceptionDetails } = yield session.send("Runtime.evaluate", {
expression: expr,
returnByValue: false,
contextId,
awaitPromise: true
});
if (exceptionDetails) return null;
return (_a = result == null ? void 0 : result.objectId) != null ? _a : null;
});
}
function resolveObjectIdForCss(session, selector, frameId) {
return __async(this, null, function* () {
var _a;
let contextId;
try {
if (frameId) {
contextId = yield executionContexts.waitForMainWorld(session, frameId, 800).catch(
() => {
var _a2;
return (_a2 = executionContexts.getMainWorld(session, frameId)) != null ? _a2 : void 0;
}
);
}
} catch (e) {
contextId = void 0;
}
const expr = `(() => {
const selector = ${JSON.stringify(selector)};
function queryOpenDeep(root) {
try {
const hit = root.querySelector(selector);
if (hit) return hit;
} catch {}
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
let n;
while ((n = walker.nextNode())) {
if (n.shadowRoot) {
const found = queryOpenDeep(n.shadowRoot);
if (found) return found;
}
}
return null;
}
const backdoor = window.__stagehandV3__;
if (backdoor && typeof backdoor.getClosedRoot === 'function') {
function* roots() {
yield document;
const queue = [];
try {
const w = document.createTreeWalker(document, NodeFilter.SHOW_ELEMENT);
let e; while ((e = w.nextNode())) {
if (e.shadowRoot) queue.push(e.shadowRoot);
try { const closed = backdoor.getClosedRoot(e); if (closed) queue.push(closed); } catch {}
}
} catch {}
while (queue.length) {
const r = queue.shift();
yield r;
try {
const w2 = document.createTreeWalker(r, NodeFilter.SHOW_ELEMENT);
let e2; while ((e2 = w2.nextNode())) {
if (e2.shadowRoot) queue.push(e2.shadowRoot);
try { const closed2 = backdoor.getClosedRoot(e2); if (closed2) queue.push(closed2); } catch {}
}
} catch {}
}
}
for (const r of roots()) {
try { const hit = r.querySelector(selector); if (hit) return hit; } catch {}
}
return null;
}
return queryOpenDeep(document);
})()`;
const { result, exceptionDetails } = yield session.send("Runtime.evaluate", {
expression: expr,
returnByValue: false,
contextId,
awaitPromise: true
});
if (exceptionDetails) return null;
return (_a = result == null ? void 0 : result.objectId) != null ? _a : null;
});
}
function decorateRoles(nodes, opts) {
const asRole = (n) => {
var _a, _b;
return String((_b = (_a = n.role) == null ? void 0 : _a.value) != null ? _b : "");
};
return nodes.map((n) => {
var _a, _b, _c;
let encodedId;
if (typeof n.backendDOMNodeId === "number") {
try {
encodedId = opts.encode(n.backendDOMNodeId);
} catch (e) {
}
}
let role = asRole(n);
const domIsScrollable = encodedId ? opts.scrollableMap[encodedId] === true : false;
const tag = encodedId ? opts.tagNameMap[encodedId] : void 0;
const isHtmlElement = tag === "html";
if ((domIsScrollable || isHtmlElement) && tag !== "#document") {
const tagLabel = tag && tag.startsWith("#") ? tag.slice(1) : tag;
role = tagLabel ? `scrollable, ${tagLabel}` : `scrollable${role ? `, ${role}` : ""}`;
}
return {
role,
name: (_a = n.name) == null ? void 0 : _a.value,
description: (_b = n.description) == null ? void 0 : _b.value,
value: (_c = n.value) == null ? void 0 : _c.value,
nodeId: n.nodeId,
backendDOMNodeId: n.backendDOMNodeId,
parentId: n.paren