Skip to content
Merged
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
4 changes: 4 additions & 0 deletions docs/src/api/class-browsercontext.md
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ await context.AddCookiesAsync(new[] { cookie1, cookie2 });

## async method: BrowserContext.addInitScript
* since: v1.8
- returns: <[Disposable]>

Adds a script which would be evaluated in one of the following scenarios:
* Whenever a page is created in the browser context or is navigated.
Expand Down Expand Up @@ -584,6 +585,7 @@ Optional list of URLs.

## async method: BrowserContext.exposeBinding
* since: v1.8
- returns: <[Disposable]>

The method adds a function called [`param: name`] on the `window` object of every frame in every page in the context.
When called, the function executes [`param: callback`] and returns a [Promise] which resolves to the return value of
Expand Down Expand Up @@ -735,6 +737,7 @@ supported. When passing by value, multiple arguments are supported.

## async method: BrowserContext.exposeFunction
* since: v1.8
- returns: <[Disposable]>

The method adds a function called [`param: name`] on the `window` object of every frame in every page in the context.
When called, the function executes [`param: callback`] and returns a [Promise] which resolves to the return value of
Expand Down Expand Up @@ -1018,6 +1021,7 @@ API testing helper associated with this context. Requests made with this API wil

## async method: BrowserContext.route
* since: v1.8
- returns: <[Disposable]>

Routing provides the capability to modify network requests that are made by any page in the browser context. Once route
is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted.
Expand Down
12 changes: 12 additions & 0 deletions docs/src/api/class-disposable.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# class: Disposable
* since: v1.59
* langs: js

[Disposable] is returned from various methods to allow undoing the corresponding action. For example,
[`method: Page.addInitScript`] returns a [Disposable] that can be used to remove the init script.

## async method: Disposable.dispose
* since: v1.59

Removes the associated resource. For example, removes the init script installed via
[`method: Page.addInitScript`] or [`method: BrowserContext.addInitScript`].
4 changes: 4 additions & 0 deletions docs/src/api/class-page.md
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,7 @@ page.

## async method: Page.addInitScript
* since: v1.8
- returns: <[Disposable]>

Adds a script which would be evaluated in one of the following scenarios:
* Whenever the page is navigated.
Expand Down Expand Up @@ -1717,6 +1718,7 @@ Optional argument to pass to [`param: expression`].

## async method: Page.exposeBinding
* since: v1.8
- returns: <[Disposable]>

The method adds a function called [`param: name`] on the `window` object of every frame in this page. When called, the
function executes [`param: callback`] and returns a [Promise] which resolves to the return value of [`param: callback`].
Expand Down Expand Up @@ -1882,6 +1884,7 @@ supported. When passing by value, multiple arguments are supported.

## async method: Page.exposeFunction
* since: v1.8
- returns: <[Disposable]>

The method adds a function called [`param: name`] on the `window` object of every frame in the page. When called, the
function executes [`param: callback`] and returns a [Promise] which resolves to the return value of [`param: callback`].
Expand Down Expand Up @@ -3563,6 +3566,7 @@ API testing helper associated with this page. This method returns the same insta

## async method: Page.route
* since: v1.8
- returns: <[Disposable]>

Routing provides the capability to modify network requests that are made by a page.

Expand Down
37 changes: 27 additions & 10 deletions packages/playwright-client/types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ export interface Page {
* [`script`](https://playwright.dev/docs/api/class-page#page-add-init-script-option-script) (only supported when
* passing a function).
*/
addInitScript<Arg>(script: PageFunction<Arg, any> | { path?: string, content?: string }, arg?: Arg): Promise<void>;
addInitScript<Arg>(script: PageFunction<Arg, any> | { path?: string, content?: string }, arg?: Arg): Promise<Disposable>;

/**
* **NOTE** Use locator-based [page.locator(selector[, options])](https://playwright.dev/docs/api/class-page#page-locator)
Expand Down Expand Up @@ -920,7 +920,7 @@ export interface Page {
* @param callback Callback function that will be called in the Playwright's context.
* @param options
*/
exposeBinding(name: string, playwrightBinding: (source: BindingSource, arg: JSHandle) => any, options: { handle: true }): Promise<void>;
exposeBinding(name: string, playwrightBinding: (source: BindingSource, arg: JSHandle) => any, options: { handle: true }): Promise<Disposable>;
/**
* The method adds a function called
* [`name`](https://playwright.dev/docs/api/class-page#page-expose-binding-option-name) on the `window` object of
Expand Down Expand Up @@ -972,7 +972,7 @@ export interface Page {
* @param callback Callback function that will be called in the Playwright's context.
* @param options
*/
exposeBinding(name: string, playwrightBinding: (source: BindingSource, ...args: any[]) => any, options?: { handle?: boolean }): Promise<void>;
exposeBinding(name: string, playwrightBinding: (source: BindingSource, ...args: any[]) => any, options?: { handle?: boolean }): Promise<Disposable>;

/**
* Removes all the listeners of the given type (or all registered listeners if no type given). Allows to wait for
Expand Down Expand Up @@ -2758,7 +2758,7 @@ export interface Page {
* @param name Name of the function on the window object
* @param callback Callback function which will be called in Playwright's context.
*/
exposeFunction(name: string, callback: Function): Promise<void>;
exposeFunction(name: string, callback: Function): Promise<Disposable>;

/**
* **NOTE** Use locator-based [locator.fill(value[, options])](https://playwright.dev/docs/api/class-locator#locator-fill)
Expand Down Expand Up @@ -4147,7 +4147,7 @@ export interface Page {
* How often a route should be used. By default it will be used every time.
*/
times?: number;
}): Promise<void>;
}): Promise<Disposable>;

