diff --git a/packages/playwright-core/src/server/bidi/bidiConnection.ts b/packages/playwright-core/src/server/bidi/bidiConnection.ts index 7138f2e06a..f348815940 100644 --- a/packages/playwright-core/src/server/bidi/bidiConnection.ts +++ b/packages/playwright-core/src/server/bidi/bidiConnection.ts @@ -72,7 +72,7 @@ export class BidiConnection { let context; if ('context' in object.params) context = object.params.context; - else if (object.method === 'log.entryAdded') + else if (object.method === 'log.entryAdded' || object.method === 'script.message') context = object.params.source?.context; if (context) { const session = this._browsingContextToSession.get(context); diff --git a/packages/playwright-core/src/server/bidi/bidiExecutionContext.ts b/packages/playwright-core/src/server/bidi/bidiExecutionContext.ts index eaacb629e6..c037ba44b4 100644 --- a/packages/playwright-core/src/server/bidi/bidiExecutionContext.ts +++ b/packages/playwright-core/src/server/bidi/bidiExecutionContext.ts @@ -23,7 +23,7 @@ import { BidiSerializer } from './third_party/bidiSerializer'; export class BidiExecutionContext implements js.ExecutionContextDelegate { private readonly _session: BidiSession; - private readonly _target: bidi.Script.Target; + readonly _target: bidi.Script.Target; constructor(session: BidiSession, realmInfo: bidi.Script.RealmInfo) { this._session = session; diff --git a/packages/playwright-core/src/server/bidi/bidiPage.ts b/packages/playwright-core/src/server/bidi/bidiPage.ts index c2d499bd67..5ce5234185 100644 --- a/packages/playwright-core/src/server/bidi/bidiPage.ts +++ b/packages/playwright-core/src/server/bidi/bidiPage.ts @@ -21,7 +21,8 @@ import type * as accessibility from '../accessibility'; import * as dom from '../dom'; import * as dialog from '../dialog'; import type * as frames from '../frames'; -import { type InitScript, Page, type PageDelegate } from '../page'; +import { Page } from '../page'; +import type { InitScript, PageDelegate } from '../page'; import type { Progress } from '../progress'; import type * as types from '../types'; import type { BidiBrowserContext } from './bidiBrowser'; @@ -33,6 +34,7 @@ import { BidiNetworkManager } from './bidiNetworkManager'; import { BrowserContext } from '../browserContext'; const UTILITY_WORLD_NAME = '__playwright_utility_world__'; +const kPlaywrightBindingChannel = 'playwrightChannel'; export class BidiPage implements PageDelegate { readonly rawMouse: RawMouseImpl; @@ -62,6 +64,7 @@ export class BidiPage implements PageDelegate { this._page.on(Page.Events.FrameDetached, (frame: frames.Frame) => this._removeContextsForFrame(frame, false)); this._sessionListeners = [ eventsHelper.addEventListener(bidiSession, 'script.realmCreated', this._onRealmCreated.bind(this)), + eventsHelper.addEventListener(bidiSession, 'script.message', this._onScriptMessage.bind(this)), eventsHelper.addEventListener(bidiSession, 'browsingContext.contextDestroyed', this._onBrowsingContextDestroyed.bind(this)), eventsHelper.addEventListener(bidiSession, 'browsingContext.navigationStarted', this._onNavigationStarted.bind(this)), eventsHelper.addEventListener(bidiSession, 'browsingContext.navigationAborted', this._onNavigationAborted.bind(this)), @@ -93,6 +96,7 @@ export class BidiPage implements PageDelegate { this.updateHttpCredentials(), this.updateRequestInterception(), this._updateViewport(), + this._installMainBinding(), this._addAllInitScripts(), ]); } @@ -327,6 +331,45 @@ export class BidiPage implements PageDelegate { throw new Error('Method not implemented.'); } + // TODO: consider calling this only when bindings are added. + private async _installMainBinding() { + const functionDeclaration = addMainBinding.toString(); + const args: bidi.Script.ChannelValue[] = [{ + type: 'channel', + value: { + channel: kPlaywrightBindingChannel, + ownership: bidi.Script.ResultOwnership.Root, + } + }]; + const promises = []; + promises.push(this._session.send('script.addPreloadScript', { + functionDeclaration, + arguments: args, + })); + promises.push(this._session.send('script.callFunction', { + functionDeclaration, + arguments: args, + target: toBidiExecutionContext(await this._page.mainFrame()._mainContext())._target, + awaitPromise: false, + userActivation: false, + })); + await Promise.all(promises); + } + + private async _onScriptMessage(event: bidi.Script.MessageParameters) { + if (event.channel !== kPlaywrightBindingChannel) + return; + const pageOrError = await this.pageOrError(); + if (pageOrError instanceof Error) + return; + const context = this._realmToContext.get(event.source.realm); + if (!context) + return; + if (event.data.type !== 'string') + return; + await this._page._onBindingCalled(event.data.value, context); + } + async addInitScript(initScript: InitScript): Promise { const { script } = await this._session.send('script.addPreloadScript', { // TODO: remove function call from the source. @@ -522,6 +565,10 @@ export class BidiPage implements PageDelegate { } } +function addMainBinding(callback: (arg: any) => void) { + (globalThis as any)['__playwright__binding__'] = callback; +} + function toBidiExecutionContext(executionContext: dom.FrameExecutionContext): BidiExecutionContext { return (executionContext as any)[contextDelegateSymbol] as BidiExecutionContext; }