summaryrefslogtreecommitdiffstats
path: root/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core
diff options
context:
space:
mode:
Diffstat (limited to 'remote/test/puppeteer/packages/puppeteer-core/src/bidi/core')
-rw-r--r--remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Browser.ts71
-rw-r--r--remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/BrowsingContext.ts129
-rw-r--r--remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Connection.ts40
-rw-r--r--remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Navigation.ts75
-rw-r--r--remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Realm.ts119
-rw-r--r--remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Request.ts33
-rw-r--r--remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Session.ts28
-rw-r--r--remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/UserContext.ts69
8 files changed, 404 insertions, 160 deletions
diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Browser.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Browser.ts
index 7c4a8ed01c..efeabc3a59 100644
--- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Browser.ts
+++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Browser.ts
@@ -11,7 +11,7 @@ import {inertIfDisposed, throwIfDisposed} from '../../util/decorators.js';
import {DisposableStack, disposeSymbol} from '../../util/disposable.js';
import type {BrowsingContext} from './BrowsingContext.js';
-import type {SharedWorkerRealm} from './Realm.js';
+import {SharedWorkerRealm} from './Realm.js';
import type {Session} from './Session.js';
import {UserContext} from './UserContext.js';
@@ -57,6 +57,7 @@ export class Browser extends EventEmitter<{
readonly #disposables = new DisposableStack();
readonly #userContexts = new Map<string, UserContext>();
readonly session: Session;
+ readonly #sharedWorkers = new Map<string, SharedWorkerRealm>();
// keep-sorted end
private constructor(session: Session) {
@@ -64,11 +65,6 @@ export class Browser extends EventEmitter<{
// keep-sorted start
this.session = session;
// keep-sorted end
-
- this.#userContexts.set(
- UserContext.DEFAULT,
- UserContext.create(this, UserContext.DEFAULT)
- );
}
async #initialize() {
@@ -80,14 +76,29 @@ export class Browser extends EventEmitter<{
});
sessionEmitter.on('script.realmCreated', info => {
- if (info.type === 'shared-worker') {
- // TODO: Create a SharedWorkerRealm.
+ if (info.type !== 'shared-worker') {
+ return;
}
+ this.#sharedWorkers.set(
+ info.realm,
+ SharedWorkerRealm.from(this, info.realm, info.origin)
+ );
});
+ await this.#syncUserContexts();
await this.#syncBrowsingContexts();
}
+ async #syncUserContexts() {
+ const {
+ result: {userContexts},
+ } = await this.session.send('browser.getUserContexts', {});
+
+ for (const context of userContexts) {
+ this.#createUserContext(context.userContext);
+ }
+ }
+
async #syncBrowsingContexts() {
// In case contexts are created or destroyed during `getTree`, we use this
// set to detect them.
@@ -99,16 +110,13 @@ export class Browser extends EventEmitter<{
sessionEmitter.on('browsingContext.contextCreated', info => {
contextIds.add(info.context);
});
- sessionEmitter.on('browsingContext.contextDestroyed', info => {
- contextIds.delete(info.context);
- });
const {result} = await this.session.send('browsingContext.getTree', {});
contexts = result.contexts;
}
// Simulating events so contexts are created naturally.
for (const info of contexts) {
- if (contextIds.has(info.context)) {
+ if (!contextIds.has(info.context)) {
this.session.emit('browsingContext.contextCreated', info);
}
if (info.children) {
@@ -117,6 +125,22 @@ export class Browser extends EventEmitter<{
}
}
+ #createUserContext(id: string) {
+ const userContext = UserContext.create(this, id);
+ this.#userContexts.set(userContext.id, userContext);
+
+ const userContextEmitter = this.#disposables.use(
+ new EventEmitter(userContext)
+ );
+ userContextEmitter.once('closed', () => {
+ userContextEmitter.removeAllListeners();
+
+ this.#userContexts.delete(userContext.id);
+ });
+
+ return userContext;
+ }
+
// keep-sorted start block=yes
get closed(): boolean {
return this.#closed;
@@ -185,30 +209,15 @@ export class Browser extends EventEmitter<{
});
}
- static userContextId = 0;
@throwIfDisposed<Browser>(browser => {
// SAFETY: By definition of `disposed`, `#reason` is defined.
return browser.#reason!;
})
async createUserContext(): Promise<UserContext> {
- // 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 = `${++Browser.userContextId}`;
-
- const userContext = UserContext.create(this, context);
- this.#userContexts.set(userContext.id, userContext);
-
- const userContextEmitter = this.#disposables.use(
- new EventEmitter(userContext)
- );
- userContextEmitter.once('closed', () => {
- userContextEmitter.removeAllListeners();
-
- this.#userContexts.delete(context);
- });
-
- return userContext;
+ const {
+ result: {userContext: context},
+ } = await this.session.send('browser.createUserContext', {});
+ return this.#createUserContext(context);
}
[disposeSymbol](): void {
diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/BrowsingContext.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/BrowsingContext.ts
index 9bec2a506c..07309576a3 100644
--- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/BrowsingContext.ts
+++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/BrowsingContext.ts
@@ -12,6 +12,7 @@ import {DisposableStack, disposeSymbol} from '../../util/disposable.js';
import type {AddPreloadScriptOptions} from './Browser.js';
import {Navigation} from './Navigation.js';
+import type {DedicatedWorkerRealm} from './Realm.js';
import {WindowRealm} from './Realm.js';
import {Request} from './Request.js';
import type {UserContext} from './UserContext.js';
@@ -60,6 +61,14 @@ export type SetViewportOptions = Omit<
/**
* @internal
*/
+export type GetCookiesOptions = Omit<
+ Bidi.Storage.GetCookiesParameters,
+ 'partition'
+>;
+
+/**
+ * @internal
+ */
export class BrowsingContext extends EventEmitter<{
/** Emitted when this context is closed. */
closed: {
@@ -95,6 +104,11 @@ export class BrowsingContext extends EventEmitter<{
DOMContentLoaded: void;
/** Emitted whenever the frame emits `load` */
load: void;
+ /** Emitted whenever a dedicated worker is created */
+ worker: {
+ /** The realm for the new dedicated worker */
+ realm: DedicatedWorkerRealm;
+ };
}> {
static from(
userContext: UserContext,
@@ -135,7 +149,7 @@ export class BrowsingContext extends EventEmitter<{
this.userContext = context;
// keep-sorted end
- this.defaultRealm = WindowRealm.from(this);
+ this.defaultRealm = this.#createWindowRealm();
}
#initialize() {
@@ -202,7 +216,16 @@ export class BrowsingContext extends EventEmitter<{
}
this.#url = info.url;
- this.#requests.clear();
+ for (const [id, request] of this.#requests) {
+ if (request.disposed) {
+ this.#requests.delete(id);
+ }
+ }
+ // If the navigation hasn't finished, then this is nested navigation. The
+ // current navigation will handle this.
+ if (this.#navigation !== undefined && !this.#navigation.disposed) {
+ return;
+ }
// Note the navigation ID is null for this event.
this.#navigation = Navigation.from(this);
@@ -224,7 +247,8 @@ export class BrowsingContext extends EventEmitter<{
if (event.context !== this.id) {
return;
}
- if (this.#requests.has(event.request.request)) {
+ if (event.redirectCount !== 0) {
+ // Means the request is a redirect. This is handled in Request.
return;
}
@@ -265,7 +289,12 @@ export class BrowsingContext extends EventEmitter<{
return this.closed;
}
get realms(): Iterable<WindowRealm> {
- return this.#realms.values();
+ // eslint-disable-next-line @typescript-eslint/no-this-alias -- Required
+ const self = this;
+ return (function* () {
+ yield self.defaultRealm;
+ yield* self.#realms.values();
+ })();
}
get top(): BrowsingContext {
let context = this as BrowsingContext;
@@ -279,6 +308,14 @@ export class BrowsingContext extends EventEmitter<{
}
// keep-sorted end
+ #createWindowRealm(sandbox?: string) {
+ const realm = WindowRealm.from(this, sandbox);
+ realm.on('worker', realm => {
+ this.emit('worker', {realm});
+ });
+ return realm;
+ }
+
@inertIfDisposed
private dispose(reason?: string): void {
this.#reason = reason;
@@ -345,33 +382,23 @@ export class BrowsingContext extends EventEmitter<{
async navigate(
url: string,
wait?: Bidi.BrowsingContext.ReadinessState
- ): Promise<Navigation> {
+ ): Promise<void> {
await this.#session.send('browsingContext.navigate', {
context: this.id,
url,
wait,
});
- return await new Promise(resolve => {
- this.once('navigation', ({navigation}) => {
- resolve(navigation);
- });
- });
}
@throwIfDisposed<BrowsingContext>(context => {
// SAFETY: Disposal implies this exists.
return context.#reason!;
})
- async reload(options: ReloadOptions = {}): Promise<Navigation> {
+ async reload(options: ReloadOptions = {}): Promise<void> {
await this.#session.send('browsingContext.reload', {
context: this.id,
...options,
});
- return await new Promise(resolve => {
- this.once('navigation', ({navigation}) => {
- resolve(navigation);
- });
- });
}
@throwIfDisposed<BrowsingContext>(context => {
@@ -436,7 +463,7 @@ export class BrowsingContext extends EventEmitter<{
return context.#reason!;
})
createWindowRealm(sandbox: string): WindowRealm {
- return WindowRealm.from(this, sandbox);
+ return this.#createWindowRealm(sandbox);
}
@throwIfDisposed<BrowsingContext>(context => {
@@ -464,6 +491,54 @@ export class BrowsingContext extends EventEmitter<{
await this.userContext.browser.removePreloadScript(script);
}
+ @throwIfDisposed<BrowsingContext>(context => {
+ // SAFETY: Disposal implies this exists.
+ return context.#reason!;
+ })
+ async getCookies(
+ options: GetCookiesOptions = {}
+ ): Promise<Bidi.Network.Cookie[]> {
+ const {
+ result: {cookies},
+ } = await this.#session.send('storage.getCookies', {
+ ...options,
+ partition: {
+ type: 'context',
+ context: this.id,
+ },
+ });
+ return cookies;
+ }
+
+ @throwIfDisposed<BrowsingContext>(context => {
+ // SAFETY: Disposal implies this exists.
+ return context.#reason!;
+ })
+ async setCookie(cookie: Bidi.Storage.PartialCookie): Promise<void> {
+ await this.#session.send('storage.setCookie', {
+ cookie,
+ partition: {
+ type: 'context',
+ context: this.id,
+ },
+ });
+ }
+
+ @throwIfDisposed<BrowsingContext>(context => {
+ // SAFETY: Disposal implies this exists.
+ return context.#reason!;
+ })
+ async setFiles(
+ element: Bidi.Script.SharedReference,
+ files: string[]
+ ): Promise<void> {
+ await this.#session.send('input.setFiles', {
+ context: this.id,
+ element,
+ files,
+ });
+ }
+
[disposeSymbol](): void {
this.#reason ??=
'Browsing context already closed, probably because the user context closed.';
@@ -472,4 +547,24 @@ export class BrowsingContext extends EventEmitter<{
this.#disposables.dispose();
super[disposeSymbol]();
}
+
+ @throwIfDisposed<BrowsingContext>(context => {
+ // SAFETY: Disposal implies this exists.
+ return context.#reason!;
+ })
+ async deleteCookie(
+ ...cookieFilters: Bidi.Storage.CookieFilter[]
+ ): Promise<void> {
+ await Promise.all(
+ cookieFilters.map(async filter => {
+ await this.#session.send('storage.deleteCookies', {
+ filter: filter,
+ partition: {
+ type: 'context',
+ context: this.id,
+ },
+ });
+ })
+ );
+ }
}
diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Connection.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Connection.ts
index b9de14372b..9c26a03503 100644
--- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Connection.ts
+++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Connection.ts
@@ -38,6 +38,21 @@ export interface Commands {
returnType: Bidi.EmptyResult;
};
+ 'browser.createUserContext': {
+ params: Bidi.EmptyParams;
+ returnType: Bidi.Browser.CreateUserContextResult;
+ };
+ 'browser.getUserContexts': {
+ params: Bidi.EmptyParams;
+ returnType: Bidi.Browser.GetUserContextsResult;
+ };
+ 'browser.removeUserContext': {
+ params: {
+ userContext: Bidi.Browser.UserContext;
+ };
+ returnType: Bidi.Browser.RemoveUserContext;
+ };
+
'browsingContext.activate': {
params: Bidi.BrowsingContext.ActivateParameters;
returnType: Bidi.EmptyResult;
@@ -91,6 +106,15 @@ export interface Commands {
params: Bidi.Input.ReleaseActionsParameters;
returnType: Bidi.EmptyResult;
};
+ 'input.setFiles': {
+ params: Bidi.Input.SetFilesParameters;
+ returnType: Bidi.EmptyResult;
+ };
+
+ 'permissions.setPermission': {
+ params: Bidi.Permissions.SetPermissionParameters;
+ returnType: Bidi.EmptyResult;
+ };
'session.end': {
params: Bidi.EmptyParams;
@@ -112,6 +136,19 @@ export interface Commands {
params: Bidi.Session.SubscriptionRequest;
returnType: Bidi.EmptyResult;
};
+
+ 'storage.deleteCookies': {
+ params: Bidi.Storage.DeleteCookiesParameters;
+ returnType: Bidi.Storage.DeleteCookiesResult;
+ };
+ 'storage.getCookies': {
+ params: Bidi.Storage.GetCookiesParameters;
+ returnType: Bidi.Storage.GetCookiesResult;
+ };
+ 'storage.setCookie': {
+ params: Bidi.Storage.SetCookieParameters;
+ returnType: Bidi.Storage.SetCookieParameters;
+ };
}
/**
@@ -133,7 +170,4 @@ export interface Connection<Events extends BidiEvents = BidiEvents>
method: T,
params: Commands[T]['params']
): Promise<{result: Commands[T]['returnType']}>;
-
- // This will pipe events into the provided emitter.
- pipeTo<Events extends BidiEvents>(emitter: EventEmitter<Events>): void;
}
diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Navigation.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Navigation.ts
index a7efbfeb2c..50040164a5 100644
--- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Navigation.ts
+++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Navigation.ts
@@ -41,9 +41,10 @@ export class Navigation extends EventEmitter<{
// keep-sorted start
#request: Request | undefined;
+ #navigation: Navigation | undefined;
readonly #browsingContext: BrowsingContext;
readonly #disposables = new DisposableStack();
- readonly #id = new Deferred<string>();
+ readonly #id = new Deferred<string | null>();
// keep-sorted end
private constructor(context: BrowsingContext) {
@@ -65,31 +66,48 @@ export class Navigation extends EventEmitter<{
this.dispose();
});
- this.#browsingContext.on('request', ({request}) => {
- if (request.navigation === this.#id.value()) {
- this.#request = request;
- this.emit('request', request);
+ browsingContextEmitter.on('request', ({request}) => {
+ if (
+ request.navigation === undefined ||
+ this.#request !== undefined ||
+ // If a request with a navigation ID comes in, then the navigation ID is
+ // for this navigation.
+ !this.#matches(request.navigation)
+ ) {
+ return;
}
+
+ this.#request = request;
+ this.emit('request', request);
});
const sessionEmitter = this.#disposables.use(
new EventEmitter(this.#session)
);
- // To get the navigation ID if any.
+ sessionEmitter.on('browsingContext.navigationStarted', info => {
+ if (
+ info.context !== this.#browsingContext.id ||
+ this.#navigation !== undefined
+ ) {
+ return;
+ }
+ this.#navigation = Navigation.from(this.#browsingContext);
+ });
+
for (const eventName of [
'browsingContext.domContentLoaded',
'browsingContext.load',
] as const) {
sessionEmitter.on(eventName, info => {
- if (info.context !== this.#browsingContext.id) {
- return;
- }
- if (!info.navigation) {
+ if (
+ info.context !== this.#browsingContext.id ||
+ info.navigation === null ||
+ !this.#matches(info.navigation)
+ ) {
return;
}
- if (!this.#id.resolved()) {
- this.#id.resolve(info.navigation);
- }
+
+ this.dispose();
});
}
@@ -99,18 +117,15 @@ export class Navigation extends EventEmitter<{
['browsingContext.navigationAborted', 'aborted'],
] as const) {
sessionEmitter.on(eventName, info => {
- if (info.context !== this.#browsingContext.id) {
- return;
- }
- if (!info.navigation) {
- return;
- }
- if (!this.#id.resolved()) {
- this.#id.resolve(info.navigation);
- }
- if (this.#id.value() !== info.navigation) {
+ if (
+ info.context !== this.#browsingContext.id ||
+ // Note we don't check if `navigation` is null since `null` means the
+ // fragment navigated.
+ !this.#matches(info.navigation)
+ ) {
return;
}
+
this.emit(event, {
url: info.url,
timestamp: new Date(info.timestamp),
@@ -120,6 +135,17 @@ export class Navigation extends EventEmitter<{
}
}
+ #matches(navigation: string | null): boolean {
+ if (this.#navigation !== undefined && !this.#navigation.disposed) {
+ return false;
+ }
+ if (!this.#id.resolved()) {
+ this.#id.resolve(navigation);
+ return true;
+ }
+ return this.#id.value() === navigation;
+ }
+
// keep-sorted start block=yes
get #session() {
return this.#browsingContext.userContext.browser.session;
@@ -130,6 +156,9 @@ export class Navigation extends EventEmitter<{
get request(): Request | undefined {
return this.#request;
}
+ get navigation(): Navigation | undefined {
+ return this.#navigation;
+ }
// keep-sorted end
@inertIfDisposed
diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Realm.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Realm.ts
index d9bbbede50..392194cec8 100644
--- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Realm.ts
+++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Realm.ts
@@ -9,7 +9,9 @@ import type * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
import {EventEmitter} from '../../common/EventEmitter.js';
import {inertIfDisposed, throwIfDisposed} from '../../util/decorators.js';
import {DisposableStack, disposeSymbol} from '../../util/disposable.js';
+import type {BidiConnection} from '../Connection.js';
+import type {Browser} from './Browser.js';
import type {BrowsingContext} from './BrowsingContext.js';
import type {Session} from './Session.js';
@@ -33,6 +35,8 @@ export type EvaluateOptions = Omit<
* @internal
*/
export abstract class Realm extends EventEmitter<{
+ /** Emitted whenever the realm has updated. */
+ updated: Realm;
/** Emitted when the realm is destroyed. */
destroyed: {reason: string};
/** Emitted when a dedicated worker is created in the realm. */
@@ -55,22 +59,12 @@ export abstract class Realm extends EventEmitter<{
// keep-sorted end
}
- protected initialize(): void {
- const sessionEmitter = this.disposables.use(new EventEmitter(this.session));
- sessionEmitter.on('script.realmDestroyed', info => {
- if (info.realm !== this.id) {
- return;
- }
- this.dispose('Realm already destroyed.');
- });
- }
-
// keep-sorted start block=yes
get disposed(): boolean {
return this.#reason !== undefined;
}
protected abstract get session(): Session;
- protected get target(): Bidi.Script.Target {
+ get target(): Bidi.Script.Target {
return {realm: this.id};
}
// keep-sorted end
@@ -128,6 +122,18 @@ export abstract class Realm extends EventEmitter<{
return result;
}
+ @throwIfDisposed<Realm>(realm => {
+ // SAFETY: Disposal implies this exists.
+ return realm.#reason!;
+ })
+ async resolveExecutionContextId(): Promise<number> {
+ const {result} = await (this.session.connection as BidiConnection).send(
+ 'cdp.resolveRealm',
+ {realm: this.id}
+ );
+ return result.executionContextId;
+ }
+
[disposeSymbol](): void {
this.#reason ??=
'Realm already destroyed, probably because all associated browsing contexts closed.';
@@ -144,7 +150,7 @@ export abstract class Realm extends EventEmitter<{
export class WindowRealm extends Realm {
static from(context: BrowsingContext, sandbox?: string): WindowRealm {
const realm = new WindowRealm(context, sandbox);
- realm.initialize();
+ realm.#initialize();
return realm;
}
@@ -153,13 +159,7 @@ export class WindowRealm extends Realm {
readonly sandbox?: string;
// keep-sorted end
- readonly #workers: {
- dedicated: Map<string, DedicatedWorkerRealm>;
- shared: Map<string, SharedWorkerRealm>;
- } = {
- dedicated: new Map(),
- shared: new Map(),
- };
+ readonly #workers = new Map<string, DedicatedWorkerRealm>();
private constructor(context: BrowsingContext, sandbox?: string) {
super('', '');
@@ -169,16 +169,26 @@ export class WindowRealm extends Realm {
// keep-sorted end
}
- override initialize(): void {
- super.initialize();
+ #initialize(): void {
+ const browsingContextEmitter = this.disposables.use(
+ new EventEmitter(this.browsingContext)
+ );
+ browsingContextEmitter.on('closed', ({reason}) => {
+ this.dispose(reason);
+ });
const sessionEmitter = this.disposables.use(new EventEmitter(this.session));
sessionEmitter.on('script.realmCreated', info => {
- if (info.type !== 'window') {
+ if (
+ info.type !== 'window' ||
+ info.context !== this.browsingContext.id ||
+ info.sandbox !== this.sandbox
+ ) {
return;
}
(this as any).id = info.realm;
(this as any).origin = info.origin;
+ this.emit('updated', this);
});
sessionEmitter.on('script.realmCreated', info => {
if (info.type !== 'dedicated-worker') {
@@ -189,32 +199,16 @@ export class WindowRealm extends Realm {
}
const realm = DedicatedWorkerRealm.from(this, info.realm, info.origin);
- this.#workers.dedicated.set(realm.id, realm);
+ this.#workers.set(realm.id, realm);
const realmEmitter = this.disposables.use(new EventEmitter(realm));
realmEmitter.once('destroyed', () => {
realmEmitter.removeAllListeners();
- this.#workers.dedicated.delete(realm.id);
+ this.#workers.delete(realm.id);
});
this.emit('worker', realm);
});
-
- this.browsingContext.userContext.browser.on('sharedworker', ({realm}) => {
- if (!realm.owners.has(this)) {
- return;
- }
-
- this.#workers.shared.set(realm.id, realm);
-
- const realmEmitter = this.disposables.use(new EventEmitter(realm));
- realmEmitter.once('destroyed', () => {
- realmEmitter.removeAllListeners();
- this.#workers.shared.delete(realm.id);
- });
-
- this.emit('sharedworker', realm);
- });
}
override get session(): Session {
@@ -244,7 +238,7 @@ export class DedicatedWorkerRealm extends Realm {
origin: string
): DedicatedWorkerRealm {
const realm = new DedicatedWorkerRealm(owner, id, origin);
- realm.initialize();
+ realm.#initialize();
return realm;
}
@@ -262,10 +256,14 @@ export class DedicatedWorkerRealm extends Realm {
this.owners = new Set([owner]);
}
- override initialize(): void {
- super.initialize();
-
+ #initialize(): void {
const sessionEmitter = this.disposables.use(new EventEmitter(this.session));
+ sessionEmitter.on('script.realmDestroyed', info => {
+ if (info.realm !== this.id) {
+ return;
+ }
+ this.dispose('Realm already destroyed.');
+ });
sessionEmitter.on('script.realmCreated', info => {
if (info.type !== 'dedicated-worker') {
return;
@@ -296,34 +294,30 @@ export class DedicatedWorkerRealm extends Realm {
* @internal
*/
export class SharedWorkerRealm extends Realm {
- static from(
- owners: [WindowRealm, ...WindowRealm[]],
- id: string,
- origin: string
- ): SharedWorkerRealm {
- const realm = new SharedWorkerRealm(owners, id, origin);
- realm.initialize();
+ static from(browser: Browser, id: string, origin: string): SharedWorkerRealm {
+ const realm = new SharedWorkerRealm(browser, id, origin);
+ realm.#initialize();
return realm;
}
// keep-sorted start
readonly #workers = new Map<string, DedicatedWorkerRealm>();
- readonly owners: Set<WindowRealm>;
+ readonly browser: Browser;
// keep-sorted end
- private constructor(
- owners: [WindowRealm, ...WindowRealm[]],
- id: string,
- origin: string
- ) {
+ private constructor(browser: Browser, id: string, origin: string) {
super(id, origin);
- this.owners = new Set(owners);
+ this.browser = browser;
}
- override initialize(): void {
- super.initialize();
-
+ #initialize(): void {
const sessionEmitter = this.disposables.use(new EventEmitter(this.session));
+ sessionEmitter.on('script.realmDestroyed', info => {
+ if (info.realm !== this.id) {
+ return;
+ }
+ this.dispose('Realm already destroyed.');
+ });
sessionEmitter.on('script.realmCreated', info => {
if (info.type !== 'dedicated-worker') {
return;
@@ -345,7 +339,6 @@ export class SharedWorkerRealm extends Realm {
}
override get session(): Session {
- // SAFETY: At least one owner will exist.
- return this.owners.values().next().value.session;
+ return this.browser.session;
}
}
diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Request.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Request.ts
index 2a445f7d87..fd616b668d 100644
--- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Request.ts
+++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Request.ts
@@ -66,10 +66,11 @@ export class Request extends EventEmitter<{
new EventEmitter(this.#session)
);
sessionEmitter.on('network.beforeRequestSent', event => {
- if (event.context !== this.#browsingContext.id) {
- return;
- }
- if (event.request.request !== this.id) {
+ if (
+ event.context !== this.#browsingContext.id ||
+ event.request.request !== this.id ||
+ event.redirectCount !== this.#event.redirectCount + 1
+ ) {
return;
}
this.#redirect = Request.from(this.#browsingContext, event);
@@ -77,10 +78,11 @@ export class Request extends EventEmitter<{
this.dispose();
});
sessionEmitter.on('network.fetchError', event => {
- if (event.context !== this.#browsingContext.id) {
- return;
- }
- if (event.request.request !== this.id) {
+ if (
+ event.context !== this.#browsingContext.id ||
+ event.request.request !== this.id ||
+ this.#event.redirectCount !== event.redirectCount
+ ) {
return;
}
this.#error = event.errorText;
@@ -88,14 +90,19 @@ export class Request extends EventEmitter<{
this.dispose();
});
sessionEmitter.on('network.responseCompleted', event => {
- if (event.context !== this.#browsingContext.id) {
- return;
- }
- if (event.request.request !== this.id) {
+ if (
+ event.context !== this.#browsingContext.id ||
+ event.request.request !== this.id ||
+ this.#event.redirectCount !== event.redirectCount
+ ) {
return;
}
this.#response = event.response;
this.emit('success', this.#response);
+ // In case this is a redirect.
+ if (this.#response.status >= 300 && this.#response.status < 400) {
+ return;
+ }
this.dispose();
});
}
@@ -126,7 +133,7 @@ export class Request extends EventEmitter<{
return this.#event.navigation ?? undefined;
}
get redirect(): Request | undefined {
- return this.redirect;
+ return this.#redirect;
}
get response(): Bidi.Network.ResponseData | undefined {
return this.#response;
diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Session.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Session.ts
index b6e28061f1..ffd39769e7 100644
--- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Session.ts
+++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Session.ts
@@ -8,7 +8,11 @@ import type * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
import {EventEmitter} from '../../common/EventEmitter.js';
import {debugError} from '../../common/util.js';
-import {inertIfDisposed, throwIfDisposed} from '../../util/decorators.js';
+import {
+ bubble,
+ inertIfDisposed,
+ throwIfDisposed,
+} from '../../util/decorators.js';
import {DisposableStack, disposeSymbol} from '../../util/disposable.js';
import {Browser} from './Browser.js';
@@ -81,7 +85,8 @@ export class Session
readonly #disposables = new DisposableStack();
readonly #info: Bidi.Session.NewResult;
readonly browser!: Browser;
- readonly connection: Connection;
+ @bubble()
+ accessor connection: Connection;
// keep-sorted end
private constructor(connection: Connection, info: Bidi.Session.NewResult) {
@@ -93,8 +98,6 @@ export class Session
}
async #initialize(): Promise<void> {
- this.connection.pipeTo(this);
-
// SAFETY: We use `any` to allow assignment of the readonly property.
(this as any).browser = await Browser.from(this);
@@ -102,6 +105,19 @@ export class Session
browserEmitter.once('closed', ({reason}) => {
this.dispose(reason);
});
+
+ // TODO: Currently, some implementations do not emit navigationStarted event
+ // for fragment navigations (as per spec) and some do. This could emits a
+ // synthetic navigationStarted to work around this inconsistency.
+ const seen = new WeakSet();
+ this.on('browsingContext.fragmentNavigated', info => {
+ if (seen.has(info)) {
+ return;
+ }
+ seen.add(info);
+ this.emit('browsingContext.navigationStarted', info);
+ this.emit('browsingContext.fragmentNavigated', info);
+ });
}
// keep-sorted start block=yes
@@ -125,10 +141,6 @@ export class Session
this[disposeSymbol]();
}
- pipeTo<Events extends BidiEvents>(emitter: EventEmitter<Events>): void {
- this.connection.pipeTo(emitter);
- }
-
/**
* Currently, there is a 1:1 relationship between the session and the
* session. In the future, we might support multiple sessions and in that
diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/UserContext.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/UserContext.ts
index 01ee5c7649..72859c6a53 100644
--- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/UserContext.ts
+++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/UserContext.ts
@@ -12,6 +12,7 @@ import {inertIfDisposed, throwIfDisposed} from '../../util/decorators.js';
import {DisposableStack, disposeSymbol} from '../../util/disposable.js';
import type {Browser} from './Browser.js';
+import type {GetCookiesOptions} from './BrowsingContext.js';
import {BrowsingContext} from './BrowsingContext.js';
/**
@@ -43,7 +44,7 @@ export class UserContext extends EventEmitter<{
reason: string;
};
}> {
- static DEFAULT = 'default';
+ static DEFAULT = 'default' as const;
static create(browser: Browser, id: string): UserContext {
const context = new UserContext(browser, id);
@@ -84,6 +85,10 @@ export class UserContext extends EventEmitter<{
return;
}
+ if (info.userContext !== this.#id) {
+ return;
+ }
+
const browsingContext = BrowsingContext.from(
this,
undefined,
@@ -143,6 +148,7 @@ export class UserContext extends EventEmitter<{
type,
...options,
referenceContext: options.referenceContext?.id,
+ userContext: this.#id,
});
const browsingContext = this.#browsingContexts.get(contextId);
@@ -161,12 +167,71 @@ export class UserContext extends EventEmitter<{
})
async remove(): Promise<void> {
try {
- // TODO: Call `removeUserContext` once available.
+ await this.#session.send('browser.removeUserContext', {
+ userContext: this.#id,
+ });
} finally {
this.dispose('User context already closed.');
}
}
+ @throwIfDisposed<UserContext>(context => {
+ // SAFETY: Disposal implies this exists.
+ return context.#reason!;
+ })
+ async getCookies(
+ options: GetCookiesOptions = {},
+ sourceOrigin: string | undefined = undefined
+ ): Promise<Bidi.Network.Cookie[]> {
+ const {
+ result: {cookies},
+ } = await this.#session.send('storage.getCookies', {
+ ...options,
+ partition: {
+ type: 'storageKey',
+ userContext: this.#id,
+ sourceOrigin,
+ },
+ });
+ return cookies;
+ }
+
+ @throwIfDisposed<UserContext>(context => {
+ // SAFETY: Disposal implies this exists.
+ return context.#reason!;
+ })
+ async setCookie(
+ cookie: Bidi.Storage.PartialCookie,
+ sourceOrigin?: string
+ ): Promise<void> {
+ await this.#session.send('storage.setCookie', {
+ cookie,
+ partition: {
+ type: 'storageKey',
+ sourceOrigin,
+ userContext: this.id,
+ },
+ });
+ }
+
+ @throwIfDisposed<UserContext>(context => {
+ // SAFETY: Disposal implies this exists.
+ return context.#reason!;
+ })
+ async setPermissions(
+ origin: string,
+ descriptor: Bidi.Permissions.PermissionDescriptor,
+ state: Bidi.Permissions.PermissionState
+ ): Promise<void> {
+ await this.#session.send('permissions.setPermission', {
+ origin,
+ descriptor,
+ state,
+ // @ts-expect-error not standard implementation.
+ 'goog:userContext': this.#id,
+ });
+ }
+
[disposeSymbol](): void {
this.#reason ??=
'User context already closed, probably because the browser disconnected/closed.';