chore: push dispatcher guid into object, reuse it in trace (#6250)

This commit is contained in:
Pavel Feldman 2021-04-20 23:03:56 -07:00 committed by GitHub
parent 06b0619260
commit 85e2db2416
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 193 additions and 161 deletions

View file

@ -131,7 +131,7 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
return; return;
} }
await this._context._setRequestInterceptor((route, request) => { await this._context._setRequestInterceptor((route, request) => {
this._dispatchEvent('route', { route: new RouteDispatcher(this._scope, route), request: RequestDispatcher.from(this._scope, request) }); this._dispatchEvent('route', { route: RouteDispatcher.from(this._scope, route), request: RequestDispatcher.from(this._scope, request) });
}); });
} }

View file

@ -17,14 +17,14 @@
import { ConsoleMessage } from '../server/console'; import { ConsoleMessage } from '../server/console';
import * as channels from '../protocol/channels'; import * as channels from '../protocol/channels';
import { Dispatcher, DispatcherScope } from './dispatcher'; import { Dispatcher, DispatcherScope } from './dispatcher';
import { createHandle } from './elementHandlerDispatcher'; import { ElementHandleDispatcher } from './elementHandlerDispatcher';
export class ConsoleMessageDispatcher extends Dispatcher<ConsoleMessage, channels.ConsoleMessageInitializer> implements channels.ConsoleMessageChannel { export class ConsoleMessageDispatcher extends Dispatcher<ConsoleMessage, channels.ConsoleMessageInitializer> implements channels.ConsoleMessageChannel {
constructor(scope: DispatcherScope, message: ConsoleMessage) { constructor(scope: DispatcherScope, message: ConsoleMessage) {
super(scope, message, 'ConsoleMessage', { super(scope, message, 'ConsoleMessage', {
type: message.type(), type: message.type(),
text: message.text(), text: message.text(),
args: message.args().map(a => createHandle(scope, a)), args: message.args().map(a => ElementHandleDispatcher.fromJSHandle(scope, a)),
location: message.location(), location: message.location(),
}); });
} }

View file

@ -18,7 +18,7 @@ import { EventEmitter } from 'events';
import * as channels from '../protocol/channels'; import * as channels from '../protocol/channels';
import { serializeError } from '../protocol/serializers'; import { serializeError } from '../protocol/serializers';
import { createScheme, Validator, ValidationError } from '../protocol/validator'; import { createScheme, Validator, ValidationError } from '../protocol/validator';
import { assert, createGuid, debugAssert, isUnderTest, monotonicTime } from '../utils/utils'; import { assert, debugAssert, isUnderTest, monotonicTime } from '../utils/utils';
import { tOptional } from '../protocol/validatorPrimitives'; import { tOptional } from '../protocol/validatorPrimitives';
import { kBrowserOrContextClosedError } from '../utils/errors'; import { kBrowserOrContextClosedError } from '../utils/errors';
import { CallMetadata, SdkObject } from '../server/instrumentation'; import { CallMetadata, SdkObject } from '../server/instrumentation';
@ -41,7 +41,7 @@ export function lookupNullableDispatcher<DispatcherType>(object: any | null): Di
return object ? lookupDispatcher(object) : undefined; return object ? lookupDispatcher(object) : undefined;
} }
export class Dispatcher<Type, Initializer> extends EventEmitter implements channels.Channel { export class Dispatcher<Type extends { guid: string }, Initializer> extends EventEmitter implements channels.Channel {
private _connection: DispatcherConnection; private _connection: DispatcherConnection;
private _isScope: boolean; private _isScope: boolean;
// Parent is always "isScope". // Parent is always "isScope".
@ -55,7 +55,7 @@ export class Dispatcher<Type, Initializer> extends EventEmitter implements chann
readonly _scope: Dispatcher<any, any>; readonly _scope: Dispatcher<any, any>;
_object: Type; _object: Type;
constructor(parent: Dispatcher<any, any> | DispatcherConnection, object: Type, type: string, initializer: Initializer, isScope?: boolean, guid = type + '@' + createGuid()) { constructor(parent: Dispatcher<any, any> | DispatcherConnection, object: Type, type: string, initializer: Initializer, isScope?: boolean) {
super(); super();
this._connection = parent instanceof DispatcherConnection ? parent : parent._connection; this._connection = parent instanceof DispatcherConnection ? parent : parent._connection;
@ -63,6 +63,7 @@ export class Dispatcher<Type, Initializer> extends EventEmitter implements chann
this._parent = parent instanceof DispatcherConnection ? undefined : parent; this._parent = parent instanceof DispatcherConnection ? undefined : parent;
this._scope = isScope ? this : this._parent!; this._scope = isScope ? this : this._parent!;
const guid = object.guid;
assert(!this._connection._dispatchers.has(guid)); assert(!this._connection._dispatchers.has(guid));
this._connection._dispatchers.set(guid, this); this._connection._dispatchers.set(guid, this);
if (this._parent) { if (this._parent) {
@ -120,9 +121,9 @@ export class Dispatcher<Type, Initializer> extends EventEmitter implements chann
} }
export type DispatcherScope = Dispatcher<any, any>; export type DispatcherScope = Dispatcher<any, any>;
class Root extends Dispatcher<{}, {}> { class Root extends Dispatcher<{ guid: '' }, {}> {
constructor(connection: DispatcherConnection) { constructor(connection: DispatcherConnection) {
super(connection, {}, '', {}, true, ''); super(connection, { guid: '' }, '', {}, true);
} }
} }
@ -201,8 +202,8 @@ export class DispatcherConnection {
let callMetadata: CallMetadata = { let callMetadata: CallMetadata = {
id, id,
...validMetadata, ...validMetadata,
pageId: sdkObject?.attribution.page?.uniqueId, pageId: sdkObject?.attribution.page?.guid,
frameId: sdkObject?.attribution.frame?.uniqueId, frameId: sdkObject?.attribution.frame?.guid,
startTime: monotonicTime(), startTime: monotonicTime(),
endTime: 0, endTime: 0,
type: dispatcher._type, type: dispatcher._type,

View file

@ -20,7 +20,7 @@ import * as channels from '../protocol/channels';
import { BrowserContextDispatcher } from './browserContextDispatcher'; import { BrowserContextDispatcher } from './browserContextDispatcher';
import { PageDispatcher } from './pageDispatcher'; import { PageDispatcher } from './pageDispatcher';
import { parseArgument, serializeResult } from './jsHandleDispatcher'; import { parseArgument, serializeResult } from './jsHandleDispatcher';
import { createHandle } from './elementHandlerDispatcher'; import { ElementHandleDispatcher } from './elementHandlerDispatcher';
export class ElectronDispatcher extends Dispatcher<Electron, channels.ElectronInitializer> implements channels.ElectronChannel { export class ElectronDispatcher extends Dispatcher<Electron, channels.ElectronInitializer> implements channels.ElectronChannel {
constructor(scope: DispatcherScope, electron: Electron) { constructor(scope: DispatcherScope, electron: Electron) {
@ -44,7 +44,7 @@ export class ElectronApplicationDispatcher extends Dispatcher<ElectronApplicatio
electronApplication.on(ElectronApplication.Events.Window, (page: ElectronPage) => { electronApplication.on(ElectronApplication.Events.Window, (page: ElectronPage) => {
this._dispatchEvent('window', { this._dispatchEvent('window', {
page: lookupDispatcher<PageDispatcher>(page), page: lookupDispatcher<PageDispatcher>(page),
browserWindow: createHandle(this._scope, page.browserWindow), browserWindow: ElementHandleDispatcher.fromJSHandle(this._scope, page.browserWindow),
}); });
}); });
} }
@ -57,7 +57,7 @@ export class ElectronApplicationDispatcher extends Dispatcher<ElectronApplicatio
async evaluateExpressionHandle(params: channels.ElectronApplicationEvaluateExpressionHandleParams): Promise<channels.ElectronApplicationEvaluateExpressionHandleResult> { async evaluateExpressionHandle(params: channels.ElectronApplicationEvaluateExpressionHandleParams): Promise<channels.ElectronApplicationEvaluateExpressionHandleResult> {
const handle = this._object._nodeElectronHandle!; const handle = this._object._nodeElectronHandle!;
const result = await handle.evaluateExpressionAndWaitForSignals(params.expression, params.isFunction, false /* returnByValue */, parseArgument(params.arg)); const result = await handle.evaluateExpressionAndWaitForSignals(params.expression, params.isFunction, false /* returnByValue */, parseArgument(params.arg));
return { handle: createHandle(this._scope, result) }; return { handle: ElementHandleDispatcher.fromJSHandle(this._scope, result) };
} }
async close(): Promise<void> { async close(): Promise<void> {

View file

@ -17,25 +17,32 @@
import { ElementHandle } from '../server/dom'; import { ElementHandle } from '../server/dom';
import * as js from '../server/javascript'; import * as js from '../server/javascript';
import * as channels from '../protocol/channels'; import * as channels from '../protocol/channels';
import { DispatcherScope, lookupNullableDispatcher } from './dispatcher'; import { DispatcherScope, existingDispatcher, lookupNullableDispatcher } from './dispatcher';
import { JSHandleDispatcher, serializeResult, parseArgument } from './jsHandleDispatcher'; import { JSHandleDispatcher, serializeResult, parseArgument } from './jsHandleDispatcher';
import { FrameDispatcher } from './frameDispatcher'; import { FrameDispatcher } from './frameDispatcher';
import { CallMetadata } from '../server/instrumentation'; import { CallMetadata } from '../server/instrumentation';
export function createHandle(scope: DispatcherScope, handle: js.JSHandle): JSHandleDispatcher {
return handle.asElement() ? new ElementHandleDispatcher(scope, handle.asElement()!) : new JSHandleDispatcher(scope, handle);
}
export class ElementHandleDispatcher extends JSHandleDispatcher implements channels.ElementHandleChannel { export class ElementHandleDispatcher extends JSHandleDispatcher implements channels.ElementHandleChannel {
readonly _elementHandle: ElementHandle; readonly _elementHandle: ElementHandle;
static createNullable(scope: DispatcherScope, handle: ElementHandle | null): ElementHandleDispatcher | undefined { static from(scope: DispatcherScope, handle: ElementHandle): ElementHandleDispatcher {
if (!handle) return existingDispatcher<ElementHandleDispatcher>(handle) || new ElementHandleDispatcher(scope, handle);
return undefined;
return new ElementHandleDispatcher(scope, handle);
} }
constructor(scope: DispatcherScope, elementHandle: ElementHandle) { static fromNullable(scope: DispatcherScope, handle: ElementHandle | null): ElementHandleDispatcher | undefined {
if (!handle)
return undefined;
return existingDispatcher<ElementHandleDispatcher>(handle) || new ElementHandleDispatcher(scope, handle);
}
static fromJSHandle(scope: DispatcherScope, handle: js.JSHandle): JSHandleDispatcher {
const result = existingDispatcher<JSHandleDispatcher>(handle);
if (result)
return result;
return handle.asElement() ? new ElementHandleDispatcher(scope, handle.asElement()!) : new JSHandleDispatcher(scope, handle);
}
private constructor(scope: DispatcherScope, elementHandle: ElementHandle) {
super(scope, elementHandle); super(scope, elementHandle);
this._elementHandle = elementHandle; this._elementHandle = elementHandle;
} }
@ -162,12 +169,12 @@ export class ElementHandleDispatcher extends JSHandleDispatcher implements chann
async querySelector(params: channels.ElementHandleQuerySelectorParams, metadata: CallMetadata): Promise<channels.ElementHandleQuerySelectorResult> { async querySelector(params: channels.ElementHandleQuerySelectorParams, metadata: CallMetadata): Promise<channels.ElementHandleQuerySelectorResult> {
const handle = await this._elementHandle.$(params.selector); const handle = await this._elementHandle.$(params.selector);
return { element: handle ? new ElementHandleDispatcher(this._scope, handle) : undefined }; return { element: ElementHandleDispatcher.fromNullable(this._scope, handle) };
} }
async querySelectorAll(params: channels.ElementHandleQuerySelectorAllParams, metadata: CallMetadata): Promise<channels.ElementHandleQuerySelectorAllResult> { async querySelectorAll(params: channels.ElementHandleQuerySelectorAllParams, metadata: CallMetadata): Promise<channels.ElementHandleQuerySelectorAllResult> {
const elements = await this._elementHandle.$$(params.selector); const elements = await this._elementHandle.$$(params.selector);
return { elements: elements.map(e => new ElementHandleDispatcher(this._scope, e)) }; return { elements: elements.map(e => ElementHandleDispatcher.from(this._scope, e)) };
} }
async evalOnSelector(params: channels.ElementHandleEvalOnSelectorParams, metadata: CallMetadata): Promise<channels.ElementHandleEvalOnSelectorResult> { async evalOnSelector(params: channels.ElementHandleEvalOnSelectorParams, metadata: CallMetadata): Promise<channels.ElementHandleEvalOnSelectorResult> {
@ -183,6 +190,6 @@ export class ElementHandleDispatcher extends JSHandleDispatcher implements chann
} }
async waitForSelector(params: channels.ElementHandleWaitForSelectorParams, metadata: CallMetadata): Promise<channels.ElementHandleWaitForSelectorResult> { async waitForSelector(params: channels.ElementHandleWaitForSelectorParams, metadata: CallMetadata): Promise<channels.ElementHandleWaitForSelectorResult> {
return { element: ElementHandleDispatcher.createNullable(this._scope, await this._elementHandle.waitForSelector(metadata, params.selector, params)) }; return { element: ElementHandleDispatcher.fromNullable(this._scope, await this._elementHandle.waitForSelector(metadata, params.selector, params)) };
} }
} }

View file

@ -17,7 +17,7 @@
import { Frame, NavigationEvent } from '../server/frames'; import { Frame, NavigationEvent } from '../server/frames';
import * as channels from '../protocol/channels'; import * as channels from '../protocol/channels';
import { Dispatcher, DispatcherScope, lookupNullableDispatcher, existingDispatcher } from './dispatcher'; import { Dispatcher, DispatcherScope, lookupNullableDispatcher, existingDispatcher } from './dispatcher';
import { ElementHandleDispatcher, createHandle } from './elementHandlerDispatcher'; import { ElementHandleDispatcher } from './elementHandlerDispatcher';
import { parseArgument, serializeResult } from './jsHandleDispatcher'; import { parseArgument, serializeResult } from './jsHandleDispatcher';
import { ResponseDispatcher, RequestDispatcher } from './networkDispatchers'; import { ResponseDispatcher, RequestDispatcher } from './networkDispatchers';
import { CallMetadata } from '../server/instrumentation'; import { CallMetadata } from '../server/instrumentation';
@ -57,7 +57,7 @@ export class FrameDispatcher extends Dispatcher<Frame, channels.FrameInitializer
} }
async frameElement(): Promise<channels.FrameFrameElementResult> { async frameElement(): Promise<channels.FrameFrameElementResult> {
return { element: new ElementHandleDispatcher(this._scope, await this._frame.frameElement()) }; return { element: ElementHandleDispatcher.from(this._scope, await this._frame.frameElement()) };
} }
async evaluateExpression(params: channels.FrameEvaluateExpressionParams, metadata: CallMetadata): Promise<channels.FrameEvaluateExpressionResult> { async evaluateExpression(params: channels.FrameEvaluateExpressionParams, metadata: CallMetadata): Promise<channels.FrameEvaluateExpressionResult> {
@ -65,11 +65,11 @@ export class FrameDispatcher extends Dispatcher<Frame, channels.FrameInitializer
} }
async evaluateExpressionHandle(params: channels.FrameEvaluateExpressionHandleParams, metadata: CallMetadata): Promise<channels.FrameEvaluateExpressionHandleResult> { async evaluateExpressionHandle(params: channels.FrameEvaluateExpressionHandleParams, metadata: CallMetadata): Promise<channels.FrameEvaluateExpressionHandleResult> {
return { handle: createHandle(this._scope, await this._frame.evaluateExpressionHandleAndWaitForSignals(params.expression, params.isFunction, parseArgument(params.arg), params.world)) }; return { handle: ElementHandleDispatcher.fromJSHandle(this._scope, await this._frame.evaluateExpressionHandleAndWaitForSignals(params.expression, params.isFunction, parseArgument(params.arg), params.world)) };
} }
async waitForSelector(params: channels.FrameWaitForSelectorParams, metadata: CallMetadata): Promise<channels.FrameWaitForSelectorResult> { async waitForSelector(params: channels.FrameWaitForSelectorParams, metadata: CallMetadata): Promise<channels.FrameWaitForSelectorResult> {
return { element: ElementHandleDispatcher.createNullable(this._scope, await this._frame.waitForSelector(metadata, params.selector, params)) }; return { element: ElementHandleDispatcher.fromNullable(this._scope, await this._frame.waitForSelector(metadata, params.selector, params)) };
} }
async dispatchEvent(params: channels.FrameDispatchEventParams, metadata: CallMetadata): Promise<void> { async dispatchEvent(params: channels.FrameDispatchEventParams, metadata: CallMetadata): Promise<void> {
@ -85,12 +85,12 @@ export class FrameDispatcher extends Dispatcher<Frame, channels.FrameInitializer
} }
async querySelector(params: channels.FrameQuerySelectorParams, metadata: CallMetadata): Promise<channels.FrameQuerySelectorResult> { async querySelector(params: channels.FrameQuerySelectorParams, metadata: CallMetadata): Promise<channels.FrameQuerySelectorResult> {
return { element: ElementHandleDispatcher.createNullable(this._scope, await this._frame.$(params.selector)) }; return { element: ElementHandleDispatcher.fromNullable(this._scope, await this._frame.$(params.selector)) };
} }
async querySelectorAll(params: channels.FrameQuerySelectorAllParams, metadata: CallMetadata): Promise<channels.FrameQuerySelectorAllResult> { async querySelectorAll(params: channels.FrameQuerySelectorAllParams, metadata: CallMetadata): Promise<channels.FrameQuerySelectorAllResult> {
const elements = await this._frame.$$(params.selector); const elements = await this._frame.$$(params.selector);
return { elements: elements.map(e => new ElementHandleDispatcher(this._scope, e)) }; return { elements: elements.map(e => ElementHandleDispatcher.from(this._scope, e)) };
} }
async content(): Promise<channels.FrameContentResult> { async content(): Promise<channels.FrameContentResult> {
@ -102,11 +102,11 @@ export class FrameDispatcher extends Dispatcher<Frame, channels.FrameInitializer
} }
async addScriptTag(params: channels.FrameAddScriptTagParams, metadata: CallMetadata): Promise<channels.FrameAddScriptTagResult> { async addScriptTag(params: channels.FrameAddScriptTagParams, metadata: CallMetadata): Promise<channels.FrameAddScriptTagResult> {
return { element: new ElementHandleDispatcher(this._scope, await this._frame.addScriptTag(params)) }; return { element: ElementHandleDispatcher.from(this._scope, await this._frame.addScriptTag(params)) };
} }
async addStyleTag(params: channels.FrameAddStyleTagParams, metadata: CallMetadata): Promise<channels.FrameAddStyleTagResult> { async addStyleTag(params: channels.FrameAddStyleTagParams, metadata: CallMetadata): Promise<channels.FrameAddStyleTagResult> {
return { element: new ElementHandleDispatcher(this._scope, await this._frame.addStyleTag(params)) }; return { element: ElementHandleDispatcher.from(this._scope, await this._frame.addStyleTag(params)) };
} }
async click(params: channels.FrameClickParams, metadata: CallMetadata): Promise<void> { async click(params: channels.FrameClickParams, metadata: CallMetadata): Promise<void> {
@ -201,7 +201,7 @@ export class FrameDispatcher extends Dispatcher<Frame, channels.FrameInitializer
} }
async waitForFunction(params: channels.FrameWaitForFunctionParams, metadata: CallMetadata): Promise<channels.FrameWaitForFunctionResult> { async waitForFunction(params: channels.FrameWaitForFunctionParams, metadata: CallMetadata): Promise<channels.FrameWaitForFunctionResult> {
return { handle: createHandle(this._scope, await this._frame._waitForFunctionExpression(metadata, params.expression, params.isFunction, parseArgument(params.arg), params)) }; return { handle: ElementHandleDispatcher.fromJSHandle(this._scope, await this._frame._waitForFunctionExpression(metadata, params.expression, params.isFunction, parseArgument(params.arg), params)) };
} }
async title(params: channels.FrameTitleParams, metadata: CallMetadata): Promise<channels.FrameTitleResult> { async title(params: channels.FrameTitleParams, metadata: CallMetadata): Promise<channels.FrameTitleResult> {

View file

@ -17,12 +17,12 @@
import * as js from '../server/javascript'; import * as js from '../server/javascript';
import * as channels from '../protocol/channels'; import * as channels from '../protocol/channels';
import { Dispatcher, DispatcherScope } from './dispatcher'; import { Dispatcher, DispatcherScope } from './dispatcher';
import { createHandle } from './elementHandlerDispatcher'; import { ElementHandleDispatcher } from './elementHandlerDispatcher';
import { parseSerializedValue, serializeValue } from '../protocol/serializers'; import { parseSerializedValue, serializeValue } from '../protocol/serializers';
export class JSHandleDispatcher extends Dispatcher<js.JSHandle, channels.JSHandleInitializer> implements channels.JSHandleChannel { export class JSHandleDispatcher extends Dispatcher<js.JSHandle, channels.JSHandleInitializer> implements channels.JSHandleChannel {
constructor(scope: DispatcherScope, jsHandle: js.JSHandle) { protected constructor(scope: DispatcherScope, jsHandle: js.JSHandle) {
// Do not call this directly, use createHandle() instead. // Do not call this directly, use createHandle() instead.
super(scope, jsHandle, jsHandle.asElement() ? 'ElementHandle' : 'JSHandle', { super(scope, jsHandle, jsHandle.asElement() ? 'ElementHandle' : 'JSHandle', {
preview: jsHandle.toString(), preview: jsHandle.toString(),
@ -36,19 +36,19 @@ export class JSHandleDispatcher extends Dispatcher<js.JSHandle, channels.JSHandl
async evaluateExpressionHandle(params: channels.JSHandleEvaluateExpressionHandleParams): Promise<channels.JSHandleEvaluateExpressionHandleResult> { async evaluateExpressionHandle(params: channels.JSHandleEvaluateExpressionHandleParams): Promise<channels.JSHandleEvaluateExpressionHandleResult> {
const jsHandle = await this._object.evaluateExpressionAndWaitForSignals(params.expression, params.isFunction, false /* returnByValue */, parseArgument(params.arg)); const jsHandle = await this._object.evaluateExpressionAndWaitForSignals(params.expression, params.isFunction, false /* returnByValue */, parseArgument(params.arg));
return { handle: createHandle(this._scope, jsHandle) }; return { handle: ElementHandleDispatcher.fromJSHandle(this._scope, jsHandle) };
} }
async getProperty(params: channels.JSHandleGetPropertyParams): Promise<channels.JSHandleGetPropertyResult> { async getProperty(params: channels.JSHandleGetPropertyParams): Promise<channels.JSHandleGetPropertyResult> {
const jsHandle = await this._object.getProperty(params.name); const jsHandle = await this._object.getProperty(params.name);
return { handle: createHandle(this._scope, jsHandle) }; return { handle: ElementHandleDispatcher.fromJSHandle(this._scope, jsHandle) };
} }
async getPropertyList(): Promise<channels.JSHandleGetPropertyListResult> { async getPropertyList(): Promise<channels.JSHandleGetPropertyListResult> {
const map = await this._object.getProperties(); const map = await this._object.getProperties();
const properties = []; const properties = [];
for (const [name, value] of map) for (const [name, value] of map)
properties.push({ name, value: createHandle(this._scope, value) }); properties.push({ name, value: ElementHandleDispatcher.fromJSHandle(this._scope, value) });
return { properties }; return { properties };
} }

View file

@ -51,7 +51,16 @@ export class RequestDispatcher extends Dispatcher<Request, channels.RequestIniti
export class ResponseDispatcher extends Dispatcher<Response, channels.ResponseInitializer> implements channels.ResponseChannel { export class ResponseDispatcher extends Dispatcher<Response, channels.ResponseInitializer> implements channels.ResponseChannel {
constructor(scope: DispatcherScope, response: Response) { static from(scope: DispatcherScope, response: Response): ResponseDispatcher {
const result = existingDispatcher<ResponseDispatcher>(response);
return result || new ResponseDispatcher(scope, response);
}
static fromNullable(scope: DispatcherScope, response: Response | null): ResponseDispatcher | undefined {
return response ? ResponseDispatcher.from(scope, response) : undefined;
}
private constructor(scope: DispatcherScope, response: Response) {
super(scope, response, 'Response', { super(scope, response, 'Response', {
// TODO: responses in popups can point to non-reported requests. // TODO: responses in popups can point to non-reported requests.
request: RequestDispatcher.from(scope, response.request()), request: RequestDispatcher.from(scope, response.request()),
@ -75,7 +84,16 @@ export class ResponseDispatcher extends Dispatcher<Response, channels.ResponseIn
export class RouteDispatcher extends Dispatcher<Route, channels.RouteInitializer> implements channels.RouteChannel { export class RouteDispatcher extends Dispatcher<Route, channels.RouteInitializer> implements channels.RouteChannel {
constructor(scope: DispatcherScope, route: Route) { static from(scope: DispatcherScope, route: Route): RouteDispatcher {
const result = existingDispatcher<RouteDispatcher>(route);
return result || new RouteDispatcher(scope, route);
}
static fromNullable(scope: DispatcherScope, route: Route | null): RouteDispatcher | undefined {
return route ? RouteDispatcher.from(scope, route) : undefined;
}
private constructor(scope: DispatcherScope, route: Route) {
super(scope, route, 'Route', { super(scope, route, 'Route', {
// Context route can point to a non-reported request. // Context route can point to a non-reported request.
request: RequestDispatcher.from(scope, route.request()) request: RequestDispatcher.from(scope, route.request())

View file

@ -26,7 +26,7 @@ import { DialogDispatcher } from './dialogDispatcher';
import { FrameDispatcher } from './frameDispatcher'; import { FrameDispatcher } from './frameDispatcher';
import { RequestDispatcher, ResponseDispatcher, RouteDispatcher, WebSocketDispatcher } from './networkDispatchers'; import { RequestDispatcher, ResponseDispatcher, RouteDispatcher, WebSocketDispatcher } from './networkDispatchers';
import { serializeResult, parseArgument } from './jsHandleDispatcher'; import { serializeResult, parseArgument } from './jsHandleDispatcher';
import { ElementHandleDispatcher, createHandle } from './elementHandlerDispatcher'; import { ElementHandleDispatcher } from './elementHandlerDispatcher';
import { FileChooser } from '../server/fileChooser'; import { FileChooser } from '../server/fileChooser';
import { CRCoverage } from '../server/chromium/crCoverage'; import { CRCoverage } from '../server/chromium/crCoverage';
import { JSHandle } from '../server/javascript'; import { JSHandle } from '../server/javascript';
@ -34,6 +34,7 @@ import { CallMetadata } from '../server/instrumentation';
import { Artifact } from '../server/artifact'; import { Artifact } from '../server/artifact';
import { ArtifactDispatcher } from './artifactDispatcher'; import { ArtifactDispatcher } from './artifactDispatcher';
import { Download } from '../server/download'; import { Download } from '../server/download';
import { createGuid } from '../utils/utils';
export class PageDispatcher extends Dispatcher<Page, channels.PageInitializer> implements channels.PageChannel { export class PageDispatcher extends Dispatcher<Page, channels.PageInitializer> implements channels.PageChannel {
private _page: Page; private _page: Page;
@ -67,7 +68,7 @@ export class PageDispatcher extends Dispatcher<Page, channels.PageInitializer> i
this._dispatchEvent('download', { url: download.url, suggestedFilename: download.suggestedFilename(), artifact: new ArtifactDispatcher(scope, download.artifact) }); this._dispatchEvent('download', { url: download.url, suggestedFilename: download.suggestedFilename(), artifact: new ArtifactDispatcher(scope, download.artifact) });
}); });
this._page.on(Page.Events.FileChooser, (fileChooser: FileChooser) => this._dispatchEvent('fileChooser', { this._page.on(Page.Events.FileChooser, (fileChooser: FileChooser) => this._dispatchEvent('fileChooser', {
element: new ElementHandleDispatcher(this._scope, fileChooser.element()), element: ElementHandleDispatcher.from(this._scope, fileChooser.element()),
isMultiple: fileChooser.isMultiple() isMultiple: fileChooser.isMultiple()
})); }));
page.on(Page.Events.FrameAttached, frame => this._onFrameAttached(frame)); page.on(Page.Events.FrameAttached, frame => this._onFrameAttached(frame));
@ -84,7 +85,7 @@ export class PageDispatcher extends Dispatcher<Page, channels.PageInitializer> i
request: RequestDispatcher.from(scope, request), request: RequestDispatcher.from(scope, request),
responseEndTiming: request._responseEndTiming responseEndTiming: request._responseEndTiming
})); }));
page.on(Page.Events.Response, response => this._dispatchEvent('response', { response: new ResponseDispatcher(this._scope, response) })); page.on(Page.Events.Response, response => this._dispatchEvent('response', { response: ResponseDispatcher.from(this._scope, response) }));
page.on(Page.Events.WebSocket, webSocket => this._dispatchEvent('webSocket', { webSocket: new WebSocketDispatcher(this._scope, webSocket) })); page.on(Page.Events.WebSocket, webSocket => this._dispatchEvent('webSocket', { webSocket: new WebSocketDispatcher(this._scope, webSocket) }));
page.on(Page.Events.Worker, worker => this._dispatchEvent('worker', { worker: new WorkerDispatcher(this._scope, worker) })); page.on(Page.Events.Worker, worker => this._dispatchEvent('worker', { worker: new WorkerDispatcher(this._scope, worker) }));
page.on(Page.Events.Video, (artifact: Artifact) => this._dispatchEvent('video', { artifact: existingDispatcher<ArtifactDispatcher>(artifact) })); page.on(Page.Events.Video, (artifact: Artifact) => this._dispatchEvent('video', { artifact: existingDispatcher<ArtifactDispatcher>(artifact) }));
@ -145,7 +146,7 @@ export class PageDispatcher extends Dispatcher<Page, channels.PageInitializer> i
return; return;
} }
await this._page._setClientRequestInterceptor((route, request) => { await this._page._setClientRequestInterceptor((route, request) => {
this._dispatchEvent('route', { route: new RouteDispatcher(this._scope, route), request: RequestDispatcher.from(this._scope, request) }); this._dispatchEvent('route', { route: RouteDispatcher.from(this._scope, route), request: RequestDispatcher.from(this._scope, request) });
}); });
} }
@ -263,21 +264,21 @@ export class WorkerDispatcher extends Dispatcher<Worker, channels.WorkerInitiali
} }
async evaluateExpressionHandle(params: channels.WorkerEvaluateExpressionHandleParams, metadata: CallMetadata): Promise<channels.WorkerEvaluateExpressionHandleResult> { async evaluateExpressionHandle(params: channels.WorkerEvaluateExpressionHandleParams, metadata: CallMetadata): Promise<channels.WorkerEvaluateExpressionHandleResult> {
return { handle: createHandle(this._scope, await this._object.evaluateExpressionHandle(params.expression, params.isFunction, parseArgument(params.arg))) }; return { handle: ElementHandleDispatcher.fromJSHandle(this._scope, await this._object.evaluateExpressionHandle(params.expression, params.isFunction, parseArgument(params.arg))) };
} }
} }
export class BindingCallDispatcher extends Dispatcher<{}, channels.BindingCallInitializer> implements channels.BindingCallChannel { export class BindingCallDispatcher extends Dispatcher<{ guid: string }, channels.BindingCallInitializer> implements channels.BindingCallChannel {
private _resolve: ((arg: any) => void) | undefined; private _resolve: ((arg: any) => void) | undefined;
private _reject: ((error: any) => void) | undefined; private _reject: ((error: any) => void) | undefined;
private _promise: Promise<any>; private _promise: Promise<any>;
constructor(scope: DispatcherScope, name: string, needsHandle: boolean, source: { context: BrowserContext, page: Page, frame: Frame }, args: any[]) { constructor(scope: DispatcherScope, name: string, needsHandle: boolean, source: { context: BrowserContext, page: Page, frame: Frame }, args: any[]) {
super(scope, {}, 'BindingCall', { super(scope, { guid: createGuid() }, 'BindingCall', {
frame: lookupDispatcher<FrameDispatcher>(source.frame), frame: lookupDispatcher<FrameDispatcher>(source.frame),
name, name,
args: needsHandle ? undefined : args.map(serializeResult), args: needsHandle ? undefined : args.map(serializeResult),
handle: needsHandle ? createHandle(scope, args[0] as JSHandle) : undefined, handle: needsHandle ? ElementHandleDispatcher.fromJSHandle(scope, args[0] as JSHandle) : undefined,
}); });
this._promise = new Promise((resolve, reject) => { this._promise = new Promise((resolve, reject) => {
this._resolve = resolve; this._resolve = resolve;

View file

@ -38,6 +38,6 @@ export class PlaywrightDispatcher extends Dispatcher<Playwright, channels.Playwr
deviceDescriptors, deviceDescriptors,
selectors: customSelectors || new SelectorsDispatcher(scope, playwright.selectors), selectors: customSelectors || new SelectorsDispatcher(scope, playwright.selectors),
preLaunchedBrowser, preLaunchedBrowser,
}, false, 'Playwright'); }, false);
} }
} }

View file

@ -17,18 +17,19 @@
import * as channels from '../protocol/channels'; import * as channels from '../protocol/channels';
import { Dispatcher, DispatcherScope } from './dispatcher'; import { Dispatcher, DispatcherScope } from './dispatcher';
import * as stream from 'stream'; import * as stream from 'stream';
import { createGuid } from '../utils/utils';
export class StreamDispatcher extends Dispatcher<stream.Readable, channels.StreamInitializer> implements channels.StreamChannel { export class StreamDispatcher extends Dispatcher<{ guid: string, stream: stream.Readable }, channels.StreamInitializer> implements channels.StreamChannel {
constructor(scope: DispatcherScope, stream: stream.Readable) { constructor(scope: DispatcherScope, stream: stream.Readable) {
super(scope, stream, 'Stream', {}); super(scope, { guid: createGuid(), stream }, 'Stream', {});
} }
async read(params: channels.StreamReadParams): Promise<channels.StreamReadResult> { async read(params: channels.StreamReadParams): Promise<channels.StreamReadResult> {
const buffer = this._object.read(Math.min(this._object.readableLength, params.size || this._object.readableLength)); const buffer = this._object.stream.read(Math.min(this._object.stream.readableLength, params.size || this._object.stream.readableLength));
return { binary: buffer ? buffer.toString('base64') : '' }; return { binary: buffer ? buffer.toString('base64') : '' };
} }
async close() { async close() {
this._object.destroy(); this._object.stream.destroy();
} }
} }

View file

@ -50,6 +50,7 @@ export interface DeviceBackend {
} }
export interface SocketBackend extends EventEmitter { export interface SocketBackend extends EventEmitter {
guid: string;
write(data: Buffer): Promise<void>; write(data: Buffer): Promise<void>;
close(): void; close(): void;
} }
@ -61,7 +62,7 @@ export class Android extends SdkObject {
readonly _playwrightOptions: PlaywrightOptions; readonly _playwrightOptions: PlaywrightOptions;
constructor(backend: Backend, playwrightOptions: PlaywrightOptions) { constructor(backend: Backend, playwrightOptions: PlaywrightOptions) {
super(playwrightOptions.rootSdkObject); super(playwrightOptions.rootSdkObject, 'android');
this._backend = backend; this._backend = backend;
this._playwrightOptions = playwrightOptions; this._playwrightOptions = playwrightOptions;
this._timeoutSettings = new TimeoutSettings(); this._timeoutSettings = new TimeoutSettings();
@ -115,7 +116,7 @@ export class AndroidDevice extends SdkObject {
private _isClosed = false; private _isClosed = false;
constructor(android: Android, backend: DeviceBackend, model: string) { constructor(android: Android, backend: DeviceBackend, model: string) {
super(android); super(android, 'android-device');
this._android = android; this._android = android;
this._backend = backend; this._backend = backend;
this.model = model; this.model = model;

View file

@ -19,6 +19,7 @@ import debug from 'debug';
import * as net from 'net'; import * as net from 'net';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { Backend, DeviceBackend, SocketBackend } from './android'; import { Backend, DeviceBackend, SocketBackend } from './android';
import { createGuid } from '../../utils/utils';
export class AdbBackend implements Backend { export class AdbBackend implements Backend {
async devices(): Promise<DeviceBackend[]> { async devices(): Promise<DeviceBackend[]> {
@ -99,6 +100,7 @@ function encodeMessage(message: string): Buffer {
} }
class BufferedSocketWrapper extends EventEmitter implements SocketBackend { class BufferedSocketWrapper extends EventEmitter implements SocketBackend {
readonly guid = createGuid();
private _socket: net.Socket; private _socket: net.Socket;
private _buffer = Buffer.from([]); private _buffer = Buffer.from([]);
private _isSocket = false; private _isSocket = false;
@ -149,7 +151,7 @@ class BufferedSocketWrapper extends EventEmitter implements SocketBackend {
await this._connectPromise; await this._connectPromise;
assert(!this._isSocket, 'Can not read by length in socket mode'); assert(!this._isSocket, 'Can not read by length in socket mode');
while (this._buffer.length < length) while (this._buffer.length < length)
await new Promise(f => this._notifyReader = f); await new Promise<void>(f => this._notifyReader = f);
const result = this._buffer.slice(0, length); const result = this._buffer.slice(0, length);
this._buffer = this._buffer.slice(length); this._buffer = this._buffer.slice(length);
debug('pw:adb:recv')(result.toString().substring(0, 100) + '...'); debug('pw:adb:recv')(result.toString().substring(0, 100) + '...');
@ -158,7 +160,7 @@ class BufferedSocketWrapper extends EventEmitter implements SocketBackend {
async readAll(): Promise<Buffer> { async readAll(): Promise<Buffer> {
while (!this._isClosed) while (!this._isClosed)
await new Promise(f => this._notifyReader = f); await new Promise<void>(f => this._notifyReader = f);
return this._buffer; return this._buffer;
} }

View file

@ -16,10 +16,11 @@
import fs from 'fs'; import fs from 'fs';
import * as util from 'util'; import * as util from 'util';
import { SdkObject } from './instrumentation';
type SaveCallback = (localPath: string, error?: string) => Promise<void>; type SaveCallback = (localPath: string, error?: string) => Promise<void>;
export class Artifact { export class Artifact extends SdkObject {
private _localPath: string; private _localPath: string;
private _unaccessibleErrorMessage: string | undefined; private _unaccessibleErrorMessage: string | undefined;
private _finishedCallback: () => void; private _finishedCallback: () => void;
@ -29,7 +30,8 @@ export class Artifact {
private _deleted = false; private _deleted = false;
private _failureError: string | null = null; private _failureError: string | null = null;
constructor(localPath: string, unaccessibleErrorMessage?: string) { constructor(parent: SdkObject, localPath: string, unaccessibleErrorMessage?: string) {
super(parent, 'artifact');
this._localPath = localPath; this._localPath = localPath;
this._unaccessibleErrorMessage = unaccessibleErrorMessage; this._unaccessibleErrorMessage = unaccessibleErrorMessage;
this._finishedCallback = () => {}; this._finishedCallback = () => {};

View file

@ -66,7 +66,7 @@ export abstract class Browser extends SdkObject {
readonly _idToVideo = new Map<string, { context: BrowserContext, artifact: Artifact }>(); readonly _idToVideo = new Map<string, { context: BrowserContext, artifact: Artifact }>();
constructor(options: BrowserOptions) { constructor(options: BrowserOptions) {
super(options.rootSdkObject); super(options.rootSdkObject, 'browser');
this.attribution.browser = this; this.attribution.browser = this;
this.options = options; this.options = options;
} }
@ -97,7 +97,7 @@ export abstract class Browser extends SdkObject {
} }
_videoStarted(context: BrowserContext, videoId: string, path: string, pageOrError: Promise<Page | Error>) { _videoStarted(context: BrowserContext, videoId: string, path: string, pageOrError: Promise<Page | Error>) {
const artifact = new Artifact(path); const artifact = new Artifact(context, path);
this._idToVideo.set(videoId, { context, artifact }); this._idToVideo.set(videoId, { context, artifact });
context.emit(BrowserContext.Events.VideoStarted, artifact); context.emit(BrowserContext.Events.VideoStarted, artifact);
pageOrError.then(page => { pageOrError.then(page => {

View file

@ -53,7 +53,7 @@ export abstract class BrowserContext extends SdkObject {
private _origins = new Set<string>(); private _origins = new Set<string>();
constructor(browser: Browser, options: types.BrowserContextOptions, browserContextId: string | undefined) { constructor(browser: Browser, options: types.BrowserContextOptions, browserContextId: string | undefined) {
super(browser); super(browser, 'browser-context');
this.attribution.context = this; this.attribution.context = this;
this._browser = browser; this._browser = browser;
this._options = options; this._options = options;

View file

@ -44,7 +44,7 @@ export abstract class BrowserType extends SdkObject {
readonly _playwrightOptions: PlaywrightOptions; readonly _playwrightOptions: PlaywrightOptions;
constructor(browserName: registry.BrowserName, playwrightOptions: PlaywrightOptions) { constructor(browserName: registry.BrowserName, playwrightOptions: PlaywrightOptions) {
super(playwrightOptions.rootSdkObject); super(playwrightOptions.rootSdkObject, 'browser-type');
this.attribution.browserType = this; this.attribution.browserType = this;
this._playwrightOptions = playwrightOptions; this._playwrightOptions = playwrightOptions;
this._name = browserName; this._name = browserName;

View file

@ -137,9 +137,11 @@ export class CRSession extends EventEmitter {
off: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this; off: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
removeListener: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this; removeListener: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
once: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this; once: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
readonly guid: string;
constructor(connection: CRConnection, rootSessionId: string, targetType: string, sessionId: string) { constructor(connection: CRConnection, rootSessionId: string, targetType: string, sessionId: string) {
super(); super();
this.guid = `cdp-session@${sessionId}`;
this.setMaxListeners(0); this.setMaxListeners(0);
this._connection = connection; this._connection = connection;
this._rootSessionId = rootSessionId; this._rootSessionId = rootSessionId;

View file

@ -799,7 +799,7 @@ class FrameSession {
lineNumber: lineNumber || 0, lineNumber: lineNumber || 0,
columnNumber: 0, columnNumber: 0,
}; };
this._page.emit(Page.Events.Console, new ConsoleMessage(level, text, [], location)); this._page.emit(Page.Events.Console, new ConsoleMessage(this._page, level, text, [], location));
} }
} }

View file

@ -14,16 +14,18 @@
* limitations under the License. * limitations under the License.
*/ */
import { SdkObject } from './instrumentation';
import * as js from './javascript'; import * as js from './javascript';
import { ConsoleMessageLocation } from './types'; import { ConsoleMessageLocation } from './types';
export class ConsoleMessage { export class ConsoleMessage extends SdkObject {
private _type: string; private _type: string;
private _text?: string; private _text?: string;
private _args: js.JSHandle[]; private _args: js.JSHandle[];
private _location: ConsoleMessageLocation; private _location: ConsoleMessageLocation;
constructor(type: string, text: string | undefined, args: js.JSHandle[], location?: ConsoleMessageLocation) { constructor(parent: SdkObject, type: string, text: string | undefined, args: js.JSHandle[], location?: ConsoleMessageLocation) {
super(parent, 'console-message');
this._type = type; this._type = type;
this._text = text; this._text = text;
this._args = args; this._args = args;

View file

@ -32,7 +32,7 @@ export class Dialog extends SdkObject {
private _defaultValue: string; private _defaultValue: string;
constructor(page: Page, type: string, message: string, onHandle: OnHandle, defaultValue?: string) { constructor(page: Page, type: string, message: string, onHandle: OnHandle, defaultValue?: string) {
super(page); super(page, 'dialog');
this._page = page; this._page = page;
this._type = type; this._type = type;
this._message = message; this._message = message;

View file

@ -27,7 +27,7 @@ export class Download {
constructor(page: Page, downloadsPath: string, uuid: string, url: string, suggestedFilename?: string) { constructor(page: Page, downloadsPath: string, uuid: string, url: string, suggestedFilename?: string) {
const unaccessibleErrorMessage = !page._browserContext._options.acceptDownloads ? 'Pass { acceptDownloads: true } when you are creating your browser context.' : undefined; const unaccessibleErrorMessage = !page._browserContext._options.acceptDownloads ? 'Pass { acceptDownloads: true } when you are creating your browser context.' : undefined;
this.artifact = new Artifact(path.join(downloadsPath, uuid), unaccessibleErrorMessage); this.artifact = new Artifact(page, path.join(downloadsPath, uuid), unaccessibleErrorMessage);
this._page = page; this._page = page;
this.url = url; this.url = url;
this._suggestedFilename = suggestedFilename; this._suggestedFilename = suggestedFilename;

View file

@ -64,7 +64,7 @@ export class ElectronApplication extends SdkObject {
readonly _timeoutSettings = new TimeoutSettings(); readonly _timeoutSettings = new TimeoutSettings();
constructor(parent: SdkObject, browser: CRBrowser, nodeConnection: CRConnection) { constructor(parent: SdkObject, browser: CRBrowser, nodeConnection: CRConnection) {
super(parent); super(parent, 'electron-app');
this._browserContext = browser._defaultContext as CRBrowserContext; this._browserContext = browser._defaultContext as CRBrowserContext;
this._browserContext.on(BrowserContext.Events.Close, () => { this._browserContext.on(BrowserContext.Events.Close, () => {
// Emit application closed after context closed. // Emit application closed after context closed.
@ -122,7 +122,7 @@ export class Electron extends SdkObject {
private _playwrightOptions: PlaywrightOptions; private _playwrightOptions: PlaywrightOptions;
constructor(playwrightOptions: PlaywrightOptions) { constructor(playwrightOptions: PlaywrightOptions) {
super(playwrightOptions.rootSdkObject); super(playwrightOptions.rootSdkObject, 'electron');
this._playwrightOptions = playwrightOptions; this._playwrightOptions = playwrightOptions;
} }

View file

@ -24,7 +24,7 @@ import { Page } from './page';
import * as types from './types'; import * as types from './types';
import { BrowserContext } from './browserContext'; import { BrowserContext } from './browserContext';
import { Progress, ProgressController } from './progress'; import { Progress, ProgressController } from './progress';
import { assert, createGuid, makeWaitForNextTask } from '../utils/utils'; import { assert, makeWaitForNextTask } from '../utils/utils';
import { debugLogger } from '../utils/debugLogger'; import { debugLogger } from '../utils/debugLogger';
import { CallMetadata, internalCallMetadata, SdkObject } from './instrumentation'; import { CallMetadata, internalCallMetadata, SdkObject } from './instrumentation';
import { ElementStateWithoutStable } from './injected/injectedScript'; import { ElementStateWithoutStable } from './injected/injectedScript';
@ -411,11 +411,9 @@ export class Frame extends SdkObject {
private _setContentCounter = 0; private _setContentCounter = 0;
readonly _detachedPromise: Promise<void>; readonly _detachedPromise: Promise<void>;
private _detachedCallback = () => {}; private _detachedCallback = () => {};
readonly uniqueId: string;
constructor(page: Page, id: string, parentFrame: Frame | null) { constructor(page: Page, id: string, parentFrame: Frame | null) {
super(page); super(page, 'frame');
this.uniqueId = parentFrame ? `frame@${createGuid()}` : page.uniqueId;
this.attribution.frame = this; this.attribution.frame = this;
this._id = id; this._id = id;
this._page = page; this._page = page;

View file

@ -16,6 +16,7 @@
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { Point, StackFrame } from '../common/types'; import { Point, StackFrame } from '../common/types';
import { createGuid } from '../utils/utils';
import type { Browser } from './browser'; import type { Browser } from './browser';
import type { BrowserContext } from './browserContext'; import type { BrowserContext } from './browserContext';
import type { BrowserType } from './browserType'; import type { BrowserType } from './browserType';
@ -50,11 +51,13 @@ export type CallMetadata = {
}; };
export class SdkObject extends EventEmitter { export class SdkObject extends EventEmitter {
guid: string;
attribution: Attribution; attribution: Attribution;
instrumentation: Instrumentation; instrumentation: Instrumentation;
protected constructor(parent: SdkObject) { protected constructor(parent: SdkObject, guidPrefix?: string, guid?: string) {
super(); super();
this.guid = guid || `${guidPrefix || ''}@${createGuid()}`;
this.setMaxListeners(0); this.setMaxListeners(0);
this.attribution = { ...parent.attribution }; this.attribution = { ...parent.attribution };
this.instrumentation = parent.instrumentation; this.instrumentation = parent.instrumentation;

View file

@ -56,7 +56,7 @@ export class ExecutionContext extends SdkObject {
private _utilityScriptPromise: Promise<JSHandle> | undefined; private _utilityScriptPromise: Promise<JSHandle> | undefined;
constructor(parent: SdkObject, delegate: ExecutionContextDelegate) { constructor(parent: SdkObject, delegate: ExecutionContextDelegate) {
super(parent); super(parent, 'execution-context');
this._delegate = delegate; this._delegate = delegate;
} }
@ -104,7 +104,7 @@ export class JSHandle<T = any> extends SdkObject {
private _previewCallback: ((preview: string) => void) | undefined; private _previewCallback: ((preview: string) => void) | undefined;
constructor(context: ExecutionContext, type: string, objectId?: ObjectId, value?: any) { constructor(context: ExecutionContext, type: string, objectId?: ObjectId, value?: any) {
super(context); super(context, 'handle');
this._context = context; this._context = context;
this._objectId = objectId; this._objectId = objectId;
this._value = value; this._value = value;

View file

@ -99,7 +99,7 @@ export class Request extends SdkObject {
constructor(routeDelegate: RouteDelegate | null, frame: frames.Frame, redirectedFrom: Request | null, documentId: string | undefined, constructor(routeDelegate: RouteDelegate | null, frame: frames.Frame, redirectedFrom: Request | null, documentId: string | undefined,
url: string, resourceType: string, method: string, postData: Buffer | null, headers: types.HeadersArray) { url: string, resourceType: string, method: string, postData: Buffer | null, headers: types.HeadersArray) {
super(frame); super(frame, 'request');
assert(!url.startsWith('data:'), 'Data urls should not fire requests'); assert(!url.startsWith('data:'), 'Data urls should not fire requests');
assert(!(routeDelegate && redirectedFrom), 'Should not be able to intercept redirects'); assert(!(routeDelegate && redirectedFrom), 'Should not be able to intercept redirects');
this._routeDelegate = routeDelegate; this._routeDelegate = routeDelegate;
@ -210,7 +210,7 @@ export class Route extends SdkObject {
private _handled = false; private _handled = false;
constructor(request: Request, delegate: RouteDelegate) { constructor(request: Request, delegate: RouteDelegate) {
super(request.frame()); super(request.frame(), 'route');
this._request = request; this._request = request;
this._delegate = delegate; this._delegate = delegate;
} }
@ -277,7 +277,7 @@ export class Response extends SdkObject {
private _timing: ResourceTiming; private _timing: ResourceTiming;
constructor(request: Request, status: number, statusText: string, headers: types.HeadersArray, timing: ResourceTiming, getResponseBodyCallback: GetResponseBodyCallback) { constructor(request: Request, status: number, statusText: string, headers: types.HeadersArray, timing: ResourceTiming, getResponseBodyCallback: GetResponseBodyCallback) {
super(request.frame()); super(request.frame(), 'response');
this._request = request; this._request = request;
this._timing = timing; this._timing = timing;
this._status = status; this._status = status;
@ -357,7 +357,7 @@ export class WebSocket extends SdkObject {
}; };
constructor(parent: SdkObject, url: string) { constructor(parent: SdkObject, url: string) {
super(parent); super(parent, 'ws');
this._url = url; this._url = url;
} }

View file

@ -28,7 +28,7 @@ import { ConsoleMessage } from './console';
import * as accessibility from './accessibility'; import * as accessibility from './accessibility';
import { FileChooser } from './fileChooser'; import { FileChooser } from './fileChooser';
import { Progress, ProgressController } from './progress'; import { Progress, ProgressController } from './progress';
import { assert, createGuid, isError } from '../utils/utils'; import { assert, isError } from '../utils/utils';
import { debugLogger } from '../utils/debugLogger'; import { debugLogger } from '../utils/debugLogger';
import { Selectors } from './selectors'; import { Selectors } from './selectors';
import { CallMetadata, SdkObject } from './instrumentation'; import { CallMetadata, SdkObject } from './instrumentation';
@ -145,14 +145,12 @@ export class Page extends SdkObject {
private _serverRequestInterceptor: network.RouteHandler | undefined; private _serverRequestInterceptor: network.RouteHandler | undefined;
_ownedContext: BrowserContext | undefined; _ownedContext: BrowserContext | undefined;
readonly selectors: Selectors; readonly selectors: Selectors;
readonly uniqueId: string;
_pageIsError: Error | undefined; _pageIsError: Error | undefined;
_video: Artifact | null = null; _video: Artifact | null = null;
_opener: Page | undefined; _opener: Page | undefined;
constructor(delegate: PageDelegate, browserContext: BrowserContext) { constructor(delegate: PageDelegate, browserContext: BrowserContext) {
super(browserContext); super(browserContext, 'page');
this.uniqueId = 'page@' + createGuid();
this.attribution.page = this; this.attribution.page = this;
this._delegate = delegate; this._delegate = delegate;
this._closedCallback = () => {}; this._closedCallback = () => {};
@ -296,7 +294,7 @@ export class Page extends SdkObject {
} }
_addConsoleMessage(type: string, args: js.JSHandle[], location: types.ConsoleMessageLocation, text?: string) { _addConsoleMessage(type: string, args: js.JSHandle[], location: types.ConsoleMessageLocation, text?: string) {
const message = new ConsoleMessage(type, text, args, location); const message = new ConsoleMessage(this, type, text, args, location);
const intercepted = this._frameManager.interceptConsoleMessage(message); const intercepted = this._frameManager.interceptConsoleMessage(message);
if (intercepted || !this.listenerCount(Page.Events.Console)) if (intercepted || !this.listenerCount(Page.Events.Console))
args.forEach(arg => arg.dispose()); args.forEach(arg => arg.dispose());
@ -519,7 +517,7 @@ export class Worker extends SdkObject {
_existingExecutionContext: js.ExecutionContext | null = null; _existingExecutionContext: js.ExecutionContext | null = null;
constructor(parent: SdkObject, url: string) { constructor(parent: SdkObject, url: string) {
super(parent); super(parent, 'worker');
this._url = url; this._url = url;
this._executionContextCallback = () => {}; this._executionContextCallback = () => {};
this._executionContextPromise = new Promise(x => this._executionContextCallback = x); this._executionContextPromise = new Promise(x => this._executionContextCallback = x);

View file

@ -46,7 +46,7 @@ export class Playwright extends SdkObject {
listeners.push(new InspectorController()); listeners.push(new InspectorController());
} }
const instrumentation = multiplexInstrumentation(listeners); const instrumentation = multiplexInstrumentation(listeners);
super({ attribution: {}, instrumentation } as any); super({ attribution: {}, instrumentation } as any, undefined, 'Playwright');
this.options = { this.options = {
registry: new Registry(path.join(__dirname, '..', '..')), registry: new Registry(path.join(__dirname, '..', '..')),
rootSdkObject: this, rootSdkObject: this,

View file

@ -19,6 +19,7 @@ import * as frames from './frames';
import * as js from './javascript'; import * as js from './javascript';
import * as types from './types'; import * as types from './types';
import { ParsedSelector, parseSelector } from './common/selectorParser'; import { ParsedSelector, parseSelector } from './common/selectorParser';
import { createGuid } from '../utils/utils';
export type SelectorInfo = { export type SelectorInfo = {
parsed: ParsedSelector, parsed: ParsedSelector,
@ -29,6 +30,7 @@ export type SelectorInfo = {
export class Selectors { export class Selectors {
readonly _builtinEngines: Set<string>; readonly _builtinEngines: Set<string>;
readonly _engines: Map<string, { source: string, contentScript: boolean }>; readonly _engines: Map<string, { source: string, contentScript: boolean }>;
readonly guid = `selectors@${createGuid()}`;
constructor() { constructor() {
// Note: keep in sync with InjectedScript class. // Note: keep in sync with InjectedScript class.

View file

@ -156,10 +156,9 @@ export class SnapshotServer {
response.statusCode = 200; response.statusCode = 200;
response.setHeader('Cache-Control', 'public, max-age=31536000'); response.setHeader('Cache-Control', 'public, max-age=31536000');
response.setHeader('Content-Type', 'application/json'); response.setHeader('Content-Type', 'application/json');
const [ pageId, query ] = request.url!.substring('/snapshot/'.length).split('?'); const [ pageOrFrameId, query ] = request.url!.substring('/snapshot/'.length).split('?');
const parsed: any = querystring.parse(query); const parsed: any = querystring.parse(query);
const snapshot = this._snapshotStorage.snapshotByName(pageOrFrameId, parsed.name);
const snapshot = parsed.name ? this._snapshotStorage.snapshotByName(pageId, parsed.name) : this._snapshotStorage.snapshotByTime(pageId, parsed.time);
const snapshotData: any = snapshot ? snapshot.render() : { html: '' }; const snapshotData: any = snapshot ? snapshot.render() : { html: '' };
response.end(JSON.stringify(snapshotData)); response.end(JSON.stringify(snapshotData));
return true; return true;

View file

@ -25,8 +25,7 @@ export interface SnapshotStorage {
resources(): ResourceSnapshot[]; resources(): ResourceSnapshot[];
resourceContent(sha1: string): Buffer | undefined; resourceContent(sha1: string): Buffer | undefined;
resourceById(resourceId: string): ResourceSnapshot | undefined; resourceById(resourceId: string): ResourceSnapshot | undefined;
snapshotByName(frameId: string, snapshotName: string): SnapshotRenderer | undefined; snapshotByName(pageOrFrameId: string, snapshotName: string): SnapshotRenderer | undefined;
snapshotByTime(frameId: string, timestamp: number): SnapshotRenderer | undefined;
} }
export abstract class BaseSnapshotStorage extends EventEmitter implements SnapshotStorage { export abstract class BaseSnapshotStorage extends EventEmitter implements SnapshotStorage {
@ -64,6 +63,8 @@ export abstract class BaseSnapshotStorage extends EventEmitter implements Snapsh
renderer: [], renderer: [],
}; };
this._frameSnapshots.set(snapshot.frameId, frameSnapshots); this._frameSnapshots.set(snapshot.frameId, frameSnapshots);
if (snapshot.isMainFrame)
this._frameSnapshots.set(snapshot.pageId, frameSnapshots);
} }
frameSnapshots.raw.push(snapshot); frameSnapshots.raw.push(snapshot);
const renderer = new SnapshotRenderer(new Map(this._contextResources), frameSnapshots.raw, frameSnapshots.raw.length - 1); const renderer = new SnapshotRenderer(new Map(this._contextResources), frameSnapshots.raw, frameSnapshots.raw.length - 1);
@ -81,17 +82,9 @@ export abstract class BaseSnapshotStorage extends EventEmitter implements Snapsh
return this._resources.slice(); return this._resources.slice();
} }
snapshotByName(frameId: string, snapshotName: string): SnapshotRenderer | undefined { snapshotByName(pageOrFrameId: string, snapshotName: string): SnapshotRenderer | undefined {
return this._frameSnapshots.get(frameId)?.renderer.find(r => r.snapshotName === snapshotName); const snapshot = this._frameSnapshots.get(pageOrFrameId);
} return snapshot?.renderer.find(r => r.snapshotName === snapshotName);
snapshotByTime(frameId: string, timestamp: number): SnapshotRenderer | undefined {
let result: SnapshotRenderer | undefined = undefined;
for (const snapshot of this._frameSnapshots.get(frameId)?.renderer.values() || []) {
if (timestamp && snapshot.snapshot().timestamp <= timestamp)
result = snapshot;
}
return result;
} }
} }

View file

@ -60,6 +60,7 @@ export type FrameSnapshot = {
html: NodeSnapshot, html: NodeSnapshot,
resourceOverrides: ResourceOverride[], resourceOverrides: ResourceOverride[],
viewport: { width: number, height: number }, viewport: { width: number, height: number },
isMainFrame: boolean,
}; };
export type ContextResources = Map<string, { resourceId: string, frameId: string }[]>; export type ContextResources = Map<string, { resourceId: string, frameId: string }[]>;

View file

@ -61,8 +61,8 @@ export class Snapshotter {
await this._context.exposeBinding(this._snapshotBinding, false, (source, data: SnapshotData) => { await this._context.exposeBinding(this._snapshotBinding, false, (source, data: SnapshotData) => {
const snapshot: FrameSnapshot = { const snapshot: FrameSnapshot = {
snapshotName: data.snapshotName, snapshotName: data.snapshotName,
pageId: source.page.uniqueId, pageId: source.page.guid,
frameId: source.frame.uniqueId, frameId: source.frame.guid,
frameUrl: data.url, frameUrl: data.url,
doctype: data.doctype, doctype: data.doctype,
html: data.html, html: data.html,
@ -71,6 +71,7 @@ export class Snapshotter {
pageTimestamp: data.timestamp, pageTimestamp: data.timestamp,
collectionTime: data.collectionTime, collectionTime: data.collectionTime,
resourceOverrides: [], resourceOverrides: [],
isMainFrame: source.page.mainFrame() === source.frame
}; };
for (const { url, content } of data.resourceOverrides) { for (const { url, content } of data.resourceOverrides) {
if (typeof content === 'string') { if (typeof content === 'string') {
@ -167,8 +168,8 @@ export class Snapshotter {
const body = await response.body().catch(e => debugLogger.log('error', e)); const body = await response.body().catch(e => debugLogger.log('error', e));
const responseSha1 = body ? calculateSha1(body) : 'none'; const responseSha1 = body ? calculateSha1(body) : 'none';
const resource: ResourceSnapshot = { const resource: ResourceSnapshot = {
pageId: page.uniqueId, pageId: page.guid,
frameId: response.frame().uniqueId, frameId: response.frame().guid,
resourceId: 'resource@' + createGuid(), resourceId: 'resource@' + createGuid(),
url, url,
contentType, contentType,
@ -203,7 +204,7 @@ export class Snapshotter {
const context = await parent._mainContext(); const context = await parent._mainContext();
await context?.evaluate(({ snapshotStreamer, frameElement, frameId }) => { await context?.evaluate(({ snapshotStreamer, frameElement, frameId }) => {
(window as any)[snapshotStreamer].markIframe(frameElement, frameId); (window as any)[snapshotStreamer].markIframe(frameElement, frameId);
}, { snapshotStreamer: this._snapshotStreamer, frameElement, frameId: frame.uniqueId }); }, { snapshotStreamer: this._snapshotStreamer, frameElement, frameId: frame.guid });
frameElement.dispose(); frameElement.dispose();
} catch (e) { } catch (e) {
} }

View file

@ -130,7 +130,7 @@ class ContextTracer {
} }
private _onPage(page: Page) { private _onPage(page: Page) {
const pageId = page.uniqueId; const pageId = page.guid;
const event: trace.PageCreatedTraceEvent = { const event: trace.PageCreatedTraceEvent = {
timestamp: monotonicTime(), timestamp: monotonicTime(),
@ -197,7 +197,7 @@ class ContextTracer {
const sha1 = calculateSha1(params.buffer); const sha1 = calculateSha1(params.buffer);
const event: trace.ScreencastFrameTraceEvent = { const event: trace.ScreencastFrameTraceEvent = {
type: 'page-screencast-frame', type: 'page-screencast-frame',
pageId: page.uniqueId, pageId: page.guid,
contextId: this._contextId, contextId: this._contextId,
sha1, sha1,
pageTimestamp: params.timestamp, pageTimestamp: params.timestamp,

View file

@ -23,15 +23,15 @@ it('should scope context handles', async ({browserType, browserOptions, server})
const GOLDEN_PRECONDITION = { const GOLDEN_PRECONDITION = {
_guid: '', _guid: '',
objects: [ objects: [
{ _guid: 'Android', objects: [] }, { _guid: 'android', objects: [] },
{ _guid: 'BrowserType', objects: [] }, { _guid: 'browser-type', objects: [] },
{ _guid: 'BrowserType', objects: [] }, { _guid: 'browser-type', objects: [] },
{ _guid: 'BrowserType', objects: [ { _guid: 'browser-type', objects: [
{ _guid: 'Browser', objects: [] } { _guid: 'browser', objects: [] }
] }, ] },
{ _guid: 'Electron', objects: [] }, { _guid: 'electron', objects: [] },
{ _guid: 'Playwright', objects: [] }, { _guid: 'Playwright', objects: [] },
{ _guid: 'Selectors', objects: [] }, { _guid: 'selectors', objects: [] },
] ]
}; };
await expectScopeState(browser, GOLDEN_PRECONDITION); await expectScopeState(browser, GOLDEN_PRECONDITION);
@ -42,23 +42,23 @@ it('should scope context handles', async ({browserType, browserOptions, server})
await expectScopeState(browser, { await expectScopeState(browser, {
_guid: '', _guid: '',
objects: [ objects: [
{ _guid: 'Android', objects: [] }, { _guid: 'android', objects: [] },
{ _guid: 'BrowserType', objects: [] }, { _guid: 'browser-type', objects: [] },
{ _guid: 'BrowserType', objects: [] }, { _guid: 'browser-type', objects: [] },
{ _guid: 'BrowserType', objects: [ { _guid: 'browser-type', objects: [
{ _guid: 'Browser', objects: [ { _guid: 'browser', objects: [
{ _guid: 'BrowserContext', objects: [ { _guid: 'browser-context', objects: [
{ _guid: 'Frame', objects: [] }, { _guid: 'frame', objects: [] },
{ _guid: 'Page', objects: [ { _guid: 'page', objects: [
{ _guid: 'Request', objects: [] }, { _guid: 'request', objects: [] },
{ _guid: 'Response', objects: [] }, { _guid: 'response', objects: [] },
]}, ]},
]}, ]},
] }, ] },
] }, ] },
{ _guid: 'Electron', objects: [] }, { _guid: 'electron', objects: [] },
{ _guid: 'Playwright', objects: [] }, { _guid: 'Playwright', objects: [] },
{ _guid: 'Selectors', objects: [] }, { _guid: 'selectors', objects: [] },
] ]
}); });
@ -74,15 +74,15 @@ it('should scope CDPSession handles', async ({browserType, browserOptions, brows
const GOLDEN_PRECONDITION = { const GOLDEN_PRECONDITION = {
_guid: '', _guid: '',
objects: [ objects: [
{ _guid: 'Android', objects: [] }, { _guid: 'android', objects: [] },
{ _guid: 'BrowserType', objects: [] }, { _guid: 'browser-type', objects: [] },
{ _guid: 'BrowserType', objects: [] }, { _guid: 'browser-type', objects: [] },
{ _guid: 'BrowserType', objects: [ { _guid: 'browser-type', objects: [
{ _guid: 'Browser', objects: [] } { _guid: 'browser', objects: [] }
] }, ] },
{ _guid: 'Electron', objects: [] }, { _guid: 'electron', objects: [] },
{ _guid: 'Playwright', objects: [] }, { _guid: 'Playwright', objects: [] },
{ _guid: 'Selectors', objects: [] }, { _guid: 'selectors', objects: [] },
] ]
}; };
await expectScopeState(browserType, GOLDEN_PRECONDITION); await expectScopeState(browserType, GOLDEN_PRECONDITION);
@ -91,17 +91,17 @@ it('should scope CDPSession handles', async ({browserType, browserOptions, brows
await expectScopeState(browserType, { await expectScopeState(browserType, {
_guid: '', _guid: '',
objects: [ objects: [
{ _guid: 'Android', objects: [] }, { _guid: 'android', objects: [] },
{ _guid: 'BrowserType', objects: [] }, { _guid: 'browser-type', objects: [] },
{ _guid: 'BrowserType', objects: [] }, { _guid: 'browser-type', objects: [] },
{ _guid: 'BrowserType', objects: [ { _guid: 'browser-type', objects: [
{ _guid: 'Browser', objects: [ { _guid: 'browser', objects: [
{ _guid: 'CDPSession', objects: [] }, { _guid: 'cdp-session', objects: [] },
] }, ] },
] }, ] },
{ _guid: 'Electron', objects: [] }, { _guid: 'electron', objects: [] },
{ _guid: 'Playwright', objects: [] }, { _guid: 'Playwright', objects: [] },
{ _guid: 'Selectors', objects: [] }, { _guid: 'selectors', objects: [] },
] ]
}); });
@ -115,13 +115,13 @@ it('should scope browser handles', async ({browserType, browserOptions}) => {
const GOLDEN_PRECONDITION = { const GOLDEN_PRECONDITION = {
_guid: '', _guid: '',
objects: [ objects: [
{ _guid: 'Android', objects: [] }, { _guid: 'android', objects: [] },
{ _guid: 'BrowserType', objects: [] }, { _guid: 'browser-type', objects: [] },
{ _guid: 'BrowserType', objects: [] }, { _guid: 'browser-type', objects: [] },
{ _guid: 'BrowserType', objects: [] }, { _guid: 'browser-type', objects: [] },
{ _guid: 'Electron', objects: [] }, { _guid: 'electron', objects: [] },
{ _guid: 'Playwright', objects: [] }, { _guid: 'Playwright', objects: [] },
{ _guid: 'Selectors', objects: [] }, { _guid: 'selectors', objects: [] },
] ]
}; };
await expectScopeState(browserType, GOLDEN_PRECONDITION); await expectScopeState(browserType, GOLDEN_PRECONDITION);
@ -131,20 +131,20 @@ it('should scope browser handles', async ({browserType, browserOptions}) => {
await expectScopeState(browserType, { await expectScopeState(browserType, {
_guid: '', _guid: '',
objects: [ objects: [
{ _guid: 'Android', objects: [] }, { _guid: 'android', objects: [] },
{ _guid: 'BrowserType', objects: [] }, { _guid: 'browser-type', objects: [] },
{ _guid: 'BrowserType', objects: [] }, { _guid: 'browser-type', objects: [] },
{ _guid: 'BrowserType', objects: [ { _guid: 'browser-type', objects: [
{ {
_guid: 'Browser', objects: [ _guid: 'browser', objects: [
{ _guid: 'BrowserContext', objects: [] } { _guid: 'browser-context', objects: [] }
] ]
}, },
] ]
}, },
{ _guid: 'Electron', objects: [] }, { _guid: 'electron', objects: [] },
{ _guid: 'Playwright', objects: [] }, { _guid: 'Playwright', objects: [] },
{ _guid: 'Selectors', objects: [] }, { _guid: 'selectors', objects: [] },
] ]
}); });