-
Notifications
You must be signed in to change notification settings - Fork 3.5k
draft feat: get_css_style tool #2612
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -77,7 +77,7 @@ import { | |
| type Page, | ||
| type ConsoleMessage, | ||
| type HTTPRequest, | ||
| type DevTools, | ||
| DevTools, | ||
| type JSONSchema7Definition, | ||
| } from './third_party/index.js'; | ||
| import {takeSnapshot} from './tools/snapshot.js'; | ||
|
|
@@ -87,6 +87,7 @@ const NAVIGATION_TIMEOUT = 10_000; | |
| import type { | ||
| ContextPage, | ||
| DevToolsData, | ||
| MatchedStyles, | ||
| Response, | ||
| } from './tools/ToolDefinition.js'; | ||
| import type { | ||
|
|
@@ -102,6 +103,12 @@ import { | |
| type DialogAction, | ||
| } from './utils/WaitForHelper.js'; | ||
|
|
||
| function isBackendNodeId( | ||
| id: number, | ||
| ): id is DevTools.Protocol.DOM.BackendNodeId { | ||
| return typeof id === 'number'; | ||
| } | ||
|
|
||
| /** | ||
| * Per-page state wrapper. Consolidates dialog, snapshot, emulation, | ||
| * and metadata that were previously scattered across Maps in McpContext. | ||
|
|
@@ -125,6 +132,7 @@ export class McpPage implements ContextPage { | |
| // Metadata | ||
| isolatedContextName?: string; | ||
| #devtoolsUniverse?: TargetUniverse; | ||
| #initDevToolsPromise?: Promise<TargetUniverse>; | ||
|
|
||
| // Dialog | ||
| #dialog?: Dialog; | ||
|
|
@@ -190,13 +198,29 @@ export class McpPage implements ContextPage { | |
| }); | ||
| } | ||
|
|
||
| async #initDevToolsUniverseNoThrow(): Promise<void> { | ||
| async ensureDevToolsUniverse(): Promise<TargetUniverse> { | ||
| if (this.#devtoolsUniverse) { | ||
| return undefined; | ||
| return this.#devtoolsUniverse; | ||
| } | ||
| try { | ||
| if (this.#initDevToolsPromise) { | ||
| return await this.#initDevToolsPromise; | ||
| } | ||
| this.#initDevToolsPromise = (async () => { | ||
| const session = await this.pptrPage.createCDPSession(); | ||
| this.#devtoolsUniverse = await createTargetUniverse(session); | ||
| const universe = await createTargetUniverse(session); | ||
| this.#devtoolsUniverse = universe; | ||
| return universe; | ||
| })(); | ||
| try { | ||
| return await this.#initDevToolsPromise; | ||
| } finally { | ||
| this.#initDevToolsPromise = undefined; | ||
| } | ||
| } | ||
|
|
||
| async #initDevToolsUniverseNoThrow(): Promise<void> { | ||
| try { | ||
| await this.ensureDevToolsUniverse(); | ||
| } catch (e) { | ||
| logger?.('Failed to initialize DevTools universe', e); | ||
| } | ||
|
|
@@ -671,6 +695,63 @@ export class McpPage implements ContextPage { | |
| return this.textSnapshot?.idToNode.get(uid); | ||
| } | ||
|
|
||
| async getMatchedStylesForUid(uid: string): Promise<MatchedStyles> { | ||
| if (!this.textSnapshot) { | ||
| throw new Error( | ||
| `No snapshot found for page ${this.id ?? '?'}. Use ${takeSnapshot.name} to capture one.`, | ||
| ); | ||
| } | ||
| const node = this.textSnapshot.idToNode.get(uid); | ||
| if (!node) { | ||
| throw new Error(`Element uid "${uid}" not found on page ${this.id}.`); | ||
| } | ||
|
|
||
| let backendNodeId = node.backendNodeId; | ||
| if (!backendNodeId) { | ||
| using handle = await this.#resolveElementHandle(node, uid); | ||
| backendNodeId = await handle.backendNodeId(); | ||
| } | ||
| if (!backendNodeId || !isBackendNodeId(backendNodeId)) { | ||
| throw new Error( | ||
| `Failed to resolve backend node ID for element with uid "${uid}".`, | ||
| ); | ||
| } | ||
|
|
||
| const devtools = await this.ensureDevToolsUniverse(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we need to ensure the universe here? I think it should automatically exist when the page is available. Let's throw an error if it does not exist instead? |
||
| const domModel = devtools.target.model(DevTools.DOMModel.DOMModel); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. uid might belong to iframes for we need to find the right target instance |
||
| const cssModel = devtools.target.model(DevTools.CSSModel.CSSModel); | ||
| if (!domModel || !cssModel) { | ||
| throw new Error('DevTools DOMModel or CSSModel is not available.'); | ||
| } | ||
|
|
||
| await domModel.requestDocument(); | ||
| const nodeMap = await domModel.pushNodesByBackendIdsToFrontend( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should not need to push nodes to the frontend. Can we just use the CSS logic relying on backend node IDs only? |
||
| new Set([backendNodeId]), | ||
| ); | ||
| const domNode = nodeMap?.get(backendNodeId); | ||
| if (!domNode) { | ||
| throw new Error( | ||
| `Element with uid "${uid}" was detached or no longer exists on the page. Please take a new snapshot with ${takeSnapshot.name}.`, | ||
| ); | ||
| } | ||
|
|
||
| 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; | ||
| } | ||
|
|
||
| async getDevToolsData(): Promise<DevToolsData> { | ||
| try { | ||
| logger?.('Getting DevTools UI data'); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1048,5 +1048,9 @@ | |
| "argType": "number" | ||
| } | ||
| ] | ||
| }, | ||
| { | ||
| "name": "get_css_styles", | ||
| "args": [] | ||
| } | ||
| ] | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could we resolve without creating a handle?