chore(bidi): set viewport and init script per context (#35247)

This commit is contained in:
Yury Semikhatsky 2025-03-18 10:31:33 -07:00 committed by GitHub
parent 66b3efa826
commit 54d48c997f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 62 additions and 11 deletions

View File

@ -20,7 +20,7 @@ import { BrowserContext, assertBrowserContextIsNotOwned } from '../browserContex
import * as network from '../network'; import * as network from '../network';
import { BidiConnection } from './bidiConnection'; import { BidiConnection } from './bidiConnection';
import { bidiBytesValueToString } from './bidiNetworkManager'; import { bidiBytesValueToString } from './bidiNetworkManager';
import { BidiPage } from './bidiPage'; import { addMainBinding, BidiPage, kPlaywrightBindingChannel } from './bidiPage';
import * as bidi from './third_party/bidiProtocol'; import * as bidi from './third_party/bidiProtocol';
import type { RegisteredListener } from '../utils/eventsHelper'; import type { RegisteredListener } from '../utils/eventsHelper';
@ -203,6 +203,7 @@ export class BidiBrowser extends Browser {
export class BidiBrowserContext extends BrowserContext { export class BidiBrowserContext extends BrowserContext {
declare readonly _browser: BidiBrowser; declare readonly _browser: BidiBrowser;
private _initScriptIds: bidi.Script.PreloadScript[] = [];
constructor(browser: BidiBrowser, browserContextId: string | undefined, options: types.BrowserContextOptions) { constructor(browser: BidiBrowser, browserContextId: string | undefined, options: types.BrowserContextOptions) {
super(browser, options, browserContextId); super(browser, options, browserContextId);
@ -213,6 +214,45 @@ export class BidiBrowserContext extends BrowserContext {
return [...this._browser._bidiPages.values()].filter(bidiPage => bidiPage._browserContext === this); return [...this._browser._bidiPages.values()].filter(bidiPage => bidiPage._browserContext === this);
} }
override async _initialize() {
const promises: Promise<any>[] = [
super._initialize(),
this._installMainBinding(),
];
// FIXME: Persistent context doesn't have an id, so we can't set the command for it.
if (this._options.viewport && this._browserContextId) {
promises.push(this._browser._browserSession.send('browsingContext.setViewport', {
viewport: {
width: this._options.viewport.width,
height: this._options.viewport.height
},
devicePixelRatio: this._options.deviceScaleFactor || 1,
userContexts: [this._browserContextId],
}));
}
await Promise.all(promises);
}
// TODO: consider calling this only when bindings are added.
private async _installMainBinding() {
// TODO: Cannot install main binding for persistent context as it doesn't have an id.
if (!this._browserContextId)
return;
const functionDeclaration = addMainBinding.toString();
const args: bidi.Script.ChannelValue[] = [{
type: 'channel',
value: {
channel: kPlaywrightBindingChannel,
ownership: bidi.Script.ResultOwnership.Root,
}
}];
await this._browser._browserSession.send('script.addPreloadScript', {
functionDeclaration,
arguments: args,
userContexts: [this._browserContextId],
});
}
override possiblyUninitializedPages(): Page[] { override possiblyUninitializedPages(): Page[] {
return this._bidiPages().map(bidiPage => bidiPage._page); return this._bidiPages().map(bidiPage => bidiPage._page);
} }
@ -293,10 +333,22 @@ export class BidiBrowserContext extends BrowserContext {
} }
async doAddInitScript(initScript: InitScript) { async doAddInitScript(initScript: InitScript) {
await Promise.all(this.pages().map(page => (page._delegate as BidiPage).addInitScript(initScript))); // TODO: support persistent context.
if (!this._browserContextId)
return;
const { script } = await this._browser._browserSession.send('script.addPreloadScript', {
// TODO: remove function call from the source.
functionDeclaration: `() => { return ${initScript.source} }`,
userContexts: [this._browserContextId],
});
if (!initScript.internal)
this._initScriptIds.push(script);
} }
async doRemoveNonInternalInitScripts() { async doRemoveNonInternalInitScripts() {
const promise = Promise.all(this._initScriptIds.map(script => this._browser._browserSession.send('script.removePreloadScript', { script })));
this._initScriptIds = [];
await promise;
} }
async doUpdateRequestInterception(): Promise<void> { async doUpdateRequestInterception(): Promise<void> {

View File

@ -37,7 +37,7 @@ import type { BidiSession } from './bidiConnection';
import type * as channels from '@protocol/channels'; import type * as channels from '@protocol/channels';
const UTILITY_WORLD_NAME = '__playwright_utility_world__'; const UTILITY_WORLD_NAME = '__playwright_utility_world__';
const kPlaywrightBindingChannel = 'playwrightChannel'; export const kPlaywrightBindingChannel = 'playwrightChannel';
export class BidiPage implements PageDelegate { export class BidiPage implements PageDelegate {
readonly rawMouse: RawMouseImpl; readonly rawMouse: RawMouseImpl;
@ -51,7 +51,7 @@ export class BidiPage implements PageDelegate {
readonly _browserContext: BidiBrowserContext; readonly _browserContext: BidiBrowserContext;
readonly _networkManager: BidiNetworkManager; readonly _networkManager: BidiNetworkManager;
private readonly _pdf: BidiPDF; private readonly _pdf: BidiPDF;
private _initScriptIds: string[] = []; private _initScriptIds: bidi.Script.PreloadScript[] = [];
constructor(browserContext: BidiBrowserContext, bidiSession: BidiSession, opener: BidiPage | null) { constructor(browserContext: BidiBrowserContext, bidiSession: BidiSession, opener: BidiPage | null) {
this._session = bidiSession; this._session = bidiSession;
@ -92,7 +92,6 @@ export class BidiPage implements PageDelegate {
await Promise.all([ await Promise.all([
this.updateHttpCredentials(), this.updateHttpCredentials(),
this.updateRequestInterception(), this.updateRequestInterception(),
this._updateViewport(),
this._installMainBinding(), this._installMainBinding(),
this._addAllInitScripts(), this._addAllInitScripts(),
]); ]);
@ -258,10 +257,6 @@ export class BidiPage implements PageDelegate {
async updateEmulateMedia(): Promise<void> { async updateEmulateMedia(): Promise<void> {
} }
async updateEmulatedViewportSize(): Promise<void> {
await this._updateViewport();
}
async updateUserAgent(): Promise<void> { async updateUserAgent(): Promise<void> {
} }
@ -271,7 +266,7 @@ export class BidiPage implements PageDelegate {
}); });
} }
private async _updateViewport(): Promise<void> { async updateEmulatedViewportSize(): Promise<void> {
const options = this._browserContext._options; const options = this._browserContext._options;
const deviceSize = this._page.emulatedSize(); const deviceSize = this._page.emulatedSize();
if (deviceSize === null) if (deviceSize === null)
@ -328,7 +323,11 @@ export class BidiPage implements PageDelegate {
} }
// TODO: consider calling this only when bindings are added. // TODO: consider calling this only when bindings are added.
// TODO: delete this method once we can add preload script for persistent context.
private async _installMainBinding() { private async _installMainBinding() {
// For non-persistent context, the main binding is installed during context creation.
if (this._browserContext._browserContextId)
return;
const functionDeclaration = addMainBinding.toString(); const functionDeclaration = addMainBinding.toString();
const args: bidi.Script.ChannelValue[] = [{ const args: bidi.Script.ChannelValue[] = [{
type: 'channel', type: 'channel',
@ -573,7 +572,7 @@ export class BidiPage implements PageDelegate {
} }
} }
function addMainBinding(callback: (arg: any) => void) { export function addMainBinding(callback: (arg: any) => void) {
(globalThis as any)['__playwright__binding__'] = callback; (globalThis as any)['__playwright__binding__'] = callback;
} }