mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 01:29:56 +00:00
562 lines
26 KiB
Diff
562 lines
26 KiB
Diff
diff --git a/build/src/McpPage.js b/build/src/McpPage.js
|
|
index 3be93a33ad4afb206223290c96fd8749cd49a131..f6887160eb88fb0d19792d20ca6466496ca48b9c 100644
|
|
--- a/build/src/McpPage.js
|
|
+++ b/build/src/McpPage.js
|
|
@@ -1,3 +1,4 @@
|
|
+// Modified by OpenClaw: temporary snapshot-identity backport; see OPENCLAW_PATCH_NOTICE.md.
|
|
/**
|
|
* @license
|
|
* Copyright 2025 Google LLC
|
|
@@ -105,7 +106,7 @@ import { DevToolsCommentBridge } from './devtools/DevToolsCommentBridge.js';
|
|
import { createTargetUniverse, } from './devtools/DevtoolsUtils.js';
|
|
import { ConsoleCollector, NetworkCollector, } from './collectors/PageCollector.js';
|
|
import { TextSnapshot } from './TextSnapshot.js';
|
|
-import { PredefinedNetworkConditions, DevTools, } from './third_party/index.js';
|
|
+import { PredefinedNetworkConditions, DevTools, CdpFrame, } from './third_party/index.js';
|
|
const DEFAULT_TIMEOUT = 5_000;
|
|
const NAVIGATION_TIMEOUT = 10_000;
|
|
import { logger } from './utils/logger.js';
|
|
@@ -549,7 +550,7 @@ export class McpPage {
|
|
logger?.(`No backendNodeId for stashed DOM element with index ${index}`);
|
|
return `stashed-${index}`;
|
|
}
|
|
- const cdpElementId = this.textSnapshot?.resolveCdpElementId(backendNodeId);
|
|
+ const cdpElementId = this.textSnapshot?.resolveCdpElementId(backendNodeId, elementHandle.frame);
|
|
if (!cdpElementId) {
|
|
logger?.(`Could not get cdpElementId for backend node ${backendNodeId}`);
|
|
return `stashed-${index}`;
|
|
@@ -639,44 +640,62 @@ export class McpPage {
|
|
}
|
|
}
|
|
async getMatchedStylesForUid(uid) {
|
|
- if (!this.textSnapshot) {
|
|
- throw new Error(`No snapshot found for page ${this.id ?? '?'}. Use take_snapshot to capture one.`);
|
|
- }
|
|
- const node = this.textSnapshot.idToNode.get(uid);
|
|
- if (!node) {
|
|
- throw new Error(`Element uid "${uid}" not found on page ${this.id}.`);
|
|
- }
|
|
- const backendNodeId = node.backendNodeId;
|
|
- if (!isBackendNodeId(backendNodeId)) {
|
|
- throw new Error(`Failed to resolve backend node ID for element with uid "${uid}".`);
|
|
- }
|
|
- if (!this.#devtoolsUniverse) {
|
|
- throw new Error(`DevTools universe is not available for page ${this.id ?? '?'}.`);
|
|
- }
|
|
- const targetManager = this.#devtoolsUniverse.universe.context.get(DevTools.TargetManager);
|
|
- let domNode;
|
|
- let cssModel = null;
|
|
- for (const dom of targetManager.models(DevTools.DOMModel.DOMModel)) {
|
|
- const nodeMap = await dom.pushNodesByBackendIdsToFrontend(new Set([backendNodeId]));
|
|
- const frontendNode = nodeMap?.get(backendNodeId);
|
|
- if (frontendNode) {
|
|
- domNode = frontendNode;
|
|
- cssModel = dom.target().model(DevTools.CSSModel.CSSModel);
|
|
- break;
|
|
+ const env_3 = { stack: [], error: void 0, hasError: false };
|
|
+ try {
|
|
+ if (!this.textSnapshot) {
|
|
+ throw new Error(`No snapshot found for page ${this.id ?? '?'}. Use take_snapshot to capture one.`);
|
|
}
|
|
+ const node = this.textSnapshot.idToNode.get(uid);
|
|
+ if (!node) {
|
|
+ throw new Error(`Element uid "${uid}" not found on page ${this.id}.`);
|
|
+ }
|
|
+ const snapshotBackendNodeId = node.backendNodeId;
|
|
+ if (!isBackendNodeId(snapshotBackendNodeId)) {
|
|
+ throw new Error(`Failed to resolve backend node ID for element with uid "${uid}".`);
|
|
+ }
|
|
+ const handle = __addDisposableResource(env_3, await this.#resolveElementHandle(node, uid), false);
|
|
+ const liveBackendNodeId = await handle.backendNodeId();
|
|
+ if (liveBackendNodeId !== snapshotBackendNodeId) {
|
|
+ throw new Error(`Element with uid "${uid}" was detached or no longer exists on the page. Please take a new snapshot with take_snapshot.`);
|
|
+ }
|
|
+ const backendNodeId = snapshotBackendNodeId;
|
|
+ if (!this.#devtoolsUniverse) {
|
|
+ throw new Error(`DevTools universe is not available for page ${this.id ?? '?'}.`);
|
|
+ }
|
|
+ const targetManager = this.#devtoolsUniverse.universe.context.get(DevTools.TargetManager);
|
|
+ const frame = handle.frame;
|
|
+ const mainFrame = this.pptrPage.mainFrame();
|
|
+ const frameTarget = frame instanceof CdpFrame ? targetManager.targetById(frame._id) : null;
|
|
+ const target = frameTarget ??
|
|
+ (frame instanceof CdpFrame &&
|
|
+ mainFrame instanceof CdpFrame &&
|
|
+ frame.client === mainFrame.client
|
|
+ ? targetManager.primaryPageTarget()
|
|
+ : null);
|
|
+ const dom = target?.model(DevTools.DOMModel.DOMModel);
|
|
+ const nodeMap = await dom?.pushNodesByBackendIdsToFrontend(new Set([backendNodeId]));
|
|
+ const domNode = nodeMap?.get(backendNodeId);
|
|
+ const cssModel = dom?.target().model(DevTools.CSSModel.CSSModel);
|
|
+ if (!domNode || !cssModel) {
|
|
+ throw new Error(`Element with uid "${uid}" was detached or no longer exists on the page. Please take a new snapshot with take_snapshot.`);
|
|
+ }
|
|
+ const targetElement = domNode.enclosingElementOrSelf();
|
|
+ if (!targetElement) {
|
|
+ throw new Error(`Element with uid "${uid}" is not an element node and has no parent element.`);
|
|
+ }
|
|
+ const matchedStyles = await cssModel.getMatchedStyles(targetElement.id);
|
|
+ if (!matchedStyles) {
|
|
+ throw new Error(`Could not retrieve matched styles for element with uid "${uid}".`);
|
|
+ }
|
|
+ return matchedStyles;
|
|
}
|
|
- if (!domNode || !cssModel) {
|
|
- throw new Error(`Element with uid "${uid}" was detached or no longer exists on the page. Please take a new snapshot with take_snapshot.`);
|
|
- }
|
|
- const targetElement = domNode.enclosingElementOrSelf();
|
|
- if (!targetElement) {
|
|
- throw new Error(`Element with uid "${uid}" is not an element node and has no parent element.`);
|
|
+ catch (e_3) {
|
|
+ env_3.error = e_3;
|
|
+ env_3.hasError = true;
|
|
}
|
|
- const matchedStyles = await cssModel.getMatchedStyles(targetElement.id);
|
|
- if (!matchedStyles) {
|
|
- throw new Error(`Could not retrieve matched styles for element with uid "${uid}".`);
|
|
+ finally {
|
|
+ __disposeResources(env_3);
|
|
}
|
|
- return matchedStyles;
|
|
}
|
|
async getDevToolsData() {
|
|
try {
|
|
diff --git a/build/src/OPENCLAW_PATCH_NOTICE.md b/build/src/OPENCLAW_PATCH_NOTICE.md
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..abdbc42e09793bf885493afc41be15a28d0fe432
|
|
--- /dev/null
|
|
+++ b/build/src/OPENCLAW_PATCH_NOTICE.md
|
|
@@ -0,0 +1,27 @@
|
|
+# Temporary OpenClaw snapshot identity patch
|
|
+
|
|
+This package remains `chrome-devtools-mcp@1.10.1`. OpenClaw modifies two
|
|
+published runtime files to backport the snapshot identity repair proposed in
|
|
+https://github.com/ChromeDevTools/chrome-devtools-mcp/pull/2788.
|
|
+
|
|
+The reviewed upstream source is commit
|
|
+`06c8d4bc44f68bde8bd3fcd97dcc47375df81fe9`, forward-ported onto the
|
|
+`chrome-devtools-mcp-v1.10.1` source tag at
|
|
+`e52c6b59b476c5e04d8dd9fd4bd017ba3b3d65df`.
|
|
+
|
|
+- `TextSnapshot.js` carries the reviewed snapshot-owner implementation.
|
|
+- `McpPage.js` supplies the known element frame for DOM-result reverse lookup
|
|
+ and resolves CSS-style UIDs through the same guarded snapshot handle. CSS
|
|
+ lookup stays within the captured renderer and fails closed when the embedded
|
|
+ DevTools target cannot represent a cross-renderer frame.
|
|
+
|
|
+Version 1.10.1 already exports its bundled `CdpFrame` implementation, so this
|
|
+patch does not modify third-party bundle code.
|
|
+
|
|
+The original package is the published npm 1.10.1 tarball with integrity
|
|
+`sha512-Klw6HWDqHC/XS1JwZldd2r49aUhbUJN9m9Mvcx4SEueIPXtzuQX+QelxAViobv8YUkDZ7HWDrmViR6LeYK0wAw==`.
|
|
+The original `LICENSE` and `build/src/third_party/THIRD_PARTY_NOTICES` remain
|
|
+unchanged.
|
|
+
|
|
+Remove this patch and notice after adopting a published upstream release that
|
|
+contains the repair and passes the same snapshot/stdio regression proof.
|
|
diff --git a/build/src/TextSnapshot.js b/build/src/TextSnapshot.js
|
|
index fe2bae7bc287e43d8a7f41f5886496a4f82c0cd7..5b2671a4b416633710886c9089762e0b9018ff66 100644
|
|
--- a/build/src/TextSnapshot.js
|
|
+++ b/build/src/TextSnapshot.js
|
|
@@ -1,3 +1,4 @@
|
|
+// Modified by OpenClaw: temporary snapshot-identity backport; see OPENCLAW_PATCH_NOTICE.md.
|
|
/**
|
|
* @license
|
|
* Copyright 2025 Google LLC
|
|
@@ -55,7 +56,17 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
|
|
var e = new Error(message);
|
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
});
|
|
+import { CdpFrame } from './third_party/index.js';
|
|
import { logger } from './utils/logger.js';
|
|
+function stableNodeKey(node, sessionId) {
|
|
+ if (!sessionId ||
|
|
+ !node.loaderId ||
|
|
+ !node.backendNodeId ||
|
|
+ node.backendNodeId < 0) {
|
|
+ return;
|
|
+ }
|
|
+ return `${sessionId}_${node.loaderId}_${node.backendNodeId}`;
|
|
+}
|
|
export class TextSnapshot {
|
|
static nextSnapshotId = 1;
|
|
static resetCounter() {
|
|
@@ -67,6 +78,7 @@ export class TextSnapshot {
|
|
selectedElementUid;
|
|
hasSelectedElement;
|
|
verbose;
|
|
+ #nodesByFrame;
|
|
constructor(data) {
|
|
this.root = data.root;
|
|
this.idToNode = data.idToNode;
|
|
@@ -74,9 +86,31 @@ export class TextSnapshot {
|
|
this.selectedElementUid = data.selectedElementUid;
|
|
this.hasSelectedElement = data.hasSelectedElement;
|
|
this.verbose = data.verbose;
|
|
+ this.#nodesByFrame = data.nodesByFrame;
|
|
}
|
|
static async create(page, options = {}) {
|
|
const verbose = options.verbose ?? false;
|
|
+ const mainFrame = page.pptrPage.mainFrame();
|
|
+ const frameStates = new Map();
|
|
+ for (const frame of page.pptrPage.frames()) {
|
|
+ if (frame instanceof CdpFrame) {
|
|
+ frameStates.set(frame, {
|
|
+ client: frame.client,
|
|
+ loaderId: frame._loaderId,
|
|
+ });
|
|
+ }
|
|
+ }
|
|
+ const currentClient = (frame) => {
|
|
+ const state = frameStates.get(frame);
|
|
+ if (!state ||
|
|
+ !(frame instanceof CdpFrame) ||
|
|
+ frame.detached ||
|
|
+ frame.client !== state.client ||
|
|
+ frame._loaderId !== state.loaderId) {
|
|
+ throw new Error('Snapshot document changed. Take a new snapshot.');
|
|
+ }
|
|
+ return state.client;
|
|
+ };
|
|
const rootNode = await page.pptrPage.accessibility.snapshot({
|
|
includeIframes: true,
|
|
interestingOnly: !verbose,
|
|
@@ -91,32 +125,90 @@ export class TextSnapshot {
|
|
let idCounter = 0;
|
|
const idToNode = new Map();
|
|
const seenUniqueIds = new Set();
|
|
- const seenBackendNodeIds = new Set();
|
|
+ const nodeFrames = new Map();
|
|
+ const documentIds = new Map();
|
|
+ const nodesByFrame = new Map();
|
|
+ const keyCounts = new Map();
|
|
+ const pending = [{ node: rootNode, frame: mainFrame }];
|
|
+ for (const entry of pending) {
|
|
+ const node = entry.node;
|
|
+ let frame = entry.frame;
|
|
+ if (node !== rootNode && node.role === 'RootWebArea') {
|
|
+ const env_1 = { stack: [], error: void 0, hasError: false };
|
|
+ try {
|
|
+ const handle = __addDisposableResource(env_1, await node.elementHandle(), false);
|
|
+ if (!handle) {
|
|
+ throw new Error('Snapshot document disappeared. Take a new snapshot.');
|
|
+ }
|
|
+ frame = handle.frame;
|
|
+ }
|
|
+ catch (e_1) {
|
|
+ env_1.error = e_1;
|
|
+ env_1.hasError = true;
|
|
+ }
|
|
+ finally {
|
|
+ __disposeResources(env_1);
|
|
+ }
|
|
+ }
|
|
+ const key = stableNodeKey(node, currentClient(frame).id());
|
|
+ if (key !== undefined) {
|
|
+ keyCounts.set(key, (keyCounts.get(key) ?? 0) + 1);
|
|
+ }
|
|
+ if (node.role === 'RootWebArea') {
|
|
+ documentIds.set(frame, node.loaderId);
|
|
+ }
|
|
+ nodeFrames.set(node, frame);
|
|
+ for (const child of node.children ?? []) {
|
|
+ pending.push({ node: child, frame });
|
|
+ }
|
|
+ }
|
|
+ // A repeated backend identity cannot identify either occurrence across captures.
|
|
+ for (const [key, count] of keyCounts) {
|
|
+ if (count > 1) {
|
|
+ uniqueBackendNodeIdToMcpId.delete(key);
|
|
+ }
|
|
+ }
|
|
const assignIds = (node) => {
|
|
- let id = '';
|
|
- // @ts-expect-error untyped backendNodeId.
|
|
- const backendNodeId = node.backendNodeId;
|
|
- // @ts-expect-error untyped loaderId.
|
|
- const uniqueBackendId = `${node.loaderId}_${backendNodeId}`;
|
|
- const existingMcpId = uniqueBackendNodeIdToMcpId.get(uniqueBackendId);
|
|
- if (existingMcpId !== undefined) {
|
|
- // Re-use MCP exposed ID if the uniqueId is the same.
|
|
- id = existingMcpId;
|
|
+ const frame = nodeFrames.get(node);
|
|
+ if (!frame) {
|
|
+ throw new Error('Snapshot node has no frame');
|
|
}
|
|
- else {
|
|
- // Only generate a new ID if we have not seen the node before.
|
|
- id = `${snapshotId}_${idCounter++}`;
|
|
- uniqueBackendNodeIdToMcpId.set(uniqueBackendId, id);
|
|
+ const key = stableNodeKey(node, currentClient(frame).id());
|
|
+ const stableKey = key !== undefined && keyCounts.get(key) === 1 ? key : undefined;
|
|
+ const id = (stableKey !== undefined
|
|
+ ? uniqueBackendNodeIdToMcpId.get(stableKey)
|
|
+ : undefined) ?? `${snapshotId}_${idCounter++}`;
|
|
+ if (stableKey !== undefined) {
|
|
+ uniqueBackendNodeIdToMcpId.set(stableKey, id);
|
|
+ seenUniqueIds.add(stableKey);
|
|
}
|
|
- seenUniqueIds.add(uniqueBackendId);
|
|
- seenBackendNodeIds.add(backendNodeId);
|
|
const nodeWithId = {
|
|
...node,
|
|
id,
|
|
children: node.children
|
|
? node.children.map(child => assignIds(child))
|
|
: [],
|
|
+ elementHandle: async () => {
|
|
+ currentClient(frame);
|
|
+ const handle = await node.elementHandle();
|
|
+ try {
|
|
+ currentClient(frame);
|
|
+ return handle;
|
|
+ }
|
|
+ catch (error) {
|
|
+ await handle?.dispose();
|
|
+ throw error;
|
|
+ }
|
|
+ },
|
|
};
|
|
+ if (nodeWithId.backendNodeId) {
|
|
+ let nodes = nodesByFrame.get(frame);
|
|
+ if (!nodes) {
|
|
+ nodes = new Map();
|
|
+ nodesByFrame.set(frame, nodes);
|
|
+ }
|
|
+ nodes.set(nodeWithId.backendNodeId, nodeWithId);
|
|
+ }
|
|
// The AXNode for an option doesn't contain its `value`.
|
|
// Therefore, set text content of the option as value.
|
|
if (node.role === 'option') {
|
|
@@ -129,13 +221,14 @@ export class TextSnapshot {
|
|
return nodeWithId;
|
|
};
|
|
const rootNodeWithId = assignIds(rootNode);
|
|
- await TextSnapshot.insertExtraNodes(page, idToNode, seenUniqueIds, snapshotId, idCounter, rootNodeWithId, seenBackendNodeIds, options.extraHandles ?? []);
|
|
+ await TextSnapshot.insertExtraNodes(page, idToNode, seenUniqueIds, snapshotId, idCounter, nodesByFrame, documentIds, currentClient, options.extraHandles ?? []);
|
|
const snapshot = new TextSnapshot({
|
|
root: rootNodeWithId,
|
|
snapshotId: String(snapshotId),
|
|
idToNode,
|
|
hasSelectedElement: false,
|
|
verbose,
|
|
+ nodesByFrame,
|
|
});
|
|
const data = options.devtoolsData ?? (await page.getDevToolsData());
|
|
if (data?.cdpBackendNodeId) {
|
|
@@ -150,65 +243,88 @@ export class TextSnapshot {
|
|
}
|
|
return snapshot;
|
|
}
|
|
- resolveCdpElementId(cdpBackendNodeId) {
|
|
+ resolveCdpElementId(cdpBackendNodeId, frame) {
|
|
if (!cdpBackendNodeId) {
|
|
logger?.('no cdpBackendNodeId');
|
|
return;
|
|
}
|
|
- // TODO: index by backendNodeId instead.
|
|
+ if (frame) {
|
|
+ return this.#nodesByFrame.get(frame)?.get(cdpBackendNodeId)?.id;
|
|
+ }
|
|
+ let match;
|
|
const queue = [this.root];
|
|
while (queue.length) {
|
|
const current = queue.pop();
|
|
if (current.backendNodeId === cdpBackendNodeId) {
|
|
- return current.id;
|
|
+ if (match !== undefined) {
|
|
+ return;
|
|
+ }
|
|
+ match = current.id;
|
|
}
|
|
for (const child of current.children) {
|
|
queue.push(child);
|
|
}
|
|
}
|
|
- return;
|
|
+ return match;
|
|
}
|
|
// ExtraHandles represent DOM nodes which might not be part of the accessibility tree, e.g. DOM nodes
|
|
// returned by third-party developer tools. We insert them into the tree by finding the closest ancestor
|
|
// in the tree and inserting the node as a child. The ancestor's child nodes are re-parented if necessary.
|
|
- static async insertExtraNodes(page, idToNode, seenUniqueIds, snapshotId, idCounter, rootNodeWithId, seenBackendNodeIds, extraHandles) {
|
|
+ static async insertExtraNodes(page, idToNode, seenUniqueIds, snapshotId, idCounter, nodesByFrame, documentIds, currentClient, extraHandles) {
|
|
const { uniqueBackendNodeIdToMcpId } = page;
|
|
const createExtraNode = async (handle) => {
|
|
- const backendNodeId = await handle.backendNodeId();
|
|
- if (!backendNodeId || seenBackendNodeIds.has(backendNodeId)) {
|
|
- return null;
|
|
- }
|
|
- const uniqueBackendId = `custom_${backendNodeId}`;
|
|
- if (seenUniqueIds.has(uniqueBackendId)) {
|
|
- return null;
|
|
+ const env_2 = { stack: [], error: void 0, hasError: false };
|
|
+ try {
|
|
+ const frame = handle.frame;
|
|
+ const client = currentClient(frame);
|
|
+ const backendNodeId = await handle.backendNodeId();
|
|
+ let nodes = nodesByFrame.get(frame);
|
|
+ if (!backendNodeId || nodes?.has(backendNodeId)) {
|
|
+ return null;
|
|
+ }
|
|
+ const documentId = documentIds.get(frame);
|
|
+ const uniqueBackendId = documentId
|
|
+ ? `custom_${client.id()}_${documentId}_${backendNodeId}`
|
|
+ : undefined;
|
|
+ const id = (uniqueBackendId !== undefined
|
|
+ ? uniqueBackendNodeIdToMcpId.get(uniqueBackendId)
|
|
+ : undefined) ?? `${snapshotId}_${idCounter++}`;
|
|
+ if (uniqueBackendId !== undefined) {
|
|
+ uniqueBackendNodeIdToMcpId.set(uniqueBackendId, id);
|
|
+ seenUniqueIds.add(uniqueBackendId);
|
|
+ }
|
|
+ const tagHandle = __addDisposableResource(env_2, await handle.getProperty('localName'), false);
|
|
+ const tagValue = await tagHandle.jsonValue();
|
|
+ const extraNode = {
|
|
+ role: tagValue,
|
|
+ id,
|
|
+ backendNodeId,
|
|
+ children: [],
|
|
+ elementHandle: async () => {
|
|
+ currentClient(frame);
|
|
+ return await handle.evaluateHandle(element => element);
|
|
+ },
|
|
+ };
|
|
+ if (!nodes) {
|
|
+ nodes = new Map();
|
|
+ nodesByFrame.set(frame, nodes);
|
|
+ }
|
|
+ nodes.set(backendNodeId, extraNode);
|
|
+ return extraNode;
|
|
}
|
|
- seenBackendNodeIds.add(backendNodeId);
|
|
- let id = '';
|
|
- const mcpId = uniqueBackendNodeIdToMcpId.get(uniqueBackendId);
|
|
- if (mcpId !== undefined) {
|
|
- id = mcpId;
|
|
+ catch (e_2) {
|
|
+ env_2.error = e_2;
|
|
+ env_2.hasError = true;
|
|
}
|
|
- else {
|
|
- id = `${snapshotId}_${idCounter++}`;
|
|
- uniqueBackendNodeIdToMcpId.set(uniqueBackendId, id);
|
|
+ finally {
|
|
+ __disposeResources(env_2);
|
|
}
|
|
- seenUniqueIds.add(uniqueBackendId);
|
|
- const tagHandle = await handle.getProperty('localName');
|
|
- const tagValue = await tagHandle.jsonValue();
|
|
- const extraNode = {
|
|
- role: tagValue,
|
|
- id,
|
|
- backendNodeId,
|
|
- children: [],
|
|
- elementHandle: async () => handle,
|
|
- };
|
|
- return extraNode;
|
|
};
|
|
const findAncestorNode = async (handle) => {
|
|
- const env_1 = { stack: [], error: void 0, hasError: false };
|
|
+ const env_3 = { stack: [], error: void 0, hasError: false };
|
|
try {
|
|
let ancestorHandle = await handle.evaluateHandle(el => el.parentElement);
|
|
- const stack = __addDisposableResource(env_1, new DisposableStack(), false);
|
|
+ const stack = __addDisposableResource(env_3, new DisposableStack(), false);
|
|
while (ancestorHandle) {
|
|
stack.use(ancestorHandle);
|
|
const ancestorElement = ancestorHandle.asElement();
|
|
@@ -217,9 +333,9 @@ export class TextSnapshot {
|
|
}
|
|
const ancestorBackendId = await ancestorElement.backendNodeId();
|
|
if (ancestorBackendId) {
|
|
- const ancestorNode = idToNode
|
|
- .values()
|
|
- .find(node => node.backendNodeId === ancestorBackendId);
|
|
+ const ancestorNode = nodesByFrame
|
|
+ .get(handle.frame)
|
|
+ ?.get(ancestorBackendId);
|
|
if (ancestorNode) {
|
|
return ancestorNode;
|
|
}
|
|
@@ -229,40 +345,37 @@ export class TextSnapshot {
|
|
}
|
|
return null;
|
|
}
|
|
- catch (e_1) {
|
|
- env_1.error = e_1;
|
|
- env_1.hasError = true;
|
|
+ catch (e_3) {
|
|
+ env_3.error = e_3;
|
|
+ env_3.hasError = true;
|
|
}
|
|
finally {
|
|
- __disposeResources(env_1);
|
|
+ __disposeResources(env_3);
|
|
}
|
|
};
|
|
- const findDescendantNodes = async (backendNodeId) => {
|
|
+ const findDescendantNodes = async (frame, backendNodeId) => {
|
|
const descendantIds = new Set();
|
|
if (!backendNodeId) {
|
|
return descendantIds;
|
|
}
|
|
try {
|
|
- // @ts-expect-error internal API
|
|
- const client = page.pptrPage._client();
|
|
- if (client) {
|
|
- const { node } = await client.send('DOM.describeNode', {
|
|
- backendNodeId,
|
|
- depth: -1,
|
|
- pierce: true,
|
|
- });
|
|
- const collect = (node) => {
|
|
- if (node.backendNodeId && node.backendNodeId !== backendNodeId) {
|
|
- descendantIds.add(node.backendNodeId);
|
|
- }
|
|
- if (node.children) {
|
|
- for (const child of node.children) {
|
|
- collect(child);
|
|
- }
|
|
+ const client = currentClient(frame);
|
|
+ const { node } = await client.send('DOM.describeNode', {
|
|
+ backendNodeId,
|
|
+ depth: -1,
|
|
+ pierce: true,
|
|
+ });
|
|
+ const collect = (node) => {
|
|
+ if (node.backendNodeId && node.backendNodeId !== backendNodeId) {
|
|
+ descendantIds.add(node.backendNodeId);
|
|
+ }
|
|
+ if (node.children) {
|
|
+ for (const child of node.children) {
|
|
+ collect(child);
|
|
}
|
|
- };
|
|
- collect(node);
|
|
- }
|
|
+ }
|
|
+ };
|
|
+ collect(node);
|
|
}
|
|
catch (e) {
|
|
logger?.(`Failed to collect descendants for backend node ${backendNodeId}`, e);
|
|
@@ -302,8 +415,15 @@ export class TextSnapshot {
|
|
continue;
|
|
}
|
|
idToNode.set(extraNode.id, extraNode);
|
|
- const attachTarget = (await findAncestorNode(handle)) || rootNodeWithId;
|
|
- const descendantIds = await findDescendantNodes(extraNode.backendNodeId);
|
|
+ const attachTarget = (await findAncestorNode(handle)) ??
|
|
+ nodesByFrame
|
|
+ .get(handle.frame)
|
|
+ ?.values()
|
|
+ .find(node => node.role === 'RootWebArea');
|
|
+ if (!attachTarget) {
|
|
+ throw new Error('Extra node has no snapshot document. Take a new snapshot.');
|
|
+ }
|
|
+ const descendantIds = await findDescendantNodes(handle.frame, extraNode.backendNodeId);
|
|
reorgInfo.push({ extraNode, attachTarget, descendantIds });
|
|
}
|
|
for (const { extraNode, attachTarget, descendantIds } of reorgInfo) {
|