chore: introduce internal protocol
This commit is contained in:
parent
492304be41
commit
d68f57dba8
116
src/protocol/browser.ts
Normal file
116
src/protocol/browser.ts
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
/**
|
||||
* Copyright 2017 Google Inc. All rights reserved.
|
||||
* Modifications copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { helper } from '../helper';
|
||||
import { ClientConnection } from './connection';
|
||||
import * as platform from '../platform';
|
||||
import { ConnectionTransport } from '../transport';
|
||||
import { Events } from '../events';
|
||||
import * as types from '../types';
|
||||
|
||||
export type BrowserContextOptions = {
|
||||
viewport?: types.Viewport | null,
|
||||
// ignoreHTTPSErrors?: boolean,
|
||||
// javaScriptEnabled?: boolean,
|
||||
// bypassCSP?: boolean,
|
||||
// userAgent?: string,
|
||||
// timezoneId?: string,
|
||||
geolocation?: types.Geolocation,
|
||||
// permissions?: { [key: string]: string[] };
|
||||
};
|
||||
|
||||
export class Browser extends platform.EventEmitter {
|
||||
readonly _connection: ClientConnection;
|
||||
readonly _browserContexts = new Map<string, BrowserContext>();
|
||||
|
||||
constructor(transport: ConnectionTransport) {
|
||||
super();
|
||||
this._connection = new ClientConnection(transport, () => this.emit(Events.Browser.Disconnected));
|
||||
}
|
||||
|
||||
async newContext(options: BrowserContextOptions = {}): Promise<BrowserContext> {
|
||||
options = { ...options };
|
||||
if (!options.viewport && options.viewport !== null)
|
||||
options.viewport = { width: 800, height: 600 };
|
||||
if (options.viewport)
|
||||
options.viewport = { ...options.viewport };
|
||||
if (options.geolocation)
|
||||
options.geolocation = verifyGeolocation(options.geolocation);
|
||||
|
||||
const { contextId } = await this._connection.send('BrowserContext.create', {
|
||||
viewportSize: options.viewport ? { width: options.viewport.width, height: options.viewport.height } : undefined,
|
||||
geolocation: options.geolocation,
|
||||
});
|
||||
return new BrowserContext(this, contextId, options);
|
||||
}
|
||||
|
||||
async disconnect() {
|
||||
const disconnected = new Promise(f => this.once(Events.Browser.Disconnected, f));
|
||||
this._connection.close();
|
||||
await disconnected;
|
||||
}
|
||||
|
||||
isConnected(): boolean {
|
||||
return !this._connection.isClosed();
|
||||
}
|
||||
}
|
||||
|
||||
export class BrowserContext {
|
||||
readonly _browser: Browser;
|
||||
readonly _browserContextId: string;
|
||||
readonly _options: BrowserContextOptions;
|
||||
|
||||
private _closed = false;
|
||||
|
||||
constructor(browser: Browser, browserContextId: string, options: BrowserContextOptions) {
|
||||
this._browser = browser;
|
||||
this._browserContextId = browserContextId;
|
||||
this._options = options;
|
||||
this._browser._browserContexts.set(this._browserContextId, this);
|
||||
}
|
||||
|
||||
async setGeolocation(geolocation: types.Geolocation | null): Promise<void> {
|
||||
if (geolocation)
|
||||
geolocation = verifyGeolocation(geolocation);
|
||||
this._options.geolocation = geolocation || undefined;
|
||||
await this._browser._connection.send('BrowserContext.setGeolocation', {
|
||||
contextId: this._browserContextId,
|
||||
geolocation: this._options.geolocation,
|
||||
});
|
||||
}
|
||||
|
||||
async close() {
|
||||
if (this._closed)
|
||||
return;
|
||||
this._closed = true;
|
||||
this._browser._browserContexts.delete(this._browserContextId);
|
||||
await this._browser._connection.send('BrowserContext.destroy', { contextId: this._browserContextId });
|
||||
}
|
||||
}
|
||||
|
||||
function verifyGeolocation(geolocation: types.Geolocation): types.Geolocation {
|
||||
const result = { ...geolocation };
|
||||
result.accuracy = result.accuracy || 0;
|
||||
const { longitude, latitude, accuracy } = result;
|
||||
if (!helper.isNumber(longitude) || longitude < -180 || longitude > 180)
|
||||
throw new Error(`Invalid longitude "${longitude}": precondition -180 <= LONGITUDE <= 180 failed.`);
|
||||
if (!helper.isNumber(latitude) || latitude < -90 || latitude > 90)
|
||||
throw new Error(`Invalid latitude "${latitude}": precondition -90 <= LATITUDE <= 90 failed.`);
|
||||
if (!helper.isNumber(accuracy) || accuracy < 0)
|
||||
throw new Error(`Invalid accuracy "${accuracy}": precondition 0 <= ACCURACY failed.`);
|
||||
return result;
|
||||
}
|
||||
205
src/protocol/connection.ts
Normal file
205
src/protocol/connection.ts
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { assert } from '../helper';
|
||||
import * as platform from '../platform';
|
||||
import { ConnectionTransport } from '../transport';
|
||||
import { Protocol } from './protocol';
|
||||
|
||||
const debugClient = platform.debug('pw:client');
|
||||
const debugServer = platform.debug('pw:server');
|
||||
|
||||
export interface ObjectTransport {
|
||||
send(message: any): void;
|
||||
close(): void; // Note: calling close is expected to issue onclose at some point.
|
||||
onmessage?: (message: any) => void,
|
||||
onclose?: () => void,
|
||||
}
|
||||
|
||||
export class JSONTransport implements ObjectTransport {
|
||||
private readonly _transport: ConnectionTransport;
|
||||
|
||||
onmessage?: (message: string) => void;
|
||||
onclose?: () => void;
|
||||
|
||||
constructor(transport: ConnectionTransport) {
|
||||
this._transport = transport;
|
||||
this._transport.onmessage = message => {
|
||||
if (this.onmessage)
|
||||
this.onmessage(JSON.parse(message));
|
||||
};
|
||||
this._transport.onclose = () => {
|
||||
if (this.onclose)
|
||||
this.onclose();
|
||||
};
|
||||
this.onmessage = undefined;
|
||||
this.onclose = undefined;
|
||||
}
|
||||
|
||||
send(message: any) {
|
||||
this._transport.send(JSON.stringify(message));
|
||||
}
|
||||
|
||||
close() {
|
||||
this._transport.close();
|
||||
}
|
||||
}
|
||||
|
||||
export class ClientConnection extends platform.EventEmitter {
|
||||
private _lastId = 0;
|
||||
private readonly _transport: ObjectTransport;
|
||||
private _closed = false;
|
||||
private _onDisconnect: () => void;
|
||||
private readonly _callbacks = new Map<number, {resolve:(o: any) => void, reject: (e: Error) => void, error: Error, method: string}>();
|
||||
|
||||
on: <T extends keyof Protocol.Events>(event: T, listener: (payload: Protocol.Events[T]) => void) => this;
|
||||
addListener: <T extends keyof Protocol.Events>(event: T, listener: (payload: Protocol.Events[T]) => void) => this;
|
||||
off: <T extends keyof Protocol.Events>(event: T, listener: (payload: Protocol.Events[T]) => void) => this;
|
||||
removeListener: <T extends keyof Protocol.Events>(event: T, listener: (payload: Protocol.Events[T]) => void) => this;
|
||||
once: <T extends keyof Protocol.Events>(event: T, listener: (payload: Protocol.Events[T]) => void) => this;
|
||||
|
||||
constructor(transport: ObjectTransport, onDisconnect: () => void) {
|
||||
super();
|
||||
this._transport = transport;
|
||||
this._transport.onmessage = this._dispatchMessage.bind(this);
|
||||
this._transport.onclose = this._onClose.bind(this);
|
||||
this._onDisconnect = onDisconnect;
|
||||
this.on = super.on;
|
||||
this.off = super.removeListener;
|
||||
this.addListener = super.addListener;
|
||||
this.removeListener = super.removeListener;
|
||||
this.once = super.once;
|
||||
}
|
||||
|
||||
send<T extends keyof Protocol.CommandParameters>(
|
||||
method: T,
|
||||
params?: Protocol.CommandParameters[T]
|
||||
): Promise<Protocol.CommandReturnValues[T]> {
|
||||
if (this._closed)
|
||||
return Promise.reject(new Error(`Internal error (${method}): Connection has been closed.`));
|
||||
const id = ++this._lastId;
|
||||
const message = { id, method, params };
|
||||
const result = new Promise<Protocol.CommandReturnValues[T]>((resolve, reject) => {
|
||||
this._callbacks.set(id, {resolve, reject, error: new Error(), method});
|
||||
});
|
||||
debugClient('SEND ► %o', message);
|
||||
this._transport.send(message);
|
||||
return result;
|
||||
}
|
||||
|
||||
private _dispatchMessage(message: any) {
|
||||
debugClient('◀ RECV %o', message);
|
||||
if (message.id && this._callbacks.has(message.id)) {
|
||||
const callback = this._callbacks.get(message.id)!;
|
||||
this._callbacks.delete(message.id);
|
||||
if (message.error)
|
||||
callback.reject(createInternalError(callback.error, callback.method, message));
|
||||
else
|
||||
callback.resolve(message.result);
|
||||
} else {
|
||||
assert(!message.id);
|
||||
Promise.resolve().then(() => this.emit(message.method, message.params));
|
||||
}
|
||||
}
|
||||
|
||||
_onClose() {
|
||||
this._closed = true;
|
||||
this._transport.onmessage = undefined;
|
||||
this._transport.onclose = undefined;
|
||||
for (const callback of this._callbacks.values())
|
||||
callback.reject(rewriteError(callback.error, `Internal error (${callback.method}): Connection has been closed.`));
|
||||
this._callbacks.clear();
|
||||
this._onDisconnect();
|
||||
}
|
||||
|
||||
isClosed() {
|
||||
return this._closed;
|
||||
}
|
||||
|
||||
close() {
|
||||
if (!this._closed)
|
||||
this._transport.close();
|
||||
}
|
||||
}
|
||||
|
||||
export class ServerConnection {
|
||||
private readonly _transport: ObjectTransport;
|
||||
private _closed = false;
|
||||
private _onDisconnect: () => void;
|
||||
private readonly _handlers = new Map<string, (arg: any) => Promise<any>>();
|
||||
|
||||
constructor(transport: ObjectTransport, onDisconnect: () => void) {
|
||||
this._transport = transport;
|
||||
this._transport.onmessage = this._dispatchMessage.bind(this);
|
||||
this._transport.onclose = this._onClose.bind(this);
|
||||
this._onDisconnect = onDisconnect;
|
||||
}
|
||||
|
||||
on<T extends keyof Protocol.CommandParameters>(command: T, handler: (payload: Protocol.CommandParameters[T]) => Promise<Protocol.CommandReturnValues[T]>) {
|
||||
this._handlers.set(command, handler);
|
||||
}
|
||||
|
||||
send<T extends keyof Protocol.Events>(event: T, params?: Protocol.Events[T]) {
|
||||
if (this._closed)
|
||||
return Promise.reject(new Error(`Internal error (${event}): Connection has been closed.`));
|
||||
const message = { event, params };
|
||||
debugServer('SEND ► %o', message);
|
||||
this._transport.send(message);
|
||||
}
|
||||
|
||||
private async _dispatchMessage(message: any) {
|
||||
debugServer('◀ RECV %o', message);
|
||||
assert(message.id);
|
||||
let result;
|
||||
try {
|
||||
const handler = this._handlers.get(message.method);
|
||||
if (!handler)
|
||||
throw new Error(`Unknown command "${message.method}"`);
|
||||
result = { id: message.id, result: await handler(message.params) };
|
||||
} catch (e) {
|
||||
result = { id: message.id, error: { message: e.message } };
|
||||
}
|
||||
this._transport.send(result);
|
||||
}
|
||||
|
||||
_onClose() {
|
||||
this._closed = true;
|
||||
this._transport.onmessage = undefined;
|
||||
this._transport.onclose = undefined;
|
||||
this._onDisconnect();
|
||||
}
|
||||
|
||||
isClosed() {
|
||||
return this._closed;
|
||||
}
|
||||
|
||||
close() {
|
||||
if (!this._closed)
|
||||
this._transport.close();
|
||||
}
|
||||
}
|
||||
|
||||
function createInternalError(error: Error, method: string, object: { error: { message: string; data: any; }; }): Error {
|
||||
let message = `Internal error (${method}): ${object.error.message}`;
|
||||
if ('data' in object.error)
|
||||
message += ` ${object.error.data}`;
|
||||
return rewriteError(error, message);
|
||||
}
|
||||
|
||||
function rewriteError(error: Error, message: string): Error {
|
||||
error.message = message;
|
||||
return error;
|
||||
}
|
||||
231
src/protocol/protocol.pdl
Normal file
231
src/protocol/protocol.pdl
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
# 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.
|
||||
|
||||
domain Types
|
||||
type Size extends object
|
||||
properties
|
||||
integer width
|
||||
integer height
|
||||
|
||||
domain BrowserContext
|
||||
type BrowserContextId extends string
|
||||
|
||||
type Geolocation extends object
|
||||
properties
|
||||
number longitude
|
||||
number latitude
|
||||
optional number accuracy
|
||||
|
||||
command create
|
||||
parameters
|
||||
optional Types.Size viewportSize
|
||||
optional Geolocation geolocation
|
||||
returns
|
||||
BrowserContextId contextId
|
||||
|
||||
command destroy
|
||||
parameters
|
||||
BrowserContextId contextId
|
||||
|
||||
command setGeolocation
|
||||
parameters
|
||||
BrowserContextId contextId
|
||||
optional Geolocation geolocation
|
||||
|
||||
domain Frame
|
||||
type FrameId extends string
|
||||
|
||||
type FrameTree extends object
|
||||
properties
|
||||
FrameId frameId
|
||||
string url
|
||||
optional array of FrameTree childFrames
|
||||
|
||||
type Lifecycle extends string
|
||||
enum
|
||||
domcontentloaded
|
||||
load
|
||||
networkidle0
|
||||
networkidle2
|
||||
|
||||
command createPage
|
||||
parameters
|
||||
BrowserContext.BrowserContextId contextId
|
||||
optional string url
|
||||
returns
|
||||
FrameId mainFrameId
|
||||
|
||||
command destroyPage
|
||||
parameters
|
||||
FrameId mainFrameId
|
||||
|
||||
event pageCreated
|
||||
parameters
|
||||
BrowserContext.BrowserContextId contextId
|
||||
FrameTree frameTree
|
||||
|
||||
event pageDestroyed
|
||||
parameters
|
||||
FrameId mainFrameId
|
||||
|
||||
event attached
|
||||
parameters
|
||||
FrameId frameId
|
||||
FrameId parentFrameId
|
||||
|
||||
event navigated
|
||||
parameters
|
||||
FrameId frameId
|
||||
string url
|
||||
|
||||
event detached
|
||||
parameters
|
||||
FrameId frameId
|
||||
|
||||
command navigate
|
||||
parameters
|
||||
FrameId frameId
|
||||
string url
|
||||
array of Lifecycle waitUntil
|
||||
optional string referrer
|
||||
returns
|
||||
optional Network.RequestId requestId
|
||||
|
||||
domain JS
|
||||
type HandleId extends string
|
||||
|
||||
type Handle extends object
|
||||
properties
|
||||
HandleId handleId
|
||||
string asString
|
||||
boolean isDOMElement
|
||||
|
||||
type Value extends object
|
||||
properties
|
||||
optional string string
|
||||
optional number number
|
||||
optional boolean null
|
||||
optional boolean undefined
|
||||
optional boolean boolean
|
||||
optional array of Value values
|
||||
optional array of string keys
|
||||
|
||||
type CallArgument extends object
|
||||
properties
|
||||
optional Value value
|
||||
optional HandleId handleId
|
||||
|
||||
command evaluateHandle
|
||||
parameters
|
||||
string functionExpression
|
||||
array of CallArgument args
|
||||
optional Frame.FrameId frameId
|
||||
optional HandleId handleId
|
||||
returns
|
||||
Handle handle
|
||||
|
||||
command evaluate
|
||||
parameters
|
||||
string functionExpression
|
||||
array of CallArgument args
|
||||
optional Frame.FrameId frameId
|
||||
optional HandleId handleId
|
||||
returns
|
||||
Value value
|
||||
|
||||
command handleAsValue
|
||||
parameters
|
||||
HandleId handleId
|
||||
returns
|
||||
Value value
|
||||
|
||||
command disposeHandle
|
||||
parameters
|
||||
HandleId handleId
|
||||
|
||||
domain DOM
|
||||
command ownerFrame
|
||||
parameters
|
||||
JS.HandleId handleId
|
||||
returns
|
||||
optional Frame.FrameId frameId
|
||||
|
||||
command contentFrame
|
||||
parameters
|
||||
JS.HandleId handleId
|
||||
returns
|
||||
optional Frame.FrameId frameId
|
||||
|
||||
command scrollIntoViewIfNeeded
|
||||
parameters
|
||||
JS.HandleId handleId
|
||||
|
||||
domain Network
|
||||
type RequestId extends string
|
||||
|
||||
type ResourceType extends string
|
||||
enum
|
||||
document
|
||||
|
||||
type Header extends object
|
||||
properties
|
||||
string name
|
||||
string value
|
||||
|
||||
type Headers extends array of Header
|
||||
|
||||
event requestStarted
|
||||
parameters
|
||||
RequestId requestId
|
||||
Frame.FrameId frameId
|
||||
string url
|
||||
string method
|
||||
ResourceType resourceType
|
||||
Headers headers
|
||||
boolean isIntercepted
|
||||
boolean isNavigation
|
||||
optional RequestId redirectedFrom
|
||||
optional string postData
|
||||
|
||||
event responseReceived
|
||||
parameters
|
||||
RequestId requestId
|
||||
|
||||
event requestFailed
|
||||
parameters
|
||||
RequestId requestId
|
||||
string errorText
|
||||
|
||||
event requestFinished
|
||||
parameters
|
||||
RequestId requestId
|
||||
|
||||
command abortRequest
|
||||
parameters
|
||||
RequestId requestId
|
||||
string errorCode
|
||||
|
||||
command fulfillRequest
|
||||
parameters
|
||||
RequestId requestId
|
||||
integer status
|
||||
Headers headers
|
||||
string contentType
|
||||
string base64body
|
||||
|
||||
command continueRequest
|
||||
parameters
|
||||
RequestId requestId
|
||||
optional Headers headers
|
||||
|
||||
270
src/protocol/protocol.ts
Normal file
270
src/protocol/protocol.ts
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
// This is generated from /utils/protocol-types-generator/index.js
|
||||
type binary = string;
|
||||
export module Protocol {
|
||||
export module Types {
|
||||
export interface Size {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
export module BrowserContext {
|
||||
export type BrowserContextId = string;
|
||||
export interface Geolocation {
|
||||
longitude: number;
|
||||
latitude: number;
|
||||
accuracy?: number;
|
||||
}
|
||||
|
||||
|
||||
export type createParameters = {
|
||||
viewportSize?: Types.Size;
|
||||
geolocation?: Geolocation;
|
||||
}
|
||||
export type createReturnValue = {
|
||||
contextId: BrowserContextId;
|
||||
}
|
||||
export type destroyParameters = {
|
||||
contextId: BrowserContextId;
|
||||
}
|
||||
export type destroyReturnValue = {
|
||||
}
|
||||
export type setGeolocationParameters = {
|
||||
contextId: BrowserContextId;
|
||||
geolocation?: Geolocation;
|
||||
}
|
||||
export type setGeolocationReturnValue = {
|
||||
}
|
||||
}
|
||||
|
||||
export module Frame {
|
||||
export type FrameId = string;
|
||||
export interface FrameTree {
|
||||
frameId: FrameId;
|
||||
url: string;
|
||||
childFrames?: FrameTree[];
|
||||
}
|
||||
export type Lifecycle = "domcontentloaded"|"load"|"networkidle0"|"networkidle2";
|
||||
|
||||
export type pageCreatedPayload = {
|
||||
contextId: BrowserContext.BrowserContextId;
|
||||
frameTree: FrameTree;
|
||||
}
|
||||
export type pageDestroyedPayload = {
|
||||
mainFrameId: FrameId;
|
||||
}
|
||||
export type attachedPayload = {
|
||||
frameId: FrameId;
|
||||
parentFrameId: FrameId;
|
||||
}
|
||||
export type navigatedPayload = {
|
||||
frameId: FrameId;
|
||||
url: string;
|
||||
}
|
||||
export type detachedPayload = {
|
||||
frameId: FrameId;
|
||||
}
|
||||
|
||||
export type createPageParameters = {
|
||||
contextId: BrowserContext.BrowserContextId;
|
||||
url?: string;
|
||||
}
|
||||
export type createPageReturnValue = {
|
||||
mainFrameId: FrameId;
|
||||
}
|
||||
export type destroyPageParameters = {
|
||||
mainFrameId: FrameId;
|
||||
}
|
||||
export type destroyPageReturnValue = {
|
||||
}
|
||||
export type navigateParameters = {
|
||||
frameId: FrameId;
|
||||
url: string;
|
||||
waitUntil: Lifecycle[];
|
||||
referrer?: string;
|
||||
}
|
||||
export type navigateReturnValue = {
|
||||
requestId?: Network.RequestId;
|
||||
}
|
||||
}
|
||||
|
||||
export module JS {
|
||||
export type HandleId = string;
|
||||
export interface Handle {
|
||||
handleId: HandleId;
|
||||
asString: string;
|
||||
isDOMElement: boolean;
|
||||
}
|
||||
export interface Value {
|
||||
string?: string;
|
||||
number?: number;
|
||||
null?: boolean;
|
||||
undefined?: boolean;
|
||||
boolean?: boolean;
|
||||
values?: Value[];
|
||||
keys?: string[];
|
||||
}
|
||||
export interface CallArgument {
|
||||
value?: Value;
|
||||
handleId?: HandleId;
|
||||
}
|
||||
|
||||
|
||||
export type evaluateHandleParameters = {
|
||||
functionExpression: string;
|
||||
args: CallArgument[];
|
||||
frameId?: Frame.FrameId;
|
||||
handleId?: HandleId;
|
||||
}
|
||||
export type evaluateHandleReturnValue = {
|
||||
handle: Handle;
|
||||
}
|
||||
export type evaluateParameters = {
|
||||
functionExpression: string;
|
||||
args: CallArgument[];
|
||||
frameId?: Frame.FrameId;
|
||||
handleId?: HandleId;
|
||||
}
|
||||
export type evaluateReturnValue = {
|
||||
value: Value;
|
||||
}
|
||||
export type handleAsValueParameters = {
|
||||
handleId: HandleId;
|
||||
}
|
||||
export type handleAsValueReturnValue = {
|
||||
value: Value;
|
||||
}
|
||||
export type disposeHandleParameters = {
|
||||
handleId: HandleId;
|
||||
}
|
||||
export type disposeHandleReturnValue = {
|
||||
}
|
||||
}
|
||||
|
||||
export module DOM {
|
||||
|
||||
|
||||
export type ownerFrameParameters = {
|
||||
handleId: JS.HandleId;
|
||||
}
|
||||
export type ownerFrameReturnValue = {
|
||||
frameId?: Frame.FrameId;
|
||||
}
|
||||
export type contentFrameParameters = {
|
||||
handleId: JS.HandleId;
|
||||
}
|
||||
export type contentFrameReturnValue = {
|
||||
frameId?: Frame.FrameId;
|
||||
}
|
||||
export type scrollIntoViewIfNeededParameters = {
|
||||
handleId: JS.HandleId;
|
||||
}
|
||||
export type scrollIntoViewIfNeededReturnValue = {
|
||||
}
|
||||
}
|
||||
|
||||
export module Network {
|
||||
export type RequestId = string;
|
||||
export type ResourceType = "document";
|
||||
export interface Header {
|
||||
name: string;
|
||||
value: string;
|
||||
}
|
||||
export type Headers = Header[];
|
||||
|
||||
export type requestStartedPayload = {
|
||||
requestId: RequestId;
|
||||
frameId: Frame.FrameId;
|
||||
url: string;
|
||||
method: string;
|
||||
resourceType: ResourceType;
|
||||
headers: Headers;
|
||||
isIntercepted: boolean;
|
||||
isNavigation: boolean;
|
||||
redirectedFrom?: RequestId;
|
||||
postData?: string;
|
||||
}
|
||||
export type responseReceivedPayload = {
|
||||
requestId: RequestId;
|
||||
}
|
||||
export type requestFailedPayload = {
|
||||
requestId: RequestId;
|
||||
errorText: string;
|
||||
}
|
||||
export type requestFinishedPayload = {
|
||||
requestId: RequestId;
|
||||
}
|
||||
|
||||
export type abortRequestParameters = {
|
||||
requestId: RequestId;
|
||||
errorCode: string;
|
||||
}
|
||||
export type abortRequestReturnValue = {
|
||||
}
|
||||
export type fulfillRequestParameters = {
|
||||
requestId: RequestId;
|
||||
status: number;
|
||||
headers: Headers;
|
||||
contentType: string;
|
||||
base64body: string;
|
||||
}
|
||||
export type fulfillRequestReturnValue = {
|
||||
}
|
||||
export type continueRequestParameters = {
|
||||
requestId: RequestId;
|
||||
headers?: Headers;
|
||||
}
|
||||
export type continueRequestReturnValue = {
|
||||
}
|
||||
}
|
||||
|
||||
export interface Events {
|
||||
"Frame.pageCreated": Frame.pageCreatedPayload;
|
||||
"Frame.pageDestroyed": Frame.pageDestroyedPayload;
|
||||
"Frame.attached": Frame.attachedPayload;
|
||||
"Frame.navigated": Frame.navigatedPayload;
|
||||
"Frame.detached": Frame.detachedPayload;
|
||||
"Network.requestStarted": Network.requestStartedPayload;
|
||||
"Network.responseReceived": Network.responseReceivedPayload;
|
||||
"Network.requestFailed": Network.requestFailedPayload;
|
||||
"Network.requestFinished": Network.requestFinishedPayload;
|
||||
}
|
||||
export interface CommandParameters {
|
||||
"BrowserContext.create": BrowserContext.createParameters;
|
||||
"BrowserContext.destroy": BrowserContext.destroyParameters;
|
||||
"BrowserContext.setGeolocation": BrowserContext.setGeolocationParameters;
|
||||
"Frame.createPage": Frame.createPageParameters;
|
||||
"Frame.destroyPage": Frame.destroyPageParameters;
|
||||
"Frame.navigate": Frame.navigateParameters;
|
||||
"JS.evaluateHandle": JS.evaluateHandleParameters;
|
||||
"JS.evaluate": JS.evaluateParameters;
|
||||
"JS.handleAsValue": JS.handleAsValueParameters;
|
||||
"JS.disposeHandle": JS.disposeHandleParameters;
|
||||
"DOM.ownerFrame": DOM.ownerFrameParameters;
|
||||
"DOM.contentFrame": DOM.contentFrameParameters;
|
||||
"DOM.scrollIntoViewIfNeeded": DOM.scrollIntoViewIfNeededParameters;
|
||||
"Network.abortRequest": Network.abortRequestParameters;
|
||||
"Network.fulfillRequest": Network.fulfillRequestParameters;
|
||||
"Network.continueRequest": Network.continueRequestParameters;
|
||||
}
|
||||
export interface CommandReturnValues {
|
||||
"BrowserContext.create": BrowserContext.createReturnValue;
|
||||
"BrowserContext.destroy": BrowserContext.destroyReturnValue;
|
||||
"BrowserContext.setGeolocation": BrowserContext.setGeolocationReturnValue;
|
||||
"Frame.createPage": Frame.createPageReturnValue;
|
||||
"Frame.destroyPage": Frame.destroyPageReturnValue;
|
||||
"Frame.navigate": Frame.navigateReturnValue;
|
||||
"JS.evaluateHandle": JS.evaluateHandleReturnValue;
|
||||
"JS.evaluate": JS.evaluateReturnValue;
|
||||
"JS.handleAsValue": JS.handleAsValueReturnValue;
|
||||
"JS.disposeHandle": JS.disposeHandleReturnValue;
|
||||
"DOM.ownerFrame": DOM.ownerFrameReturnValue;
|
||||
"DOM.contentFrame": DOM.contentFrameReturnValue;
|
||||
"DOM.scrollIntoViewIfNeeded": DOM.scrollIntoViewIfNeededReturnValue;
|
||||
"Network.abortRequest": Network.abortRequestReturnValue;
|
||||
"Network.fulfillRequest": Network.fulfillRequestReturnValue;
|
||||
"Network.continueRequest": Network.continueRequestReturnValue;
|
||||
}
|
||||
}
|
||||
2
utils/generateInternalProtocol.js
Normal file
2
utils/generateInternalProtocol.js
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
const { generateInternalProtocol } = require('./protocol-types-generator');
|
||||
generateInternalProtocol();
|
||||
|
|
@ -216,4 +216,196 @@ function firefoxTypeToString(type, indent=' ') {
|
|||
return type['$type'];
|
||||
}
|
||||
|
||||
module.exports = {generateChromiunProtocol, generateFirefoxProtocol, generateWebKitProtocol};
|
||||
function generateInternalProtocol() {
|
||||
const pdlPath = path.join(__dirname, '..', '..', 'src', 'protocol', 'protocol.pdl');
|
||||
const tsPath = path.join(__dirname, '..', '..', 'src', 'protocol', 'protocol.ts');
|
||||
const json = pdlToJSON(fs.readFileSync(pdlPath, 'utf-8'));
|
||||
fs.writeFileSync(tsPath, jsonToTS(json));
|
||||
console.log(`Wrote protocol.ts to ${path.relative(process.cwd(), tsPath)}`);
|
||||
}
|
||||
|
||||
function pdlToJSON(pdl) {
|
||||
const lines = pdl.split('\n');
|
||||
let lineIndex = 0;
|
||||
let current;
|
||||
|
||||
function next() {
|
||||
while (lineIndex < lines.length) {
|
||||
let line = lines[lineIndex++];
|
||||
let indent = 0;
|
||||
while (line[indent] === ' ')
|
||||
indent++;
|
||||
let commentIndex = line.indexOf('#');
|
||||
if (commentIndex !== -1)
|
||||
line = line.substring(0, commentIndex);
|
||||
line = line.trim();
|
||||
if (line) {
|
||||
current = { line, indent };
|
||||
return current;
|
||||
}
|
||||
}
|
||||
current = { indent: -1, line: '' };
|
||||
return current;
|
||||
}
|
||||
|
||||
function readDomains() {
|
||||
const domains = [];
|
||||
while (current.indent !== -1)
|
||||
domains.push(readDomain());
|
||||
return domains;
|
||||
}
|
||||
|
||||
function readDomain() {
|
||||
const indent = current.indent;
|
||||
const match = current.line.match(/^domain\s+([a-zA-Z0-9_]+)$/);
|
||||
next();
|
||||
const domain = {
|
||||
domain: match[1],
|
||||
commands: [],
|
||||
events: [],
|
||||
types: [],
|
||||
};
|
||||
while (current.indent > indent) {
|
||||
if (current.line.startsWith('command'))
|
||||
domain.commands.push(readCommand());
|
||||
else if (current.line.startsWith('event'))
|
||||
domain.events.push(readEvent());
|
||||
else if (current.line.startsWith('type'))
|
||||
domain.types.push(readType());
|
||||
else
|
||||
throw new Error(`Cannot parse: ${current.line}`);
|
||||
}
|
||||
return domain;
|
||||
}
|
||||
|
||||
function readCommand() {
|
||||
const indent = current.indent;
|
||||
const match = current.line.match(/^command\s+([a-zA-Z0-9_]+)$/);
|
||||
next();
|
||||
const command = {
|
||||
name: match[1],
|
||||
parameters: [],
|
||||
returns: [],
|
||||
};
|
||||
while (current.indent > indent) {
|
||||
if (current.line === 'parameters') {
|
||||
const i = current.indent;
|
||||
next();
|
||||
while (current.indent > i)
|
||||
command.parameters.push(readProperty());
|
||||
} else if (current.line === 'returns') {
|
||||
const i = current.indent;
|
||||
next();
|
||||
while (current.indent > i)
|
||||
command.returns.push(readProperty());
|
||||
} else {
|
||||
throw new Error(`Cannot parse: ${current.line}`);
|
||||
}
|
||||
}
|
||||
return command;
|
||||
}
|
||||
|
||||
function readEvent() {
|
||||
const indent = current.indent;
|
||||
const match = current.line.match(/^event\s+([a-zA-Z0-9_]+)$/);
|
||||
next();
|
||||
const event = {
|
||||
name: match[1],
|
||||
parameters: [],
|
||||
returns: [],
|
||||
};
|
||||
while (current.indent > indent) {
|
||||
if (current.line === 'parameters') {
|
||||
const i = current.indent;
|
||||
next();
|
||||
while (current.indent > i)
|
||||
event.parameters.push(readProperty());
|
||||
} else {
|
||||
throw new Error(`Cannot parse: ${current.line}`);
|
||||
}
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
function readType() {
|
||||
const match = current.line.match(/^type\s+([a-zA-Z0-9_]+)\s+extends(.*)$/);
|
||||
const type = {
|
||||
id: match[1],
|
||||
};
|
||||
let t = type;
|
||||
const words = match[2].split(' ').filter(s => !!s);
|
||||
while (words[0] === 'array' && words[1] === 'of') {
|
||||
t.type = 'array';
|
||||
const items = {};
|
||||
t.items = items;
|
||||
t = items;
|
||||
words.shift();
|
||||
words.shift();
|
||||
}
|
||||
if (words.length !== 1)
|
||||
throw new Error(`Cannot parse: ${current.line}`);
|
||||
if (['boolean', 'integer', 'number'].includes(words[0])) {
|
||||
t.type = words[0];
|
||||
next();
|
||||
} else if (words[0] === 'object') {
|
||||
t.type = 'object';
|
||||
next();
|
||||
if (current.line !== 'properties')
|
||||
throw new Error(`Cannot parse: ${current.line}`);
|
||||
const indent = current.indent;
|
||||
next();
|
||||
t.properties = [];
|
||||
while (current.indent > indent)
|
||||
t.properties.push(readProperty());
|
||||
} else if (words[0] === 'string') {
|
||||
t.type = 'string';
|
||||
next();
|
||||
if (current.line === 'enum') {
|
||||
const indent = current.indent;
|
||||
next();
|
||||
t.enum = [];
|
||||
while (current.indent > indent) {
|
||||
if (!current.line.match(/^[a-zA-Z0-9_]+$/))
|
||||
throw new Error(`Cannot parse: ${current.line}`);
|
||||
t.enum.push(current.line);
|
||||
next();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
t.$ref = words[0];
|
||||
next();
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
function readProperty() {
|
||||
const match = current.line.match(/^(optional)?\s*(.*)\s+([a-zA-Z0-9_]+)$/);
|
||||
const prop = {
|
||||
optional: !!match[1],
|
||||
name: match[3],
|
||||
};
|
||||
let t = prop;
|
||||
const words = match[2].split(' ').filter(s => !!s);
|
||||
while (words[0] === 'array' && words[1] === 'of') {
|
||||
t.type = 'array';
|
||||
const items = {};
|
||||
t.items = items;
|
||||
t = items;
|
||||
words.shift();
|
||||
words.shift();
|
||||
}
|
||||
if (words.length !== 1)
|
||||
throw new Error(`Cannot parse: ${current.line}`);
|
||||
if (['string', 'boolean', 'integer', 'number'].includes(words[0]))
|
||||
t.type = words[0];
|
||||
else
|
||||
t.$ref = words[0];
|
||||
next();
|
||||
return prop;
|
||||
}
|
||||
|
||||
next();
|
||||
return { domains: readDomains() };
|
||||
}
|
||||
|
||||
module.exports = {generateChromiunProtocol, generateFirefoxProtocol, generateWebKitProtocol, generateInternalProtocol};
|
||||
|
|
|
|||
Loading…
Reference in a new issue