From 7184989636921b3900df14a14570c6a625e03929 Mon Sep 17 00:00:00 2001 From: Holger Benl Date: Thu, 6 Nov 2025 17:07:57 +0100 Subject: [PATCH] chore(bidi): add support for context locators --- .../src/server/bidi/bidiBrowser.ts | 13 ++-- .../src/server/bidi/bidiExecutionContext.ts | 4 +- .../src/server/bidi/bidiPage.ts | 60 ++++++++++++------- packages/playwright-core/src/server/frames.ts | 4 +- 4 files changed, 52 insertions(+), 29 deletions(-) diff --git a/packages/playwright-core/src/server/bidi/bidiBrowser.ts b/packages/playwright-core/src/server/bidi/bidiBrowser.ts index 28cdd80a9841d..34180837d5cca 100644 --- a/packages/playwright-core/src/server/bidi/bidiBrowser.ts +++ b/packages/playwright-core/src/server/bidi/bidiBrowser.ts @@ -135,10 +135,15 @@ export class BidiBrowser extends Browser { if (!parentFrame) continue; page._session.addFrameBrowsingContext(event.context); - page._page.frameManager.frameAttached(event.context, parentFrameId); - const frame = page._page.frameManager.frame(event.context); - if (frame) - frame._url = event.url; + const frame = page._page.frameManager.frameAttached(event.context, parentFrameId); + frame._url = event.url; + page._frameNamePromises.set( + event.context, + page._getFrameNode(frame).then(node => { + const attributes = node?.value?.attributes; + return attributes?.name ?? attributes?.id ?? ''; + }) + ); return; } return; diff --git a/packages/playwright-core/src/server/bidi/bidiExecutionContext.ts b/packages/playwright-core/src/server/bidi/bidiExecutionContext.ts index c7e0a745ebafe..cef9cbdfa9898 100644 --- a/packages/playwright-core/src/server/bidi/bidiExecutionContext.ts +++ b/packages/playwright-core/src/server/bidi/bidiExecutionContext.ts @@ -141,11 +141,11 @@ export class BidiExecutionContext implements js.ExecutionContextDelegate { }; } - async remoteObjectForNodeId(context: dom.FrameExecutionContext, nodeId: bidi.Script.SharedReference): Promise { + async remoteObjectForNodeId(context: dom.FrameExecutionContext, nodeId: bidi.Script.SharedReference): Promise { const result = await this._remoteValueForReference(nodeId, true); if (!('handle' in result)) throw new Error('Can\'t get remote object for nodeId'); - return createHandle(context, result); + return createHandle(context, result) as dom.ElementHandle; } async contentFrameIdForFrame(handle: dom.ElementHandle) { diff --git a/packages/playwright-core/src/server/bidi/bidiPage.ts b/packages/playwright-core/src/server/bidi/bidiPage.ts index 2ec693768ff19..f8b7e1b5ef276 100644 --- a/packages/playwright-core/src/server/bidi/bidiPage.ts +++ b/packages/playwright-core/src/server/bidi/bidiPage.ts @@ -52,6 +52,7 @@ export class BidiPage implements PageDelegate { readonly _networkManager: BidiNetworkManager; private readonly _pdf: BidiPDF; private _initScriptIds = new Map(); + readonly _frameNamePromises = new Map>(); constructor(browserContext: BidiBrowserContext, bidiSession: BidiSession, opener: BidiPage | null) { this._session = bidiSession; @@ -193,18 +194,33 @@ export class BidiPage implements PageDelegate { this._page.frameManager.frameRequestedNavigation(frameId, params.navigation!); } - private _onNavigationCommitted(params: bidi.BrowsingContext.NavigationInfo) { + private async _onNavigationCommitted(params: bidi.BrowsingContext.NavigationInfo) { const frameId = params.context; - this._page.frameManager.frameCommittedNewDocumentNavigation(frameId, params.url, '', params.navigation!, /* initial */ false); + const namePromise = this._frameNamePromises.get(frameId) ?? ''; + await this._page.frameManager.frameCommittedNewDocumentNavigation(frameId, params.url, namePromise, params.navigation!, /* initial */ false); + this._frameNamePromises.delete(frameId); } - private _onDomContentLoaded(params: bidi.BrowsingContext.NavigationInfo) { + private async _onDomContentLoaded(params: bidi.BrowsingContext.NavigationInfo) { const frameId = params.context; + const frame = this._page.frameManager.frame(frameId)!; + await this._waitForChildFrameNames(frame, false); this._page.frameManager.frameLifecycleEvent(frameId, 'domcontentloaded'); } - private _onLoad(params: bidi.BrowsingContext.NavigationInfo) { - this._page.frameManager.frameLifecycleEvent(params.context, 'load'); + private async _onLoad(params: bidi.BrowsingContext.NavigationInfo) { + const frameId = params.context; + const frame = this._page.frameManager.frame(frameId)!; + await this._waitForChildFrameNames(frame, true); + this._page.frameManager.frameLifecycleEvent(frameId, 'load'); + } + + private async _waitForChildFrameNames(frame: frames.Frame, recursive: boolean) { + for (const childFrame of frame.childFrames()) { + await this._frameNamePromises.get(childFrame._id); + if (recursive) + await this._waitForChildFrameNames(childFrame, recursive); + } } private _onNavigationAborted(params: bidi.BrowsingContext.NavigationInfo) { @@ -582,24 +598,24 @@ export class BidiPage implements PageDelegate { const parent = frame.parentFrame(); if (!parent) throw new Error('Frame has been detached.'); - const parentContext = await parent._mainContext(); - const list = await parentContext.evaluateHandle(() => { return [...document.querySelectorAll('iframe,frame')]; }); - const length = await list.evaluate(list => list.length); - let foundElement = null; - for (let i = 0; i < length; i++) { - const element = await list.evaluateHandle((list, i) => list[i], i); - const candidate = await element.contentFrame(); - if (frame === candidate) { - foundElement = element; - break; - } else { - element.dispose(); - } - } - list.dispose(); - if (!foundElement) + const node = await this._getFrameNode(frame); + if (!node?.sharedId) throw new Error('Frame has been detached.'); - return foundElement; + const parentFrameExecutionContext = await parent._mainContext(); + return await toBidiExecutionContext(parentFrameExecutionContext).remoteObjectForNodeId(parentFrameExecutionContext, { sharedId: node.sharedId }); + } + + async _getFrameNode(frame: frames.Frame): Promise { + const parent = frame.parentFrame(); + if (!parent) + return undefined; + + const result = await this._session.send('browsingContext.locateNodes', { + context: parent._id, + locator: { type: 'context', value: { context: frame._id } }, + }); + const node = result.nodes[0]; + return node; } shouldToggleStyleSheetToSyncAnimations(): boolean { diff --git a/packages/playwright-core/src/server/frames.ts b/packages/playwright-core/src/server/frames.ts index b89faf28fb34e..c8ef8df724b95 100644 --- a/packages/playwright-core/src/server/frames.ts +++ b/packages/playwright-core/src/server/frames.ts @@ -208,11 +208,13 @@ export class FrameManager { frame.setPendingDocument({ documentId, request }); } - frameCommittedNewDocumentNavigation(frameId: string, url: string, name: string, documentId: string, initial: boolean) { + async frameCommittedNewDocumentNavigation(frameId: string, url: string, name: string | Promise, documentId: string, initial: boolean) { const frame = this._frames.get(frameId)!; this.removeChildFramesRecursively(frame); this.clearWebSockets(frame); frame._url = url; + if (typeof name !== 'string') + name = await name; frame._name = name; let keepPending: DocumentInfo | undefined;