| 152 | // returned by third-party developer tools. We insert them into the tree by finding the closest ancestor |
| 153 | // in the tree and inserting the node as a child. The ancestor's child nodes are re-parented if necessary. |
| 154 | private static async insertExtraNodes( |
| 155 | page: McpPage, |
| 156 | idToNode: Map<string, TextSnapshotNode>, |
| 157 | seenUniqueIds: Set<string>, |
| 158 | snapshotId: number, |
| 159 | idCounter: number, |
| 160 | rootNodeWithId: TextSnapshotNode, |
| 161 | seenBackendNodeIds: Set<number>, |
| 162 | extraHandles: ElementHandle[], |
| 163 | ): Promise<void> { |
| 164 | const {uniqueBackendNodeIdToMcpId} = page; |
| 165 | |
| 166 | const createExtraNode = async ( |
| 167 | handle: ElementHandle, |
| 168 | ): Promise<TextSnapshotNode | null> => { |
| 169 | const backendNodeId = await handle.backendNodeId(); |
| 170 | if (!backendNodeId || seenBackendNodeIds.has(backendNodeId)) { |
| 171 | return null; |
| 172 | } |
| 173 | const uniqueBackendId = `custom_${backendNodeId}`; |
| 174 | if (seenUniqueIds.has(uniqueBackendId)) { |
| 175 | return null; |
| 176 | } |
| 177 | seenBackendNodeIds.add(backendNodeId); |
| 178 | |
| 179 | let id = ''; |
| 180 | const mcpId = uniqueBackendNodeIdToMcpId.get(uniqueBackendId); |
| 181 | if (mcpId !== undefined) { |
| 182 | id = mcpId; |
| 183 | } else { |
| 184 | id = `${snapshotId}_${idCounter++}`; |
| 185 | uniqueBackendNodeIdToMcpId.set(uniqueBackendId, id); |
| 186 | } |
| 187 | seenUniqueIds.add(uniqueBackendId); |
| 188 | |
| 189 | const tagHandle = await handle.getProperty('localName'); |
| 190 | const tagValue = await tagHandle.jsonValue(); |
| 191 | const extraNode: TextSnapshotNode = { |
| 192 | role: tagValue, |
| 193 | id, |
| 194 | backendNodeId, |
| 195 | children: [], |
| 196 | elementHandle: async () => handle, |
| 197 | }; |
| 198 | return extraNode; |
| 199 | }; |
| 200 | |
| 201 | const findAncestorNode = async ( |
| 202 | handle: ElementHandle, |
| 203 | ): Promise<TextSnapshotNode | null> => { |
| 204 | let ancestorHandle = await handle.evaluateHandle(el => el.parentElement); |
| 205 | |
| 206 | while (ancestorHandle) { |
| 207 | const ancestorElement = ancestorHandle.asElement(); |
| 208 | if (!ancestorElement) { |
| 209 | await ancestorHandle.dispose(); |
| 210 | return null; |
| 211 | } |