feat(rpc): introduce protocol.pdl (#3054)

We now generate channels.ts from the protocol definition. There are still some shortcomings,
like union types - these will be addressed in follow ups.
This commit is contained in:
Dmitry Gozman 2020-07-20 17:38:06 -07:00 committed by GitHub
parent 726f636b5c
commit 5848ed8f41
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 3420 additions and 494 deletions

File diff suppressed because it is too large Load diff

View file

@ -29,6 +29,6 @@ export class Accessibility {
async snapshot(options: { interestingOnly?: boolean; root?: ElementHandle } = {}): Promise<types.SerializedAXNode | null> { async snapshot(options: { interestingOnly?: boolean; root?: ElementHandle } = {}): Promise<types.SerializedAXNode | null> {
const root = options.root ? options.root._elementChannel : undefined; const root = options.root ? options.root._elementChannel : undefined;
const result = await this._channel.accessibilitySnapshot({ interestingOnly: options.interestingOnly, root }); const result = await this._channel.accessibilitySnapshot({ interestingOnly: options.interestingOnly, root });
return result.rootAXNode; return result.rootAXNode || null;
} }
} }

View file

@ -15,7 +15,7 @@
*/ */
import * as types from '../../types'; import * as types from '../../types';
import { BrowserChannel, BrowserInitializer, BrowserContextOptions } from '../channels'; import { BrowserChannel, BrowserInitializer, BrowserNewContextParams } 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';
@ -55,7 +55,7 @@ export class Browser extends ChannelOwner<BrowserChannel, BrowserInitializer> {
const logger = options.logger; const logger = options.logger;
options = { ...options, logger: undefined }; options = { ...options, logger: undefined };
return this._wrapApiCall('browser.newContext', async () => { return this._wrapApiCall('browser.newContext', async () => {
const contextOptions: BrowserContextOptions = { const contextOptions: BrowserNewContextParams = {
...options, ...options,
extraHTTPHeaders: options.extraHTTPHeaders ? headersObjectToArray(options.extraHTTPHeaders) : undefined, extraHTTPHeaders: options.extraHTTPHeaders ? headersObjectToArray(options.extraHTTPHeaders) : undefined,
}; };

View file

@ -141,7 +141,7 @@ export class BrowserContext extends ChannelOwner<BrowserContextChannel, BrowserC
async setGeolocation(geolocation: types.Geolocation | null): Promise<void> { async setGeolocation(geolocation: types.Geolocation | null): Promise<void> {
return this._wrapApiCall('browserContext.setGeolocation', async () => { return this._wrapApiCall('browserContext.setGeolocation', async () => {
await this._channel.setGeolocation({ geolocation }); await this._channel.setGeolocation({ geolocation: geolocation || undefined });
}); });
} }
@ -159,7 +159,7 @@ export class BrowserContext extends ChannelOwner<BrowserContextChannel, BrowserC
async setHTTPCredentials(httpCredentials: types.Credentials | null): Promise<void> { async setHTTPCredentials(httpCredentials: types.Credentials | null): Promise<void> {
return this._wrapApiCall('browserContext.setHTTPCredentials', async () => { return this._wrapApiCall('browserContext.setHTTPCredentials', async () => {
await this._channel.setHTTPCredentials({ httpCredentials }); await this._channel.setHTTPCredentials({ httpCredentials: httpCredentials || undefined });
}); });
} }

View file

@ -15,7 +15,7 @@
*/ */
import * as types from '../../types'; import * as types from '../../types';
import { BrowserTypeChannel, BrowserTypeInitializer, LaunchPersistentContextOptions, LaunchOptions, LaunchServerOptions } from '../channels'; import { BrowserTypeChannel, BrowserTypeInitializer, BrowserTypeLaunchParams, BrowserTypeLaunchServerParams, BrowserTypeLaunchPersistentContextParams } 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';
@ -45,7 +45,7 @@ export class BrowserType extends ChannelOwner<BrowserTypeChannel, BrowserTypeIni
const logger = options.logger; const logger = options.logger;
options = { ...options, logger: undefined }; options = { ...options, logger: undefined };
return this._wrapApiCall('browserType.launch', async () => { return this._wrapApiCall('browserType.launch', async () => {
const launchOptions: LaunchOptions = { const launchOptions: BrowserTypeLaunchParams = {
...options, ...options,
ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : undefined, ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : undefined,
ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs), ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs),
@ -61,7 +61,7 @@ export class BrowserType extends ChannelOwner<BrowserTypeChannel, BrowserTypeIni
const logger = options.logger; const logger = options.logger;
options = { ...options, logger: undefined }; options = { ...options, logger: undefined };
return this._wrapApiCall('browserType.launchServer', async () => { return this._wrapApiCall('browserType.launchServer', async () => {
const launchServerOptions: LaunchServerOptions = { const launchServerOptions: BrowserTypeLaunchServerParams = {
...options, ...options,
ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : undefined, ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : undefined,
ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs), ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs),
@ -75,7 +75,7 @@ export class BrowserType extends ChannelOwner<BrowserTypeChannel, BrowserTypeIni
const logger = options.logger; const logger = options.logger;
options = { ...options, logger: undefined }; options = { ...options, logger: undefined };
return this._wrapApiCall('browserType.launchPersistentContext', async () => { return this._wrapApiCall('browserType.launchPersistentContext', async () => {
const persistentOptions: LaunchPersistentContextOptions = { const persistentOptions: BrowserTypeLaunchPersistentContextParams = {
...options, ...options,
ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : undefined, ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : undefined,
ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs), ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs),

View file

@ -37,11 +37,11 @@ export class Download extends ChannelOwner<DownloadChannel, DownloadInitializer>
} }
async path(): Promise<string | null> { async path(): Promise<string | null> {
return (await this._channel.path()).value; return (await this._channel.path()).value || null;
} }
async failure(): Promise<string | null> { async failure(): Promise<string | null> {
return (await this._channel.failure()).error; return (await this._channel.failure()).error || null;
} }
async createReadStream(): Promise<Readable | null> { async createReadStream(): Promise<Readable | null> {

View file

@ -15,17 +15,18 @@
*/ */
import * as types from '../../types'; import * as types from '../../types';
import { ElectronChannel, ElectronInitializer, ElectronLaunchOptions, ElectronApplicationChannel, ElectronApplicationInitializer } from '../channels'; import { ElectronChannel, ElectronInitializer, ElectronApplicationChannel, ElectronApplicationInitializer, ElectronLaunchParams } from '../channels';
import { BrowserContext } from './browserContext'; import { BrowserContext } from './browserContext';
import { ChannelOwner } from './channelOwner'; import { ChannelOwner } from './channelOwner';
import { Page } from './page'; import { Page } from './page';
import { serializeArgument, FuncOn, parseResult, SmartHandle, JSHandle } from './jsHandle'; import { serializeArgument, FuncOn, parseResult, SmartHandle, JSHandle } from './jsHandle';
import { ElectronEvents } from '../../server/electron'; import { ElectronEvents, ElectronLaunchOptionsBase } from '../../server/electron';
import { TimeoutSettings } from '../../timeoutSettings'; import { TimeoutSettings } from '../../timeoutSettings';
import { Waiter } from './waiter'; import { Waiter } from './waiter';
import { TimeoutError } from '../../errors'; import { TimeoutError } from '../../errors';
import { Events } from '../../events'; import { Events } from '../../events';
import { LoggerSink } from '../../loggerSink'; import { LoggerSink } from '../../loggerSink';
import { envObjectToArray } from '../serializers';
export class Electron extends ChannelOwner<ElectronChannel, ElectronInitializer> { export class Electron extends ChannelOwner<ElectronChannel, ElectronInitializer> {
static from(electron: ElectronChannel): Electron { static from(electron: ElectronChannel): Electron {
@ -36,11 +37,16 @@ export class Electron extends ChannelOwner<ElectronChannel, ElectronInitializer>
super(parent, type, guid, initializer, true); super(parent, type, guid, initializer, true);
} }
async launch(executablePath: string, options: ElectronLaunchOptions & { logger?: LoggerSink } = {}): Promise<ElectronApplication> { async launch(executablePath: string, options: ElectronLaunchOptionsBase & { logger?: LoggerSink } = {}): Promise<ElectronApplication> {
const logger = options.logger; const logger = options.logger;
options = { ...options, logger: undefined }; options = { ...options, logger: undefined };
return this._wrapApiCall('electron.launch', async () => { return this._wrapApiCall('electron.launch', async () => {
return ElectronApplication.from((await this._channel.launch({ executablePath, ...options })).electronApplication); const params: ElectronLaunchParams = {
...options,
env: options.env ? envObjectToArray(options.env) : undefined,
executablePath,
};
return ElectronApplication.from((await this._channel.launch(params)).electronApplication);
}, logger); }, logger);
} }
} }

View file

@ -29,7 +29,7 @@ export class ElementHandle<T extends Node = Node> extends JSHandle<T> {
return (handle as any)._object; return (handle as any)._object;
} }
static fromNullable(handle: ElementHandleChannel | null): ElementHandle | null { static fromNullable(handle: ElementHandleChannel | undefined): ElementHandle | null {
return handle ? ElementHandle.from(handle) : null; return handle ? ElementHandle.from(handle) : null;
} }
@ -56,13 +56,15 @@ export class ElementHandle<T extends Node = Node> extends JSHandle<T> {
async getAttribute(name: string): Promise<string | null> { async getAttribute(name: string): Promise<string | null> {
return this._wrapApiCall('elementHandle.getAttribute', async () => { return this._wrapApiCall('elementHandle.getAttribute', async () => {
return (await this._elementChannel.getAttribute({ name })).value; const value = (await this._elementChannel.getAttribute({ name })).value;
return value === undefined ? null : value;
}); });
} }
async textContent(): Promise<string | null> { async textContent(): Promise<string | null> {
return this._wrapApiCall('elementHandle.textContent', async () => { return this._wrapApiCall('elementHandle.textContent', async () => {
return (await this._elementChannel.textContent()).value; const value = (await this._elementChannel.textContent()).value;
return value === undefined ? null : value;
}); });
} }
@ -165,7 +167,8 @@ export class ElementHandle<T extends Node = Node> extends JSHandle<T> {
async boundingBox(): Promise<types.Rect | null> { async boundingBox(): Promise<types.Rect | null> {
return this._wrapApiCall('elementHandle.boundingBox', async () => { return this._wrapApiCall('elementHandle.boundingBox', async () => {
return (await this._elementChannel.boundingBox()).value; const value = (await this._elementChannel.boundingBox()).value;
return value === undefined ? null : value;
}); });
} }

View file

@ -53,7 +53,7 @@ export class Frame extends ChannelOwner<FrameChannel, FrameInitializer> {
return (frame as any)._object; return (frame as any)._object;
} }
static fromNullable(frame: FrameChannel | null): Frame | null { static fromNullable(frame: FrameChannel | undefined): Frame | null {
return frame ? Frame.from(frame) : null; return frame ? Frame.from(frame) : null;
} }
@ -132,7 +132,7 @@ export class Frame extends ChannelOwner<FrameChannel, FrameInitializer> {
}); });
} }
const request = navigatedEvent.newDocument ? network.Request.fromNullable(navigatedEvent.newDocument.request || null) : null; const request = navigatedEvent.newDocument ? network.Request.fromNullable(navigatedEvent.newDocument.request) : null;
const response = request ? await waiter.waitForPromise(request._finalRequest().response()) : null; const response = request ? await waiter.waitForPromise(request._finalRequest().response()) : null;
waiter.dispose(); waiter.dispose();
return response; return response;
@ -306,7 +306,8 @@ export class Frame extends ChannelOwner<FrameChannel, FrameInitializer> {
async textContent(selector: string, options: types.TimeoutOptions = {}): Promise<null|string> { async textContent(selector: string, options: types.TimeoutOptions = {}): Promise<null|string> {
return this._wrapApiCall(this._apiName('textContent'), async () => { return this._wrapApiCall(this._apiName('textContent'), async () => {
return (await this._channel.textContent({ selector, ...options })).value; const value = (await this._channel.textContent({ selector, ...options })).value;
return value === undefined ? null : value;
}); });
} }
@ -324,7 +325,8 @@ export class Frame extends ChannelOwner<FrameChannel, FrameInitializer> {
async getAttribute(selector: string, name: string, options: types.TimeoutOptions = {}): Promise<string | null> { async getAttribute(selector: string, name: string, options: types.TimeoutOptions = {}): Promise<string | null> {
return this._wrapApiCall(this._apiName('getAttribute'), async () => { return this._wrapApiCall(this._apiName('getAttribute'), async () => {
return (await this._channel.getAttribute({ selector, name, ...options })).value; const value = (await this._channel.getAttribute({ selector, name, ...options })).value;
return value === undefined ? null : value;
}); });
} }

View file

@ -14,10 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
import { JSHandleChannel, JSHandleInitializer, SerializedArgument, Channel } from '../channels'; import { JSHandleChannel, JSHandleInitializer, SerializedArgument, SerializedValue, Channel } from '../channels';
import { ElementHandle } from './elementHandle'; import { ElementHandle } from './elementHandle';
import { ChannelOwner } from './channelOwner'; import { ChannelOwner } from './channelOwner';
import { serializeAsCallArgument, parseEvaluationResultValue, SerializedValue } from '../../common/utilityScriptSerializers'; import { serializeAsCallArgument, parseEvaluationResultValue } from '../../common/utilityScriptSerializers';
type NoHandles<Arg> = Arg extends JSHandle ? never : (Arg extends object ? { [Key in keyof Arg]: NoHandles<Arg[Key]> } : Arg); type NoHandles<Arg> = Arg extends JSHandle ? never : (Arg extends object ? { [Key in keyof Arg]: NoHandles<Arg[Key]> } : Arg);
type Unboxed<Arg> = type Unboxed<Arg> =
@ -42,10 +42,6 @@ export class JSHandle<T = any> extends ChannelOwner<JSHandleChannel, JSHandleIni
return (handle as any)._object; return (handle as any)._object;
} }
static fromNullable(handle: JSHandleChannel | null): JSHandle | null {
return handle ? JSHandle.from(handle) : null;
}
constructor(parent: ChannelOwner, type: string, guid: string, initializer: JSHandleInitializer) { constructor(parent: ChannelOwner, type: string, guid: string, initializer: JSHandleInitializer) {
super(parent, type, guid, initializer); super(parent, type, guid, initializer);
this._preview = this._initializer.preview; this._preview = this._initializer.preview;
@ -112,5 +108,5 @@ export function serializeArgument(arg: any): SerializedArgument {
} }
export function parseResult(arg: SerializedValue): any { export function parseResult(arg: SerializedValue): any {
return parseEvaluationResultValue(arg, []); return parseEvaluationResultValue(arg as any, []);
} }

View file

@ -19,7 +19,7 @@ import * as types from '../../types';
import { RequestChannel, ResponseChannel, RouteChannel, RequestInitializer, ResponseInitializer, RouteInitializer } 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 { normalizeFulfillParameters, headersArrayToObject, normalizeContinueOverrides } from '../serializers'; import { normalizeFulfillParameters, headersArrayToObject, normalizeContinueOverrides, parseError } from '../serializers';
export type NetworkCookie = { export type NetworkCookie = {
name: string, name: string,
@ -54,7 +54,7 @@ export class Request extends ChannelOwner<RequestChannel, RequestInitializer> {
return (request as any)._object; return (request as any)._object;
} }
static fromNullable(request: RequestChannel | null): Request | null { static fromNullable(request: RequestChannel | undefined): Request | null {
return request ? Request.from(request) : null; return request ? Request.from(request) : null;
} }
@ -79,7 +79,7 @@ export class Request extends ChannelOwner<RequestChannel, RequestInitializer> {
} }
postData(): string | null { postData(): string | null {
return this._initializer.postData; return this._initializer.postData || null;
} }
postDataJSON(): Object | null { postDataJSON(): Object | null {
@ -174,7 +174,7 @@ export class Response extends ChannelOwner<ResponseChannel, ResponseInitializer>
return (response as any)._object; return (response as any)._object;
} }
static fromNullable(response: ResponseChannel | null): Response | null { static fromNullable(response: ResponseChannel | undefined): Response | null {
return response ? Response.from(response) : null; return response ? Response.from(response) : null;
} }
@ -204,7 +204,10 @@ export class Response extends ChannelOwner<ResponseChannel, ResponseInitializer>
} }
async finished(): Promise<Error | null> { async finished(): Promise<Error | null> {
return (await this._channel.finished()).error; const result = await this._channel.finished();
if (result.error)
return parseError(result.error);
return null;
} }
async body(): Promise<Buffer> { async body(): Promise<Buffer> {

View file

@ -20,8 +20,8 @@ import { Events } from '../../events';
import { assert, assertMaxArguments, helper, Listener } from '../../helper'; import { assert, assertMaxArguments, helper, Listener } from '../../helper';
import { TimeoutSettings } from '../../timeoutSettings'; import { TimeoutSettings } from '../../timeoutSettings';
import * as types from '../../types'; import * as types from '../../types';
import { BindingCallChannel, BindingCallInitializer, PageChannel, PageInitializer, PDFOptions } from '../channels'; import { BindingCallChannel, BindingCallInitializer, PageChannel, PageInitializer, PagePdfParams } from '../channels';
import { parseError, serializeError, headersObjectToArray } from '../serializers'; import { parseError, headersObjectToArray, serializeError } from '../serializers';
import { Accessibility } from './accessibility'; import { Accessibility } from './accessibility';
import { BrowserContext } from './browserContext'; import { BrowserContext } from './browserContext';
import { ChannelOwner } from './channelOwner'; import { ChannelOwner } from './channelOwner';
@ -69,7 +69,7 @@ export class Page extends ChannelOwner<PageChannel, PageInitializer> {
return (page as any)._object; return (page as any)._object;
} }
static fromNullable(page: PageChannel | null): Page | null { static fromNullable(page: PageChannel | undefined): Page | null {
return page ? Page.from(page) : null; return page ? Page.from(page) : null;
} }
@ -86,7 +86,7 @@ export class Page extends ChannelOwner<PageChannel, PageInitializer> {
this._mainFrame = Frame.from(initializer.mainFrame); this._mainFrame = Frame.from(initializer.mainFrame);
this._mainFrame._page = this; this._mainFrame._page = this;
this._frames.add(this._mainFrame); this._frames.add(this._mainFrame);
this._viewportSize = initializer.viewportSize; this._viewportSize = initializer.viewportSize || null;
this._closed = initializer.isClosed; this._closed = initializer.isClosed;
this._channel.on('bindingCall', ({ binding }) => this._onBinding(BindingCall.from(binding))); this._channel.on('bindingCall', ({ binding }) => this._onBinding(BindingCall.from(binding)));
@ -115,8 +115,8 @@ export class Page extends ChannelOwner<PageChannel, PageInitializer> {
} }
} }
private _onRequestFailed(request: Request, failureText: string | null) { private _onRequestFailed(request: Request, failureText: string | undefined) {
request._failureText = failureText; request._failureText = failureText || null;
this.emit(Events.Page.RequestFailed, request); this.emit(Events.Page.RequestFailed, request);
} }
@ -518,7 +518,7 @@ export class Page extends ChannelOwner<PageChannel, PageInitializer> {
async _pdf(options: types.PDFOptions = {}): Promise<Buffer> { async _pdf(options: types.PDFOptions = {}): Promise<Buffer> {
const path = options.path; const path = options.path;
const transportOptions: PDFOptions = { ...options } as PDFOptions; const transportOptions: PagePdfParams = { ...options } as PagePdfParams;
if (path) if (path)
delete (transportOptions as any).path; delete (transportOptions as any).path;
if (transportOptions.margin) if (transportOptions.margin)

1483
src/rpc/protocol.pdl Normal file

File diff suppressed because it is too large Load diff

View file

@ -21,24 +21,25 @@ import * as util from 'util';
import { TimeoutError } from '../errors'; import { TimeoutError } from '../errors';
import * as types from '../types'; import * as types from '../types';
import { helper, assert } from '../helper'; import { helper, assert } from '../helper';
import { SerializedError } from './channels';
import { serializeAsCallArgument, parseEvaluationResultValue } from '../common/utilityScriptSerializers';
export function serializeError(e: any): SerializedError {
export function serializeError(e: any): types.Error {
if (helper.isError(e)) if (helper.isError(e))
return { message: e.message, stack: e.stack, name: e.name }; return { error: { message: e.message, stack: e.stack, name: e.name } };
return { value: e }; return { value: serializeAsCallArgument(e, value => ({ fallThrough: value })) };
} }
export function parseError(error: types.Error): any { export function parseError(error: SerializedError): Error {
if (error.message === undefined) if (!error.error)
return error.value; return parseEvaluationResultValue(error.value as any, []);
if (error.name === 'TimeoutError') { if (error.error.name === 'TimeoutError') {
const e = new TimeoutError(error.message); const e = new TimeoutError(error.error.message);
e.stack = error.stack; e.stack = error.error.stack || '';
return e; return e;
} }
const e = new Error(error.message); const e = new Error(error.error.message);
e.stack = error.stack; e.stack = error.error.stack || '';
return e; return e;
} }

View file

@ -19,7 +19,7 @@ import { BrowserContextBase, BrowserContext } from '../../browserContext';
import { Events } from '../../events'; import { Events } from '../../events';
import { Dispatcher, DispatcherScope, lookupDispatcher } from './dispatcher'; import { Dispatcher, DispatcherScope, lookupDispatcher } from './dispatcher';
import { PageDispatcher, BindingCallDispatcher, WorkerDispatcher } from './pageDispatcher'; import { PageDispatcher, BindingCallDispatcher, WorkerDispatcher } from './pageDispatcher';
import { PageChannel, BrowserContextChannel, BrowserContextInitializer, CDPSessionChannel } from '../channels'; import { PageChannel, BrowserContextChannel, BrowserContextInitializer, CDPSessionChannel, BrowserContextSetGeolocationParams, BrowserContextSetHTTPCredentialsParams } from '../channels';
import { RouteDispatcher, RequestDispatcher } from './networkDispatchers'; import { RouteDispatcher, RequestDispatcher } from './networkDispatchers';
import { CRBrowserContext } from '../../chromium/crBrowser'; import { CRBrowserContext } from '../../chromium/crBrowser';
import { CDPSessionDispatcher } from './cdpSessionDispatcher'; import { CDPSessionDispatcher } from './cdpSessionDispatcher';
@ -91,8 +91,8 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, Browser
await this._context.clearPermissions(); await this._context.clearPermissions();
} }
async setGeolocation(params: { geolocation: types.Geolocation | null }): Promise<void> { async setGeolocation(params: BrowserContextSetGeolocationParams): Promise<void> {
await this._context.setGeolocation(params.geolocation); await this._context.setGeolocation(params.geolocation || null);
} }
async setExtraHTTPHeaders(params: { headers: types.HeadersArray }): Promise<void> { async setExtraHTTPHeaders(params: { headers: types.HeadersArray }): Promise<void> {
@ -103,8 +103,8 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, Browser
await this._context.setOffline(params.offline); await this._context.setOffline(params.offline);
} }
async setHTTPCredentials(params: { httpCredentials: types.Credentials | null }): Promise<void> { async setHTTPCredentials(params: BrowserContextSetHTTPCredentialsParams): Promise<void> {
await this._context.setHTTPCredentials(params.httpCredentials); await this._context.setHTTPCredentials(params.httpCredentials || null);
} }
async addInitScript(params: { source: string }): Promise<void> { async addInitScript(params: { source: string }): Promise<void> {

View file

@ -17,7 +17,7 @@
import { Browser, BrowserBase } from '../../browser'; import { Browser, BrowserBase } from '../../browser';
import { BrowserContextBase } from '../../browserContext'; import { BrowserContextBase } from '../../browserContext';
import { Events } from '../../events'; import { Events } from '../../events';
import { BrowserChannel, BrowserContextChannel, BrowserInitializer, CDPSessionChannel, Binary, BrowserContextOptions } from '../channels'; import { BrowserChannel, BrowserContextChannel, BrowserInitializer, CDPSessionChannel, Binary, BrowserNewContextParams } from '../channels';
import { BrowserContextDispatcher } from './browserContextDispatcher'; import { BrowserContextDispatcher } from './browserContextDispatcher';
import { CDPSessionDispatcher } from './cdpSessionDispatcher'; import { CDPSessionDispatcher } from './cdpSessionDispatcher';
import { Dispatcher, DispatcherScope } from './dispatcher'; import { Dispatcher, DispatcherScope } from './dispatcher';
@ -34,7 +34,7 @@ export class BrowserDispatcher extends Dispatcher<Browser, BrowserInitializer> i
}); });
} }
async newContext(params: BrowserContextOptions): Promise<{ context: BrowserContextChannel }> { async newContext(params: BrowserNewContextParams): Promise<{ context: BrowserContextChannel }> {
const options = { const options = {
...params, ...params,
extraHTTPHeaders: params.extraHTTPHeaders ? headersArrayToObject(params.extraHTTPHeaders) : undefined, extraHTTPHeaders: params.extraHTTPHeaders ? headersArrayToObject(params.extraHTTPHeaders) : undefined,

View file

@ -18,7 +18,7 @@ import { BrowserBase } from '../../browser';
import { BrowserTypeBase, BrowserType } from '../../server/browserType'; import { BrowserTypeBase, BrowserType } 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, BrowserTypeInitializer, BrowserServerChannel, LaunchPersistentContextOptions, LaunchOptions, LaunchServerOptions } from '../channels'; import { BrowserChannel, BrowserTypeChannel, BrowserContextChannel, BrowserTypeInitializer, BrowserServerChannel, BrowserTypeLaunchParams, BrowserTypeLaunchPersistentContextParams, BrowserTypeLaunchServerParams } 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';
@ -33,7 +33,7 @@ export class BrowserTypeDispatcher extends Dispatcher<BrowserType, BrowserTypeIn
}, true, browserType.name()); }, true, browserType.name());
} }
async launch(params: LaunchOptions): Promise<{ browser: BrowserChannel }> { async launch(params: BrowserTypeLaunchParams): Promise<{ browser: BrowserChannel }> {
const options = { const options = {
...params, ...params,
ignoreDefaultArgs: params.ignoreAllDefaultArgs ? true : params.ignoreDefaultArgs, ignoreDefaultArgs: params.ignoreAllDefaultArgs ? true : params.ignoreDefaultArgs,
@ -43,7 +43,7 @@ export class BrowserTypeDispatcher extends Dispatcher<BrowserType, BrowserTypeIn
return { browser: new BrowserDispatcher(this._scope, browser as BrowserBase) }; return { browser: new BrowserDispatcher(this._scope, browser as BrowserBase) };
} }
async launchPersistentContext(params: LaunchPersistentContextOptions): Promise<{ context: BrowserContextChannel }> { async launchPersistentContext(params: BrowserTypeLaunchPersistentContextParams): Promise<{ context: BrowserContextChannel }> {
const options = { const options = {
...params, ...params,
ignoreDefaultArgs: params.ignoreAllDefaultArgs ? true : params.ignoreDefaultArgs, ignoreDefaultArgs: params.ignoreAllDefaultArgs ? true : params.ignoreDefaultArgs,
@ -54,7 +54,7 @@ export class BrowserTypeDispatcher extends Dispatcher<BrowserType, BrowserTypeIn
return { context: new BrowserContextDispatcher(this._scope, browserContext as BrowserContextBase) }; return { context: new BrowserContextDispatcher(this._scope, browserContext as BrowserContextBase) };
} }
async launchServer(params: LaunchServerOptions): Promise<{ server: BrowserServerChannel }> { async launchServer(params: BrowserTypeLaunchServerParams): Promise<{ server: BrowserServerChannel }> {
const options = { const options = {
...params, ...params,
ignoreDefaultArgs: params.ignoreAllDefaultArgs ? true : params.ignoreDefaultArgs, ignoreDefaultArgs: params.ignoreAllDefaultArgs ? true : params.ignoreDefaultArgs,

View file

@ -31,8 +31,8 @@ export function existingDispatcher<DispatcherType>(object: any): DispatcherType
return object[dispatcherSymbol]; return object[dispatcherSymbol];
} }
export function lookupNullableDispatcher<DispatcherType>(object: any | null): DispatcherType | null { export function lookupNullableDispatcher<DispatcherType>(object: any | null): DispatcherType | undefined {
return object ? lookupDispatcher(object) : null; return object ? lookupDispatcher(object) : undefined;
} }
export class Dispatcher<Type, Initializer> extends EventEmitter implements Channel { export class Dispatcher<Type, Initializer> extends EventEmitter implements Channel {

View file

@ -27,20 +27,22 @@ export class DownloadDispatcher extends Dispatcher<Download, DownloadInitializer
}); });
} }
async path(): Promise<{ value: string | null }> { async path(): Promise<{ value?: string }> {
return { value: await this._object.path() }; const path = await this._object.path();
return { value: path || undefined };
} }
async stream(): Promise<{ stream: StreamChannel | null }> { async stream(): Promise<{ stream?: StreamChannel }> {
const stream = await this._object.createReadStream(); const stream = await this._object.createReadStream();
if (!stream) if (!stream)
return { stream: null }; return {};
await new Promise(f => stream.on('readable', f)); await new Promise(f => stream.on('readable', f));
return { stream: new StreamDispatcher(this._scope, stream) }; return { stream: new StreamDispatcher(this._scope, stream) };
} }
async failure(): Promise<{ error: string | null }> { async failure(): Promise<{ error?: string }> {
return { error: await this._object.failure() }; const error = await this._object.failure();
return { error: error || undefined };
} }
async delete(): Promise<void> { async delete(): Promise<void> {

View file

@ -16,21 +16,26 @@
import { Dispatcher, DispatcherScope, lookupDispatcher } from './dispatcher'; import { Dispatcher, DispatcherScope, lookupDispatcher } from './dispatcher';
import { Electron, ElectronApplication, ElectronEvents, ElectronPage } from '../../server/electron'; import { Electron, ElectronApplication, ElectronEvents, ElectronPage } from '../../server/electron';
import { ElectronApplicationChannel, ElectronApplicationInitializer, PageChannel, JSHandleChannel, ElectronInitializer, ElectronChannel, ElectronLaunchOptions, SerializedArgument } from '../channels'; import { ElectronApplicationChannel, ElectronApplicationInitializer, PageChannel, JSHandleChannel, ElectronInitializer, ElectronChannel, SerializedArgument, ElectronLaunchParams } from '../channels';
import { BrowserContextDispatcher } from './browserContextDispatcher'; import { BrowserContextDispatcher } from './browserContextDispatcher';
import { BrowserContextBase } from '../../browserContext'; import { BrowserContextBase } from '../../browserContext';
import { PageDispatcher } from './pageDispatcher'; import { PageDispatcher } from './pageDispatcher';
import { parseArgument, serializeResult } from './jsHandleDispatcher'; import { parseArgument, serializeResult } from './jsHandleDispatcher';
import { createHandle } from './elementHandlerDispatcher'; import { createHandle } from './elementHandlerDispatcher';
import { SerializedValue } from '../../common/utilityScriptSerializers'; import { SerializedValue } from '../../common/utilityScriptSerializers';
import { envArrayToObject } from '../serializers';
export class ElectronDispatcher extends Dispatcher<Electron, ElectronInitializer> implements ElectronChannel { export class ElectronDispatcher extends Dispatcher<Electron, ElectronInitializer> implements ElectronChannel {
constructor(scope: DispatcherScope, electron: Electron) { constructor(scope: DispatcherScope, electron: Electron) {
super(scope, electron, 'electron', {}, true); super(scope, electron, 'electron', {}, true);
} }
async launch(params: { executablePath: string } & ElectronLaunchOptions): Promise<{ electronApplication: ElectronApplicationChannel }> { async launch(params: ElectronLaunchParams): Promise<{ electronApplication: ElectronApplicationChannel }> {
const electronApplication = await this._object.launch(params.executablePath, params); const options = {
...params,
env: params.env ? envArrayToObject(params.env) : undefined,
};
const electronApplication = await this._object.launch(params.executablePath, options);
return { electronApplication: new ElectronApplicationDispatcher(this._scope, electronApplication) }; return { electronApplication: new ElectronApplicationDispatcher(this._scope, electronApplication) };
} }
} }

View file

@ -30,9 +30,9 @@ export function createHandle(scope: DispatcherScope, handle: js.JSHandle): JSHan
export class ElementHandleDispatcher extends JSHandleDispatcher implements ElementHandleChannel { export class ElementHandleDispatcher extends JSHandleDispatcher implements ElementHandleChannel {
readonly _elementHandle: ElementHandle; readonly _elementHandle: ElementHandle;
static createNullable(scope: DispatcherScope, handle: ElementHandle | null): ElementHandleDispatcher | null { static createNullable(scope: DispatcherScope, handle: ElementHandle | null): ElementHandleDispatcher | undefined {
if (!handle) if (!handle)
return null; return undefined;
return new ElementHandleDispatcher(scope, handle); return new ElementHandleDispatcher(scope, handle);
} }
@ -41,20 +41,22 @@ export class ElementHandleDispatcher extends JSHandleDispatcher implements Eleme
this._elementHandle = elementHandle; this._elementHandle = elementHandle;
} }
async ownerFrame(): Promise<{ frame: FrameChannel | null }> { async ownerFrame(): Promise<{ frame?: FrameChannel }> {
return { frame: lookupNullableDispatcher<FrameDispatcher>(await this._elementHandle.ownerFrame()) }; return { frame: lookupNullableDispatcher<FrameDispatcher>(await this._elementHandle.ownerFrame()) };
} }
async contentFrame(): Promise<{ frame: FrameChannel | null }> { async contentFrame(): Promise<{ frame?: FrameChannel }> {
return { frame: lookupNullableDispatcher<FrameDispatcher>(await this._elementHandle.contentFrame()) }; return { frame: lookupNullableDispatcher<FrameDispatcher>(await this._elementHandle.contentFrame()) };
} }
async getAttribute(params: { name: string }): Promise<{ value: string | null }> { async getAttribute(params: { name: string }): Promise<{ value?: string }> {
return { value: await this._elementHandle.getAttribute(params.name) }; const value = await this._elementHandle.getAttribute(params.name);
return { value: value === null ? undefined : value };
} }
async textContent(): Promise<{ value: string | null }> { async textContent(): Promise<{ value?: string }> {
return { value: await this._elementHandle.textContent() }; const value = await this._elementHandle.textContent();
return { value: value === null ? undefined : value };
} }
async innerText(): Promise<{ value: string }> { async innerText(): Promise<{ value: string }> {
@ -121,17 +123,18 @@ export class ElementHandleDispatcher extends JSHandleDispatcher implements Eleme
await this._elementHandle.uncheck(params); await this._elementHandle.uncheck(params);
} }
async boundingBox(): Promise<{ value: types.Rect | null }> { async boundingBox(): Promise<{ value?: types.Rect }> {
return { value: await this._elementHandle.boundingBox() }; const value = await this._elementHandle.boundingBox();
return { value: value || undefined };
} }
async screenshot(params: types.ElementScreenshotOptions): Promise<{ binary: Binary }> { async screenshot(params: types.ElementScreenshotOptions): Promise<{ binary: Binary }> {
return { binary: (await this._elementHandle.screenshot(params)).toString('base64') }; return { binary: (await this._elementHandle.screenshot(params)).toString('base64') };
} }
async querySelector(params: { selector: string }): Promise<{ element: ElementHandleChannel | null }> { async querySelector(params: { selector: string }): Promise<{ element?: ElementHandleChannel }> {
const handle = await this._elementHandle.$(params.selector); const handle = await this._elementHandle.$(params.selector);
return { element: handle ? new ElementHandleDispatcher(this._scope, handle) : null }; return { element: handle ? new ElementHandleDispatcher(this._scope, handle) : undefined };
} }
async querySelectorAll(params: { selector: string }): Promise<{ elements: ElementHandleChannel[] }> { async querySelectorAll(params: { selector: string }): Promise<{ elements: ElementHandleChannel[] }> {

View file

@ -53,11 +53,11 @@ export class FrameDispatcher extends Dispatcher<Frame, FrameInitializer> impleme
}); });
} }
async goto(params: { url: string } & types.GotoOptions): Promise<{ response: ResponseChannel | null }> { async goto(params: { url: string } & types.GotoOptions): Promise<{ response?: ResponseChannel }> {
return { response: lookupNullableDispatcher<ResponseDispatcher>(await this._frame.goto(params.url, params)) }; return { response: lookupNullableDispatcher<ResponseDispatcher>(await this._frame.goto(params.url, params)) };
} }
async waitForNavigation(params: types.WaitForNavigationOptions): Promise<{ response: ResponseChannel | null }> { async waitForNavigation(params: types.WaitForNavigationOptions): Promise<{ response?: ResponseChannel }> {
return { response: lookupNullableDispatcher<ResponseDispatcher>(await this._frame.waitForNavigation(params)) }; return { response: lookupNullableDispatcher<ResponseDispatcher>(await this._frame.waitForNavigation(params)) };
} }
@ -73,7 +73,7 @@ export class FrameDispatcher extends Dispatcher<Frame, FrameInitializer> impleme
return { handle: createHandle(this._scope, await this._frame._evaluateExpressionHandle(params.expression, params.isFunction, parseArgument(params.arg))) }; return { handle: createHandle(this._scope, await this._frame._evaluateExpressionHandle(params.expression, params.isFunction, parseArgument(params.arg))) };
} }
async waitForSelector(params: { selector: string } & types.WaitForElementOptions): Promise<{ element: ElementHandleChannel | null }> { async waitForSelector(params: { selector: string } & types.WaitForElementOptions): Promise<{ element?: ElementHandleChannel }> {
return { element: ElementHandleDispatcher.createNullable(this._scope, await this._frame.waitForSelector(params.selector, params)) }; return { element: ElementHandleDispatcher.createNullable(this._scope, await this._frame.waitForSelector(params.selector, params)) };
} }
@ -89,7 +89,7 @@ export class FrameDispatcher extends Dispatcher<Frame, FrameInitializer> impleme
return { value: serializeResult(await this._frame._$$evalExpression(params.selector, params.expression, params.isFunction, parseArgument(params.arg))) }; return { value: serializeResult(await this._frame._$$evalExpression(params.selector, params.expression, params.isFunction, parseArgument(params.arg))) };
} }
async querySelector(params: { selector: string }): Promise<{ element: ElementHandleChannel | null }> { async querySelector(params: { selector: string }): Promise<{ element?: ElementHandleChannel }> {
return { element: ElementHandleDispatcher.createNullable(this._scope, await this._frame.$(params.selector)) }; return { element: ElementHandleDispatcher.createNullable(this._scope, await this._frame.$(params.selector)) };
} }
@ -130,8 +130,9 @@ export class FrameDispatcher extends Dispatcher<Frame, FrameInitializer> impleme
await this._frame.focus(params.selector, params); await this._frame.focus(params.selector, params);
} }
async textContent(params: { selector: string } & types.TimeoutOptions): Promise<{ value: string | null }> { async textContent(params: { selector: string } & types.TimeoutOptions): Promise<{ value?: string }> {
return { value: await this._frame.textContent(params.selector, params) }; const value = await this._frame.textContent(params.selector, params);
return { value: value === null ? undefined : value };
} }
async innerText(params: { selector: string } & types.TimeoutOptions): Promise<{ value: string }> { async innerText(params: { selector: string } & types.TimeoutOptions): Promise<{ value: string }> {
@ -142,8 +143,9 @@ export class FrameDispatcher extends Dispatcher<Frame, FrameInitializer> impleme
return { value: await this._frame.innerHTML(params.selector, params) }; return { value: await this._frame.innerHTML(params.selector, params) };
} }
async getAttribute(params: { selector: string, name: string } & types.TimeoutOptions): Promise<{ value: string | null }> { async getAttribute(params: { selector: string, name: string } & types.TimeoutOptions): Promise<{ value?: string }> {
return { value: await this._frame.getAttribute(params.selector, params.name, params) }; const value = await this._frame.getAttribute(params.selector, params.name, params);
return { value: value === null ? undefined : value };
} }
async hover(params: { selector: string } & types.PointerActionOptions & types.TimeoutOptions & { force?: boolean }): Promise<void> { async hover(params: { selector: string } & types.PointerActionOptions & types.TimeoutOptions & { force?: boolean }): Promise<void> {

View file

@ -63,7 +63,7 @@ export class JSHandleDispatcher extends Dispatcher<js.JSHandle, JSHandleInitiali
// Generic channel parser converts guids to JSHandleDispatchers, // Generic channel parser converts guids to JSHandleDispatchers,
// and this function takes care of coverting them into underlying JSHandles. // and this function takes care of coverting them into underlying JSHandles.
export function parseArgument(arg: SerializedArgument): any { export function parseArgument(arg: SerializedArgument): any {
return parseEvaluationResultValue(arg.value, arg.handles.map(arg => (arg as JSHandleDispatcher)._object)); return parseEvaluationResultValue(arg.value as any, arg.handles.map(arg => (arg as JSHandleDispatcher)._object));
} }
export function serializeResult(arg: any): SerializedValue { export function serializeResult(arg: any): SerializedValue {

View file

@ -15,10 +15,10 @@
*/ */
import { Request, Response, Route } from '../../network'; import { Request, Response, Route } from '../../network';
import { RequestChannel, ResponseChannel, RouteChannel, ResponseInitializer, RequestInitializer, RouteInitializer, Binary } from '../channels'; import { RequestChannel, ResponseChannel, RouteChannel, ResponseInitializer, RequestInitializer, RouteInitializer, Binary, SerializedError } from '../channels';
import { Dispatcher, DispatcherScope, lookupNullableDispatcher, existingDispatcher } from './dispatcher'; import { Dispatcher, DispatcherScope, lookupNullableDispatcher, existingDispatcher } from './dispatcher';
import { FrameDispatcher } from './frameDispatcher'; import { FrameDispatcher } from './frameDispatcher';
import { headersObjectToArray, headersArrayToObject } from '../serializers'; import { headersObjectToArray, headersArrayToObject, serializeError } from '../serializers';
import * as types from '../../types'; import * as types from '../../types';
export class RequestDispatcher extends Dispatcher<Request, RequestInitializer> implements RequestChannel { export class RequestDispatcher extends Dispatcher<Request, RequestInitializer> implements RequestChannel {
@ -28,24 +28,25 @@ export class RequestDispatcher extends Dispatcher<Request, RequestInitializer> i
return result || new RequestDispatcher(scope, request); return result || new RequestDispatcher(scope, request);
} }
static fromNullable(scope: DispatcherScope, request: Request | null): RequestDispatcher | null { static fromNullable(scope: DispatcherScope, request: Request | null): RequestDispatcher | undefined {
return request ? RequestDispatcher.from(scope, request) : null; return request ? RequestDispatcher.from(scope, request) : undefined;
} }
private constructor(scope: DispatcherScope, request: Request) { private constructor(scope: DispatcherScope, request: Request) {
const postData = request.postData();
super(scope, request, 'request', { super(scope, request, 'request', {
frame: FrameDispatcher.from(scope, request.frame()), 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: postData === null ? undefined : postData,
headers: headersObjectToArray(request.headers()), headers: headersObjectToArray(request.headers()),
isNavigationRequest: request.isNavigationRequest(), isNavigationRequest: request.isNavigationRequest(),
redirectedFrom: RequestDispatcher.fromNullable(scope, request.redirectedFrom()), redirectedFrom: RequestDispatcher.fromNullable(scope, request.redirectedFrom()),
}); });
} }
async response(): Promise<{ response: ResponseChannel | null }> { async response(): Promise<{ response?: ResponseChannel }> {
return { response: lookupNullableDispatcher<ResponseDispatcher>(await this._object.response()) }; return { response: lookupNullableDispatcher<ResponseDispatcher>(await this._object.response()) };
} }
} }
@ -63,8 +64,9 @@ export class ResponseDispatcher extends Dispatcher<Response, ResponseInitializer
}); });
} }
async finished(): Promise<{ error: Error | null }> { async finished(): Promise<{ error?: SerializedError }> {
return { error: await this._object.finished() }; const error = await this._object.finished();
return { error: error ? serializeError(error) : undefined };
} }
async body(): Promise<{ binary: Binary }> { async body(): Promise<{ binary: Binary }> {

View file

@ -20,7 +20,7 @@ import { Frame } from '../../frames';
import { Request } from '../../network'; import { Request } from '../../network';
import { Page, Worker } from '../../page'; import { Page, Worker } from '../../page';
import * as types from '../../types'; import * as types from '../../types';
import { BindingCallChannel, BindingCallInitializer, ElementHandleChannel, PageChannel, PageInitializer, ResponseChannel, WorkerInitializer, WorkerChannel, JSHandleChannel, Binary, PDFOptions, SerializedArgument } from '../channels'; import { BindingCallChannel, BindingCallInitializer, ElementHandleChannel, PageChannel, PageInitializer, ResponseChannel, WorkerInitializer, WorkerChannel, JSHandleChannel, Binary, SerializedArgument, PagePdfParams, SerializedError } from '../channels';
import { Dispatcher, DispatcherScope, lookupDispatcher, lookupNullableDispatcher } from './dispatcher'; import { Dispatcher, DispatcherScope, lookupDispatcher, lookupNullableDispatcher } from './dispatcher';
import { parseError, serializeError, headersArrayToObject } from '../serializers'; import { parseError, serializeError, headersArrayToObject } from '../serializers';
import { ConsoleMessageDispatcher } from './consoleMessageDispatcher'; import { ConsoleMessageDispatcher } from './consoleMessageDispatcher';
@ -42,7 +42,7 @@ export class PageDispatcher extends Dispatcher<Page, PageInitializer> implements
// If we split pageCreated and pageReady, there should be no main frame during pageCreated. // If we split pageCreated and pageReady, there should be no main frame during pageCreated.
super(scope, page, 'page', { super(scope, page, 'page', {
mainFrame: FrameDispatcher.from(scope, page.mainFrame()), mainFrame: FrameDispatcher.from(scope, page.mainFrame()),
viewportSize: page.viewportSize(), viewportSize: page.viewportSize() || undefined,
isClosed: page.isClosed() isClosed: page.isClosed()
}); });
this._page = page; this._page = page;
@ -79,7 +79,7 @@ export class PageDispatcher extends Dispatcher<Page, PageInitializer> implements
this._page.setDefaultTimeout(params.timeout); this._page.setDefaultTimeout(params.timeout);
} }
async opener(): Promise<{ page: PageChannel | null }> { async opener(): Promise<{ page?: PageChannel }> {
return { page: lookupNullableDispatcher<PageDispatcher>(await this._page.opener()) }; return { page: lookupNullableDispatcher<PageDispatcher>(await this._page.opener()) };
} }
@ -95,15 +95,15 @@ export class PageDispatcher extends Dispatcher<Page, PageInitializer> implements
await this._page.setExtraHTTPHeaders(headersArrayToObject(params.headers)); await this._page.setExtraHTTPHeaders(headersArrayToObject(params.headers));
} }
async reload(params: types.NavigateOptions): Promise<{ response: ResponseChannel | null }> { async reload(params: types.NavigateOptions): Promise<{ response?: ResponseChannel }> {
return { response: lookupNullableDispatcher<ResponseDispatcher>(await this._page.reload(params)) }; return { response: lookupNullableDispatcher<ResponseDispatcher>(await this._page.reload(params)) };
} }
async goBack(params: types.NavigateOptions): Promise<{ response: ResponseChannel | null }> { async goBack(params: types.NavigateOptions): Promise<{ response?: ResponseChannel }> {
return { response: lookupNullableDispatcher<ResponseDispatcher>(await this._page.goBack(params)) }; return { response: lookupNullableDispatcher<ResponseDispatcher>(await this._page.goBack(params)) };
} }
async goForward(params: types.NavigateOptions): Promise<{ response: ResponseChannel | null }> { async goForward(params: types.NavigateOptions): Promise<{ response?: ResponseChannel }> {
return { response: lookupNullableDispatcher<ResponseDispatcher>(await this._page.goForward(params)) }; return { response: lookupNullableDispatcher<ResponseDispatcher>(await this._page.goForward(params)) };
} }
@ -180,15 +180,15 @@ export class PageDispatcher extends Dispatcher<Page, PageInitializer> implements
await this._page.mouse.click(params.x, params.y, params); await this._page.mouse.click(params.x, params.y, params);
} }
async accessibilitySnapshot(params: { interestingOnly?: boolean, root?: ElementHandleChannel }): Promise<{ rootAXNode: types.SerializedAXNode | null }> { async accessibilitySnapshot(params: { interestingOnly?: boolean, root?: ElementHandleChannel }): Promise<{ rootAXNode?: types.SerializedAXNode }> {
const rootAXNode = await this._page.accessibility.snapshot({ const rootAXNode = await this._page.accessibility.snapshot({
interestingOnly: params.interestingOnly, interestingOnly: params.interestingOnly,
root: params.root ? (params.root as ElementHandleDispatcher)._elementHandle : undefined root: params.root ? (params.root as ElementHandleDispatcher)._elementHandle : undefined
}); });
return { rootAXNode }; return { rootAXNode: rootAXNode || undefined };
} }
async pdf(params: PDFOptions): Promise<{ pdf: Binary }> { async pdf(params: PagePdfParams): Promise<{ pdf: Binary }> {
if (!this._page.pdf) if (!this._page.pdf)
throw new Error('PDF generation is only supported for Headless Chromium'); throw new Error('PDF generation is only supported for Headless Chromium');
const buffer = await this._page.pdf(params); const buffer = await this._page.pdf(params);
@ -263,11 +263,11 @@ export class BindingCallDispatcher extends Dispatcher<{}, BindingCallInitializer
return this._promise; return this._promise;
} }
resolve(params: { result: SerializedArgument }) { async resolve(params: { result: SerializedArgument }) {
this._resolve!(parseArgument(params.result)); this._resolve!(parseArgument(params.result));
} }
reject(params: { error: types.Error }) { async reject(params: { error: SerializedError }) {
this._reject!(parseError(params.error)); this._reject!(parseError(params.error));
} }
} }

