Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions packages/playwright-core/src/server/bidi/bidiBrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,11 @@ export class BidiExecutionContext implements js.ExecutionContextDelegate {
};
}

async remoteObjectForNodeId(context: dom.FrameExecutionContext, nodeId: bidi.Script.SharedReference): Promise<js.JSHandle> {
async remoteObjectForNodeId(context: dom.FrameExecutionContext, nodeId: bidi.Script.SharedReference): Promise<dom.ElementHandle> {
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) {
Expand Down
60 changes: 38 additions & 22 deletions packages/playwright-core/src/server/bidi/bidiPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export class BidiPage implements PageDelegate {
readonly _networkManager: BidiNetworkManager;
private readonly _pdf: BidiPDF;
private _initScriptIds = new Map<InitScript, string>();
readonly _frameNamePromises = new Map<string, Promise<string>>();

constructor(browserContext: BidiBrowserContext, bidiSession: BidiSession, opener: BidiPage | null) {
this._session = bidiSession;
Expand Down Expand Up @@ -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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Event listeners should be synchronous, we don't want to mess up the event order due to random awaits.

The frame name usually doesn't change, so we could query it on frame attach and if it arrives before the first navigation it will automatically propagate to the clients. This would be racy in some cases, but doesn't require async event handlers. We now have locators (and frame locators) that allow to select iframe elements and don't depend on this method, this could be a fallback for the folks that need to find a frame.

I don't think we should make all these handlers async just for the frame.name(). I'd rather keep the property best effort. If we want a proper fix, we'd need to make the name available synchronously.

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) {
Expand Down Expand Up @@ -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<bidi.Script.NodeRemoteValue | undefined> {
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 {
Expand Down
4 changes: 3 additions & 1 deletion packages/playwright-core/src/server/frames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>, 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;
Expand Down
Loading