/**
* If specified the network requests that are made in the page will be served from the HAR file. Read more about
Expand Down Expand Up @@ -8471,7 +8471,7 @@ export interface BrowserContext {
* @param callback Callback function that will be called in the Playwright's context.
* @param options
*/
exposeBinding(name: string, playwrightBinding: (source: BindingSource, arg: JSHandle) => any, options: { handle: true }): Promise<void>;
exposeBinding(name: string, playwrightBinding: (source: BindingSource, arg: JSHandle) => any, options: { handle: true }): Promise<Disposable>;
/**
* The method adds a function called
* [`name`](https://playwright.dev/docs/api/class-browsercontext#browser-context-expose-binding-option-name) on the
Expand Down Expand Up @@ -8519,7 +8519,7 @@ export interface BrowserContext {
* @param callback Callback function that will be called in the Playwright's context.
* @param options
*/
exposeBinding(name: string, playwrightBinding: (source: BindingSource, ...args: any[]) => any, options?: { handle?: boolean }): Promise<void>;
exposeBinding(name: string, playwrightBinding: (source: BindingSource, ...args: any[]) => any, options?: { handle?: boolean }): Promise<Disposable>;

/**
* Adds a script which would be evaluated in one of the following scenarios:
Expand Down Expand Up @@ -8556,7 +8556,7 @@ export interface BrowserContext {
* [`script`](https://playwright.dev/docs/api/class-browsercontext#browser-context-add-init-script-option-script)
* (only supported when passing a function).
*/
addInitScript<Arg>(script: PageFunction<Arg, any> | { path?: string, content?: string }, arg?: Arg): Promise<void>;
addInitScript<Arg>(script: PageFunction<Arg, any> | { path?: string, content?: string }, arg?: Arg): Promise<Disposable>;

/**
* Removes all the listeners of the given type (or all registered listeners if no type given). Allows to wait for
Expand Down Expand Up @@ -9345,7 +9345,7 @@ export interface BrowserContext {
* @param name Name of the function on the window object.
* @param callback Callback function that will be called in the Playwright's context.
*/
exposeFunction(name: string, callback: Function): Promise<void>;
exposeFunction(name: string, callback: Function): Promise<Disposable>;

/**
* Grants specified permissions to the browser context. Only grants corresponding permissions to the given origin if
Expand Down Expand Up @@ -9469,7 +9469,7 @@ export interface BrowserContext {
* How often a route should be used. By default it will be used every time.
*/
times?: number;
}): Promise<void>;
}): Promise<Disposable>;

/**
* If specified the network requests that are made in the context will be served from the HAR file. Read more about
Expand Down Expand Up @@ -19531,6 +19531,23 @@ export interface Dialog {
type(): string;
}

/**
* [Disposable](https://playwright.dev/docs/api/class-disposable) is returned from various methods to allow undoing
* the corresponding action. For example,
* [page.addInitScript(script[, arg])](https://playwright.dev/docs/api/class-page#page-add-init-script) returns a
* [Disposable](https://playwright.dev/docs/api/class-disposable) that can be used to remove the init script.
*/
export interface Disposable {
/**
* Removes the associated resource. For example, removes the init script installed via
* [page.addInitScript(script[, arg])](https://playwright.dev/docs/api/class-page#page-add-init-script) or
* [browserContext.addInitScript(script[, arg])](https://playwright.dev/docs/api/class-browsercontext#browser-context-add-init-script).
*/
dispose(): Promise<void>;

[Symbol.asyncDispose](): Promise<void>;
}

