From ab5d4ac60200d1cea5bcd4910f9ccb323128e79a Mon Sep 17 00:00:00 2001 From: jrandolf <101637635+jrandolf@users.noreply.github.com> Date: Mon, 22 Jan 2024 15:53:48 +0100 Subject: [PATCH] refactor: adopt `core/UserContext` on `BidiBrowserContext` (#11714) --- packages/puppeteer-core/src/bidi/Browser.ts | 50 +++++++------------ .../puppeteer-core/src/bidi/BrowserContext.ts | 27 +++++++--- packages/puppeteer-core/src/bidi/Target.ts | 29 ++++++++++- .../puppeteer-core/src/bidi/core/Browser.ts | 26 ++++++++-- .../src/bidi/core/UserContext.ts | 24 ++++++--- 5 files changed, 106 insertions(+), 50 deletions(-) diff --git a/packages/puppeteer-core/src/bidi/Browser.ts b/packages/puppeteer-core/src/bidi/Browser.ts index 9b97d32a5b5..64f46e8b3f3 100644 --- a/packages/puppeteer-core/src/bidi/Browser.ts +++ b/packages/puppeteer-core/src/bidi/Browser.ts @@ -27,6 +27,7 @@ import {BrowsingContext, BrowsingContextEvent} from './BrowsingContext.js'; import type {BidiConnection} from './Connection.js'; import type {Browser as BrowserCore} from './core/Browser.js'; import {Session} from './core/Session.js'; +import type {UserContext} from './core/UserContext.js'; import { BiDiBrowserTarget, BiDiBrowsingContextTarget, @@ -95,9 +96,8 @@ export class BidiBrowser extends Browser { #closeCallback?: BrowserCloseCallback; #browserCore: BrowserCore; #defaultViewport: Viewport | null; - #defaultContext: BidiBrowserContext; #targets = new Map(); - #contexts: BidiBrowserContext[] = []; + #browserContexts = new WeakMap(); #browserTarget: BiDiBrowserTarget; #connectionEventHandlers = new Map< @@ -111,18 +111,14 @@ export class BidiBrowser extends Browser { ['browsingContext.navigationStarted', this.#onContextNavigation.bind(this)], ]); - constructor(browserCore: BrowserCore, opts: BidiBrowserOptions) { + private constructor(browserCore: BrowserCore, opts: BidiBrowserOptions) { super(); this.#process = opts.process; this.#closeCallback = opts.closeCallback; this.#browserCore = browserCore; this.#defaultViewport = opts.defaultViewport; - this.#defaultContext = new BidiBrowserContext(this, { - defaultViewport: this.#defaultViewport, - isDefault: true, - }); - this.#browserTarget = new BiDiBrowserTarget(this.#defaultContext); - this.#contexts.push(this.#defaultContext); + this.#browserTarget = new BiDiBrowserTarget(this); + this.#createBrowserContext(this.#browserCore.defaultUserContext); } #initialize() { @@ -150,6 +146,14 @@ export class BidiBrowser extends Browser { throw new UnsupportedOperation(); } + #createBrowserContext(userContext: UserContext) { + const browserContext = new BidiBrowserContext(this, userContext, { + defaultViewport: this.#defaultViewport, + }); + this.#browserContexts.set(userContext, browserContext); + return browserContext; + } + #onContextDomLoaded(event: Bidi.BrowsingContext.Info) { const target = this.#targets.get(event.context); if (target) { @@ -255,13 +259,8 @@ export class BidiBrowser extends Browser { override async createIncognitoBrowserContext( _options?: BrowserContextOptions ): Promise { - // TODO: implement incognito context https://github.com/w3c/webdriver-bidi/issues/289. - const context = new BidiBrowserContext(this, { - defaultViewport: this.#defaultViewport, - isDefault: false, - }); - this.#contexts.push(context); - return context; + const userContext = await this.#browserCore.createUserContext(); + return this.#createBrowserContext(userContext); } override async version(): Promise { @@ -269,28 +268,17 @@ export class BidiBrowser extends Browser { } override browserContexts(): BidiBrowserContext[] { - // TODO: implement incognito context https://github.com/w3c/webdriver-bidi/issues/289. - return this.#contexts; - } - - async _closeContext(browserContext: BidiBrowserContext): Promise { - this.#contexts = this.#contexts.filter(c => { - return c !== browserContext; + return [...this.#browserCore.userContexts].map(context => { + return this.#browserContexts.get(context)!; }); - for (const target of browserContext.targets()) { - const page = await target?.page(); - await page?.close().catch(error => { - debugError(error); - }); - } } override defaultBrowserContext(): BidiBrowserContext { - return this.#defaultContext; + return this.#browserContexts.get(this.#browserCore.defaultUserContext)!; } override newPage(): Promise { - return this.#defaultContext.newPage(); + return this.defaultBrowserContext().newPage(); } override targets(): Target[] { diff --git a/packages/puppeteer-core/src/bidi/BrowserContext.ts b/packages/puppeteer-core/src/bidi/BrowserContext.ts index 4f0ad5e4080..7619603e034 100644 --- a/packages/puppeteer-core/src/bidi/BrowserContext.ts +++ b/packages/puppeteer-core/src/bidi/BrowserContext.ts @@ -11,10 +11,12 @@ import {BrowserContext} from '../api/BrowserContext.js'; import type {Page} from '../api/Page.js'; import type {Target} from '../api/Target.js'; import {UnsupportedOperation} from '../common/Errors.js'; +import {debugError} from '../common/util.js'; import type {Viewport} from '../common/Viewport.js'; import type {BidiBrowser} from './Browser.js'; import type {BidiConnection} from './Connection.js'; +import {UserContext} from './core/UserContext.js'; import type {BidiPage} from './Page.js'; /** @@ -22,7 +24,6 @@ import type {BidiPage} from './Page.js'; */ export interface BidiBrowserContextOptions { defaultViewport: Viewport | null; - isDefault: boolean; } /** @@ -32,14 +33,18 @@ export class BidiBrowserContext extends BrowserContext { #browser: BidiBrowser; #connection: BidiConnection; #defaultViewport: Viewport | null; - #isDefault = false; + #userContext: UserContext; - constructor(browser: BidiBrowser, options: BidiBrowserContextOptions) { + constructor( + browser: BidiBrowser, + userContext: UserContext, + options: BidiBrowserContextOptions + ) { super(); this.#browser = browser; + this.#userContext = userContext; this.#connection = this.#browser.connection; this.#defaultViewport = options.defaultViewport; - this.#isDefault = options.isDefault; } override targets(): Target[] { @@ -90,11 +95,19 @@ export class BidiBrowserContext extends BrowserContext { } override async close(): Promise { - if (this.#isDefault) { + if (!this.isIncognito()) { throw new Error('Default context cannot be closed!'); } - await this.#browser._closeContext(this); + // TODO: Remove once we have adopted the new browsing contexts. + for (const target of this.targets()) { + const page = await target?.page(); + await page?.close().catch(error => { + debugError(error); + }); + } + + await this.#userContext.remove(); } override browser(): BidiBrowser { @@ -113,7 +126,7 @@ export class BidiBrowserContext extends BrowserContext { } override isIncognito(): boolean { - return !this.#isDefault; + return this.#userContext.id !== UserContext.DEFAULT; } override overridePermissions(): never { diff --git a/packages/puppeteer-core/src/bidi/Target.ts b/packages/puppeteer-core/src/bidi/Target.ts index 5823e1012cb..fb01c346386 100644 --- a/packages/puppeteer-core/src/bidi/Target.ts +++ b/packages/puppeteer-core/src/bidi/Target.ts @@ -53,7 +53,14 @@ export abstract class BidiTarget extends Target { /** * @internal */ -export class BiDiBrowserTarget extends BidiTarget { +export class BiDiBrowserTarget extends Target { + #browser: BidiBrowser; + + constructor(browser: BidiBrowser) { + super(); + this.#browser = browser; + } + override url(): string { return ''; } @@ -61,6 +68,26 @@ export class BiDiBrowserTarget extends BidiTarget { override type(): TargetType { return TargetType.BROWSER; } + + override asPage(): Promise { + throw new UnsupportedOperation(); + } + + override browser(): BidiBrowser { + return this.#browser; + } + + override browserContext(): BidiBrowserContext { + return this.#browser.defaultBrowserContext(); + } + + override opener(): never { + throw new UnsupportedOperation(); + } + + override createCDPSession(): Promise { + throw new UnsupportedOperation(); + } } /** diff --git a/packages/puppeteer-core/src/bidi/core/Browser.ts b/packages/puppeteer-core/src/bidi/core/Browser.ts index fff01b3504f..cbaaecf1932 100644 --- a/packages/puppeteer-core/src/bidi/core/Browser.ts +++ b/packages/puppeteer-core/src/bidi/core/Browser.ts @@ -52,7 +52,7 @@ export class Browser extends EventEmitter<{ // keep-sorted start #reason: string | undefined; - readonly #userContexts = new Map(); + readonly #userContexts = new Map(); readonly session: Session; // keep-sorted end @@ -63,7 +63,10 @@ export class Browser extends EventEmitter<{ this.session = session; // keep-sorted end - this.#userContexts.set('', UserContext.create(this, '')); + this.#userContexts.set( + UserContext.DEFAULT, + UserContext.create(this, UserContext.DEFAULT) + ); } async #initialize() { @@ -127,7 +130,7 @@ export class Browser extends EventEmitter<{ get defaultUserContext(): UserContext { // SAFETY: A UserContext is always created for the default context. - return this.#userContexts.get('')!; + return this.#userContexts.get(UserContext.DEFAULT)!; } get userContexts(): Iterable { @@ -187,4 +190,21 @@ export class Browser extends EventEmitter<{ script, }); } + + async createUserContext(): Promise { + // TODO: implement incognito context https://github.com/w3c/webdriver-bidi/issues/289. + // TODO: Call `createUserContext` once available. + // Generating a monotonically increasing context id. + const context = `${++id}`; + + const userContext = UserContext.create(this, context); + userContext.once('destroyed', () => { + this.#userContexts.delete(context); + }); + + this.#userContexts.set(userContext.id, userContext); + return userContext; + } } + +let id = 0; diff --git a/packages/puppeteer-core/src/bidi/core/UserContext.ts b/packages/puppeteer-core/src/bidi/core/UserContext.ts index 57da3b00cf6..a1dd8ed58a1 100644 --- a/packages/puppeteer-core/src/bidi/core/UserContext.ts +++ b/packages/puppeteer-core/src/bidi/core/UserContext.ts @@ -33,7 +33,16 @@ export class UserContext extends EventEmitter<{ /** The new browsing context. */ browsingContext: BrowsingContext; }; + /** + * Emitted when the user context is destroyed. + */ + destroyed: { + /** The user context that was destroyed. */ + userContext: UserContext; + }; }> { + static DEFAULT = 'default'; + static create(browser: Browser, id: string): UserContext { const context = new UserContext(browser, id); context.#initialize(); @@ -43,8 +52,6 @@ export class UserContext extends EventEmitter<{ // keep-sorted start // Note these are only top-level contexts. readonly #browsingContexts = new Map(); - // @ts-expect-error -- TODO: This will be used once the WebDriver BiDi - // protocol supports it. readonly #id: string; readonly browser: Browser; // keep-sorted end @@ -91,6 +98,9 @@ export class UserContext extends EventEmitter<{ get browsingContexts(): Iterable { return this.#browsingContexts.values(); } + get id(): string { + return this.#id; + } // keep-sorted end async createBrowsingContext( @@ -115,11 +125,9 @@ export class UserContext extends EventEmitter<{ return browsingContext; } - async close(): Promise { - const promises = []; - for (const browsingContext of this.#browsingContexts.values()) { - promises.push(browsingContext.close()); - } - await Promise.all(promises); + async remove(): Promise { + // TODO: Call `removeUserContext` once available. + this.emit('destroyed', {userContext: this}); + this.removeAllListeners(); } }