diff --git a/packages/playwright-core/src/cli/driver.ts b/packages/playwright-core/src/cli/driver.ts index 0cec6b4dae013..c6fd75249e6fd 100644 --- a/packages/playwright-core/src/cli/driver.ts +++ b/packages/playwright-core/src/cli/driver.ts @@ -20,7 +20,7 @@ import fs from 'fs'; import * as playwright from '../..'; import type { BrowserType } from '../client/browserType'; import type { LaunchServerOptions } from '../client/types'; -import { createPlaywright, DispatcherConnection, Root, PlaywrightDispatcher } from '../server'; +import { createPlaywright, DispatcherConnection, RootDispatcher, PlaywrightDispatcher } from '../server'; import type { Playwright } from '../server'; import { IpcTransport, PipeTransport } from '../protocol/transport'; import { PlaywrightServer } from '../remote/playwrightServer'; @@ -38,7 +38,7 @@ export function printApiJson() { export function runDriver() { const dispatcherConnection = new DispatcherConnection(); - new Root(dispatcherConnection, async (rootScope, { sdkLanguage }) => { + new RootDispatcher(dispatcherConnection, async (rootScope, { sdkLanguage }) => { const playwright = createPlaywright(sdkLanguage); return new PlaywrightDispatcher(rootScope, playwright); }); diff --git a/packages/playwright-core/src/client/browserContext.ts b/packages/playwright-core/src/client/browserContext.ts index 64a70d6dcd197..4fefccc5b43c0 100644 --- a/packages/playwright-core/src/client/browserContext.ts +++ b/packages/playwright-core/src/client/browserContext.ts @@ -79,7 +79,7 @@ export class BrowserContext extends ChannelOwner this._channel.on('bindingCall', ({ binding }) => this._onBinding(BindingCall.from(binding))); this._channel.on('close', () => this._onClose()); this._channel.on('page', ({ page }) => this._onPage(Page.from(page))); - this._channel.on('route', ({ route, request }) => this._onRoute(network.Route.from(route), network.Request.from(request))); + this._channel.on('route', ({ route }) => this._onRoute(network.Route.from(route))); this._channel.on('backgroundPage', ({ page }) => { const backgroundPage = Page.from(page); this._backgroundPages.add(backgroundPage); @@ -147,14 +147,14 @@ export class BrowserContext extends ChannelOwner response._finishedPromise.resolve(); } - async _onRoute(route: network.Route, request: network.Request) { + async _onRoute(route: network.Route) { const routeHandlers = this._routes.slice(); for (const routeHandler of routeHandlers) { - if (!routeHandler.matches(request.url())) + if (!routeHandler.matches(route.request().url())) continue; if (routeHandler.willExpire()) this._routes.splice(this._routes.indexOf(routeHandler), 1); - const handled = await routeHandler.handle(route, request); + const handled = await routeHandler.handle(route); if (!this._routes.length) this._wrapApiCall(() => this._disableInterception(), true).catch(() => {}); if (handled) diff --git a/packages/playwright-core/src/client/network.ts b/packages/playwright-core/src/client/network.ts index 2dee4482a9a45..4cfe0c7afa3c4 100644 --- a/packages/playwright-core/src/client/network.ts +++ b/packages/playwright-core/src/client/network.ts @@ -604,14 +604,14 @@ export class RouteHandler { return urlMatches(this._baseURL, requestURL, this.url); } - public async handle(route: Route, request: Request): Promise { + public async handle(route: Route): Promise { ++this.handledCount; const handledPromise = route._startHandling(); // Extract handler into a variable to avoid [RouteHandler.handler] in the stack. const handler = this.handler; const [handled] = await Promise.all([ handledPromise, - handler(route, request), + handler(route, route.request()), ]); return handled; } diff --git a/packages/playwright-core/src/client/page.ts b/packages/playwright-core/src/client/page.ts index 45a917eb76847..840aab711c420 100644 --- a/packages/playwright-core/src/client/page.ts +++ b/packages/playwright-core/src/client/page.ts @@ -47,7 +47,8 @@ import { Keyboard, Mouse, Touchscreen } from './input'; import { assertMaxArguments, JSHandle, parseResult, serializeArgument } from './jsHandle'; import type { FrameLocator, Locator, LocatorOptions } from './locator'; import type { RouteHandlerCallback } from './network'; -import { Request, Response, Route, RouteHandler, validateHeaders, WebSocket } from './network'; +import { Response, Route, RouteHandler, validateHeaders, WebSocket } from './network'; +import type { Request } from './network'; import type { FilePayload, Headers, LifecycleEvent, SelectOption, SelectOptionOptions, Size, URLMatch, WaitForEventOptions, WaitForFunctionOptions } from './types'; import { Video } from './video'; import { Waiter } from './waiter'; @@ -145,7 +146,7 @@ export class Page extends ChannelOwner implements api.Page this._channel.on('frameAttached', ({ frame }) => this._onFrameAttached(Frame.from(frame))); this._channel.on('frameDetached', ({ frame }) => this._onFrameDetached(Frame.from(frame))); this._channel.on('pageError', ({ error }) => this.emit(Events.Page.PageError, parseError(error))); - this._channel.on('route', ({ route, request }) => this._onRoute(Route.from(route), Request.from(request))); + this._channel.on('route', ({ route }) => this._onRoute(Route.from(route))); this._channel.on('video', ({ artifact }) => { const artifactObject = Artifact.from(artifact); this._forceVideo()._artifactReady(artifactObject); @@ -177,20 +178,20 @@ export class Page extends ChannelOwner implements api.Page this.emit(Events.Page.FrameDetached, frame); } - private async _onRoute(route: Route, request: Request) { + private async _onRoute(route: Route) { const routeHandlers = this._routes.slice(); for (const routeHandler of routeHandlers) { - if (!routeHandler.matches(request.url())) + if (!routeHandler.matches(route.request().url())) continue; if (routeHandler.willExpire()) this._routes.splice(this._routes.indexOf(routeHandler), 1); - const handled = await routeHandler.handle(route, request); + const handled = await routeHandler.handle(route); if (!this._routes.length) this._wrapApiCall(() => this._disableInterception(), true).catch(() => {}); if (handled) return; } - await this._browserContext._onRoute(route, request); + await this._browserContext._onRoute(route); } async _onBinding(bindingCall: BindingCall) { diff --git a/packages/playwright-core/src/inProcessFactory.ts b/packages/playwright-core/src/inProcessFactory.ts index 4c118441832ad..3ee2cacae9935 100644 --- a/packages/playwright-core/src/inProcessFactory.ts +++ b/packages/playwright-core/src/inProcessFactory.ts @@ -15,7 +15,7 @@ */ import type { Playwright as PlaywrightAPI } from './client/playwright'; -import { createPlaywright, DispatcherConnection, Root, PlaywrightDispatcher } from './server'; +import { createPlaywright, DispatcherConnection, RootDispatcher, PlaywrightDispatcher } from './server'; import { Connection } from './client/connection'; import { BrowserServerLauncherImpl } from './browserServerImpl'; @@ -29,7 +29,7 @@ export function createInProcessPlaywright(): PlaywrightAPI { dispatcherConnection.onmessage = message => clientConnection.dispatch(message); clientConnection.onmessage = message => dispatcherConnection.dispatch(message); - const rootScope = new Root(dispatcherConnection); + const rootScope = new RootDispatcher(dispatcherConnection); // Initialize Playwright channel. new PlaywrightDispatcher(rootScope, playwright); diff --git a/packages/playwright-core/src/protocol/channels.ts b/packages/playwright-core/src/protocol/channels.ts index 571151db012f0..78ded9364dfeb 100644 --- a/packages/playwright-core/src/protocol/channels.ts +++ b/packages/playwright-core/src/protocol/channels.ts @@ -1258,7 +1258,6 @@ export type BrowserContextPageEvent = { }; export type BrowserContextRouteEvent = { route: RouteChannel, - request: RequestChannel, }; export type BrowserContextVideoEvent = { artifact: ArtifactChannel, @@ -1586,7 +1585,6 @@ export type PagePageErrorEvent = { }; export type PageRouteEvent = { route: RouteChannel, - request: RequestChannel, }; export type PageVideoEvent = { artifact: ArtifactChannel, diff --git a/packages/playwright-core/src/protocol/protocol.yml b/packages/playwright-core/src/protocol/protocol.yml index 1791fd11a744d..715e63380dc9b 100644 --- a/packages/playwright-core/src/protocol/protocol.yml +++ b/packages/playwright-core/src/protocol/protocol.yml @@ -992,7 +992,6 @@ BrowserContext: route: parameters: route: Route - request: Request video: parameters: @@ -1422,7 +1421,6 @@ Page: route: parameters: route: Route - request: Request video: parameters: diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index 97d5d44002ce3..19ef63e7e68b0 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -656,7 +656,6 @@ scheme.BrowserContextPageEvent = tObject({ }); scheme.BrowserContextRouteEvent = tObject({ route: tChannel(['Route']), - request: tChannel(['Request']), }); scheme.BrowserContextVideoEvent = tObject({ artifact: tChannel(['Artifact']), @@ -842,7 +841,6 @@ scheme.PagePageErrorEvent = tObject({ }); scheme.PageRouteEvent = tObject({ route: tChannel(['Route']), - request: tChannel(['Request']), }); scheme.PageVideoEvent = tObject({ artifact: tChannel(['Artifact']), diff --git a/packages/playwright-core/src/remote/playwrightConnection.ts b/packages/playwright-core/src/remote/playwrightConnection.ts index c8900352ba2fc..9f6655792c652 100644 --- a/packages/playwright-core/src/remote/playwrightConnection.ts +++ b/packages/playwright-core/src/remote/playwrightConnection.ts @@ -15,8 +15,8 @@ */ import type { WebSocket } from '../utilsBundle'; -import type { Playwright, DispatcherScope } from '../server'; -import { createPlaywright, DispatcherConnection, Root, PlaywrightDispatcher } from '../server'; +import type { Playwright } from '../server'; +import { createPlaywright, DispatcherConnection, RootDispatcher, PlaywrightDispatcher } from '../server'; import { Browser } from '../server/browser'; import { serverSideCallMetadata } from '../server/instrumentation'; import { gracefullyCloseAll } from '../utils/processLauncher'; @@ -45,7 +45,7 @@ export class PlaywrightConnection { private _disconnected = false; private _preLaunched: PreLaunched; private _options: Options; - private _root: Root; + private _root: RootDispatcher; constructor(lock: Promise, mode: Mode, ws: WebSocket, options: Options, preLaunched: PreLaunched, log: (m: string) => void, onClose: () => void) { this._ws = ws; @@ -72,7 +72,7 @@ export class PlaywrightConnection { ws.on('close', () => this._onDisconnect()); ws.on('error', error => this._onDisconnect(error)); - this._root = new Root(this._dispatcherConnection, async scope => { + this._root = new RootDispatcher(this._dispatcherConnection, async scope => { if (mode === 'reuse-browser') return await this._initReuseBrowsersMode(scope); if (mode === 'use-pre-launched-browser') @@ -83,7 +83,7 @@ export class PlaywrightConnection { }); } - private async _initPlaywrightConnectMode(scope: DispatcherScope) { + private async _initPlaywrightConnectMode(scope: RootDispatcher) { this._debugLog(`engaged playwright.connect mode`); const playwright = createPlaywright('javascript'); // Close all launched browsers on disconnect. @@ -93,7 +93,7 @@ export class PlaywrightConnection { return new PlaywrightDispatcher(scope, playwright, socksProxy); } - private async _initLaunchBrowserMode(scope: DispatcherScope) { + private async _initLaunchBrowserMode(scope: RootDispatcher) { this._debugLog(`engaged launch mode for "${this._options.browserName}"`); const playwright = createPlaywright('javascript'); @@ -112,7 +112,7 @@ export class PlaywrightConnection { return new PlaywrightDispatcher(scope, playwright, socksProxy, browser); } - private async _initPreLaunchedBrowserMode(scope: DispatcherScope) { + private async _initPreLaunchedBrowserMode(scope: RootDispatcher) { this._debugLog(`engaged pre-launched mode`); const playwright = this._preLaunched.playwright!; const browser = this._preLaunched.browser!; @@ -130,7 +130,7 @@ export class PlaywrightConnection { return playwrightDispatcher; } - private async _initReuseBrowsersMode(scope: DispatcherScope) { + private async _initReuseBrowsersMode(scope: RootDispatcher) { this._debugLog(`engaged reuse browsers mode for ${this._options.browserName}`); const playwright = this._preLaunched.playwright!; const requestedOptions = launchOptionsHash(this._options.launchOptions); diff --git a/packages/playwright-core/src/server/dispatchers/androidDispatcher.ts b/packages/playwright-core/src/server/dispatchers/androidDispatcher.ts index 283369937ac11..597db65c23b1b 100644 --- a/packages/playwright-core/src/server/dispatchers/androidDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/androidDispatcher.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import type { DispatcherScope } from './dispatcher'; +import type { RootDispatcher } from './dispatcher'; import { Dispatcher, existingDispatcher } from './dispatcher'; import type { Android, SocketBackend } from '../android/android'; import { AndroidDevice } from '../android/android'; @@ -22,9 +22,9 @@ import type * as channels from '../../protocol/channels'; import { BrowserContextDispatcher } from './browserContextDispatcher'; import type { CallMetadata } from '../instrumentation'; -export class AndroidDispatcher extends Dispatcher implements channels.AndroidChannel { +export class AndroidDispatcher extends Dispatcher implements channels.AndroidChannel { _type_Android = true; - constructor(scope: DispatcherScope, android: Android) { + constructor(scope: RootDispatcher, android: Android) { super(scope, android, 'Android', {}, true); } @@ -40,16 +40,16 @@ export class AndroidDispatcher extends Dispatcher implements channels.AndroidDeviceChannel { +export class AndroidDeviceDispatcher extends Dispatcher implements channels.AndroidDeviceChannel { _type_EventTarget = true; _type_AndroidDevice = true; - static from(scope: DispatcherScope, device: AndroidDevice): AndroidDeviceDispatcher { + static from(scope: AndroidDispatcher, device: AndroidDevice): AndroidDeviceDispatcher { const result = existingDispatcher(device); return result || new AndroidDeviceDispatcher(scope, device); } - constructor(scope: DispatcherScope, device: AndroidDevice) { + constructor(scope: AndroidDispatcher, device: AndroidDevice) { super(scope, device, 'AndroidDevice', { model: device.model, serial: device.serial, @@ -174,10 +174,10 @@ export class AndroidDeviceDispatcher extends Dispatcher implements channels.AndroidSocketChannel { +export class AndroidSocketDispatcher extends Dispatcher implements channels.AndroidSocketChannel { _type_AndroidSocket = true; - constructor(scope: DispatcherScope, socket: SocketBackend) { + constructor(scope: AndroidDeviceDispatcher, socket: SocketBackend) { super(scope, socket, 'AndroidSocket', {}, true); this.addObjectListener('data', (data: Buffer) => this._dispatchEvent('data', { data })); this.addObjectListener('close', () => { diff --git a/packages/playwright-core/src/server/dispatchers/artifactDispatcher.ts b/packages/playwright-core/src/server/dispatchers/artifactDispatcher.ts index ef460e2b38efc..96a2a11e2162d 100644 --- a/packages/playwright-core/src/server/dispatchers/artifactDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/artifactDispatcher.ts @@ -15,14 +15,14 @@ */ import type * as channels from '../../protocol/channels'; -import type { DispatcherScope } from './dispatcher'; import { Dispatcher } from './dispatcher'; +import type { DispatcherScope } from './dispatcher'; import { StreamDispatcher } from './streamDispatcher'; import fs from 'fs'; import { mkdirIfNeeded } from '../../utils/fileUtils'; import type { Artifact } from '../artifact'; -export class ArtifactDispatcher extends Dispatcher implements channels.ArtifactChannel { +export class ArtifactDispatcher extends Dispatcher implements channels.ArtifactChannel { _type_Artifact = true; constructor(scope: DispatcherScope, artifact: Artifact) { super(scope, artifact, 'Artifact', { diff --git a/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts b/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts index 5253e5ea31b9b..af6765842b96c 100644 --- a/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts @@ -15,8 +15,8 @@ */ import { BrowserContext } from '../browserContext'; -import type { DispatcherScope } from './dispatcher'; import { Dispatcher, lookupDispatcher } from './dispatcher'; +import type { DispatcherScope } from './dispatcher'; import { PageDispatcher, BindingCallDispatcher, WorkerDispatcher } from './pageDispatcher'; import type { FrameDispatcher } from './frameDispatcher'; import type * as channels from '../../protocol/channels'; @@ -34,15 +34,15 @@ import * as path from 'path'; import { createGuid } from '../../utils'; import { WritableStreamDispatcher } from './writableStreamDispatcher'; -export class BrowserContextDispatcher extends Dispatcher implements channels.BrowserContextChannel { +export class BrowserContextDispatcher extends Dispatcher implements channels.BrowserContextChannel { _type_EventTarget = true; _type_BrowserContext = true; private _context: BrowserContext; constructor(parentScope: DispatcherScope, context: BrowserContext) { // We will reparent these to the context below. - const requestContext = APIRequestContextDispatcher.from(parentScope, context.fetchRequest); - const tracing = TracingDispatcher.from(parentScope, context.tracing); + const requestContext = APIRequestContextDispatcher.from(parentScope as BrowserContextDispatcher, context.fetchRequest); + const tracing = TracingDispatcher.from(parentScope as BrowserContextDispatcher, context.tracing); super(parentScope, context, 'BrowserContext', { isChromium: context._browser.options.isChromium, @@ -70,8 +70,10 @@ export class BrowserContextDispatcher extends Dispatcher this._dispatchEvent('page', { page: new PageDispatcher(this._scope, page) })); + this._dispatchEvent('page', { page: PageDispatcher.from(this._scope, page) }); + this.addObjectListener(BrowserContext.Events.Page, page => { + this._dispatchEvent('page', { page: PageDispatcher.from(this._scope, page) }); + }); this.addObjectListener(BrowserContext.Events.Close, () => { this._dispatchEvent('close'); this._dispose(); @@ -79,8 +81,8 @@ export class BrowserContextDispatcher extends Dispatcher this._dispatchEvent('backgroundPage', { page: new PageDispatcher(this._scope, page) })); + this._dispatchEvent('backgroundPage', { page: PageDispatcher.from(this._scope, page) }); + this.addObjectListener(CRBrowserContext.CREvents.BackgroundPage, page => this._dispatchEvent('backgroundPage', { page: PageDispatcher.from(this._scope, page) })); for (const serviceWorker of (context as CRBrowserContext).serviceWorkers()) this._dispatchEvent('serviceWorker', { worker: new WorkerDispatcher(this._scope, serviceWorker) }); this.addObjectListener(CRBrowserContext.CREvents.ServiceWorker, serviceWorker => this._dispatchEvent('serviceWorker', { worker: new WorkerDispatcher(this._scope, serviceWorker) })); @@ -128,7 +130,8 @@ export class BrowserContextDispatcher extends Dispatcher { await this._context.exposeBinding(params.name, !!params.needsHandle, (source, ...args) => { - const binding = new BindingCallDispatcher(this._scope, params.name, !!params.needsHandle, source, args); + const pageDispatcher = PageDispatcher.from(this._scope, source.page); + const binding = new BindingCallDispatcher(pageDispatcher, params.name, !!params.needsHandle, source, args); this._dispatchEvent('bindingCall', { binding }); return binding.promise(); }); @@ -184,7 +187,7 @@ export class BrowserContextDispatcher extends Dispatcher { - this._dispatchEvent('route', { route: RouteDispatcher.from(this._scope, route), request: RequestDispatcher.from(this._scope, request) }); + this._dispatchEvent('route', { route: RouteDispatcher.from(RequestDispatcher.from(this._scope, request), route) }); }); } diff --git a/packages/playwright-core/src/server/dispatchers/browserDispatcher.ts b/packages/playwright-core/src/server/dispatchers/browserDispatcher.ts index fdf709496931f..e9556c437cc69 100644 --- a/packages/playwright-core/src/server/dispatchers/browserDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/browserDispatcher.ts @@ -19,7 +19,7 @@ import type * as channels from '../../protocol/channels'; import { BrowserContextDispatcher } from './browserContextDispatcher'; import { CDPSessionDispatcher } from './cdpSessionDispatcher'; import { existingDispatcher } from './dispatcher'; -import type { DispatcherScope } from './dispatcher'; +import type { RootDispatcher } from './dispatcher'; import { Dispatcher } from './dispatcher'; import type { CRBrowser } from '../chromium/crBrowser'; import type { PageDispatcher } from './pageDispatcher'; @@ -27,11 +27,12 @@ import type { CallMetadata } from '../instrumentation'; import { serverSideCallMetadata } from '../instrumentation'; import { BrowserContext } from '../browserContext'; import { Selectors } from '../selectors'; +import type { BrowserTypeDispatcher } from './browserTypeDispatcher'; -export class BrowserDispatcher extends Dispatcher implements channels.BrowserChannel { +export class BrowserDispatcher extends Dispatcher implements channels.BrowserChannel { _type_Browser = true; - constructor(scope: DispatcherScope, browser: Browser) { + constructor(scope: BrowserTypeDispatcher, browser: Browser) { super(scope, browser, 'Browser', { version: browser.version(), name: browser.options.name }, true); this.addObjectListener(Browser.Events.Disconnected, () => this._didClose()); } @@ -81,12 +82,12 @@ export class BrowserDispatcher extends Dispatcher implements channels.BrowserChannel { +export class ConnectedBrowserDispatcher extends Dispatcher implements channels.BrowserChannel { _type_Browser = true; private _contexts = new Set(); readonly selectors: Selectors; - constructor(scope: DispatcherScope, browser: Browser) { + constructor(scope: RootDispatcher, browser: Browser) { super(scope, browser, 'Browser', { version: browser.version(), name: browser.options.name }, true); // When we have a remotely-connected browser, each client gets a fresh Selector instance, // so that two clients do not interfere between each other. @@ -141,7 +142,7 @@ export class ConnectedBrowserDispatcher extends Dispatcher { +async function newContextForReuse(browser: Browser, scope: BrowserDispatcher, params: channels.BrowserNewContextForReuseParams, selectors: Selectors | null, metadata: CallMetadata): Promise { const { context, needsReset } = await browser.newContextForReuse(params, metadata); if (needsReset) { const oldContextDispatcher = existingDispatcher(context); diff --git a/packages/playwright-core/src/server/dispatchers/browserTypeDispatcher.ts b/packages/playwright-core/src/server/dispatchers/browserTypeDispatcher.ts index 7554311642016..07d5090f79cdd 100644 --- a/packages/playwright-core/src/server/dispatchers/browserTypeDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/browserTypeDispatcher.ts @@ -17,7 +17,7 @@ import type { BrowserType } from '../browserType'; import { BrowserDispatcher } from './browserDispatcher'; import type * as channels from '../../protocol/channels'; -import type { DispatcherScope } from './dispatcher'; +import type { RootDispatcher } from './dispatcher'; import { Dispatcher } from './dispatcher'; import { BrowserContextDispatcher } from './browserContextDispatcher'; import type { CallMetadata } from '../instrumentation'; @@ -29,9 +29,9 @@ import { ProgressController } from '../progress'; import { WebSocketTransport } from '../transport'; import { findValidator, ValidationError, type ValidatorContext } from '../../protocol/validator'; -export class BrowserTypeDispatcher extends Dispatcher implements channels.BrowserTypeChannel { +export class BrowserTypeDispatcher extends Dispatcher implements channels.BrowserTypeChannel { _type_BrowserType = true; - constructor(scope: DispatcherScope, browserType: BrowserType) { + constructor(scope: RootDispatcher, browserType: BrowserType) { super(scope, browserType, 'BrowserType', { executablePath: browserType.executablePath(), name: browserType.name() @@ -53,7 +53,7 @@ export class BrowserTypeDispatcher extends Dispatcher implements channels.CDPSessionChannel { +export class CDPSessionDispatcher extends Dispatcher implements channels.CDPSessionChannel { _type_CDPSession = true; - constructor(scope: DispatcherScope, crSession: CRSession) { + constructor(scope: BrowserDispatcher | BrowserContextDispatcher, crSession: CRSession) { super(scope, crSession, 'CDPSession', {}, true); crSession._eventListener = (method, params) => { this._dispatchEvent('event', { method, params }); diff --git a/packages/playwright-core/src/server/dispatchers/consoleMessageDispatcher.ts b/packages/playwright-core/src/server/dispatchers/consoleMessageDispatcher.ts index 48e46fcbd8ecd..37e8b4a63fd23 100644 --- a/packages/playwright-core/src/server/dispatchers/consoleMessageDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/consoleMessageDispatcher.ts @@ -16,13 +16,14 @@ import type { ConsoleMessage } from '../console'; import type * as channels from '../../protocol/channels'; -import type { DispatcherScope } from './dispatcher'; +import type { PageDispatcher } from './pageDispatcher'; import { Dispatcher } from './dispatcher'; import { ElementHandleDispatcher } from './elementHandlerDispatcher'; -export class ConsoleMessageDispatcher extends Dispatcher implements channels.ConsoleMessageChannel { +export class ConsoleMessageDispatcher extends Dispatcher implements channels.ConsoleMessageChannel { _type_ConsoleMessage = true; - constructor(scope: DispatcherScope, message: ConsoleMessage) { + + constructor(scope: PageDispatcher, message: ConsoleMessage) { super(scope, message, 'ConsoleMessage', { type: message.type(), text: message.text(), diff --git a/packages/playwright-core/src/server/dispatchers/dialogDispatcher.ts b/packages/playwright-core/src/server/dispatchers/dialogDispatcher.ts index 1e5c791c8eb84..694737f1c1b31 100644 --- a/packages/playwright-core/src/server/dispatchers/dialogDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/dialogDispatcher.ts @@ -16,12 +16,13 @@ import type { Dialog } from '../dialog'; import type * as channels from '../../protocol/channels'; -import type { DispatcherScope } from './dispatcher'; import { Dispatcher } from './dispatcher'; +import type { PageDispatcher } from './pageDispatcher'; -export class DialogDispatcher extends Dispatcher implements channels.DialogChannel { +export class DialogDispatcher extends Dispatcher implements channels.DialogChannel { _type_Dialog = true; - constructor(scope: DispatcherScope, dialog: Dialog) { + + constructor(scope: PageDispatcher, dialog: Dialog) { super(scope, dialog, 'Dialog', { type: dialog.type(), message: dialog.message(), diff --git a/packages/playwright-core/src/server/dispatchers/dispatcher.ts b/packages/playwright-core/src/server/dispatchers/dispatcher.ts index fc1e37b53410f..5abc2003ce54c 100644 --- a/packages/playwright-core/src/server/dispatchers/dispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/dispatcher.ts @@ -44,28 +44,28 @@ export function lookupNullableDispatcher(object: any | null): Di return object ? lookupDispatcher(object) : undefined; } -export class Dispatcher extends EventEmitter implements channels.Channel { +export class Dispatcher extends EventEmitter implements channels.Channel { private _connection: DispatcherConnection; private _isScope: boolean; // Parent is always "isScope". - private _parent: Dispatcher | undefined; + private _parent: ParentScopeType | undefined; // Only "isScope" channel owners have registered dispatchers inside. - private _dispatchers = new Map>(); + private _dispatchers = new Map(); protected _disposed = false; protected _eventListeners: RegisteredListener[] = []; readonly _guid: string; readonly _type: string; - readonly _scope: Dispatcher; + readonly _scope: ScopeType; _object: Type; - constructor(parent: Dispatcher | DispatcherConnection, object: Type, type: string, initializer: channels.InitializerTraits, isScope?: boolean) { + constructor(parent: ParentScopeType | DispatcherConnection, object: Type, type: string, initializer: channels.InitializerTraits, isScope?: boolean) { super(); this._connection = parent instanceof DispatcherConnection ? parent : parent._connection; this._isScope = !!isScope; this._parent = parent instanceof DispatcherConnection ? undefined : parent; - this._scope = isScope ? this : this._parent!; + this._scope = (isScope ? this : this._parent!) as any as ScopeType; const guid = object.guid; assert(!this._connection._dispatchers.has(guid)); @@ -84,11 +84,15 @@ export class Dispatcher extends Even this._connection.sendCreate(this._parent, type, guid, initializer, this._parent._object); } + parentScope(): ParentScopeType | undefined { + return this._parent; + } + addObjectListener(eventName: (string | symbol), handler: (...args: any[]) => void) { this._eventListeners.push(eventsHelper.addEventListener(this._object as unknown as EventEmitter, eventName, handler)); } - adopt(child: Dispatcher) { + adopt(child: DispatcherScope) { assert(this._isScope); const oldParent = child._parent!; oldParent._dispatchers.delete(child._guid); @@ -140,11 +144,12 @@ export class Dispatcher extends Even } } -export type DispatcherScope = Dispatcher; -export class Root extends Dispatcher<{ guid: '' }, any> { +export type DispatcherScope = Dispatcher; + +export class RootDispatcher extends Dispatcher<{ guid: '' }, any, any> { private _initialized = false; - constructor(connection: DispatcherConnection, private readonly createPlaywright?: (scope: DispatcherScope, options: channels.RootInitializeParams) => Promise) { + constructor(connection: DispatcherConnection, private readonly createPlaywright?: (scope: RootDispatcher, options: channels.RootInitializeParams) => Promise) { super(connection, { guid: '' }, 'Root', {}, true); } @@ -159,7 +164,7 @@ export class Root extends Dispatcher<{ guid: '' }, any> { } export class DispatcherConnection { - readonly _dispatchers = new Map>(); + readonly _dispatchers = new Map(); onmessage = (message: object) => {}; private _waitOperations = new Map(); private _isLocal: boolean; @@ -168,23 +173,23 @@ export class DispatcherConnection { this._isLocal = !!isLocal; } - sendEvent(dispatcher: Dispatcher, event: string, params: any, sdkObject?: SdkObject) { + sendEvent(dispatcher: DispatcherScope, event: string, params: any, sdkObject?: SdkObject) { const validator = findValidator(dispatcher._type, event, 'Event'); params = validator(params, '', { tChannelImpl: this._tChannelImplToWire.bind(this), binary: this._isLocal ? 'buffer' : 'toBase64' }); this._sendMessageToClient(dispatcher._guid, dispatcher._type, event, params, sdkObject); } - sendCreate(parent: Dispatcher, type: string, guid: string, initializer: any, sdkObject?: SdkObject) { + sendCreate(parent: DispatcherScope, type: string, guid: string, initializer: any, sdkObject?: SdkObject) { const validator = findValidator(type, '', 'Initializer'); initializer = validator(initializer, '', { tChannelImpl: this._tChannelImplToWire.bind(this), binary: this._isLocal ? 'buffer' : 'toBase64' }); this._sendMessageToClient(parent._guid, type, '__create__', { type, initializer, guid }, sdkObject); } - sendAdopt(parent: Dispatcher, dispatcher: Dispatcher) { + sendAdopt(parent: DispatcherScope, dispatcher: DispatcherScope) { this._sendMessageToClient(parent._guid, dispatcher._type, '__adopt__', { guid: dispatcher._guid }); } - sendDispose(dispatcher: Dispatcher) { + sendDispose(dispatcher: DispatcherScope) { this._sendMessageToClient(dispatcher._guid, dispatcher._type, '__dispose__', {}); } diff --git a/packages/playwright-core/src/server/dispatchers/electronDispatcher.ts b/packages/playwright-core/src/server/dispatchers/electronDispatcher.ts index 5a99b4e85912f..514a47c3ed2d2 100644 --- a/packages/playwright-core/src/server/dispatchers/electronDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/electronDispatcher.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import type { DispatcherScope } from './dispatcher'; +import type { RootDispatcher } from './dispatcher'; import { Dispatcher } from './dispatcher'; import type { Electron } from '../electron/electron'; import { ElectronApplication } from '../electron/electron'; @@ -24,9 +24,10 @@ import type { PageDispatcher } from './pageDispatcher'; import { parseArgument, serializeResult } from './jsHandleDispatcher'; import { ElementHandleDispatcher } from './elementHandlerDispatcher'; -export class ElectronDispatcher extends Dispatcher implements channels.ElectronChannel { +export class ElectronDispatcher extends Dispatcher implements channels.ElectronChannel { _type_Electron = true; - constructor(scope: DispatcherScope, electron: Electron) { + + constructor(scope: RootDispatcher, electron: Electron) { super(scope, electron, 'Electron', {}, true); } @@ -36,11 +37,11 @@ export class ElectronDispatcher extends Dispatcher implements channels.ElectronApplicationChannel { +export class ElectronApplicationDispatcher extends Dispatcher implements channels.ElectronApplicationChannel { _type_EventTarget = true; _type_ElectronApplication = true; - constructor(scope: DispatcherScope, electronApplication: ElectronApplication) { + constructor(scope: ElectronDispatcher, electronApplication: ElectronApplication) { super(scope, electronApplication, 'ElectronApplication', { context: new BrowserContextDispatcher(scope, electronApplication.context()) }, true); diff --git a/packages/playwright-core/src/server/dispatchers/elementHandlerDispatcher.ts b/packages/playwright-core/src/server/dispatchers/elementHandlerDispatcher.ts index cc442b95c5a70..b0288ddebe03e 100644 --- a/packages/playwright-core/src/server/dispatchers/elementHandlerDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/elementHandlerDispatcher.ts @@ -18,37 +18,38 @@ import type { ElementHandle } from '../dom'; import type { Frame } from '../frames'; import type * as js from '../javascript'; import type * as channels from '../../protocol/channels'; -import type { DispatcherScope } from './dispatcher'; import { existingDispatcher, lookupNullableDispatcher } from './dispatcher'; import { JSHandleDispatcher, serializeResult, parseArgument } from './jsHandleDispatcher'; +import type { JSHandleDispatcherParentScope } from './jsHandleDispatcher'; import type { FrameDispatcher } from './frameDispatcher'; import type { CallMetadata } from '../instrumentation'; import type { WritableStreamDispatcher } from './writableStreamDispatcher'; import { assert } from '../../utils'; import path from 'path'; + export class ElementHandleDispatcher extends JSHandleDispatcher implements channels.ElementHandleChannel { _type_ElementHandle = true; readonly _elementHandle: ElementHandle; - static from(scope: DispatcherScope, handle: ElementHandle): ElementHandleDispatcher { + static from(scope: JSHandleDispatcherParentScope, handle: ElementHandle): ElementHandleDispatcher { return existingDispatcher(handle) || new ElementHandleDispatcher(scope, handle); } - static fromNullable(scope: DispatcherScope, handle: ElementHandle | null): ElementHandleDispatcher | undefined { + static fromNullable(scope: JSHandleDispatcherParentScope, handle: ElementHandle | null): ElementHandleDispatcher | undefined { if (!handle) return undefined; return existingDispatcher(handle) || new ElementHandleDispatcher(scope, handle); } - static fromJSHandle(scope: DispatcherScope, handle: js.JSHandle): JSHandleDispatcher { + static fromJSHandle(scope: JSHandleDispatcherParentScope, handle: js.JSHandle): JSHandleDispatcher { const result = existingDispatcher(handle); if (result) return result; return handle.asElement() ? new ElementHandleDispatcher(scope, handle.asElement()!) : new JSHandleDispatcher(scope, handle); } - private constructor(scope: DispatcherScope, elementHandle: ElementHandle) { + private constructor(scope: JSHandleDispatcherParentScope, elementHandle: ElementHandle) { super(scope, elementHandle); this._elementHandle = elementHandle; } diff --git a/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts b/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts index c0e3381c7e1ef..da3551adee2ab 100644 --- a/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts @@ -17,7 +17,6 @@ import type { NavigationEvent } from '../frames'; import { Frame } from '../frames'; import type * as channels from '../../protocol/channels'; -import type { DispatcherScope } from './dispatcher'; import { Dispatcher, lookupNullableDispatcher, existingDispatcher } from './dispatcher'; import { ElementHandleDispatcher } from './elementHandlerDispatcher'; import { parseArgument, serializeResult } from './jsHandleDispatcher'; @@ -27,29 +26,30 @@ import type { CallMetadata } from '../instrumentation'; import type { WritableStreamDispatcher } from './writableStreamDispatcher'; import { assert } from '../../utils'; import path from 'path'; +import type { PageDispatcher } from './pageDispatcher'; -export class FrameDispatcher extends Dispatcher implements channels.FrameChannel { +export class FrameDispatcher extends Dispatcher implements channels.FrameChannel { _type_Frame = true; private _frame: Frame; - static from(scope: DispatcherScope, frame: Frame): FrameDispatcher { + static from(scope: PageDispatcher, frame: Frame): FrameDispatcher { const result = existingDispatcher(frame); return result || new FrameDispatcher(scope, frame); } - static fromNullable(scope: DispatcherScope, frame: Frame | null): FrameDispatcher | undefined { + static fromNullable(scope: PageDispatcher, frame: Frame | null): FrameDispatcher | undefined { if (!frame) return; return FrameDispatcher.from(scope, frame); } - private constructor(scope: DispatcherScope, frame: Frame) { + private constructor(scope: PageDispatcher, frame: Frame) { super(scope, frame, 'Frame', { url: frame.url(), name: frame.name(), parentFrame: FrameDispatcher.fromNullable(scope, frame.parentFrame()), loadStates: Array.from(frame._subtreeLifecycleEvents), - }); + }, true); this._frame = frame; this.addObjectListener(Frame.Events.AddLifecycle, lifecycleEvent => { this._dispatchEvent('loadstate', { add: lifecycleEvent }); @@ -62,7 +62,7 @@ export class FrameDispatcher extends Dispatcher im return; const params = { url: event.url, name: event.name, error: event.error ? event.error.message : undefined }; if (event.newDocument) - (params as any).newDocument = { request: RequestDispatcher.fromNullable(this._scope, event.newDocument.request || null) }; + (params as any).newDocument = { request: RequestDispatcher.fromNullable(scope.parentScope()!, event.newDocument.request || null) }; this._dispatchEvent('navigated', params); }); } diff --git a/packages/playwright-core/src/server/dispatchers/jsHandleDispatcher.ts b/packages/playwright-core/src/server/dispatchers/jsHandleDispatcher.ts index 0d9e2c0114ebf..9d55a8e4d24bf 100644 --- a/packages/playwright-core/src/server/dispatchers/jsHandleDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/jsHandleDispatcher.ts @@ -16,15 +16,18 @@ import type * as js from '../javascript'; import type * as channels from '../../protocol/channels'; -import type { DispatcherScope } from './dispatcher'; import { Dispatcher } from './dispatcher'; import { ElementHandleDispatcher } from './elementHandlerDispatcher'; import { parseSerializedValue, serializeValue } from '../../protocol/serializers'; +import type { PageDispatcher, WorkerDispatcher } from './pageDispatcher'; +import type { ElectronApplicationDispatcher } from './electronDispatcher'; -export class JSHandleDispatcher extends Dispatcher implements channels.JSHandleChannel { +export type JSHandleDispatcherParentScope = PageDispatcher | WorkerDispatcher | ElectronApplicationDispatcher; + +export class JSHandleDispatcher extends Dispatcher implements channels.JSHandleChannel { _type_JSHandle = true; - protected constructor(scope: DispatcherScope, jsHandle: js.JSHandle) { + protected constructor(scope: JSHandleDispatcherParentScope, jsHandle: js.JSHandle) { // Do not call this directly, use createHandle() instead. super(scope, jsHandle, jsHandle.asElement() ? 'ElementHandle' : 'JSHandle', { preview: jsHandle.toString(), diff --git a/packages/playwright-core/src/server/dispatchers/jsonPipeDispatcher.ts b/packages/playwright-core/src/server/dispatchers/jsonPipeDispatcher.ts index 719bd01105168..4599728e6fa11 100644 --- a/packages/playwright-core/src/server/dispatchers/jsonPipeDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/jsonPipeDispatcher.ts @@ -15,14 +15,14 @@ */ import type * as channels from '../../protocol/channels'; -import type { DispatcherScope } from './dispatcher'; import { Dispatcher } from './dispatcher'; import { createGuid } from '../../utils'; import { serializeError } from '../../protocol/serializers'; +import type { BrowserTypeDispatcher } from './browserTypeDispatcher'; -export class JsonPipeDispatcher extends Dispatcher<{ guid: string }, channels.JsonPipeChannel> implements channels.JsonPipeChannel { +export class JsonPipeDispatcher extends Dispatcher<{ guid: string }, channels.JsonPipeChannel, BrowserTypeDispatcher> implements channels.JsonPipeChannel { _type_JsonPipe = true; - constructor(scope: DispatcherScope) { + constructor(scope: BrowserTypeDispatcher) { super(scope, { guid: 'jsonPipe@' + createGuid() }, 'JsonPipe', {}); } diff --git a/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts b/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts index 2b0372810b79b..1489a787aa97f 100644 --- a/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts @@ -20,18 +20,18 @@ import path from 'path'; import type * as channels from '../../protocol/channels'; import { ManualPromise } from '../../utils/manualPromise'; import { assert, createGuid } from '../../utils'; -import type { DispatcherScope } from './dispatcher'; +import type { RootDispatcher } from './dispatcher'; import { Dispatcher } from './dispatcher'; import { yazl, yauzl } from '../../zipBundle'; import { ZipFile } from '../../utils/zipFile'; import type * as har from '../har/har'; import type { HeadersArray } from '../types'; -export class LocalUtilsDispatcher extends Dispatcher<{ guid: string }, channels.LocalUtilsChannel> implements channels.LocalUtilsChannel { +export class LocalUtilsDispatcher extends Dispatcher<{ guid: string }, channels.LocalUtilsChannel, RootDispatcher> implements channels.LocalUtilsChannel { _type_LocalUtils: boolean; private _harBakends = new Map(); - constructor(scope: DispatcherScope) { + constructor(scope: RootDispatcher) { super(scope, { guid: 'localUtils@' + createGuid() }, 'LocalUtils', {}); this._type_LocalUtils = true; } diff --git a/packages/playwright-core/src/server/dispatchers/networkDispatchers.ts b/packages/playwright-core/src/server/dispatchers/networkDispatchers.ts index 0e145d20c0d27..74d93f696c3c8 100644 --- a/packages/playwright-core/src/server/dispatchers/networkDispatchers.ts +++ b/packages/playwright-core/src/server/dispatchers/networkDispatchers.ts @@ -19,36 +19,39 @@ import type { APIRequestContext } from '../fetch'; import type { CallMetadata } from '../instrumentation'; import type { Request, Response, Route } from '../network'; import { WebSocket } from '../network'; -import type { DispatcherScope } from './dispatcher'; +import type { RootDispatcher } from './dispatcher'; import { Dispatcher, existingDispatcher, lookupNullableDispatcher } from './dispatcher'; -import { FrameDispatcher } from './frameDispatcher'; import { WorkerDispatcher } from './pageDispatcher'; import { TracingDispatcher } from './tracingDispatcher'; +import type { BrowserContextDispatcher } from './browserContextDispatcher'; +import type { PageDispatcher } from './pageDispatcher'; +import { FrameDispatcher } from './frameDispatcher'; -export class RequestDispatcher extends Dispatcher implements channels.RequestChannel { +export class RequestDispatcher extends Dispatcher implements channels.RequestChannel { _type_Request: boolean; - static from(scope: DispatcherScope, request: Request): RequestDispatcher { + static from(scope: BrowserContextDispatcher, request: Request): RequestDispatcher { const result = existingDispatcher(request); return result || new RequestDispatcher(scope, request); } - static fromNullable(scope: DispatcherScope, request: Request | null): RequestDispatcher | undefined { + static fromNullable(scope: BrowserContextDispatcher, request: Request | null): RequestDispatcher | undefined { return request ? RequestDispatcher.from(scope, request) : undefined; } - private constructor(scope: DispatcherScope, request: Request) { + private constructor(contextScope: BrowserContextDispatcher, request: Request) { const postData = request.postDataBuffer(); + const scope = parentScopeForRequest(contextScope, request); super(scope, request, 'Request', { - frame: FrameDispatcher.fromNullable(scope, request.frame()), - serviceWorker: WorkerDispatcher.fromNullable(scope, request.serviceWorker()), + frame: request.frame() ? scope : undefined, + serviceWorker: request.serviceWorker() ? scope : undefined, url: request.url(), resourceType: request.resourceType(), method: request.method(), postData: postData === null ? undefined : postData, headers: request.headers(), isNavigationRequest: request.isNavigationRequest(), - redirectedFrom: RequestDispatcher.fromNullable(scope, request.redirectedFrom()), + redirectedFrom: RequestDispatcher.fromNullable(contextScope, request.redirectedFrom()), }); this._type_Request = true; } @@ -62,22 +65,23 @@ export class RequestDispatcher extends Dispatcher implements channels.ResponseChannel { +export class ResponseDispatcher extends Dispatcher implements channels.ResponseChannel { _type_Response = true; - static from(scope: DispatcherScope, response: Response): ResponseDispatcher { + static from(scope: BrowserContextDispatcher, response: Response): ResponseDispatcher { const result = existingDispatcher(response); return result || new ResponseDispatcher(scope, response); } - static fromNullable(scope: DispatcherScope, response: Response | null): ResponseDispatcher | undefined { + static fromNullable(scope: BrowserContextDispatcher, response: Response | null): ResponseDispatcher | undefined { return response ? ResponseDispatcher.from(scope, response) : undefined; } - private constructor(scope: DispatcherScope, response: Response) { + private constructor(contextScope: BrowserContextDispatcher, response: Response) { + const scope = parentScopeForRequest(contextScope, response.request()); super(scope, response, 'Response', { // TODO: responses in popups can point to non-reported requests. - request: RequestDispatcher.from(scope, response.request()), + request: RequestDispatcher.from(contextScope, response.request()), url: response.url(), status: response.status(), statusText: response.statusText(), @@ -108,18 +112,18 @@ export class ResponseDispatcher extends Dispatcher implements channels.RouteChannel { +export class RouteDispatcher extends Dispatcher implements channels.RouteChannel { _type_Route = true; - static from(scope: DispatcherScope, route: Route): RouteDispatcher { + static from(scope: RequestDispatcher, route: Route): RouteDispatcher { const result = existingDispatcher(route); return result || new RouteDispatcher(scope, route); } - private constructor(scope: DispatcherScope, route: Route) { + private constructor(scope: RequestDispatcher, route: Route) { super(scope, route, 'Route', { // Context route can point to a non-reported request. - request: RequestDispatcher.from(scope, route.request()) + request: scope }); } @@ -145,11 +149,11 @@ export class RouteDispatcher extends Dispatcher im } } -export class WebSocketDispatcher extends Dispatcher implements channels.WebSocketChannel { +export class WebSocketDispatcher extends Dispatcher implements channels.WebSocketChannel { _type_EventTarget = true; _type_WebSocket = true; - constructor(scope: DispatcherScope, webSocket: WebSocket) { + constructor(scope: PageDispatcher, webSocket: WebSocket) { super(scope, webSocket, 'WebSocket', { url: webSocket.url(), }); @@ -160,21 +164,21 @@ export class WebSocketDispatcher extends Dispatcher implements channels.APIRequestContextChannel { +export class APIRequestContextDispatcher extends Dispatcher implements channels.APIRequestContextChannel { _type_APIRequestContext = true; - static from(scope: DispatcherScope, request: APIRequestContext): APIRequestContextDispatcher { + static from(scope: RootDispatcher | BrowserContextDispatcher, request: APIRequestContext): APIRequestContextDispatcher { const result = existingDispatcher(request); return result || new APIRequestContextDispatcher(scope, request); } - static fromNullable(scope: DispatcherScope, request: APIRequestContext | null): APIRequestContextDispatcher | undefined { + static fromNullable(scope: RootDispatcher | BrowserContextDispatcher, request: APIRequestContext | null): APIRequestContextDispatcher | undefined { return request ? APIRequestContextDispatcher.from(scope, request) : undefined; } - private constructor(parentScope: DispatcherScope, request: APIRequestContext) { + private constructor(parentScope: RootDispatcher | BrowserContextDispatcher, request: APIRequestContext) { // We will reparent these to the context below. - const tracing = TracingDispatcher.from(parentScope, request.tracing()); + const tracing = TracingDispatcher.from(parentScope as any as APIRequestContextDispatcher, request.tracing()); super(parentScope, request, 'APIRequestContext', { tracing, @@ -217,3 +221,11 @@ export class APIRequestContextDispatcher extends Dispatcher implements channels.PageChannel { +export class PageDispatcher extends Dispatcher implements channels.PageChannel { _type_EventTarget = true; _type_Page = true; private _page: Page; - static fromNullable(parentScope: DispatcherScope, page: Page | undefined): PageDispatcher | undefined { + static from(parentScope: BrowserContextDispatcher, page: Page): PageDispatcher { + return PageDispatcher.fromNullable(parentScope, page)!; + } + + static fromNullable(parentScope: BrowserContextDispatcher, page: Page | undefined): PageDispatcher | undefined { if (!page) return undefined; const result = existingDispatcher(page); return result || new PageDispatcher(parentScope, page); } - constructor(parentScope: DispatcherScope, page: Page) { + private constructor(parentScope: BrowserContextDispatcher, page: Page) { // TODO: theoretically, there could be more than one frame already. // If we split pageCreated and pageReady, there should be no main frame during pageCreated. // We will reparent it to the page below using adopt. - const mainFrame = FrameDispatcher.from(parentScope, page.mainFrame()); + const mainFrame = FrameDispatcher.from(parentScope as any as PageDispatcher, page.mainFrame()); super(parentScope, page, 'Page', { mainFrame, @@ -154,7 +159,7 @@ export class PageDispatcher extends Dispatcher imple return; } await this._page.setClientRequestInterceptor((route, request) => { - this._dispatchEvent('route', { route: RouteDispatcher.from(this._scope, route), request: RequestDispatcher.from(this._scope, request) }); + this._dispatchEvent('route', { route: RouteDispatcher.from(RequestDispatcher.from(this.parentScope()!, request), route) }); }); } @@ -291,19 +296,20 @@ export class PageDispatcher extends Dispatcher imple } -export class WorkerDispatcher extends Dispatcher implements channels.WorkerChannel { - static fromNullable(scope: DispatcherScope, worker: Worker | null): WorkerDispatcher | undefined { +export class WorkerDispatcher extends Dispatcher implements channels.WorkerChannel { + _type_Worker = true; + + static fromNullable(scope: PageDispatcher | BrowserContextDispatcher, worker: Worker | null): WorkerDispatcher | undefined { if (!worker) return undefined; const result = existingDispatcher(worker); return result || new WorkerDispatcher(scope, worker); } - _type_Worker = true; - constructor(scope: DispatcherScope, worker: Worker) { + constructor(scope: PageDispatcher | BrowserContextDispatcher, worker: Worker) { super(scope, worker, 'Worker', { url: worker.url() - }); + }, true); this.addObjectListener(Worker.Events.Close, () => this._dispatchEvent('close')); } @@ -316,13 +322,13 @@ export class WorkerDispatcher extends Dispatcher } } -export class BindingCallDispatcher extends Dispatcher<{ guid: string }, channels.BindingCallChannel> implements channels.BindingCallChannel { +export class BindingCallDispatcher extends Dispatcher<{ guid: string }, channels.BindingCallChannel, PageDispatcher | BrowserContextDispatcher> implements channels.BindingCallChannel { _type_BindingCall = true; private _resolve: ((arg: any) => void) | undefined; private _reject: ((error: any) => void) | undefined; private _promise: Promise; - constructor(scope: DispatcherScope, name: string, needsHandle: boolean, source: { context: BrowserContext, page: Page, frame: Frame }, args: any[]) { + constructor(scope: PageDispatcher, name: string, needsHandle: boolean, source: { context: BrowserContext, page: Page, frame: Frame }, args: any[]) { super(scope, { guid: 'bindingCall@' + createGuid() }, 'BindingCall', { frame: lookupDispatcher(source.frame), name, diff --git a/packages/playwright-core/src/server/dispatchers/playwrightDispatcher.ts b/packages/playwright-core/src/server/dispatchers/playwrightDispatcher.ts index 84547ccb4a974..9bdb4f1d87883 100644 --- a/packages/playwright-core/src/server/dispatchers/playwrightDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/playwrightDispatcher.ts @@ -23,7 +23,7 @@ import { SocksProxy } from '../../common/socksProxy'; import type * as types from '../types'; import { AndroidDispatcher } from './androidDispatcher'; import { BrowserTypeDispatcher } from './browserTypeDispatcher'; -import type { DispatcherScope } from './dispatcher'; +import type { RootDispatcher } from './dispatcher'; import { Dispatcher } from './dispatcher'; import { ElectronDispatcher } from './electronDispatcher'; import { LocalUtilsDispatcher } from './localUtilsDispatcher'; @@ -32,11 +32,11 @@ import { SelectorsDispatcher } from './selectorsDispatcher'; import { ConnectedBrowserDispatcher } from './browserDispatcher'; import { createGuid } from '../../utils'; -export class PlaywrightDispatcher extends Dispatcher implements channels.PlaywrightChannel { +export class PlaywrightDispatcher extends Dispatcher implements channels.PlaywrightChannel { _type_Playwright; private _browserDispatcher: ConnectedBrowserDispatcher | undefined; - constructor(scope: DispatcherScope, playwright: Playwright, socksProxy?: SocksProxy, preLaunchedBrowser?: Browser) { + constructor(scope: RootDispatcher, playwright: Playwright, socksProxy?: SocksProxy, preLaunchedBrowser?: Browser) { const descriptors = require('../deviceDescriptors') as types.Devices; const deviceDescriptors = Object.entries(descriptors) .map(([name, descriptor]) => ({ name, descriptor })); @@ -72,11 +72,11 @@ export class PlaywrightDispatcher extends Dispatcher implements channels.SocksSupportChannel { +class SocksSupportDispatcher extends Dispatcher<{ guid: string }, channels.SocksSupportChannel, RootDispatcher> implements channels.SocksSupportChannel { _type_SocksSupport: boolean; private _socksProxy: SocksProxy; - constructor(scope: DispatcherScope, socksProxy: SocksProxy) { + constructor(scope: RootDispatcher, socksProxy: SocksProxy) { super(scope, { guid: 'socksSupport@' + createGuid() }, 'SocksSupport', {}); this._type_SocksSupport = true; this._socksProxy = socksProxy; diff --git a/packages/playwright-core/src/server/dispatchers/selectorsDispatcher.ts b/packages/playwright-core/src/server/dispatchers/selectorsDispatcher.ts index 8aaa381445d23..b77892396ce05 100644 --- a/packages/playwright-core/src/server/dispatchers/selectorsDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/selectorsDispatcher.ts @@ -14,14 +14,15 @@ * limitations under the License. */ -import type { DispatcherScope } from './dispatcher'; +import type { RootDispatcher } from './dispatcher'; import { Dispatcher } from './dispatcher'; import type * as channels from '../../protocol/channels'; import type { Selectors } from '../selectors'; -export class SelectorsDispatcher extends Dispatcher implements channels.SelectorsChannel { +export class SelectorsDispatcher extends Dispatcher implements channels.SelectorsChannel { _type_Selectors = true; - constructor(scope: DispatcherScope, selectors: Selectors) { + + constructor(scope: RootDispatcher, selectors: Selectors) { super(scope, selectors, 'Selectors', {}); } diff --git a/packages/playwright-core/src/server/dispatchers/streamDispatcher.ts b/packages/playwright-core/src/server/dispatchers/streamDispatcher.ts index b186efd692165..0e1fb617ee59f 100644 --- a/packages/playwright-core/src/server/dispatchers/streamDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/streamDispatcher.ts @@ -20,9 +20,10 @@ import { Dispatcher } from './dispatcher'; import type * as stream from 'stream'; import { createGuid } from '../../utils'; -export class StreamDispatcher extends Dispatcher<{ guid: string, stream: stream.Readable }, channels.StreamChannel> implements channels.StreamChannel { +export class StreamDispatcher extends Dispatcher<{ guid: string, stream: stream.Readable }, channels.StreamChannel, DispatcherScope> implements channels.StreamChannel { _type_Stream = true; private _ended: boolean = false; + constructor(scope: DispatcherScope, stream: stream.Readable) { super(scope, { guid: 'stream@' + createGuid(), stream }, 'Stream', {}); // In Node v12.9.0+ we can use readableEnded. diff --git a/packages/playwright-core/src/server/dispatchers/tracingDispatcher.ts b/packages/playwright-core/src/server/dispatchers/tracingDispatcher.ts index 8b5b99a039fd1..07a849b73edf0 100644 --- a/packages/playwright-core/src/server/dispatchers/tracingDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/tracingDispatcher.ts @@ -17,18 +17,19 @@ import type * as channels from '../../protocol/channels'; import type { Tracing } from '../trace/recorder/tracing'; import { ArtifactDispatcher } from './artifactDispatcher'; -import type { DispatcherScope } from './dispatcher'; import { Dispatcher, existingDispatcher } from './dispatcher'; +import type { BrowserContextDispatcher } from './browserContextDispatcher'; +import type { APIRequestContextDispatcher } from './networkDispatchers'; -export class TracingDispatcher extends Dispatcher implements channels.TracingChannel { +export class TracingDispatcher extends Dispatcher implements channels.TracingChannel { _type_Tracing = true; - static from(scope: DispatcherScope, tracing: Tracing): TracingDispatcher { + static from(scope: BrowserContextDispatcher | APIRequestContextDispatcher, tracing: Tracing): TracingDispatcher { const result = existingDispatcher(tracing); return result || new TracingDispatcher(scope, tracing); } - constructor(scope: DispatcherScope, tracing: Tracing) { + constructor(scope: BrowserContextDispatcher | APIRequestContextDispatcher, tracing: Tracing) { super(scope, tracing, 'Tracing', {}, true); } diff --git a/packages/playwright-core/src/server/dispatchers/writableStreamDispatcher.ts b/packages/playwright-core/src/server/dispatchers/writableStreamDispatcher.ts index d48b5fe0d894b..244c0b7a930aa 100644 --- a/packages/playwright-core/src/server/dispatchers/writableStreamDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/writableStreamDispatcher.ts @@ -15,14 +15,14 @@ */ import type * as channels from '../../protocol/channels'; -import type { DispatcherScope } from './dispatcher'; import { Dispatcher } from './dispatcher'; import type * as fs from 'fs'; import { createGuid } from '../../utils'; +import type { BrowserContextDispatcher } from './browserContextDispatcher'; -export class WritableStreamDispatcher extends Dispatcher<{ guid: string, stream: fs.WriteStream }, channels.WritableStreamChannel> implements channels.WritableStreamChannel { +export class WritableStreamDispatcher extends Dispatcher<{ guid: string, stream: fs.WriteStream }, channels.WritableStreamChannel, BrowserContextDispatcher> implements channels.WritableStreamChannel { _type_WritableStream = true; - constructor(scope: DispatcherScope, stream: fs.WriteStream) { + constructor(scope: BrowserContextDispatcher, stream: fs.WriteStream) { super(scope, { guid: 'writableStream@' + createGuid(), stream }, 'WritableStream', {}); } diff --git a/packages/playwright-core/src/server/index.ts b/packages/playwright-core/src/server/index.ts index 5354d86ab3294..3daf49da02871 100644 --- a/packages/playwright-core/src/server/index.ts +++ b/packages/playwright-core/src/server/index.ts @@ -23,7 +23,7 @@ export { installBrowsersForNpmInstall, writeDockerVersion } from './registry'; -export { DispatcherConnection, Root } from './dispatchers/dispatcher'; +export { DispatcherConnection, RootDispatcher } from './dispatchers/dispatcher'; export { PlaywrightDispatcher } from './dispatchers/playwrightDispatcher'; export { createPlaywright } from './playwright'; diff --git a/tests/library/channels.spec.ts b/tests/library/channels.spec.ts index 5f802e20a4d48..b1dac8b6e8492 100644 --- a/tests/library/channels.spec.ts +++ b/tests/library/channels.spec.ts @@ -53,14 +53,14 @@ it('should scope context handles', async ({ browserType, server, expectScopeStat { _guid: 'selectors', objects: [] }, ] }; - await expectScopeState(browser, GOLDEN_PRECONDITION); + expectScopeState(browser, GOLDEN_PRECONDITION); const context = await browser.newContext(); const page = await context.newPage(); // Firefox Beta 96 yields a console warning for the pages that // don't use ` tag. await page.goto(server.PREFIX + '/empty-standard-mode.html'); - await expectScopeState(browser, { + expectScopeState(browser, { _guid: '', objects: [ { _guid: 'android', objects: [] }, @@ -70,11 +70,12 @@ it('should scope context handles', async ({ browserType, server, expectScopeStat { _guid: 'browser', objects: [ { _guid: 'browser-context', objects: [ { _guid: 'page', objects: [ - { _guid: 'frame', objects: [] }, + { _guid: 'frame', objects: [ + { _guid: 'request', objects: [] }, + { _guid: 'response', objects: [] }, + ] }, ] }, - { _guid: 'request', objects: [] }, { _guid: 'request-context', objects: [] }, - { _guid: 'response', objects: [] }, { _guid: 'tracing', objects: [] } ] }, ] }, @@ -87,7 +88,7 @@ it('should scope context handles', async ({ browserType, server, expectScopeStat }); await context.close(); - await expectScopeState(browser, GOLDEN_PRECONDITION); + expectScopeState(browser, GOLDEN_PRECONDITION); await browser.close(); }); @@ -110,10 +111,10 @@ it('should scope CDPSession handles', async ({ browserType, browserName, expectS { _guid: 'selectors', objects: [] }, ] }; - await expectScopeState(browserType, GOLDEN_PRECONDITION); + expectScopeState(browserType, GOLDEN_PRECONDITION); const session = await browser.newBrowserCDPSession(); - await expectScopeState(browserType, { + expectScopeState(browserType, { _guid: '', objects: [ { _guid: 'android', objects: [] }, @@ -132,7 +133,7 @@ it('should scope CDPSession handles', async ({ browserType, browserName, expectS }); await session.detach(); - await expectScopeState(browserType, GOLDEN_PRECONDITION); + expectScopeState(browserType, GOLDEN_PRECONDITION); await browser.close(); }); @@ -151,11 +152,11 @@ it('should scope browser handles', async ({ browserType, expectScopeState }) => { _guid: 'selectors', objects: [] }, ] }; - await expectScopeState(browserType, GOLDEN_PRECONDITION); + expectScopeState(browserType, GOLDEN_PRECONDITION); const browser = await browserType.launch(); await browser.newContext(); - await expectScopeState(browserType, { + expectScopeState(browserType, { _guid: '', objects: [ { _guid: 'android', objects: [] }, @@ -180,7 +181,7 @@ it('should scope browser handles', async ({ browserType, expectScopeState }) => }); await browser.close(); - await expectScopeState(browserType, GOLDEN_PRECONDITION); + expectScopeState(browserType, GOLDEN_PRECONDITION); }); it('should work with the domain module', async ({ browserType, server, browserName }) => {