View file

@ -34,15 +34,14 @@ import { EventEmitter } from 'events';
import { helper } from '../helper'; import { helper } from '../helper';
import { LoggerSink } from '../loggerSink'; import { LoggerSink } from '../loggerSink';
type ElectronLaunchOptions = { export type ElectronLaunchOptionsBase = {
args?: string[], args?: string[],
cwd?: string, cwd?: string,
env?: {[key: string]: string|number|boolean}, env?: types.Env,
handleSIGINT?: boolean, handleSIGINT?: boolean,
handleSIGTERM?: boolean, handleSIGTERM?: boolean,
handleSIGHUP?: boolean, handleSIGHUP?: boolean,
timeout?: number, timeout?: number,
logger?: LoggerSink,
}; };
export const ElectronEvents = { export const ElectronEvents = {
@ -165,7 +164,7 @@ export class ElectronApplication extends EventEmitter {
} }
export class Electron { export class Electron {
async launch(executablePath: string, options: ElectronLaunchOptions = {}): Promise<ElectronApplication> { async launch(executablePath: string, options: ElectronLaunchOptionsBase & { logger?: LoggerSink } = {}): Promise<ElectronApplication> {
const { const {
args = [], args = [],
env = process.env, env = process.env,

View file

@ -348,8 +348,7 @@ export type ConsoleMessageLocation = {
}; };
export type Error = { export type Error = {
message?: string, message: string,
name?: string, name: string,
stack?: string, stack?: string,
value?: any
}; };

217
utils/generate_channels.js Executable file
View file

@ -0,0 +1,217 @@
#!/usr/bin/env node
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const fs = require('fs');
const path = require('path');
const channels = new Set();
function tokenize(source) {
const lines = source.split('\n').filter(line => {
const trimmed = line.trim();
return !!trimmed && trimmed[0] != '#';
});
const stack = [{ indent: -1, list: [], words: '' }];
for (const line of lines) {
const indent = line.length - line.trimLeft().length;
const o = { indent, list: [], words: line.split(' ').filter(word => !!word) };
let current = stack[stack.length - 1];
while (indent <= current.indent) {
stack.pop();
current = stack[stack.length - 1];
}
current.list.push(o);
stack.push(o);
}
return stack[0].list;
}
function raise(item) {
throw new Error(item.words.join(' '));
}
function titleCase(name) {
return name[0].toUpperCase() + name.substring(1);
}
function inlineType(type, item, indent) {
const array = type.endsWith('[]');
if (array)
type = type.substring(0, type.length - 2);
let inner = '';
if (type === 'enum') {
const literals = item.list.map(literal => {
if (literal.words.length > 1 || literal.list.length)
raise(literal);
return literal.words[0];
});
inner = literals.map(literal => `'${literal}'`).join(' | ');
if (array)
inner = `(${inner})`;
} else if (['string', 'boolean', 'number', 'undefined'].includes(type)) {
inner = type;
} else if (type === 'object') {
inner = `{\n${properties(item, indent + ' ')}\n${indent}}`;
} else if (type === 'binary') {
inner = 'Binary';
} else if (type === 'Error') {
inner = 'SerializedError';
} else if (channels.has(type)) {
inner = type + 'Channel';
} else {
inner = type;
}
return inner + (array ? '[]' : '');
}
function properties(item, indent) {
const result = [];
for (const prop of item.list) {
if (prop.words.length !== 2)
raise(prop);
let name = prop.words[0];
if (!name.endsWith(':'))
raise(item);
name = name.substring(0, name.length - 1);
const optional = name.endsWith('?');
if (optional)
name = name.substring(0, name.length - 1);
result.push(`${indent}${name}${optional ? '?' : ''}: ${inlineType(prop.words[1], prop, indent)},`);
}
return result.join('\n');
}
function objectType(name, item, indent) {
if (!item.list.length)
return `export type ${name} = {};`;
return `export type ${name} = {\n${properties(item, indent)}\n};`
}
const result = [
`/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// This file is generated by ${path.basename(__filename)}, do not edit manually.
import { EventEmitter } from 'events';
export type Binary = string;
export interface Channel extends EventEmitter {
}
`];
const pdl = fs.readFileSync(path.join(__dirname, '..', 'src', 'rpc', 'protocol.pdl'), 'utf-8');
const list = tokenize(pdl);
for (const item of list) {
if (item.words[0] === 'interface')
channels.add(item.words[1]);
}
for (const item of list) {
if (item.words[0] === 'union') {
if (item.words.length !== 2)
raise(item);
result.push(`export type ${item.words[1]} = ${item.list.map(clause => {
if (clause.words.length !== 1)
raise(clause);
return inlineType(clause.words[0], clause, ' ');
}).join(' | ')};`);
} else if (item.words[0] === 'type') {
if (item.words.length !== 2)
raise(item);
result.push(`export type ${item.words[1]} = {`);
result.push(properties(item, ' '));
result.push(`};`);
} else if (item.words[0] === 'interface') {
const channelName = item.words[1];
result.push(`// ----------- ${channelName} -----------`);
const init = item.list.find(i => i.words[0] === 'initializer');
if (init && init.words.length > 1)
raise(init);
result.push(objectType(channelName + 'Initializer', init || { list: [] }, ' '));
let extendsName = 'Channel';
if (item.words.length === 4 && item.words[2] === 'extends')
extendsName = item.words[3] + 'Channel';
else if (item.words.length !== 2)
raise(item);
result.push(`export interface ${channelName}Channel extends ${extendsName} {`);
const types = new Map();
for (const method of item.list) {
if (method === init)
continue;
if (method.words[0] === 'command') {
if (method.words.length !== 2)
raise(method);
const methodName = method.words[1];
const parameters = method.list.find(i => i.words[0] === 'parameters');
const paramsName = `${channelName}${titleCase(methodName)}Params`;
types.set(paramsName, parameters || { list: [] });
const returns = method.list.find(i => i.words[0] === 'returns');
const resultName = `${channelName}${titleCase(methodName)}Result`;
types.set(resultName, returns);
result.push(` ${methodName}(params${parameters ? '' : '?'}: ${paramsName}): Promise<${resultName}>;`);
} else if (method.words[0] === 'event') {
if (method.words.length !== 2)
raise(method);
const eventName = method.words[1];
const parameters = method.list.find(i => i.words[0] === 'parameters');
const paramsName = `${channelName}${titleCase(eventName)}Event`;
types.set(paramsName, parameters || { list: [] });
result.push(` on(event: '${eventName}', callback: (params: ${paramsName}) => void): this;`);
} else {
raise(method);
}
}
result.push(`}`);
for (const [name, item] of types) {
if (!item)
result.push(`export type ${name} = void;`);
else
result.push(objectType(name, item, ' '));
}
} else {
raise(item);
}
result.push(``);
}
fs.writeFileSync(path.join(__dirname, '..', 'src', 'rpc', 'channels.ts'), result.join('\n'), 'utf-8');