chore(rpc): strongly-type the initializer, remove __init__ phase (#2729)

This commit is contained in:
Pavel Feldman 2020-06-26 12:28:27 -07:00 committed by GitHub
parent 18d6140d3e
commit 02f7501725
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 256 additions and 292 deletions

View file

@ -28,12 +28,17 @@ export interface BrowserTypeChannel extends Channel {
launch(params: { options?: types.LaunchOptions }): Promise<BrowserChannel>; launch(params: { options?: types.LaunchOptions }): Promise<BrowserChannel>;
launchPersistentContext(params: { userDataDir: string, options?: types.LaunchOptions & types.BrowserContextOptions }): Promise<BrowserContextChannel>; launchPersistentContext(params: { userDataDir: string, options?: types.LaunchOptions & types.BrowserContextOptions }): Promise<BrowserContextChannel>;
} }
export type BrowserTypeInitializer = {
executablePath: string,
name: string
};
export interface BrowserChannel extends Channel { export interface BrowserChannel extends Channel {
close(): Promise<void>; close(): Promise<void>;
newContext(params: { options?: types.BrowserContextOptions }): Promise<BrowserContextChannel>; newContext(params: { options?: types.BrowserContextOptions }): Promise<BrowserContextChannel>;
newPage(params: { options?: types.BrowserContextOptions }): Promise<PageChannel>; newPage(params: { options?: types.BrowserContextOptions }): Promise<PageChannel>;
} }
export type BrowserInitializer = {};
export interface BrowserContextChannel extends Channel { export interface BrowserContextChannel extends Channel {
addCookies(params: { cookies: types.SetNetworkCookieParam[] }): Promise<void>; addCookies(params: { cookies: types.SetNetworkCookieParam[] }): Promise<void>;
@ -54,6 +59,7 @@ export interface BrowserContextChannel extends Channel {
setOffline(params: { offline: boolean }): Promise<void>; setOffline(params: { offline: boolean }): Promise<void>;
waitForEvent(params: { event: string }): Promise<any>; waitForEvent(params: { event: string }): Promise<any>;
} }
export type BrowserContextInitializer = {};
export interface PageChannel extends Channel { export interface PageChannel extends Channel {
on(event: 'bindingCall', callback: (params: BindingCallChannel) => void): this; on(event: 'bindingCall', callback: (params: BindingCallChannel) => void): this;
@ -101,6 +107,10 @@ export interface PageChannel extends Channel {
// A11Y // A11Y
accessibilitySnapshot(params: { options: { interestingOnly?: boolean, root?: ElementHandleChannel } }): Promise<types.SerializedAXNode | null>; accessibilitySnapshot(params: { options: { interestingOnly?: boolean, root?: ElementHandleChannel } }): Promise<types.SerializedAXNode | null>;
} }
export type PageInitializer = {
mainFrame: FrameChannel,
viewportSize: types.Size | null
};
export interface FrameChannel extends Channel { export interface FrameChannel extends Channel {
$$eval(params: { selector: string; expression: string, isFunction: boolean, arg: any }): Promise<any>; $$eval(params: { selector: string; expression: string, isFunction: boolean, arg: any }): Promise<any>;
@ -137,6 +147,11 @@ export interface FrameChannel extends Channel {
waitForNavigation(params: { options: types.WaitForNavigationOptions }): Promise<ResponseChannel | null>; waitForNavigation(params: { options: types.WaitForNavigationOptions }): Promise<ResponseChannel | null>;
waitForSelector(params: { selector: string, options: types.WaitForElementOptions }): Promise<ElementHandleChannel | null>; waitForSelector(params: { selector: string, options: types.WaitForElementOptions }): Promise<ElementHandleChannel | null>;
} }
export type FrameInitializer = {
url: string,
name: string,
parentFrame: FrameChannel | null
};
export interface JSHandleChannel extends Channel { export interface JSHandleChannel extends Channel {
dispose(): Promise<void>; dispose(): Promise<void>;
@ -145,6 +160,9 @@ export interface JSHandleChannel extends Channel {
getPropertyList(): Promise<{ name: string, value: JSHandleChannel}[]>; getPropertyList(): Promise<{ name: string, value: JSHandleChannel}[]>;
jsonValue(): Promise<any>; jsonValue(): Promise<any>;
} }
export type JSHandleInitializer = {
preview: string,
};
export interface ElementHandleChannel extends JSHandleChannel { export interface ElementHandleChannel extends JSHandleChannel {
$$eval(params: { selector: string; expression: string, isFunction: boolean, arg: any }): Promise<any>; $$eval(params: { selector: string; expression: string, isFunction: boolean, arg: any }): Promise<any>;
@ -178,22 +196,53 @@ export interface ElementHandleChannel extends JSHandleChannel {
export interface RequestChannel extends Channel { export interface RequestChannel extends Channel {
response(): Promise<ResponseChannel | null>; response(): Promise<ResponseChannel | null>;
} }
export type RequestInitializer = {
frame: FrameChannel,
url: string,
resourceType: string,
method: string,
postData: string | null,
headers: types.Headers,
isNavigationRequest: boolean,
redirectedFrom: RequestChannel | null,
};
export interface RouteChannel extends Channel { export interface RouteChannel extends Channel {
abort(params: { errorCode: string }): Promise<void>; abort(params: { errorCode: string }): Promise<void>;
continue(params: { overrides: { method?: string, headers?: types.Headers, postData?: string } }): Promise<void>; continue(params: { overrides: { method?: string, headers?: types.Headers, postData?: string } }): Promise<void>;
fulfill(params: { response: types.FulfillResponse & { path?: string } }): Promise<void>; fulfill(params: { response: types.FulfillResponse & { path?: string } }): Promise<void>;
} }
export type RouteInitializer = {
request: RequestChannel,
};
export interface ResponseChannel extends Channel { export interface ResponseChannel extends Channel {
body(): Promise<Buffer>; body(): Promise<Buffer>;
finished(): Promise<Error | null>; finished(): Promise<Error | null>;
} }
export type ResponseInitializer = {
request: RequestChannel,
url: string,
status: number,
statusText: string,
headers: types.Headers,
};
export interface ConsoleMessageChannel extends Channel { export interface ConsoleMessageChannel extends Channel {
} }
export type ConsoleMessageInitializer = {
type: string,
text: string,
args: JSHandleChannel[],
location: types.ConsoleMessageLocation,
};
export interface BindingCallChannel extends Channel { export interface BindingCallChannel extends Channel {
reject(params: { error: types.Error }): void; reject(params: { error: types.Error }): void;
resolve(params: { result: any }): void; resolve(params: { result: any }): void;
} }
export type BindingCallInitializer = {
frame: FrameChannel,
name: string,
args: any[]
};

View file

@ -15,13 +15,13 @@
*/ */
import * as types from '../../types'; import * as types from '../../types';
import { BrowserChannel } from '../channels'; import { BrowserChannel, BrowserInitializer } from '../channels';
import { BrowserContext } from './browserContext'; import { BrowserContext } from './browserContext';
import { Page } from './page'; import { Page } from './page';
import { ChannelOwner } from './channelOwner'; import { ChannelOwner } from './channelOwner';
import { Connection } from '../connection'; import { Connection } from '../connection';
export class Browser extends ChannelOwner<BrowserChannel> { export class Browser extends ChannelOwner<BrowserChannel, BrowserInitializer> {
readonly _contexts = new Set<BrowserContext>(); readonly _contexts = new Set<BrowserContext>();
private _isConnected = true; private _isConnected = true;
@ -34,12 +34,10 @@ export class Browser extends ChannelOwner<BrowserChannel> {
return browser ? Browser.from(browser) : null; return browser ? Browser.from(browser) : null;
} }
constructor(connection: Connection, channel: BrowserChannel) { constructor(connection: Connection, channel: BrowserChannel, initializer: BrowserInitializer) {
super(connection, channel); super(connection, channel, initializer);
} }
_initialize() {}
async newContext(options?: types.BrowserContextOptions): Promise<BrowserContext> { async newContext(options?: types.BrowserContextOptions): Promise<BrowserContext> {
const context = BrowserContext.from(await this._channel.newContext({ options })); const context = BrowserContext.from(await this._channel.newContext({ options }));
this._contexts.add(context); this._contexts.add(context);

View file

@ -19,13 +19,14 @@ import * as frames from './frame';
import { Page, BindingCall, waitForEvent } from './page'; import { Page, BindingCall, waitForEvent } from './page';
import * as types from '../../types'; import * as types from '../../types';
import * as network from './network'; import * as network from './network';
import { BrowserContextChannel } from '../channels'; import { BrowserContextChannel, BrowserContextInitializer } from '../channels';
import { ChannelOwner } from './channelOwner'; import { ChannelOwner } from './channelOwner';
import { helper } from '../../helper'; import { helper } from '../../helper';
import { Browser } from './browser'; import { Browser } from './browser';
import { Connection } from '../connection'; import { Connection } from '../connection';
import { Events } from '../../events';
export class BrowserContext extends ChannelOwner<BrowserContextChannel> { export class BrowserContext extends ChannelOwner<BrowserContextChannel, BrowserContextInitializer> {
_pages = new Set<Page>(); _pages = new Set<Page>();
private _routes: { url: types.URLMatch, handler: network.RouteHandler }[] = []; private _routes: { url: types.URLMatch, handler: network.RouteHandler }[] = [];
_browser: Browser | undefined; _browser: Browser | undefined;
@ -39,11 +40,16 @@ export class BrowserContext extends ChannelOwner<BrowserContextChannel> {
return context ? BrowserContext.from(context) : null; return context ? BrowserContext.from(context) : null;
} }
constructor(connection: Connection, channel: BrowserContextChannel) { constructor(connection: Connection, channel: BrowserContextChannel, initializer: BrowserContextInitializer) {
super(connection, channel); super(connection, channel, initializer);
channel.on('page', page => this._onPage(Page.from(page)));
} }
_initialize() {} private _onPage(page: Page): void {
page._browserContext = this;
this._pages.add(page);
this.emit(Events.BrowserContext.Page, page);
}
_onRoute(route: network.Route, request: network.Request) { _onRoute(route: network.Route, request: network.Request) {
for (const {url, handler} of this._routes) { for (const {url, handler} of this._routes) {
@ -56,7 +62,7 @@ export class BrowserContext extends ChannelOwner<BrowserContextChannel> {
} }
async _onBinding(bindingCall: BindingCall) { async _onBinding(bindingCall: BindingCall) {
const func = this._bindings.get(bindingCall.name); const func = this._bindings.get(bindingCall._initializer.name);
if (!func) if (!func)
return; return;
bindingCall.call(func); bindingCall.call(func);
@ -157,5 +163,6 @@ export class BrowserContext extends ChannelOwner<BrowserContextChannel> {
async close(): Promise<void> { async close(): Promise<void> {
await this._channel.close(); await this._channel.close();
this._browser!._contexts.delete(this); this._browser!._contexts.delete(this);
this.emit(Events.BrowserContext.Close);
} }
} }

View file

@ -15,31 +15,23 @@
*/ */
import * as types from '../../types'; import * as types from '../../types';
import { BrowserTypeChannel } from '../channels'; import { BrowserTypeChannel, BrowserTypeInitializer } from '../channels';
import { Browser } from './browser'; import { Browser } from './browser';
import { BrowserContext } from './browserContext'; import { BrowserContext } from './browserContext';
import { ChannelOwner } from './channelOwner'; import { ChannelOwner } from './channelOwner';
import { Connection } from '../connection'; import { Connection } from '../connection';
export class BrowserType extends ChannelOwner<BrowserTypeChannel> { export class BrowserType extends ChannelOwner<BrowserTypeChannel, BrowserTypeInitializer> {
private _executablePath: string = ''; constructor(connection: Connection, channel: BrowserTypeChannel, initializer: BrowserTypeInitializer) {
private _name: string = ''; super(connection, channel, initializer);
constructor(connection: Connection, channel: BrowserTypeChannel) {
super(connection, channel);
}
_initialize(payload: { executablePath: string, name: string }) {
this._executablePath = payload.executablePath;
this._name = payload.name;
} }
executablePath(): string { executablePath(): string {
return this._executablePath; return this._initializer.executablePath;
} }
name(): string { name(): string {
return this._name; return this._initializer.name;
} }
async launch(options?: types.LaunchOptions): Promise<Browser> { async launch(options?: types.LaunchOptions): Promise<Browser> {

View file

@ -18,17 +18,17 @@ import { EventEmitter } from 'events';
import { Channel } from '../channels'; import { Channel } from '../channels';
import { Connection } from '../connection'; import { Connection } from '../connection';
export abstract class ChannelOwner<T extends Channel> extends EventEmitter { export abstract class ChannelOwner<T extends Channel, Initializer> extends EventEmitter {
readonly _channel: T; readonly _channel: T;
readonly _initializer: Initializer;
readonly _connection: Connection; readonly _connection: Connection;
static clientSymbol = Symbol('client'); static clientSymbol = Symbol('client');
constructor(connection: Connection, channel: T) { constructor(connection: Connection, channel: T, initializer: Initializer) {
super(); super();
this._connection = connection; this._connection = connection;
this._channel = channel; this._channel = channel;
this._initializer = initializer;
(channel as any)[ChannelOwner.clientSymbol] = this; (channel as any)[ChannelOwner.clientSymbol] = this;
} }
abstract _initialize(payload: any): void;
} }

View file

@ -17,45 +17,33 @@
import * as util from 'util'; import * as util from 'util';
import { ConsoleMessageLocation } from '../../types'; import { ConsoleMessageLocation } from '../../types';
import { JSHandle } from './jsHandle'; import { JSHandle } from './jsHandle';
import { ConsoleMessageChannel, JSHandleChannel } from '../channels'; import { ConsoleMessageChannel, ConsoleMessageInitializer } from '../channels';
import { ChannelOwner } from './channelOwner'; import { ChannelOwner } from './channelOwner';
import { Connection } from '../connection'; import { Connection } from '../connection';
export class ConsoleMessage extends ChannelOwner<ConsoleMessageChannel> { export class ConsoleMessage extends ChannelOwner<ConsoleMessageChannel, ConsoleMessageInitializer> {
private _type: string = '';
private _text: string = '';
private _args: JSHandle[] = [];
private _location: ConsoleMessageLocation = {};
static from(request: ConsoleMessageChannel): ConsoleMessage { static from(request: ConsoleMessageChannel): ConsoleMessage {
return request._object; return request._object;
} }
constructor(connection: Connection, channel: ConsoleMessageChannel) { constructor(connection: Connection, channel: ConsoleMessageChannel, initializer: ConsoleMessageInitializer) {
super(connection, channel); super(connection, channel, initializer);
}
_initialize(params: { type: string, text: string, args: JSHandleChannel[], location: ConsoleMessageLocation }) {
this._type = params.type;
this._text = params.text;
this._args = params.args.map(JSHandle.from);
this._location = params.location;
} }
type(): string { type(): string {
return this._type; return this._initializer.type;
} }
text(): string { text(): string {
return this._text; return this._initializer.text;
} }
args(): JSHandle[] { args(): JSHandle[] {
return this._args; return this._initializer.args.map(JSHandle.from);
} }
location(): ConsoleMessageLocation { location(): ConsoleMessageLocation {
return this._location; return this._initializer.location;
} }
[util.inspect.custom]() { [util.inspect.custom]() {

View file

@ -15,7 +15,7 @@
*/ */
import * as types from '../../types'; import * as types from '../../types';
import { ElementHandleChannel } from '../channels'; import { ElementHandleChannel, JSHandleInitializer } from '../channels';
import { Frame } from './frame'; import { Frame } from './frame';
import { FuncOn, JSHandle, convertArg } from './jsHandle'; import { FuncOn, JSHandle, convertArg } from './jsHandle';
import { Connection } from '../connection'; import { Connection } from '../connection';
@ -31,8 +31,8 @@ export class ElementHandle<T extends Node = Node> extends JSHandle<T> {
return handle ? ElementHandle.from(handle) : null; return handle ? ElementHandle.from(handle) : null;
} }
constructor(connection: Connection, channel: ElementHandleChannel) { constructor(connection: Connection, channel: ElementHandleChannel, initializer: JSHandleInitializer) {
super(connection, channel); super(connection, channel, initializer);
this._elementChannel = channel; this._elementChannel = channel;
} }

View file

@ -17,7 +17,7 @@
import { assertMaxArguments } from '../../helper'; import { assertMaxArguments } from '../../helper';
import * as types from '../../types'; import * as types from '../../types';
import { FrameChannel } from '../channels'; import { FrameChannel, FrameInitializer } from '../channels';
import { BrowserContext } from './browserContext'; import { BrowserContext } from './browserContext';
import { ChannelOwner } from './channelOwner'; import { ChannelOwner } from './channelOwner';
import { ElementHandle, convertSelectOptionValues } from './elementHandle'; import { ElementHandle, convertSelectOptionValues } from './elementHandle';
@ -33,12 +33,13 @@ export type GotoOptions = types.NavigateOptions & {
export type FunctionWithSource = (source: { context: BrowserContext, page: Page, frame: Frame }, ...args: any) => any; export type FunctionWithSource = (source: { context: BrowserContext, page: Page, frame: Frame }, ...args: any) => any;
export class Frame extends ChannelOwner<FrameChannel> { export class Frame extends ChannelOwner<FrameChannel, FrameInitializer> {
_parentFrame: Frame | null = null; _parentFrame: Frame | null = null;
_url = ''; _url = '';
_name = ''; _name = '';
private _detached = false; private _detached = false;
_childFrames = new Set<Frame>(); _childFrames = new Set<Frame>();
_page: Page | undefined;
static from(frame: FrameChannel): Frame { static from(frame: FrameChannel): Frame {
return frame._object; return frame._object;
@ -48,16 +49,13 @@ export class Frame extends ChannelOwner<FrameChannel> {
return frame ? Frame.from(frame) : null; return frame ? Frame.from(frame) : null;
} }
constructor(connection: Connection, channel: FrameChannel) { constructor(connection: Connection, channel: FrameChannel, initializer: FrameInitializer) {
super(connection, channel); super(connection, channel, initializer);
} this._parentFrame = Frame.fromNullable(initializer.parentFrame);
_initialize(params: { name: string, url: string, parentFrame: FrameChannel | null }) {
this._parentFrame = params.parentFrame ? params.parentFrame._object : null;
if (this._parentFrame) if (this._parentFrame)
this._parentFrame._childFrames.add(this); this._parentFrame._childFrames.add(this);
this._name = params.name; this._name = initializer.name;
this._url = params.url; this._url = initializer.url;
} }
async goto(url: string, options: GotoOptions = {}): Promise<network.Response | null> { async goto(url: string, options: GotoOptions = {}): Promise<network.Response | null> {

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { JSHandleChannel } from '../channels'; import { JSHandleChannel, JSHandleInitializer } from '../channels';
import { ElementHandle } from './elementHandle'; import { ElementHandle } from './elementHandle';
import { ChannelOwner } from './channelOwner'; import { ChannelOwner } from './channelOwner';
import { Connection } from '../connection'; import { Connection } from '../connection';
@ -35,10 +35,7 @@ export type Func1<Arg, R> = string | ((arg: Unboxed<Arg>) => R | Promise<R>);
export type FuncOn<On, Arg2, R> = string | ((on: On, arg2: Unboxed<Arg2>) => R | Promise<R>); export type FuncOn<On, Arg2, R> = string | ((on: On, arg2: Unboxed<Arg2>) => R | Promise<R>);
export type SmartHandle<T> = T extends Node ? ElementHandle<T> : JSHandle<T>; export type SmartHandle<T> = T extends Node ? ElementHandle<T> : JSHandle<T>;
export class JSHandle<T = any> extends ChannelOwner<JSHandleChannel> { export class JSHandle<T = any> extends ChannelOwner<JSHandleChannel, JSHandleInitializer> {
protected _jsChannel: JSHandleChannel;
private _preview: string | undefined;
static from(handle: JSHandleChannel): JSHandle { static from(handle: JSHandleChannel): JSHandle {
return handle._object; return handle._object;
} }
@ -47,25 +44,20 @@ export class JSHandle<T = any> extends ChannelOwner<JSHandleChannel> {
return handle ? JSHandle.from(handle) : null; return handle ? JSHandle.from(handle) : null;
} }
constructor(conection: Connection, channel: JSHandleChannel) { constructor(conection: Connection, channel: JSHandleChannel, initializer: JSHandleInitializer) {
super(conection, channel); super(conection, channel, initializer);
this._jsChannel = channel;
}
_initialize(params: { preview: string }) {
this._preview = params.preview;
} }
async evaluate<R, Arg>(pageFunction: FuncOn<T, Arg, R>, arg: Arg): Promise<R>; async evaluate<R, Arg>(pageFunction: FuncOn<T, Arg, R>, arg: Arg): Promise<R>;
async evaluate<R>(pageFunction: FuncOn<T, void, R>, arg?: any): Promise<R>; async evaluate<R>(pageFunction: FuncOn<T, void, R>, arg?: any): Promise<R>;
async evaluate<R, Arg>(pageFunction: FuncOn<T, Arg, R>, arg: Arg): Promise<R> { async evaluate<R, Arg>(pageFunction: FuncOn<T, Arg, R>, arg: Arg): Promise<R> {
return await this._jsChannel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: convertArg(arg) }); return await this._channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: convertArg(arg) });
} }
async evaluateHandle<R, Arg>(pageFunction: FuncOn<T, Arg, R>, arg: Arg): Promise<SmartHandle<R>>; async evaluateHandle<R, Arg>(pageFunction: FuncOn<T, Arg, R>, arg: Arg): Promise<SmartHandle<R>>;
async evaluateHandle<R>(pageFunction: FuncOn<T, void, R>, arg?: any): Promise<SmartHandle<R>>; async evaluateHandle<R>(pageFunction: FuncOn<T, void, R>, arg?: any): Promise<SmartHandle<R>>;
async evaluateHandle<R, Arg>(pageFunction: FuncOn<T, Arg, R>, arg: Arg): Promise<SmartHandle<R>> { async evaluateHandle<R, Arg>(pageFunction: FuncOn<T, Arg, R>, arg: Arg): Promise<SmartHandle<R>> {
const handleChannel = await this._jsChannel.evaluateExpressionHandle({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: convertArg(arg) }); const handleChannel = await this._channel.evaluateExpressionHandle({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: convertArg(arg) });
return JSHandle.from(handleChannel) as SmartHandle<R>; return JSHandle.from(handleChannel) as SmartHandle<R>;
} }
@ -83,13 +75,13 @@ export class JSHandle<T = any> extends ChannelOwner<JSHandleChannel> {
async getProperties(): Promise<Map<string, JSHandle>> { async getProperties(): Promise<Map<string, JSHandle>> {
const map = new Map<string, JSHandle>(); const map = new Map<string, JSHandle>();
for (const { name, value } of await this._jsChannel.getPropertyList()) for (const { name, value } of await this._channel.getPropertyList())
map.set(name, JSHandle.from(value)); map.set(name, JSHandle.from(value));
return map; return map;
} }
async jsonValue(): Promise<T> { async jsonValue(): Promise<T> {
return await this._jsChannel.jsonValue(); return await this._channel.jsonValue();
} }
asElement(): ElementHandle | null { asElement(): ElementHandle | null {
@ -97,11 +89,11 @@ export class JSHandle<T = any> extends ChannelOwner<JSHandleChannel> {
} }
async dispose() { async dispose() {
return await this._jsChannel.dispose(); return await this._channel.dispose();
} }
toString(): string { toString(): string {
return this._preview!; return this._initializer.preview;
} }
} }

View file

@ -16,7 +16,7 @@
import { URLSearchParams } from 'url'; import { URLSearchParams } from 'url';
import * as types from '../../types'; import * as types from '../../types';
import { RequestChannel, ResponseChannel, FrameChannel, RouteChannel } from '../channels'; import { RequestChannel, ResponseChannel, RouteChannel, RequestInitializer, ResponseInitializer, RouteInitializer } from '../channels';
import { ChannelOwner } from './channelOwner'; import { ChannelOwner } from './channelOwner';
import { Frame } from './frame'; import { Frame } from './frame';
import { Connection } from '../connection'; import { Connection } from '../connection';
@ -44,17 +44,10 @@ export type SetNetworkCookieParam = {
sameSite?: 'Strict' | 'Lax' | 'None' sameSite?: 'Strict' | 'Lax' | 'None'
}; };
export class Request extends ChannelOwner<RequestChannel> { export class Request extends ChannelOwner<RequestChannel, RequestInitializer> {
private _redirectedFrom: Request | null = null; private _redirectedFrom: Request | null = null;
private _redirectedTo: Request | null = null; private _redirectedTo: Request | null = null;
private _isNavigationRequest = false;
_failureText: string | null = null; _failureText: string | null = null;
private _url: string = '';
private _resourceType = '';
private _method = '';
private _postData: string | null = null;
private _headers: types.Headers = {};
private _frame: Frame | undefined;
static from(request: RequestChannel): Request { static from(request: RequestChannel): Request {
return request._object; return request._object;
@ -64,42 +57,31 @@ export class Request extends ChannelOwner<RequestChannel> {
return request ? Request.from(request) : null; return request ? Request.from(request) : null;
} }
constructor(connection: Connection, channel: RequestChannel) { constructor(connection: Connection, channel: RequestChannel, initializer: RequestInitializer) {
super(connection, channel); super(connection, channel, initializer);
} this._redirectedFrom = Request.fromNullable(initializer.redirectedFrom);
_initialize(payload: { frame: FrameChannel, redirectedFrom: RequestChannel | null, isNavigationRequest: boolean,
url: string, resourceType: string, method: string, postData: string | null, headers: types.Headers }) {
this._frame = payload.frame._object as Frame;
this._isNavigationRequest = payload.isNavigationRequest;
this._redirectedFrom = Request.fromNullable(payload.redirectedFrom);
if (this._redirectedFrom) if (this._redirectedFrom)
this._redirectedFrom._redirectedTo = this; this._redirectedFrom._redirectedTo = this;
this._url = payload.url;
this._resourceType = payload.resourceType;
this._method = payload.method;
this._postData = payload.postData;
this._headers = payload.headers;
} }
url(): string { url(): string {
return this._url; return this._initializer.url;
} }
resourceType(): string { resourceType(): string {
return this._resourceType; return this._initializer.resourceType;
} }
method(): string { method(): string {
return this._method; return this._initializer.method;
} }
postData(): string | null { postData(): string | null {
return this._postData; return this._initializer.postData;
} }
postDataJSON(): Object | null { postDataJSON(): Object | null {
if (!this._postData) if (!this._initializer.postData)
return null; return null;
const contentType = this.headers()['content-type']; const contentType = this.headers()['content-type'];
@ -108,17 +90,17 @@ export class Request extends ChannelOwner<RequestChannel> {
if (contentType === 'application/x-www-form-urlencoded') { if (contentType === 'application/x-www-form-urlencoded') {
const entries: Record<string, string> = {}; const entries: Record<string, string> = {};
const parsed = new URLSearchParams(this._postData); const parsed = new URLSearchParams(this._initializer.postData);
for (const [k, v] of parsed.entries()) for (const [k, v] of parsed.entries())
entries[k] = v; entries[k] = v;
return entries; return entries;
} }
return JSON.parse(this._postData); return JSON.parse(this._initializer.postData);
} }
headers(): {[key: string]: string} { headers(): {[key: string]: string} {
return { ...this._headers }; return { ...this._initializer.headers };
} }
async response(): Promise<Response | null> { async response(): Promise<Response | null> {
@ -126,11 +108,11 @@ export class Request extends ChannelOwner<RequestChannel> {
} }
frame(): Frame { frame(): Frame {
return this._frame!; return Frame.from(this._initializer.frame);
} }
isNavigationRequest(): boolean { isNavigationRequest(): boolean {
return this._isNavigationRequest; return this._initializer.isNavigationRequest;
} }
redirectedFrom(): Request | null { redirectedFrom(): Request | null {
@ -150,23 +132,17 @@ export class Request extends ChannelOwner<RequestChannel> {
} }
} }
export class Route extends ChannelOwner<RouteChannel> { export class Route extends ChannelOwner<RouteChannel, RouteInitializer> {
private _request: Request | undefined;
static from(route: RouteChannel): Route { static from(route: RouteChannel): Route {
return route._object; return route._object;
} }
constructor(connection: Connection, channel: RouteChannel) { constructor(connection: Connection, channel: RouteChannel, initializer: RouteInitializer) {
super(connection, channel); super(connection, channel, initializer);
}
_initialize(params: { request: RequestChannel }) {
this._request = Request.from(params.request);
} }
request(): Request { request(): Request {
return this._request!; return Request.from(this._initializer.request);
} }
async abort(errorCode: string = 'failed') { async abort(errorCode: string = 'failed') {
@ -184,13 +160,7 @@ export class Route extends ChannelOwner<RouteChannel> {
export type RouteHandler = (route: Route, request: Request) => void; export type RouteHandler = (route: Route, request: Request) => void;
export class Response extends ChannelOwner<ResponseChannel> { export class Response extends ChannelOwner<ResponseChannel, ResponseInitializer> {
private _request: Request | undefined;
private _status: number = 0;
private _statusText: string = '';
private _url: string = '';
private _headers: types.Headers = {};
static from(response: ResponseChannel): Response { static from(response: ResponseChannel): Response {
return response._object; return response._object;
} }
@ -199,36 +169,28 @@ export class Response extends ChannelOwner<ResponseChannel> {
return response ? Response.from(response) : null; return response ? Response.from(response) : null;
} }
constructor(connection: Connection, channel: ResponseChannel) { constructor(connection: Connection, channel: ResponseChannel, initializer: ResponseInitializer) {
super(connection, channel); super(connection, channel, initializer);
}
_initialize(payload: { request: RequestChannel, url: string, status: number, statusText: string, headers: types.Headers }) {
this._request = Request.from(payload.request);
this._status = payload.status;
this._statusText = payload.statusText;
this._url = payload.url;
this._headers = payload.headers;
} }
url(): string { url(): string {
return this._url; return this._initializer.url;
} }
ok(): boolean { ok(): boolean {
return this._status === 0 || (this._status >= 200 && this._status <= 299); return this._initializer.status === 0 || (this._initializer.status >= 200 && this._initializer.status <= 299);
} }
status(): number { status(): number {
return this._status; return this._initializer.status;
} }
statusText(): string { statusText(): string {
return this._statusText; return this._initializer.statusText;
} }
headers(): object { headers(): object {
return { ...this._headers }; return { ...this._initializer.headers };
} }
async finished(): Promise<Error | null> { async finished(): Promise<Error | null> {
@ -250,10 +212,10 @@ export class Response extends ChannelOwner<ResponseChannel> {
} }
request(): Request { request(): Request {
return this._request!; return Request.from(this._initializer.request);
} }
frame(): Frame { frame(): Frame {
return this._request!.frame(); return Request.from(this._initializer.request).frame();
} }
} }

View file

@ -19,7 +19,7 @@ import { EventEmitter } from 'events';
import { Events } from '../../events'; import { Events } from '../../events';
import { assert, assertMaxArguments, helper, Listener, serializeError, parseError } from '../../helper'; import { assert, assertMaxArguments, helper, Listener, serializeError, parseError } from '../../helper';
import * as types from '../../types'; import * as types from '../../types';
import { BrowserContextChannel, FrameChannel, PageChannel, BindingCallChannel, Channel } from '../channels'; import { PageChannel, BindingCallChannel, Channel, PageInitializer, BindingCallInitializer } from '../channels';
import { BrowserContext } from './browserContext'; import { BrowserContext } from './browserContext';
import { ChannelOwner } from './channelOwner'; import { ChannelOwner } from './channelOwner';
import { ElementHandle } from './elementHandle'; import { ElementHandle } from './elementHandle';
@ -29,17 +29,17 @@ import { Request, Response, RouteHandler, Route } from './network';
import { Connection } from '../connection'; import { Connection } from '../connection';
import { Keyboard, Mouse } from './input'; import { Keyboard, Mouse } from './input';
import { Accessibility } from './accessibility'; import { Accessibility } from './accessibility';
import { ConsoleMessage } from './console'; import { ConsoleMessage } from './consoleMessage';
export class Page extends ChannelOwner<PageChannel> { export class Page extends ChannelOwner<PageChannel, PageInitializer> {
readonly pdf: ((options?: types.PDFOptions) => Promise<Buffer>) | undefined; readonly pdf: ((options?: types.PDFOptions) => Promise<Buffer>) | undefined;
private _browserContext: BrowserContext | undefined; _browserContext: BrowserContext | undefined;
private _mainFrame: Frame | undefined; private _mainFrame: Frame;
private _frames = new Set<Frame>(); private _frames = new Set<Frame>();
private _workers: Worker[] = []; private _workers: Worker[] = [];
private _closed = false; private _closed = false;
private _viewportSize: types.Size | null = null; private _viewportSize: types.Size | null;
private _routes: { url: types.URLMatch, handler: RouteHandler }[] = []; private _routes: { url: types.URLMatch, handler: RouteHandler }[] = [];
readonly accessibility: Accessibility; readonly accessibility: Accessibility;
@ -55,18 +55,16 @@ export class Page extends ChannelOwner<PageChannel> {
return page ? Page.from(page) : null; return page ? Page.from(page) : null;
} }
constructor(connection: Connection, channel: PageChannel) { constructor(connection: Connection, channel: PageChannel, initializer: PageInitializer) {
super(connection, channel); super(connection, channel, initializer);
this.accessibility = new Accessibility(channel); this.accessibility = new Accessibility(channel);
this.keyboard = new Keyboard(channel); this.keyboard = new Keyboard(channel);
this.mouse = new Mouse(channel); this.mouse = new Mouse(channel);
}
_initialize(payload: { browserContext: BrowserContextChannel, mainFrame: FrameChannel, viewportSize: types.Size }) { this._mainFrame = Frame.from(initializer.mainFrame);
this._browserContext = BrowserContext.from(payload.browserContext); this._mainFrame._page = this;
this._mainFrame = Frame.from(payload.mainFrame);
this._frames.add(this._mainFrame); this._frames.add(this._mainFrame);
this._viewportSize = payload.viewportSize; this._viewportSize = initializer.viewportSize;
this._channel.on('bindingCall', bindingCall => this._onBinding(BindingCall.from(bindingCall))); this._channel.on('bindingCall', bindingCall => this._onBinding(BindingCall.from(bindingCall)));
this._channel.on('close', () => this._onClose()); this._channel.on('close', () => this._onClose());
@ -88,6 +86,7 @@ export class Page extends ChannelOwner<PageChannel> {
} }
private _onFrameAttached(frame: Frame) { private _onFrameAttached(frame: Frame) {
frame._page = this;
this._frames.add(frame); this._frames.add(frame);
if (frame._parentFrame) if (frame._parentFrame)
frame._parentFrame._childFrames.add(frame); frame._parentFrame._childFrames.add(frame);
@ -118,7 +117,7 @@ export class Page extends ChannelOwner<PageChannel> {
} }
async _onBinding(bindingCall: BindingCall) { async _onBinding(bindingCall: BindingCall) {
const func = this._bindings.get(bindingCall.name); const func = this._bindings.get(bindingCall._initializer.name);
if (func) if (func)
bindingCall.call(func); bindingCall.call(func);
this._browserContext!._onBinding(bindingCall); this._browserContext!._onBinding(bindingCall);
@ -138,7 +137,7 @@ export class Page extends ChannelOwner<PageChannel> {
} }
mainFrame(): Frame { mainFrame(): Frame {
return this._mainFrame!; return this._mainFrame;
} }
frame(options: string | { name?: string, url?: types.URLMatch }): Frame | null { frame(options: string | { name?: string, url?: types.URLMatch }): Frame | null {
@ -165,48 +164,48 @@ export class Page extends ChannelOwner<PageChannel> {
} }
async $(selector: string): Promise<ElementHandle<Element> | null> { async $(selector: string): Promise<ElementHandle<Element> | null> {
return await this._mainFrame!.$(selector); return await this._mainFrame.$(selector);
} }
async waitForSelector(selector: string, options?: types.WaitForElementOptions): Promise<ElementHandle<Element> | null> { async waitForSelector(selector: string, options?: types.WaitForElementOptions): Promise<ElementHandle<Element> | null> {
return await this._mainFrame!.waitForSelector(selector, options); return await this._mainFrame.waitForSelector(selector, options);
} }
async dispatchEvent(selector: string, type: string, eventInit?: Object, options?: types.TimeoutOptions): Promise<void> { async dispatchEvent(selector: string, type: string, eventInit?: Object, options?: types.TimeoutOptions): Promise<void> {
return await this._mainFrame!.dispatchEvent(selector, type, eventInit, options); return await this._mainFrame.dispatchEvent(selector, type, eventInit, options);
} }
async evaluateHandle<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg): Promise<SmartHandle<R>>; async evaluateHandle<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg): Promise<SmartHandle<R>>;
async evaluateHandle<R>(pageFunction: Func1<void, R>, arg?: any): Promise<SmartHandle<R>>; async evaluateHandle<R>(pageFunction: Func1<void, R>, arg?: any): Promise<SmartHandle<R>>;
async evaluateHandle<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg): Promise<SmartHandle<R>> { async evaluateHandle<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg): Promise<SmartHandle<R>> {
assertMaxArguments(arguments.length, 2); assertMaxArguments(arguments.length, 2);
return await this._mainFrame!.evaluateHandle(pageFunction, arg); return await this._mainFrame.evaluateHandle(pageFunction, arg);
} }
async $eval<R, Arg>(selector: string, pageFunction: FuncOn<Element, Arg, R>, arg: Arg): Promise<R>; async $eval<R, Arg>(selector: string, pageFunction: FuncOn<Element, Arg, R>, arg: Arg): Promise<R>;
async $eval<R>(selector: string, pageFunction: FuncOn<Element, void, R>, arg?: any): Promise<R>; async $eval<R>(selector: string, pageFunction: FuncOn<Element, void, R>, arg?: any): Promise<R>;
async $eval<R, Arg>(selector: string, pageFunction: FuncOn<Element, Arg, R>, arg: Arg): Promise<R> { async $eval<R, Arg>(selector: string, pageFunction: FuncOn<Element, Arg, R>, arg: Arg): Promise<R> {
assertMaxArguments(arguments.length, 3); assertMaxArguments(arguments.length, 3);
return await this._mainFrame!.$eval(selector, pageFunction, arg); return await this._mainFrame.$eval(selector, pageFunction, arg);
} }
async $$eval<R, Arg>(selector: string, pageFunction: FuncOn<Element[], Arg, R>, arg: Arg): Promise<R>; async $$eval<R, Arg>(selector: string, pageFunction: FuncOn<Element[], Arg, R>, arg: Arg): Promise<R>;
async $$eval<R>(selector: string, pageFunction: FuncOn<Element[], void, R>, arg?: any): Promise<R>; async $$eval<R>(selector: string, pageFunction: FuncOn<Element[], void, R>, arg?: any): Promise<R>;
async $$eval<R, Arg>(selector: string, pageFunction: FuncOn<Element[], Arg, R>, arg: Arg): Promise<R> { async $$eval<R, Arg>(selector: string, pageFunction: FuncOn<Element[], Arg, R>, arg: Arg): Promise<R> {
assertMaxArguments(arguments.length, 3); assertMaxArguments(arguments.length, 3);
return await this._mainFrame!.$$eval(selector, pageFunction, arg); return await this._mainFrame.$$eval(selector, pageFunction, arg);
} }
async $$(selector: string): Promise<ElementHandle<Element>[]> { async $$(selector: string): Promise<ElementHandle<Element>[]> {
return await this._mainFrame!.$$(selector); return await this._mainFrame.$$(selector);
} }
async addScriptTag(options: { url?: string; path?: string; content?: string; type?: string; }): Promise<ElementHandle> { async addScriptTag(options: { url?: string; path?: string; content?: string; type?: string; }): Promise<ElementHandle> {
return await this._mainFrame!.addScriptTag(options); return await this._mainFrame.addScriptTag(options);
} }
async addStyleTag(options: { url?: string; path?: string; content?: string; }): Promise<ElementHandle> { async addStyleTag(options: { url?: string; path?: string; content?: string; }): Promise<ElementHandle> {
return await this._mainFrame!.addStyleTag(options); return await this._mainFrame.addStyleTag(options);
} }
async exposeFunction(name: string, playwrightFunction: Function) { async exposeFunction(name: string, playwrightFunction: Function) {
@ -247,11 +246,11 @@ export class Page extends ChannelOwner<PageChannel> {
} }
async waitForLoadState(state?: types.LifecycleEvent, options?: types.TimeoutOptions): Promise<void> { async waitForLoadState(state?: types.LifecycleEvent, options?: types.TimeoutOptions): Promise<void> {
return this._mainFrame!.waitForLoadState(state, options); return this._mainFrame.waitForLoadState(state, options);
} }
async waitForNavigation(options?: types.WaitForNavigationOptions): Promise<Response | null> { async waitForNavigation(options?: types.WaitForNavigationOptions): Promise<Response | null> {
return this._mainFrame!.waitForNavigation(options); return this._mainFrame.waitForNavigation(options);
} }
async waitForRequest(urlOrPredicate: string | RegExp | ((r: Request) => boolean), options: types.TimeoutOptions = {}): Promise<Request> { async waitForRequest(urlOrPredicate: string | RegExp | ((r: Request) => boolean), options: types.TimeoutOptions = {}): Promise<Request> {
@ -455,31 +454,24 @@ export class Worker extends EventEmitter {
} }
} }
export class BindingCall extends ChannelOwner<BindingCallChannel> { export class BindingCall extends ChannelOwner<BindingCallChannel, BindingCallInitializer> {
name: string = '';
source: { context: BrowserContext, page: Page, frame: Frame } | undefined;
args: any[] = [];
static from(channel: BindingCallChannel): BindingCall { static from(channel: BindingCallChannel): BindingCall {
return channel._object; return channel._object;
} }
constructor(connection: Connection, channel: BindingCallChannel) { constructor(connection: Connection, channel: BindingCallChannel, initializer: BindingCallInitializer) {
super(connection, channel); super(connection, channel, initializer);
}
_initialize(params: { name: string, context: BrowserContextChannel, page: PageChannel, frame: FrameChannel, args: any[] }) {
this.name = params.name;
this.source = {
context: BrowserContext.from(params.context),
page: Page.from(params.page),
frame: Frame.from(params.frame)
};
this.args = params.args;
} }
async call(func: FunctionWithSource) { async call(func: FunctionWithSource) {
try { try {
this._channel.resolve({ result: await func(this.source!, ...this.args) }); const frame = Frame.from(this._initializer.frame);
const source = {
context: frame._page!._browserContext!,
page: frame._page!,
frame
};
this._channel.resolve({ result: await func(source, ...this._initializer.args) });
} catch (e) { } catch (e) {
this._channel.reject({ error: serializeError(e) }); this._channel.reject({ error: serializeError(e) });
} }

View file

@ -26,62 +26,75 @@ import { Request, Response, Route } from './client/network';
import { Page, BindingCall } from './client/page'; import { Page, BindingCall } from './client/page';
import debug = require('debug'); import debug = require('debug');
import { Channel } from './channels'; import { Channel } from './channels';
import { ConsoleMessage } from './client/console'; import { ConsoleMessage } from './client/consoleMessage';
export class Connection { export class Connection {
private _channels = new Map<string, Channel>(); private _channels = new Map<string, Channel>();
private _waitingForObject = new Map<string, any>();
sendMessageToServerTransport = (message: any): Promise<any> => Promise.resolve(); sendMessageToServerTransport = (message: any): Promise<any> => Promise.resolve();
constructor() {} constructor() {}
createRemoteObject(type: string, guid: string): any { private _createRemoteObject(type: string, guid: string, initializer: any): any {
const channel = this._createChannel(guid) as any; const channel = this._createChannel(guid) as any;
this._channels.set(guid, channel); this._channels.set(guid, channel);
let result: ChannelOwner<any>; let result: ChannelOwner<any, any>;
initializer = this._replaceGuidsWithChannels(initializer);
switch (type) { switch (type) {
case 'browserType': case 'browserType':
result = new BrowserType(this, channel); result = new BrowserType(this, channel, initializer);
break; break;
case 'browser': case 'browser':
result = new Browser(this, channel); result = new Browser(this, channel, initializer);
break; break;
case 'context': case 'context':
result = new BrowserContext(this, channel); result = new BrowserContext(this, channel, initializer);
break; break;
case 'page': case 'page':
result = new Page(this, channel); result = new Page(this, channel, initializer);
break; break;
case 'frame': case 'frame':
result = new Frame(this, channel); result = new Frame(this, channel, initializer);
break; break;
case 'request': case 'request':
result = new Request(this, channel); result = new Request(this, channel, initializer);
break; break;
case 'response': case 'response':
result = new Response(this, channel); result = new Response(this, channel, initializer);
break; break;
case 'route': case 'route':
result = new Route(this, channel); result = new Route(this, channel, initializer);
break; break;
case 'bindingCall': case 'bindingCall':
result = new BindingCall(this, channel); result = new BindingCall(this, channel, initializer);
break; break;
case 'jsHandle': case 'jsHandle':
result = new JSHandle(this, channel); result = new JSHandle(this, channel, initializer);
break; break;
case 'elementHandle': case 'elementHandle':
result = new ElementHandle(this, channel); result = new ElementHandle(this, channel, initializer);
break; break;
case 'consoleMessage': case 'consoleMessage':
result = new ConsoleMessage(this, channel); result = new ConsoleMessage(this, channel, initializer);
break; break;
default: default:
throw new Error('Missing type ' + type); throw new Error('Missing type ' + type);
} }
channel._object = result; channel._object = result;
const callback = this._waitingForObject.get(guid);
if (callback) {
callback(result);
this._waitingForObject.delete(guid);
}
return result; return result;
} }
waitForObjectWithKnownName(guid: string): Promise<any> {
if (this._channels.has(guid))
return this._channels.get(guid)!._object;
return new Promise(f => this._waitingForObject.set(guid, f));
}
async sendMessageToServer(message: { guid: string, method: string, params: any }) { async sendMessageToServer(message: { guid: string, method: string, params: any }) {
const converted = {...message, params: this._replaceChannelsWithGuids(message.params)}; const converted = {...message, params: this._replaceChannelsWithGuids(message.params)};
debug('pw:channel:command')(converted); debug('pw:channel:command')(converted);
@ -95,15 +108,11 @@ export class Connection {
const { guid, method, params } = message; const { guid, method, params } = message;
if (method === '__create__') { if (method === '__create__') {
this.createRemoteObject(params.type, guid); this._createRemoteObject(params.type, guid, params.initializer);
return; return;
} }
const channel = this._channels.get(guid)!; const channel = this._channels.get(guid)!;
if (message.method === '__init__') {
channel._object._initialize(this._replaceGuidsWithChannels(params));
return;
}
channel.emit(method, this._replaceGuidsWithChannels(params)); channel.emit(method, this._replaceGuidsWithChannels(params));
} }

View file

@ -18,13 +18,13 @@ import { EventEmitter } from 'events';
import { helper } from '../helper'; import { helper } from '../helper';
import { Channel } from './channels'; import { Channel } from './channels';
export class Dispatcher extends EventEmitter implements Channel { export class Dispatcher<Initializer> extends EventEmitter implements Channel {
readonly _guid: string; readonly _guid: string;
readonly _type: string; readonly _type: string;
protected _scope: DispatcherScope; protected _scope: DispatcherScope;
_object: any; _object: any;
constructor(scope: DispatcherScope, object: any, type: string, guid = type + '@' + helper.guid()) { constructor(scope: DispatcherScope, object: any, type: string, initializer: Initializer, guid = type + '@' + helper.guid()) {
super(); super();
this._type = type; this._type = type;
this._guid = guid; this._guid = guid;
@ -32,20 +32,16 @@ export class Dispatcher extends EventEmitter implements Channel {
this._scope = scope; this._scope = scope;
scope.dispatchers.set(this._guid, this); scope.dispatchers.set(this._guid, this);
object[scope.dispatcherSymbol] = this; object[scope.dispatcherSymbol] = this;
this._scope.sendMessageToClient(this._guid, '__create__', { type }); this._scope.sendMessageToClient(this._guid, '__create__', { type, initializer });
} }
_initialize(payload: any) { _dispatchEvent(method: string, params: Dispatcher<any> | any = {}) {
this._scope.sendMessageToClient(this._guid, '__init__', payload);
}
_dispatchEvent(method: string, params: Dispatcher | any = {}) {
this._scope.sendMessageToClient(this._guid, method, params); this._scope.sendMessageToClient(this._guid, method, params);
} }
} }
export class DispatcherScope { export class DispatcherScope {
readonly dispatchers = new Map<string, Dispatcher>(); readonly dispatchers = new Map<string, Dispatcher<any>>();
readonly dispatcherSymbol = Symbol('dispatcher'); readonly dispatcherSymbol = Symbol('dispatcher');
sendMessageToClientTransport = (message: any) => {}; sendMessageToClientTransport = (message: any) => {};

View file

@ -17,14 +17,13 @@
import * as types from '../../types'; import * as types from '../../types';
import { BrowserContextBase, BrowserContext } from '../../browserContext'; import { BrowserContextBase, BrowserContext } from '../../browserContext';
import { Events } from '../../events'; import { Events } from '../../events';
import { BrowserDispatcher } from './browserDispatcher';
import { Dispatcher, DispatcherScope } from '../dispatcher'; import { Dispatcher, DispatcherScope } from '../dispatcher';
import { PageDispatcher, BindingCallDispatcher } from './pageDispatcher'; import { PageDispatcher, BindingCallDispatcher } from './pageDispatcher';
import { PageChannel, BrowserContextChannel } from '../channels'; import { PageChannel, BrowserContextChannel, BrowserContextInitializer } from '../channels';
import { RouteDispatcher, RequestDispatcher } from './networkDispatchers'; import { RouteDispatcher, RequestDispatcher } from './networkDispatchers';
import { Page } from '../../page'; import { Page } from '../../page';
export class BrowserContextDispatcher extends Dispatcher implements BrowserContextChannel { export class BrowserContextDispatcher extends Dispatcher<BrowserContextInitializer> implements BrowserContextChannel {
private _context: BrowserContextBase; private _context: BrowserContextBase;
static from(scope: DispatcherScope, browserContext: BrowserContext): BrowserContextDispatcher { static from(scope: DispatcherScope, browserContext: BrowserContext): BrowserContextDispatcher {
@ -34,10 +33,7 @@ export class BrowserContextDispatcher extends Dispatcher implements BrowserConte
} }
constructor(scope: DispatcherScope, context: BrowserContextBase) { constructor(scope: DispatcherScope, context: BrowserContextBase) {
super(scope, context, 'context'); super(scope, context, 'context', {});
this._initialize({
browser: BrowserDispatcher.from(scope, context._browserBase)
});
this._context = context; this._context = context;
context.on(Events.BrowserContext.Page, page => this._dispatchEvent('page', PageDispatcher.from(this._scope, page))); context.on(Events.BrowserContext.Page, page => this._dispatchEvent('page', PageDispatcher.from(this._scope, page)));
context.on(Events.BrowserContext.Close, () => { context.on(Events.BrowserContext.Close, () => {

View file

@ -18,11 +18,11 @@ import { BrowserBase } from '../../browser';
import { BrowserContextBase } from '../../browserContext'; import { BrowserContextBase } from '../../browserContext';
import * as types from '../../types'; import * as types from '../../types';
import { BrowserContextDispatcher } from './browserContextDispatcher'; import { BrowserContextDispatcher } from './browserContextDispatcher';
import { BrowserChannel, BrowserContextChannel, PageChannel } from '../channels'; import { BrowserChannel, BrowserContextChannel, PageChannel, BrowserInitializer } from '../channels';
import { Dispatcher, DispatcherScope } from '../dispatcher'; import { Dispatcher, DispatcherScope } from '../dispatcher';
import { PageDispatcher } from './pageDispatcher'; import { PageDispatcher } from './pageDispatcher';
export class BrowserDispatcher extends Dispatcher implements BrowserChannel { export class BrowserDispatcher extends Dispatcher<BrowserInitializer> implements BrowserChannel {
private _browser: BrowserBase; private _browser: BrowserBase;
static from(scope: DispatcherScope, browser: BrowserBase): BrowserDispatcher { static from(scope: DispatcherScope, browser: BrowserBase): BrowserDispatcher {
@ -38,8 +38,7 @@ export class BrowserDispatcher extends Dispatcher implements BrowserChannel {
} }
constructor(scope: DispatcherScope, browser: BrowserBase) { constructor(scope: DispatcherScope, browser: BrowserBase) {
super(scope, browser, 'browser'); super(scope, browser, 'browser', {});
this._initialize({});
this._browser = browser; this._browser = browser;
} }

View file

@ -18,12 +18,12 @@ import { BrowserBase } from '../../browser';
import { BrowserTypeBase } from '../../server/browserType'; import { BrowserTypeBase } from '../../server/browserType';
import * as types from '../../types'; import * as types from '../../types';
import { BrowserDispatcher } from './browserDispatcher'; import { BrowserDispatcher } from './browserDispatcher';
import { BrowserChannel, BrowserTypeChannel, BrowserContextChannel } from '../channels'; import { BrowserChannel, BrowserTypeChannel, BrowserContextChannel, BrowserTypeInitializer } from '../channels';
import { Dispatcher, DispatcherScope } from '../dispatcher'; import { Dispatcher, DispatcherScope } from '../dispatcher';
import { BrowserContextBase } from '../../browserContext'; import { BrowserContextBase } from '../../browserContext';
import { BrowserContextDispatcher } from './browserContextDispatcher'; import { BrowserContextDispatcher } from './browserContextDispatcher';
export class BrowserTypeDispatcher extends Dispatcher implements BrowserTypeChannel { export class BrowserTypeDispatcher extends Dispatcher<BrowserTypeInitializer> implements BrowserTypeChannel {
private _browserType: BrowserTypeBase; private _browserType: BrowserTypeBase;
static from(scope: DispatcherScope, browserType: BrowserTypeBase): BrowserTypeDispatcher { static from(scope: DispatcherScope, browserType: BrowserTypeBase): BrowserTypeDispatcher {
@ -33,8 +33,10 @@ export class BrowserTypeDispatcher extends Dispatcher implements BrowserTypeChan
} }
constructor(scope: DispatcherScope, browserType: BrowserTypeBase) { constructor(scope: DispatcherScope, browserType: BrowserTypeBase) {
super(scope, browserType, 'browserType', browserType.name()); super(scope, browserType, 'browserType', {
this._initialize({ executablePath: browserType.executablePath(), name: browserType.name() }); executablePath: browserType.executablePath(),
name: browserType.name()
}, browserType.name());
this._browserType = browserType; this._browserType = browserType;
} }

View file

@ -15,11 +15,11 @@
*/ */
import { ConsoleMessage } from '../../console'; import { ConsoleMessage } from '../../console';
import { ConsoleMessageChannel } from '../channels'; import { ConsoleMessageChannel, ConsoleMessageInitializer } from '../channels';
import { Dispatcher, DispatcherScope } from '../dispatcher'; import { Dispatcher, DispatcherScope } from '../dispatcher';
import { ElementHandleDispatcher } from './elementHandlerDispatcher'; import { ElementHandleDispatcher } from './elementHandlerDispatcher';
export class ConsoleMessageDispatcher extends Dispatcher implements ConsoleMessageChannel { export class ConsoleMessageDispatcher extends Dispatcher<ConsoleMessageInitializer> implements ConsoleMessageChannel {
static from(scope: DispatcherScope, message: ConsoleMessage): ConsoleMessageDispatcher { static from(scope: DispatcherScope, message: ConsoleMessage): ConsoleMessageDispatcher {
if ((message as any)[scope.dispatcherSymbol]) if ((message as any)[scope.dispatcherSymbol])
return (message as any)[scope.dispatcherSymbol]; return (message as any)[scope.dispatcherSymbol];
@ -27,11 +27,10 @@ export class ConsoleMessageDispatcher extends Dispatcher implements ConsoleMessa
} }
constructor(scope: DispatcherScope, message: ConsoleMessage) { constructor(scope: DispatcherScope, message: ConsoleMessage) {
super(scope, message, 'consoleMessage'); super(scope, message, 'consoleMessage', {
this._initialize({
type: message.type(), type: message.type(),
text: message.text(), text: message.text(),
args: message.args().map(a => ElementHandleDispatcher.from(this._scope, a)), args: message.args().map(a => ElementHandleDispatcher.from(scope, a)),
location: message.location(), location: message.location(),
}); });
} }

View file

@ -51,9 +51,8 @@ export class ElementHandleDispatcher extends JSHandleDispatcher implements Eleme
constructor(scope: DispatcherScope, elementHandle: ElementHandle) { constructor(scope: DispatcherScope, elementHandle: ElementHandle) {
super(scope, elementHandle, true); super(scope, elementHandle);
this._elementHandle = elementHandle; this._elementHandle = elementHandle;
this._initialize({ preview: elementHandle.toString(), frame: FrameDispatcher.from(scope, elementHandle._context.frame) });
this._elementHandle = elementHandle; this._elementHandle = elementHandle;
} }

View file

@ -16,13 +16,13 @@
import { Frame } from '../../frames'; import { Frame } from '../../frames';
import * as types from '../../types'; import * as types from '../../types';
import { ElementHandleChannel, FrameChannel, JSHandleChannel, ResponseChannel } from '../channels'; import { ElementHandleChannel, FrameChannel, JSHandleChannel, ResponseChannel, FrameInitializer } from '../channels';
import { Dispatcher, DispatcherScope } from '../dispatcher'; import { Dispatcher, DispatcherScope } from '../dispatcher';
import { ElementHandleDispatcher, convertSelectOptionValues } from './elementHandlerDispatcher'; import { ElementHandleDispatcher, convertSelectOptionValues } from './elementHandlerDispatcher';
import { JSHandleDispatcher } from './jsHandleDispatcher'; import { JSHandleDispatcher } from './jsHandleDispatcher';
import { ResponseDispatcher } from './networkDispatchers'; import { ResponseDispatcher } from './networkDispatchers';
export class FrameDispatcher extends Dispatcher implements FrameChannel { export class FrameDispatcher extends Dispatcher<FrameInitializer> implements FrameChannel {
private _frame: Frame; private _frame: Frame;
static from(scope: DispatcherScope, frame: Frame): FrameDispatcher { static from(scope: DispatcherScope, frame: Frame): FrameDispatcher {
@ -38,14 +38,12 @@ export class FrameDispatcher extends Dispatcher implements FrameChannel {
} }
constructor(scope: DispatcherScope, frame: Frame) { constructor(scope: DispatcherScope, frame: Frame) {
super(scope, frame, 'frame'); super(scope, frame, 'frame', {
this._frame = frame;
const parentFrame = frame.parentFrame();
this._initialize({
url: frame.url(), url: frame.url(),
name: frame.name(), name: frame.name(),
parentFrame: FrameDispatcher.fromNullable(this._scope, parentFrame) parentFrame: FrameDispatcher.fromNullable(scope, frame.parentFrame())
}); });
this._frame = frame;
} }
async goto(params: { url: string, options: types.GotoOptions }): Promise<ResponseChannel | null> { async goto(params: { url: string, options: types.GotoOptions }): Promise<ResponseChannel | null> {

View file

@ -15,17 +15,17 @@
*/ */
import * as js from '../../javascript'; import * as js from '../../javascript';
import { JSHandleChannel } from '../channels'; import { JSHandleChannel, JSHandleInitializer } from '../channels';
import { Dispatcher, DispatcherScope } from '../dispatcher'; import { Dispatcher, DispatcherScope } from '../dispatcher';
import { convertArg } from './frameDispatcher'; import { convertArg } from './frameDispatcher';
export class JSHandleDispatcher extends Dispatcher implements JSHandleChannel { export class JSHandleDispatcher extends Dispatcher<JSHandleInitializer> implements JSHandleChannel {
readonly _jsHandle: js.JSHandle<any>; readonly _jsHandle: js.JSHandle<any>;
constructor(scope: DispatcherScope, jsHandle: js.JSHandle, omitInit?: boolean) { constructor(scope: DispatcherScope, jsHandle: js.JSHandle) {
super(scope, jsHandle, jsHandle.asElement() ? 'elementHandle' : 'jsHandle'); super(scope, jsHandle, jsHandle.asElement() ? 'elementHandle' : 'jsHandle', {
if (!omitInit) preview: jsHandle.toString(),
this._initialize({ preview: jsHandle.toString() }); });
this._jsHandle = jsHandle; this._jsHandle = jsHandle;
} }

View file

@ -16,11 +16,11 @@
import { Request, Response, Route } from '../../network'; import { Request, Response, Route } from '../../network';
import * as types from '../../types'; import * as types from '../../types';
import { RequestChannel, ResponseChannel, RouteChannel } from '../channels'; import { RequestChannel, ResponseChannel, RouteChannel, ResponseInitializer, RequestInitializer, RouteInitializer } from '../channels';
import { Dispatcher, DispatcherScope } from '../dispatcher'; import { Dispatcher, DispatcherScope } from '../dispatcher';
import { FrameDispatcher } from './frameDispatcher'; import { FrameDispatcher } from './frameDispatcher';
export class RequestDispatcher extends Dispatcher implements RequestChannel { export class RequestDispatcher extends Dispatcher<RequestInitializer> implements RequestChannel {
private _request: Request; private _request: Request;
static from(scope: DispatcherScope, request: Request): RequestDispatcher { static from(scope: DispatcherScope, request: Request): RequestDispatcher {
@ -34,18 +34,15 @@ export class RequestDispatcher extends Dispatcher implements RequestChannel {
} }
constructor(scope: DispatcherScope, request: Request) { constructor(scope: DispatcherScope, request: Request) {
super(scope, request, 'request'); super(scope, request, 'request', {
this._initialize({ frame: FrameDispatcher.from(scope, request.frame()),
url: request.url(), url: request.url(),
resourceType: request.resourceType(), resourceType: request.resourceType(),
method: request.method(), method: request.method(),
postData: request.postData(), postData: request.postData(),
headers: request.headers(), headers: request.headers(),
isNavigationRequest: request.isNavigationRequest(), isNavigationRequest: request.isNavigationRequest(),
failure: request.failure(), redirectedFrom: RequestDispatcher.fromNullable(scope, request.redirectedFrom()),
frame: FrameDispatcher.from(this._scope, request.frame()),
redirectedFrom: RequestDispatcher.fromNullable(this._scope, request.redirectedFrom()),
redirectedTo: RequestDispatcher.fromNullable(this._scope, request.redirectedTo()),
}); });
this._request = request; this._request = request;
} }
@ -55,7 +52,7 @@ export class RequestDispatcher extends Dispatcher implements RequestChannel {
} }
} }
export class ResponseDispatcher extends Dispatcher implements ResponseChannel { export class ResponseDispatcher extends Dispatcher<ResponseInitializer> implements ResponseChannel {
private _response: Response; private _response: Response;
static from(scope: DispatcherScope, response: Response): ResponseDispatcher { static from(scope: DispatcherScope, response: Response): ResponseDispatcher {
@ -69,12 +66,9 @@ export class ResponseDispatcher extends Dispatcher implements ResponseChannel {
} }
constructor(scope: DispatcherScope, response: Response) { constructor(scope: DispatcherScope, response: Response) {
super(scope, response, 'response'); super(scope, response, 'response', {
this._initialize({ request: RequestDispatcher.from(scope, response.request())!,
frame: FrameDispatcher.from(this._scope, response.frame()),
request: RequestDispatcher.from(this._scope, response.request())!,
url: response.url(), url: response.url(),
ok: response.ok(),
status: response.status(), status: response.status(),
statusText: response.statusText(), statusText: response.statusText(),
headers: response.headers(), headers: response.headers(),
@ -91,7 +85,7 @@ export class ResponseDispatcher extends Dispatcher implements ResponseChannel {
} }
} }
export class RouteDispatcher extends Dispatcher implements RouteChannel { export class RouteDispatcher extends Dispatcher<RouteInitializer> implements RouteChannel {
private _route: Route; private _route: Route;
static from(scope: DispatcherScope, route: Route): RouteDispatcher { static from(scope: DispatcherScope, route: Route): RouteDispatcher {
@ -105,8 +99,9 @@ export class RouteDispatcher extends Dispatcher implements RouteChannel {
} }
constructor(scope: DispatcherScope, route: Route) { constructor(scope: DispatcherScope, route: Route) {
super(scope, route, 'route'); super(scope, route, 'route', {
this._initialize({ request: RequestDispatcher.from(this._scope, route.request()) }); request: RequestDispatcher.from(scope, route.request())
});
this._route = route; this._route = route;
} }

View file

@ -19,16 +19,15 @@ import { Request } from '../../network';
import { Frame } from '../../frames'; import { Frame } from '../../frames';
import { Page } from '../../page'; import { Page } from '../../page';
import * as types from '../../types'; import * as types from '../../types';
import { ElementHandleChannel, PageChannel, ResponseChannel, BindingCallChannel } from '../channels'; import { ElementHandleChannel, PageChannel, ResponseChannel, BindingCallChannel, PageInitializer, BindingCallInitializer } from '../channels';
import { Dispatcher, DispatcherScope } from '../dispatcher'; import { Dispatcher, DispatcherScope } from '../dispatcher';
import { BrowserContextDispatcher } from './browserContextDispatcher';
import { FrameDispatcher } from './frameDispatcher'; import { FrameDispatcher } from './frameDispatcher';
import { RequestDispatcher, ResponseDispatcher, RouteDispatcher } from './networkDispatchers'; import { RequestDispatcher, ResponseDispatcher, RouteDispatcher } from './networkDispatchers';
import { ConsoleMessageDispatcher } from './consoleMessageDispatcher'; import { ConsoleMessageDispatcher } from './consoleMessageDispatcher';
import { BrowserContext } from '../../browserContext'; import { BrowserContext } from '../../browserContext';
import { serializeError, parseError } from '../../helper'; import { serializeError, parseError } from '../../helper';
export class PageDispatcher extends Dispatcher implements PageChannel { export class PageDispatcher extends Dispatcher<PageInitializer> implements PageChannel {
private _page: Page; private _page: Page;
static from(scope: DispatcherScope, page: Page): PageDispatcher { static from(scope: DispatcherScope, page: Page): PageDispatcher {
@ -44,11 +43,9 @@ export class PageDispatcher extends Dispatcher implements PageChannel {
} }
constructor(scope: DispatcherScope, page: Page) { constructor(scope: DispatcherScope, page: Page) {
super(scope, page, 'page'); super(scope, page, 'page', {
this._initialize({
browserContext: BrowserContextDispatcher.from(scope, page._browserContext),
mainFrame: FrameDispatcher.from(scope, page.mainFrame()), mainFrame: FrameDispatcher.from(scope, page.mainFrame()),
frames: page.frames().map(f => FrameDispatcher.from(this._scope, f)), viewportSize: page.viewportSize()
}); });
this._page = page; this._page = page;
page.on(Events.Page.Close, () => this._dispatchEvent('close')); page.on(Events.Page.Close, () => this._dispatchEvent('close'));
@ -196,18 +193,15 @@ export class PageDispatcher extends Dispatcher implements PageChannel {
} }
export class BindingCallDispatcher extends Dispatcher implements BindingCallChannel { export class BindingCallDispatcher extends Dispatcher<BindingCallInitializer> implements 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, source: { context: BrowserContext, page: Page, frame: Frame }, args: any[]) { constructor(scope: DispatcherScope, name: string, source: { context: BrowserContext, page: Page, frame: Frame }, args: any[]) {
super(scope, {}, 'bindingCall'); super(scope, {}, 'bindingCall', {
this._initialize({
name,
context: BrowserContextDispatcher.from(scope, source.context),
page: PageDispatcher.from(scope, source.page),
frame: FrameDispatcher.from(scope, source.frame), frame: FrameDispatcher.from(scope, source.frame),
name,
args args
}); });
this._promise = new Promise((resolve, reject) => { this._promise = new Promise((resolve, reject) => {

View file

@ -113,16 +113,15 @@ function collect(browserNames) {
for (const browserName of browserNames) { for (const browserName of browserNames) {
const browserType = playwright[browserName]; const browserType = playwright[browserName];
let overridenBrowserType = browserType;
// Channel substitute
if (process.env.PWCHANNEL) {
BrowserTypeDispatcher.from(dispatcherScope, browserType);
overridenBrowserType = connection.createRemoteObject('browserType', browserType.name());
}
const browserTypeEnvironment = new Environment('BrowserType'); const browserTypeEnvironment = new Environment('BrowserType');
browserTypeEnvironment.beforeAll(async state => { browserTypeEnvironment.beforeAll(async state => {
// Channel substitute
let overridenBrowserType = browserType;
if (process.env.PWCHANNEL) {
BrowserTypeDispatcher.from(dispatcherScope, browserType);
overridenBrowserType = await connection.waitForObjectWithKnownName(browserType.name());
}
state.browserType = overridenBrowserType; state.browserType = overridenBrowserType;
}); });
browserTypeEnvironment.afterAll(async state => { browserTypeEnvironment.afterAll(async state => {