/**
* [Download](https://playwright.dev/docs/api/class-download) objects are dispatched by page via the
* [page.on('download')](https://playwright.dev/docs/api/class-page#page-event-download) event.
Expand Down
1 change: 1 addition & 0 deletions packages/playwright-core/src/client/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export { Clock } from './clock';
export { ConsoleMessage } from './consoleMessage';
export { Coverage } from './coverage';
export { Dialog } from './dialog';
export type { Disposable } from './disposable';
export { Download } from './download';
export { Electron, ElectronApplication } from './electron';
export { FrameLocator, Locator } from './locator';
Expand Down
18 changes: 11 additions & 7 deletions packages/playwright-core/src/client/browserContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { evaluationScript } from './clientHelper';
import { Clock } from './clock';
import { ConsoleMessage } from './consoleMessage';
import { Dialog } from './dialog';
import { DisposableObject, DisposableStub } from './disposable';
import { TargetClosedError, parseError } from './errors';
import { Events } from './events';
import { APIRequestContext } from './fetch';
Expand Down Expand Up @@ -349,25 +350,28 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
await this._channel.setHTTPCredentials({ httpCredentials: httpCredentials || undefined });
}

async addInitScript(script: Function | string | { path?: string, content?: string }, arg?: any): Promise<void> {
async addInitScript(script: Function | string | { path?: string, content?: string }, arg?: any) {
const source = await evaluationScript(this._platform, script, arg);
await this._channel.addInitScript({ source });
return DisposableObject.from((await this._channel.addInitScript({ source })).disposable);
}

async exposeBinding(name: string, callback: (source: structs.BindingSource, ...args: any[]) => any, options: { handle?: boolean } = {}): Promise<void> {
await this._channel.exposeBinding({ name, needsHandle: options.handle });
async exposeBinding(name: string, callback: (source: structs.BindingSource, ...args: any[]) => any, options: { handle?: boolean } = {}): Promise<DisposableObject> {
const result = await this._channel.exposeBinding({ name, needsHandle: options.handle });
this._bindings.set(name, callback);
return DisposableObject.from(result.disposable);
}

async exposeFunction(name: string, callback: Function): Promise<void> {
await this._channel.exposeBinding({ name });
async exposeFunction(name: string, callback: Function): Promise<DisposableObject> {
const result = await this._channel.exposeBinding({ name });
const binding = (source: structs.BindingSource, ...args: any[]) => callback(...args);
this._bindings.set(name, binding);
return DisposableObject.from(result.disposable);
}

async route(url: URLMatch, handler: network.RouteHandlerCallback, options: { times?: number } = {}): Promise<void> {
async route(url: URLMatch, handler: network.RouteHandlerCallback, options: { times?: number } = {}): Promise<DisposableStub> {
this._routes.unshift(new network.RouteHandler(this._platform, this._options.baseURL, url, handler, options.times));
await this._updateInterceptionPatterns({ title: 'Route requests' });
return new DisposableStub(() => this.unroute(url, handler));
}

async routeWebSocket(url: URLMatch, handler: network.WebSocketRouteHandlerCallback): Promise<void> {
Expand Down
4 changes: 4 additions & 0 deletions packages/playwright-core/src/client/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { CDPSession } from './cdpSession';
import { ChannelOwner } from './channelOwner';
import { createInstrumentation } from './clientInstrumentation';
import { Dialog } from './dialog';
import { DisposableObject } from './disposable';
import { Electron, ElectronApplication } from './electron';
import { ElementHandle } from './elementHandle';
import { TargetClosedError, parseError } from './errors';
Expand Down Expand Up @@ -270,6 +271,9 @@ export class Connection extends EventEmitter {
case 'Dialog':
result = new Dialog(parent, type, guid, initializer);
break;
case 'Disposable':
result = new DisposableObject(parent, type, guid, initializer);
break;
case 'Electron':
result = new Electron(parent, type, guid, initializer);
break;
Expand Down
74 changes: 74 additions & 0 deletions packages/playwright-core/src/client/disposable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { ChannelOwner } from './channelOwner';
import { isTargetClosedError } from './errors';

import type * as channels from '@protocol/channels';

export interface Disposable {
dispose: () => Promise<void>;
}

export class DisposableObject<T extends channels.DisposableChannel = channels.DisposableChannel> extends ChannelOwner<T> implements Disposable {
static from(channel: channels.DisposableChannel): DisposableObject {
return (channel as any)._object;
}

async [Symbol.asyncDispose]() {
await this.dispose();
}

async dispose() {
try {
await this._channel.dispose();
} catch (e) {
if (isTargetClosedError(e))
return;
throw e;
}
}
}

export class DisposableStub implements Disposable {
private _dispose: (() => Promise<void>) | undefined;

constructor(dispose: () => Promise<void>) {
this._dispose = dispose;
}

async [Symbol.asyncDispose]() {
await this.dispose();
}

async dispose() {
if (!this._dispose)
return;
try {
const dispose = this._dispose;
this._dispose = undefined;
await dispose();
} catch (e) {
if (isTargetClosedError(e))
return;
throw e;
}
}
}

export function disposeAll(disposables: Disposable[]) {
return Promise.all(disposables.map(d => d.dispose()));
}
7 changes: 2 additions & 5 deletions packages/playwright-core/src/client/eventEmitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import type { EventEmitter as EventEmitterType } from 'events';
import type { Platform } from './platform';
import type { Disposable } from './disposable';

type EventType = string | symbol;
type Listener = (...args: any[]) => any;
Expand Down Expand Up @@ -397,18 +398,14 @@ function wrappedListener(l: Listener): Listener {
return (l as any).listener;
}

export type Disposable = {
dispose: () => void;
};

class EventsHelper {
static addEventListener(
emitter: EventEmitterType,
eventName: (string | symbol),
handler: (...args: any[]) => void): Disposable {
emitter.on(eventName, handler);
return {
dispose: () => emitter.removeListener(eventName, handler)
dispose: async () => { emitter.removeListener(eventName, handler); }
};
}
}
Expand Down
Loading
Loading