chore: use devtools-protocol package (#6172)

* chore: Use devtools-protocol package

Rather than maintain our own protocol we can instead use the devtools-protocol package and pin it to the version of Chromium that Puppeteer is shipping with.

The only changes are naming changes between the bespoke protocol that Puppeteer created and the devtools-protocol one.
This commit is contained in:
Jack Franklin 2020-07-10 11:51:52 +01:00 committed by GitHub
parent f666be3f5f
commit 31309b0e20
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 227 additions and 15882 deletions

View File

@ -6,8 +6,6 @@ node6/*
node6-test/* node6-test/*
experimental/ experimental/
lib/ lib/
src/externs.d.ts
src/protocol.d.ts
/index.d.ts /index.d.ts
# We ignore this file because it uses ES imports which we don't yet use # We ignore this file because it uses ES imports which we don't yet use
# in the Puppeteer src, so it trips up the ESLint-TypeScript parser. # in the Puppeteer src, so it trips up the ESLint-TypeScript parser.

View File

@ -63,9 +63,9 @@ jobs:
env: env:
- CHROMIUM=true - CHROMIUM=true
script: script:
- npm run compare-protocol-d-ts
- npm run lint - npm run lint
- npm run ensure-new-docs-up-to-date - npm run ensure-new-docs-up-to-date
- npm run ensure-correct-devtools-protocol-revision
# This bot runs separately as it changes package.json to test puppeteer-core # This bot runs separately as it changes package.json to test puppeteer-core
# and we don't want that leaking into other bots and causing issues. # and we don't want that leaking into other bots and causing issues.

View File

@ -331,7 +331,7 @@
* [target.worker()](#targetworker) * [target.worker()](#targetworker)
- [class: CDPSession](#class-cdpsession) - [class: CDPSession](#class-cdpsession)
* [cdpSession.detach()](#cdpsessiondetach) * [cdpSession.detach()](#cdpsessiondetach)
* [cdpSession.send(method[, params])](#cdpsessionsendmethod-params) * [cdpSession.send(method[, ...paramArgs])](#cdpsessionsendmethod-paramargs)
- [class: Coverage](#class-coverage) - [class: Coverage](#class-coverage)
* [coverage.startCSSCoverage([options])](#coveragestartcsscoverageoptions) * [coverage.startCSSCoverage([options])](#coveragestartcsscoverageoptions)
* [coverage.startJSCoverage([options])](#coveragestartjscoverageoptions) * [coverage.startJSCoverage([options])](#coveragestartjscoverageoptions)
@ -3946,9 +3946,9 @@ await client.send('Animation.setPlaybackRate', {
Detaches the cdpSession from the target. Once detached, the cdpSession object won't emit any events and can't be used Detaches the cdpSession from the target. Once detached, the cdpSession object won't emit any events and can't be used
to send messages. to send messages.
#### cdpSession.send(method[, params]) #### cdpSession.send(method[, ...paramArgs])
- `method` <[string]> protocol method name - `method` <[string]> protocol method name
- `params` <[Object]> Optional method parameters - `...paramArgs` <[Object]> Optional method parameters
- returns: <[Promise]<[Object]>> - returns: <[Promise]<[Object]>>
### class: Coverage ### class: Coverage

View File

@ -41,5 +41,5 @@ await client.send('Animation.setPlaybackRate', {
| Method | Modifiers | Description | | Method | Modifiers | Description |
| --- | --- | --- | | --- | --- | --- |
| [detach()](./puppeteer.cdpsession.detach.md) | | Detaches the cdpSession from the target. Once detached, the cdpSession object won't emit any events and can't be used to send messages. | | [detach()](./puppeteer.cdpsession.detach.md) | | Detaches the cdpSession from the target. Once detached, the cdpSession object won't emit any events and can't be used to send messages. |
| [send(method, params)](./puppeteer.cdpsession.send.md) | | | | [send(method, paramArgs)](./puppeteer.cdpsession.send.md) | | |

View File

@ -7,7 +7,7 @@
<b>Signature:</b> <b>Signature:</b>
```typescript ```typescript
send<T extends keyof Protocol.CommandParameters>(method: T, params?: Protocol.CommandParameters[T]): Promise<Protocol.CommandReturnValues[T]>; send<T extends keyof ProtocolMapping.Commands>(method: T, ...paramArgs: ProtocolMapping.Commands[T]['paramsType']): Promise<ProtocolMapping.Commands[T]['returnType']>;
``` ```
## Parameters ## Parameters
@ -15,9 +15,9 @@ send<T extends keyof Protocol.CommandParameters>(method: T, params?: Protocol.Co
| Parameter | Type | Description | | Parameter | Type | Description |
| --- | --- | --- | | --- | --- | --- |
| method | T | | | method | T | |
| params | Protocol.CommandParameters\[T\] | | | paramArgs | ProtocolMapping.Commands\[T\]\['paramsType'\] | |
<b>Returns:</b> <b>Returns:</b>
Promise&lt;Protocol.CommandReturnValues\[T\]&gt; Promise&lt;ProtocolMapping.Commands\[T\]\['returnType'\]&gt;

View File

@ -7,14 +7,14 @@
<b>Signature:</b> <b>Signature:</b>
```typescript ```typescript
deleteCookie(...cookies: Protocol.Network.deleteCookiesParameters[]): Promise<void>; deleteCookie(...cookies: Protocol.Network.DeleteCookiesRequest[]): Promise<void>;
``` ```
## Parameters ## Parameters
| Parameter | Type | Description | | Parameter | Type | Description |
| --- | --- | --- | | --- | --- | --- |
| cookies | Protocol.Network.deleteCookiesParameters\[\] | | | cookies | Protocol.Network.DeleteCookiesRequest\[\] | |
<b>Returns:</b> <b>Returns:</b>

View File

@ -24,16 +24,15 @@
"doc": "node utils/doclint/cli.js", "doc": "node utils/doclint/cli.js",
"clean-lib": "rm -rf lib", "clean-lib": "rm -rf lib",
"tsc": "npm run clean-lib && tsc --version && npm run tsc-cjs && npm run tsc-esm", "tsc": "npm run clean-lib && tsc --version && npm run tsc-cjs && npm run tsc-esm",
"tsc-cjs": "tsc -p . && cp src/protocol.d.ts lib/cjs", "tsc-cjs": "tsc -p .",
"tsc-esm": "tsc --build tsconfig-esm.json && cp src/protocol.d.ts lib/esm", "tsc-esm": "tsc --build tsconfig-esm.json",
"typecheck": "tsc -p . --noEmit", "typecheck": "tsc -p . --noEmit",
"apply-next-version": "node utils/apply_next_version.js", "apply-next-version": "node utils/apply_next_version.js",
"update-protocol-d-ts": "node utils/protocol-types-generator update",
"compare-protocol-d-ts": "node utils/protocol-types-generator compare",
"test-install": "scripts/test-install.sh", "test-install": "scripts/test-install.sh",
"generate-docs": "npm run tsc && api-extractor run --local --verbose && api-documenter markdown -i temp -o new-docs", "generate-docs": "npm run tsc && api-extractor run --local --verbose && api-documenter markdown -i temp -o new-docs",
"ensure-new-docs-up-to-date": "npm run generate-docs && exit `git status --porcelain | head -255 | wc -l`", "ensure-new-docs-up-to-date": "npm run generate-docs && exit `git status --porcelain | head -255 | wc -l`",
"generate-dependency-graph": "echo 'Requires graphviz installed locally!' && depcruise --exclude 'api.ts' --do-not-follow '^node_modules' --output-type dot src/index.ts | dot -T png > dependency-chart.png" "generate-dependency-graph": "echo 'Requires graphviz installed locally!' && depcruise --exclude 'api.ts' --do-not-follow '^node_modules' --output-type dot src/index.ts | dot -T png > dependency-chart.png",
"ensure-correct-devtools-protocol-revision": "ts-node scripts/ensure-correct-devtools-protocol-package"
}, },
"files": [ "files": [
"lib/", "lib/",
@ -46,6 +45,7 @@
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"debug": "^4.1.0", "debug": "^4.1.0",
"devtools-protocol": "0.0.754670",
"extract-zip": "^2.0.0", "extract-zip": "^2.0.0",
"https-proxy-agent": "^4.0.0", "https-proxy-agent": "^4.0.0",
"mime": "^2.0.3", "mime": "^2.0.3",

View File

@ -0,0 +1,83 @@
/**
* Copyright 2020 Google Inc. All rights reserved.
*
* 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 script ensures that the pinned version of devtools-protocol in
* package.json is the right version for the current revision of Chromium that
* Puppeteer ships with.
*
* The devtools-protocol package publisher runs every hour and checks if there
* are protocol changes. If there are, it will be versioned with the revision
* number of the commit that last changed the .pdl files.
*
* Chromium branches/releases are figured out at a later point in time, so it's
* not true that each Chromium revision will have an exact matching revision
* version of devtools-protocol. To ensure we're using a devtools-protocol that
* is aligned with our revision, we want to find the largest package number
* that's <= the revision that Puppeteer is using.
*
* This script uses npm's `view` function to list all versions in a range and
* find the one closest to our Chromium revision.
*/
import { PUPPETEER_REVISIONS } from '../src/revisions';
import { execSync } from 'child_process';
import packageJson from '../package.json';
const currentProtocolPackageInstalledVersion =
packageJson.dependencies['devtools-protocol'];
/**
* Ensure that the devtools-protocol version is pinned.
*/
if (/^[^0-9]/.test(currentProtocolPackageInstalledVersion)) {
console.log(
`ERROR: devtools-protocol package is not pinned to a specific version.\n`
);
process.exit(1);
}
// find the right revision for our Chromium revision
const command = `npm view "devtools-protocol@<=0.0.${PUPPETEER_REVISIONS.chromium}" version | tail -1`;
console.log(
'Checking npm for devtools-protocol revisions:\n',
`'${command}'`,
'\n'
);
const output = execSync(command, {
encoding: 'utf8',
});
const bestRevisionFromNpm = output.split(' ')[1].replace(/'|\n/g, '');
if (currentProtocolPackageInstalledVersion !== bestRevisionFromNpm) {
console.log(`ERROR: bad devtools-protocol revision detected:
Current Puppeteer Chromium revision: ${PUPPETEER_REVISIONS.chromium}
Current devtools-protocol version in package.json: ${currentProtocolPackageInstalledVersion}
Expected devtools-protocol version: ${bestRevisionFromNpm}`);
process.exit(1);
}
console.log(
`Correct devtools-protocol version found (${bestRevisionFromNpm}).`
);
process.exit(0);

View File

@ -16,7 +16,7 @@
import { CDPSession } from './Connection'; import { CDPSession } from './Connection';
import { ElementHandle } from './JSHandle'; import { ElementHandle } from './JSHandle';
import Protocol from '../protocol'; import { Protocol } from 'devtools-protocol';
/** /**
* Represents a Node and the properties of it that are relevant to Accessibility. * Represents a Node and the properties of it that are relevant to Accessibility.

View File

@ -18,8 +18,8 @@ import { assert } from './assert';
import { helper } from './helper'; import { helper } from './helper';
import { Target } from './Target'; import { Target } from './Target';
import { EventEmitter } from './EventEmitter'; import { EventEmitter } from './EventEmitter';
import Protocol from '../protocol';
import { Connection, ConnectionEmittedEvents } from './Connection'; import { Connection, ConnectionEmittedEvents } from './Connection';
import { Protocol } from 'devtools-protocol';
import { Page } from './Page'; import { Page } from './Page';
import { ChildProcess } from 'child_process'; import { ChildProcess } from 'child_process';
import { Viewport } from './PuppeteerViewport'; import { Viewport } from './PuppeteerViewport';
@ -272,7 +272,7 @@ export class Browser extends EventEmitter {
} }
private async _targetCreated( private async _targetCreated(
event: Protocol.Target.targetCreatedPayload event: Protocol.Target.TargetCreatedEvent
): Promise<void> { ): Promise<void> {
const targetInfo = event.targetInfo; const targetInfo = event.targetInfo;
const { browserContextId } = targetInfo; const { browserContextId } = targetInfo;
@ -314,7 +314,7 @@ export class Browser extends EventEmitter {
} }
private _targetInfoChanged( private _targetInfoChanged(
event: Protocol.Target.targetInfoChangedPayload event: Protocol.Target.TargetInfoChangedEvent
): void { ): void {
const target = this._targets.get(event.targetInfo.targetId); const target = this._targets.get(event.targetInfo.targetId);
assert(target, 'target should exist before targetInfoChanged'); assert(target, 'target should exist before targetInfoChanged');
@ -500,7 +500,7 @@ export class Browser extends EventEmitter {
return !this._connection._closed; return !this._connection._closed;
} }
private _getVersion(): Promise<Protocol.Browser.getVersionReturnValue> { private _getVersion(): Promise<Protocol.Browser.GetVersionResponse> {
return this._connection.send('Browser.getVersion'); return this._connection.send('Browser.getVersion');
} }
} }

View File

@ -18,7 +18,8 @@ import { debug } from './Debug';
const debugProtocolSend = debug('puppeteer:protocol:SEND ►'); const debugProtocolSend = debug('puppeteer:protocol:SEND ►');
const debugProtocolReceive = debug('puppeteer:protocol:RECV ◀'); const debugProtocolReceive = debug('puppeteer:protocol:RECV ◀');
import Protocol from '../protocol'; import { Protocol } from 'devtools-protocol';
import { ProtocolMapping } from 'devtools-protocol/types/protocol-mapping';
import { ConnectionTransport } from './ConnectionTransport'; import { ConnectionTransport } from './ConnectionTransport';
import { EventEmitter } from './EventEmitter'; import { EventEmitter } from './EventEmitter';
@ -77,10 +78,17 @@ export class Connection extends EventEmitter {
return this._url; return this._url;
} }
send<T extends keyof Protocol.CommandParameters>( send<T extends keyof ProtocolMapping.Commands>(
method: T, method: T,
params?: Protocol.CommandParameters[T] ...paramArgs: ProtocolMapping.Commands[T]['paramsType']
): Promise<Protocol.CommandReturnValues[T]> { ): Promise<ProtocolMapping.Commands[T]['returnType']> {
// There is only ever 1 param arg passed, but the Protocol defines it as an
// array of 0 or 1 items See this comment:
// https://github.com/ChromeDevTools/devtools-protocol/pull/113#issuecomment-412603285
// which explains why the protocol defines the params this way for better
// type-inference.
// So now we check if there are any params or not and deal with them accordingly.
const params = paramArgs.length ? paramArgs[0] : undefined;
const id = this._rawSend({ method, params }); const id = this._rawSend({ method, params });
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this._callbacks.set(id, { resolve, reject, error: new Error(), method }); this._callbacks.set(id, { resolve, reject, error: new Error(), method });
@ -232,10 +240,10 @@ export class CDPSession extends EventEmitter {
this._sessionId = sessionId; this._sessionId = sessionId;
} }
send<T extends keyof Protocol.CommandParameters>( send<T extends keyof ProtocolMapping.Commands>(
method: T, method: T,
params?: Protocol.CommandParameters[T] ...paramArgs: ProtocolMapping.Commands[T]['paramsType']
): Promise<Protocol.CommandReturnValues[T]> { ): Promise<ProtocolMapping.Commands[T]['returnType']> {
if (!this._connection) if (!this._connection)
return Promise.reject( return Promise.reject(
new Error( new Error(
@ -243,6 +251,9 @@ export class CDPSession extends EventEmitter {
) )
); );
// See the comment in Connection#send explaining why we do this.
const params = paramArgs.length ? paramArgs[0] : undefined;
const id = this._connection._rawSend({ const id = this._connection._rawSend({
sessionId: this._sessionId, sessionId: this._sessionId,
method, method,

View File

@ -16,7 +16,7 @@
import { assert } from './assert'; import { assert } from './assert';
import { helper, debugError, PuppeteerEventListener } from './helper'; import { helper, debugError, PuppeteerEventListener } from './helper';
import Protocol from '../protocol'; import { Protocol } from 'devtools-protocol';
import { CDPSession } from './Connection'; import { CDPSession } from './Connection';
import { EVALUATION_SCRIPT_URL } from './ExecutionContext'; import { EVALUATION_SCRIPT_URL } from './ExecutionContext';
@ -223,7 +223,7 @@ class JSCoverage {
} }
async _onScriptParsed( async _onScriptParsed(
event: Protocol.Debugger.scriptParsedPayload event: Protocol.Debugger.ScriptParsedEvent
): Promise<void> { ): Promise<void> {
// Ignore puppeteer-injected scripts // Ignore puppeteer-injected scripts
if (event.url === EVALUATION_SCRIPT_URL) return; if (event.url === EVALUATION_SCRIPT_URL) return;
@ -246,10 +246,10 @@ class JSCoverage {
this._enabled = false; this._enabled = false;
const result = await Promise.all< const result = await Promise.all<
Protocol.Profiler.takePreciseCoverageReturnValue, Protocol.Profiler.TakePreciseCoverageResponse,
Protocol.Profiler.stopPreciseCoverageReturnValue, void,
Protocol.Profiler.disableReturnValue, void,
Protocol.Debugger.disableReturnValue void
>([ >([
this._client.send('Profiler.takePreciseCoverage'), this._client.send('Profiler.takePreciseCoverage'),
this._client.send('Profiler.stopPreciseCoverage'), this._client.send('Profiler.stopPreciseCoverage'),
@ -322,9 +322,7 @@ class CSSCoverage {
this._stylesheetSources.clear(); this._stylesheetSources.clear();
} }
async _onStyleSheet( async _onStyleSheet(event: Protocol.CSS.StyleSheetAddedEvent): Promise<void> {
event: Protocol.CSS.styleSheetAddedPayload
): Promise<void> {
const header = event.header; const header = event.header;
// Ignore anonymous scripts // Ignore anonymous scripts
if (!header.sourceURL) return; if (!header.sourceURL) return;

View File

@ -16,7 +16,7 @@
import { assert } from './assert'; import { assert } from './assert';
import { CDPSession } from './Connection'; import { CDPSession } from './Connection';
import Protocol from '../protocol'; import { Protocol } from 'devtools-protocol';
/** /**
* Dialog instances are dispatched by the {@link Page} via the `dialog` event. * Dialog instances are dispatched by the {@link Page} via the `dialog` event.

View File

@ -15,7 +15,7 @@
*/ */
import { CDPSession } from './Connection'; import { CDPSession } from './Connection';
import { Viewport } from './PuppeteerViewport'; import { Viewport } from './PuppeteerViewport';
import Protocol from '../protocol'; import { Protocol } from 'devtools-protocol';
export class EmulationManager { export class EmulationManager {
_client: CDPSession; _client: CDPSession;

View File

@ -20,7 +20,7 @@ import { createJSHandle, JSHandle, ElementHandle } from './JSHandle';
import { CDPSession } from './Connection'; import { CDPSession } from './Connection';
import { DOMWorld } from './DOMWorld'; import { DOMWorld } from './DOMWorld';
import { Frame } from './FrameManager'; import { Frame } from './FrameManager';
import Protocol from '../protocol'; import { Protocol } from 'devtools-protocol';
import { EvaluateHandleFn, SerializableOrJSHandle } from './EvalTypes'; import { EvaluateHandleFn, SerializableOrJSHandle } from './EvalTypes';
export const EVALUATION_SCRIPT_URL = '__puppeteer_evaluation_script__'; export const EVALUATION_SCRIPT_URL = '__puppeteer_evaluation_script__';
@ -301,7 +301,7 @@ export class ExecutionContext {
return { value: arg }; return { value: arg };
} }
function rewriteError(error: Error): Protocol.Runtime.evaluateReturnValue { function rewriteError(error: Error): Protocol.Runtime.EvaluateResponse {
if (error.message.includes('Object reference chain is too long')) if (error.message.includes('Object reference chain is too long'))
return { result: { type: 'undefined' } }; return { result: { type: 'undefined' } };
if (error.message.includes("Object couldn't be returned by value")) if (error.message.includes("Object couldn't be returned by value"))

View File

@ -15,7 +15,7 @@
*/ */
import { ElementHandle } from './JSHandle'; import { ElementHandle } from './JSHandle';
import Protocol from '../protocol'; import { Protocol } from 'devtools-protocol';
import { assert } from './assert'; import { assert } from './assert';
/** /**
@ -45,7 +45,7 @@ export class FileChooser {
*/ */
constructor( constructor(
element: ElementHandle, element: ElementHandle,
event: Protocol.Page.fileChooserOpenedPayload event: Protocol.Page.FileChooserOpenedEvent
) { ) {
this._element = element; this._element = element;
this._multiple = event.mode !== 'selectSingle'; this._multiple = event.mode !== 'selectSingle';

View File

@ -27,7 +27,7 @@ import { JSHandle, ElementHandle } from './JSHandle';
import { MouseButton } from './Input'; import { MouseButton } from './Input';
import { Page } from './Page'; import { Page } from './Page';
import { HTTPResponse } from './HTTPResponse'; import { HTTPResponse } from './HTTPResponse';
import Protocol from '../protocol'; import { Protocol } from 'devtools-protocol';
import { import {
SerializableOrJSHandle, SerializableOrJSHandle,
EvaluateHandleFn, EvaluateHandleFn,
@ -108,10 +108,7 @@ export class FrameManager extends EventEmitter {
} }
async initialize(): Promise<void> { async initialize(): Promise<void> {
const result = await Promise.all< const result = await Promise.all<{}, Protocol.Page.GetFrameTreeResponse>([
Protocol.Page.enableReturnValue,
Protocol.Page.getFrameTreeReturnValue
>([
this._client.send('Page.enable'), this._client.send('Page.enable'),
this._client.send('Page.getFrameTree'), this._client.send('Page.getFrameTree'),
]); ]);
@ -121,7 +118,7 @@ export class FrameManager extends EventEmitter {
await Promise.all([ await Promise.all([
this._client.send('Page.setLifecycleEventsEnabled', { enabled: true }), this._client.send('Page.setLifecycleEventsEnabled', { enabled: true }),
this._client this._client
.send('Runtime.enable', {}) .send('Runtime.enable')
.then(() => this._ensureIsolatedWorld(UTILITY_WORLD_NAME)), .then(() => this._ensureIsolatedWorld(UTILITY_WORLD_NAME)),
this._networkManager.initialize(), this._networkManager.initialize(),
]); ]);
@ -210,7 +207,7 @@ export class FrameManager extends EventEmitter {
return watcher.navigationResponse(); return watcher.navigationResponse();
} }
_onLifecycleEvent(event: Protocol.Page.lifecycleEventPayload): void { _onLifecycleEvent(event: Protocol.Page.LifecycleEventEvent): void {
const frame = this._frames.get(event.frameId); const frame = this._frames.get(event.frameId);
if (!frame) return; if (!frame) return;
frame._onLifecycleEvent(event.loaderId, event.name); frame._onLifecycleEvent(event.loaderId, event.name);

View File

@ -18,7 +18,7 @@ import { Frame } from './FrameManager';
import { HTTPResponse } from './HTTPResponse'; import { HTTPResponse } from './HTTPResponse';
import { assert } from './assert'; import { assert } from './assert';
import { helper, debugError } from './helper'; import { helper, debugError } from './helper';
import Protocol from '../protocol'; import { Protocol } from 'devtools-protocol';
/** /**
* @public * @public
@ -123,7 +123,7 @@ export class HTTPRequest {
frame: Frame, frame: Frame,
interceptionId: string, interceptionId: string,
allowInterception: boolean, allowInterception: boolean,
event: Protocol.Network.requestWillBeSentPayload, event: Protocol.Network.RequestWillBeSentEvent,
redirectChain: HTTPRequest[] redirectChain: HTTPRequest[]
) { ) {
this._client = client; this._client = client;

View File

@ -17,7 +17,7 @@ import { CDPSession } from './Connection';
import { Frame } from './FrameManager'; import { Frame } from './FrameManager';
import { HTTPRequest } from './HTTPRequest'; import { HTTPRequest } from './HTTPRequest';
import { SecurityDetails } from './SecurityDetails'; import { SecurityDetails } from './SecurityDetails';
import Protocol from '../protocol'; import { Protocol } from 'devtools-protocol';
/** /**
* @public * @public

View File

@ -22,7 +22,7 @@ import { CDPSession } from './Connection';
import { KeyInput } from './USKeyboardLayout'; import { KeyInput } from './USKeyboardLayout';
import { FrameManager, Frame } from './FrameManager'; import { FrameManager, Frame } from './FrameManager';
import { getQueryHandlerAndSelector } from './QueryHandler'; import { getQueryHandlerAndSelector } from './QueryHandler';
import Protocol from '../protocol'; import { Protocol } from 'devtools-protocol';
import { import {
EvaluateFn, EvaluateFn,
SerializableOrJSHandle, SerializableOrJSHandle,
@ -445,11 +445,12 @@ export class ElementHandle<
}; };
} }
private _getBoxModel(): Promise<void | Protocol.DOM.getBoxModelReturnValue> { private _getBoxModel(): Promise<void | Protocol.DOM.GetBoxModelResponse> {
const params: Protocol.DOM.GetBoxModelRequest = {
objectId: this._remoteObject.objectId,
};
return this._client return this._client
.send('DOM.getBoxModel', { .send('DOM.getBoxModel', params)
objectId: this._remoteObject.objectId,
})
.catch((error) => debugError(error)); .catch((error) => debugError(error));
} }

View File

@ -16,7 +16,7 @@
import { EventEmitter } from './EventEmitter'; import { EventEmitter } from './EventEmitter';
import { assert } from './assert'; import { assert } from './assert';
import { helper, debugError } from './helper'; import { helper, debugError } from './helper';
import Protocol from '../protocol'; import { Protocol } from 'devtools-protocol';
import { CDPSession } from './Connection'; import { CDPSession } from './Connection';
import { FrameManager } from './FrameManager'; import { FrameManager } from './FrameManager';
import { HTTPRequest } from './HTTPRequest'; import { HTTPRequest } from './HTTPRequest';
@ -53,7 +53,7 @@ export class NetworkManager extends EventEmitter {
_requestIdToRequest = new Map<string, HTTPRequest>(); _requestIdToRequest = new Map<string, HTTPRequest>();
_requestIdToRequestWillBeSentEvent = new Map< _requestIdToRequestWillBeSentEvent = new Map<
string, string,
Protocol.Network.requestWillBeSentPayload Protocol.Network.RequestWillBeSentEvent
>(); >();
_extraHTTPHeaders: Record<string, string> = {}; _extraHTTPHeaders: Record<string, string> = {};
_offline = false; _offline = false;
@ -182,7 +182,7 @@ export class NetworkManager extends EventEmitter {
}); });
} }
_onRequestWillBeSent(event: Protocol.Network.requestWillBeSentPayload): void { _onRequestWillBeSent(event: Protocol.Network.RequestWillBeSentEvent): void {
// Request interception doesn't happen for data URLs with Network Service. // Request interception doesn't happen for data URLs with Network Service.
if ( if (
this._protocolRequestInterceptionEnabled && this._protocolRequestInterceptionEnabled &&
@ -201,7 +201,7 @@ export class NetworkManager extends EventEmitter {
this._onRequest(event, null); this._onRequest(event, null);
} }
_onAuthRequired(event: Protocol.Fetch.authRequiredPayload): void { _onAuthRequired(event: Protocol.Fetch.AuthRequiredEvent): void {
/* TODO(jacktfranklin): This is defined in protocol.d.ts but not /* TODO(jacktfranklin): This is defined in protocol.d.ts but not
* in an easily referrable way - we should look at exposing it. * in an easily referrable way - we should look at exposing it.
*/ */
@ -225,7 +225,7 @@ export class NetworkManager extends EventEmitter {
.catch(debugError); .catch(debugError);
} }
_onRequestPaused(event: Protocol.Fetch.requestPausedPayload): void { _onRequestPaused(event: Protocol.Fetch.RequestPausedEvent): void {
if ( if (
!this._userRequestInterceptionEnabled && !this._userRequestInterceptionEnabled &&
this._protocolRequestInterceptionEnabled this._protocolRequestInterceptionEnabled
@ -251,7 +251,7 @@ export class NetworkManager extends EventEmitter {
} }
_onRequest( _onRequest(
event: Protocol.Network.requestWillBeSentPayload, event: Protocol.Network.RequestWillBeSentEvent,
interceptionId?: string interceptionId?: string
): void { ): void {
let redirectChain = []; let redirectChain = [];
@ -280,7 +280,7 @@ export class NetworkManager extends EventEmitter {
} }
_onRequestServedFromCache( _onRequestServedFromCache(
event: Protocol.Network.requestServedFromCachePayload event: Protocol.Network.RequestServedFromCacheEvent
): void { ): void {
const request = this._requestIdToRequest.get(event.requestId); const request = this._requestIdToRequest.get(event.requestId);
if (request) request._fromMemoryCache = true; if (request) request._fromMemoryCache = true;
@ -302,7 +302,7 @@ export class NetworkManager extends EventEmitter {
this.emit(NetworkManagerEmittedEvents.RequestFinished, request); this.emit(NetworkManagerEmittedEvents.RequestFinished, request);
} }
_onResponseReceived(event: Protocol.Network.responseReceivedPayload): void { _onResponseReceived(event: Protocol.Network.ResponseReceivedEvent): void {
const request = this._requestIdToRequest.get(event.requestId); const request = this._requestIdToRequest.get(event.requestId);
// FileUpload sends a response without a matching request. // FileUpload sends a response without a matching request.
if (!request) return; if (!request) return;
@ -311,7 +311,7 @@ export class NetworkManager extends EventEmitter {
this.emit(NetworkManagerEmittedEvents.Response, response); this.emit(NetworkManagerEmittedEvents.Response, response);
} }
_onLoadingFinished(event: Protocol.Network.loadingFinishedPayload): void { _onLoadingFinished(event: Protocol.Network.LoadingFinishedEvent): void {
const request = this._requestIdToRequest.get(event.requestId); const request = this._requestIdToRequest.get(event.requestId);
// For certain requestIds we never receive requestWillBeSent event. // For certain requestIds we never receive requestWillBeSent event.
// @see https://crbug.com/750469 // @see https://crbug.com/750469
@ -325,7 +325,7 @@ export class NetworkManager extends EventEmitter {
this.emit(NetworkManagerEmittedEvents.RequestFinished, request); this.emit(NetworkManagerEmittedEvents.RequestFinished, request);
} }
_onLoadingFailed(event: Protocol.Network.loadingFailedPayload): void { _onLoadingFailed(event: Protocol.Network.LoadingFailedEvent): void {
const request = this._requestIdToRequest.get(event.requestId); const request = this._requestIdToRequest.get(event.requestId);
// For certain requestIds we never receive requestWillBeSent event. // For certain requestIds we never receive requestWillBeSent event.
// @see https://crbug.com/750469 // @see https://crbug.com/750469

View File

@ -40,7 +40,7 @@ import { TimeoutSettings } from './TimeoutSettings';
import { FileChooser } from './FileChooser'; import { FileChooser } from './FileChooser';
import { ConsoleMessage, ConsoleMessageType } from './ConsoleMessage'; import { ConsoleMessage, ConsoleMessageType } from './ConsoleMessage';
import { PuppeteerLifeCycleEvent } from './LifecycleWatcher'; import { PuppeteerLifeCycleEvent } from './LifecycleWatcher';
import Protocol from '../protocol'; import { Protocol } from 'devtools-protocol';
import { import {
SerializableOrJSHandle, SerializableOrJSHandle,
EvaluateHandleFn, EvaluateHandleFn,
@ -527,13 +527,13 @@ export class Page extends EventEmitter {
waitForDebuggerOnStart: false, waitForDebuggerOnStart: false,
flatten: true, flatten: true,
}), }),
this._client.send('Performance.enable', {}), this._client.send('Performance.enable'),
this._client.send('Log.enable', {}), this._client.send('Log.enable'),
]); ]);
} }
private async _onFileChooser( private async _onFileChooser(
event: Protocol.Page.fileChooserOpenedPayload event: Protocol.Page.FileChooserOpenedEvent
): Promise<void> { ): Promise<void> {
if (!this._fileChooserInterceptors.size) return; if (!this._fileChooserInterceptors.size) return;
const frame = this._frameManager.frame(event.frameId); const frame = this._frameManager.frame(event.frameId);
@ -638,7 +638,7 @@ export class Page extends EventEmitter {
this.emit('error', new Error('Page crashed!')); this.emit('error', new Error('Page crashed!'));
} }
private _onLogEntryAdded(event: Protocol.Log.entryAddedPayload): void { private _onLogEntryAdded(event: Protocol.Log.EntryAddedEvent): void {
const { level, text, args, source, url, lineNumber } = event.entry; const { level, text, args, source, url, lineNumber } = event.entry;
if (args) args.map((arg) => helper.releaseObject(this._client, arg)); if (args) args.map((arg) => helper.releaseObject(this._client, arg));
if (source !== 'worker') if (source !== 'worker')
@ -1012,7 +1012,7 @@ export class Page extends EventEmitter {
} }
async deleteCookie( async deleteCookie(
...cookies: Protocol.Network.deleteCookiesParameters[] ...cookies: Protocol.Network.DeleteCookiesRequest[]
): Promise<void> { ): Promise<void> {
const pageURL = this.url(); const pageURL = this.url();
for (const cookie of cookies) { for (const cookie of cookies) {
@ -1121,7 +1121,7 @@ export class Page extends EventEmitter {
return this._buildMetricsObject(response.metrics); return this._buildMetricsObject(response.metrics);
} }
private _emitMetrics(event: Protocol.Performance.metricsPayload): void { private _emitMetrics(event: Protocol.Performance.MetricsEvent): void {
this.emit(PageEmittedEvents.Metrics, { this.emit(PageEmittedEvents.Metrics, {
title: event.title, title: event.title,
metrics: this._buildMetricsObject(event.metrics), metrics: this._buildMetricsObject(event.metrics),
@ -1148,7 +1148,7 @@ export class Page extends EventEmitter {
} }
private async _onConsoleAPI( private async _onConsoleAPI(
event: Protocol.Runtime.consoleAPICalledPayload event: Protocol.Runtime.ConsoleAPICalledEvent
): Promise<void> { ): Promise<void> {
if (event.executionContextId === 0) { if (event.executionContextId === 0) {
// DevTools protocol stores the last 1000 console messages. These // DevTools protocol stores the last 1000 console messages. These
@ -1174,7 +1174,7 @@ export class Page extends EventEmitter {
} }
private async _onBindingCalled( private async _onBindingCalled(
event: Protocol.Runtime.bindingCalledPayload event: Protocol.Runtime.BindingCalledEvent
): Promise<void> { ): Promise<void> {
const { name, seq, args } = JSON.parse(event.payload); const { name, seq, args } = JSON.parse(event.payload);
let expression = null; let expression = null;
@ -1264,7 +1264,7 @@ export class Page extends EventEmitter {
this.emit(PageEmittedEvents.Console, message); this.emit(PageEmittedEvents.Console, message);
} }
private _onDialog(event: Protocol.Page.javascriptDialogOpeningPayload): void { private _onDialog(event: Protocol.Page.JavascriptDialogOpeningEvent): void {
let dialogType = null; let dialogType = null;
const validDialogTypes = new Set<Protocol.Page.DialogType>([ const validDialogTypes = new Set<Protocol.Page.DialogType>([
'alert', 'alert',
@ -1307,10 +1307,10 @@ export class Page extends EventEmitter {
} }
async reload(options?: WaitForOptions): Promise<HTTPResponse | null> { async reload(options?: WaitForOptions): Promise<HTTPResponse | null> {
const result = await Promise.all< const result = await Promise.all<HTTPResponse, void>([
HTTPResponse, this.waitForNavigation(options),
Protocol.Page.reloadReturnValue this._client.send('Page.reload'),
>([this.waitForNavigation(options), this._client.send('Page.reload')]); ]);
return result[0]; return result[0];
} }
@ -1386,10 +1386,7 @@ export class Page extends EventEmitter {
const history = await this._client.send('Page.getNavigationHistory'); const history = await this._client.send('Page.getNavigationHistory');
const entry = history.entries[history.currentIndex + delta]; const entry = history.entries[history.currentIndex + delta];
if (!entry) return null; if (!entry) return null;
const result = await Promise.all< const result = await Promise.all([
HTTPResponse,
Protocol.Page.navigateToHistoryEntryReturnValue
>([
this.waitForNavigation(options), this.waitForNavigation(options),
this._client.send('Page.navigateToHistoryEntry', { entryId: entry.id }), this._client.send('Page.navigateToHistoryEntry', { entryId: entry.id }),
]); ]);

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import Protocol from '../protocol'; import { Protocol } from 'devtools-protocol';
/** /**
* The SecurityDetails class represents the security details of a * The SecurityDetails class represents the security details of a

View File

@ -19,7 +19,7 @@ import { WebWorker } from './WebWorker';
import { CDPSession } from './Connection'; import { CDPSession } from './Connection';
import { Browser, BrowserContext } from './Browser'; import { Browser, BrowserContext } from './Browser';
import { Viewport } from './PuppeteerViewport'; import { Viewport } from './PuppeteerViewport';
import Protocol from '../protocol'; import { Protocol } from 'devtools-protocol';
/** /**
* @public * @public

View File

@ -18,7 +18,7 @@ import { debugError } from './helper';
import { ExecutionContext } from './ExecutionContext'; import { ExecutionContext } from './ExecutionContext';
import { JSHandle } from './JSHandle'; import { JSHandle } from './JSHandle';
import { CDPSession } from './Connection'; import { CDPSession } from './Connection';
import Protocol from '../protocol'; import { Protocol } from 'devtools-protocol';
import { EvaluateHandleFn, SerializableOrJSHandle } from './EvalTypes'; import { EvaluateHandleFn, SerializableOrJSHandle } from './EvalTypes';
/** /**
@ -96,7 +96,7 @@ export class WebWorker extends EventEmitter {
}); });
// This might fail if the target is closed before we recieve all execution contexts. // This might fail if the target is closed before we recieve all execution contexts.
this._client.send('Runtime.enable', {}).catch(debugError); this._client.send('Runtime.enable').catch(debugError);
this._client.on('Runtime.consoleAPICalled', (event) => this._client.on('Runtime.consoleAPICalled', (event) =>
consoleAPICalled( consoleAPICalled(
event.type, event.type,

View File

@ -18,7 +18,7 @@ import { debug } from './Debug';
import * as fs from 'fs'; import * as fs from 'fs';
import { CDPSession } from './Connection'; import { CDPSession } from './Connection';
import { promisify } from 'util'; import { promisify } from 'util';
import Protocol from '../protocol'; import { Protocol } from 'devtools-protocol';
import { CommonEventEmitter } from './EventEmitter'; import { CommonEventEmitter } from './EventEmitter';
import { assert } from './assert'; import { assert } from './assert';

15503
src/protocol.d.ts vendored

File diff suppressed because it is too large Load Diff

View File

@ -26,3 +26,7 @@ fs.writeFileSync(
path.join(__dirname, '..', 'package.json'), path.join(__dirname, '..', 'package.json'),
JSON.stringify(package, undefined, 2) + '\n' JSON.stringify(package, undefined, 2) + '\n'
); );
console.log(
'IMPORTANT: you should update the pinned version of devtools-protocol to match the new revsion.'
);

View File

@ -128,9 +128,19 @@ function checkSources(sources) {
); );
const name = symbol.getName(); const name = symbol.getName();
if (symbol.valueDeclaration && symbol.valueDeclaration.dotDotDotToken) { if (symbol.valueDeclaration && symbol.valueDeclaration.dotDotDotToken) {
const innerType = serializeType(type.typeArguments[0], circular); try {
innerType.name = '...' + innerType.name; const innerType = serializeType(type.typeArguments[0], circular);
return Documentation.Member.createProperty('...' + name, innerType); innerType.name = '...' + innerType.name;
return Documentation.Member.createProperty('...' + name, innerType);
} catch (error) {
/**
* DocLint struggles with the paramArgs type on CDPSession.send because
* it uses a complex type from the devtools-protocol method. Doclint
* isn't going to be here for much longer so we'll just silence this
* warning than try to add support which would warrant a huge rewrite.
*/
if (name !== 'paramArgs') throw error;
}
} }
return Documentation.Member.createProperty( return Documentation.Member.createProperty(
name, name,

View File

@ -251,15 +251,23 @@ function compareDocumentations(actual, expected) {
const actualArgs = Array.from(actualMethod.args.keys()); const actualArgs = Array.from(actualMethod.args.keys());
const expectedArgs = Array.from(expectedMethod.args.keys()); const expectedArgs = Array.from(expectedMethod.args.keys());
const argsDiff = diff(actualArgs, expectedArgs); const argsDiff = diff(actualArgs, expectedArgs);
if (argsDiff.extra.length || argsDiff.missing.length) { if (argsDiff.extra.length || argsDiff.missing.length) {
const text = [ /* Doclint cannot handle the parameter type of the CDPSession send method.
`Method ${className}.${methodName}() fails to describe its parameters:`, * so we just ignore it.
]; */
for (const arg of argsDiff.missing) const isCdpSessionSend =
text.push(`- Argument not found: ${arg}`); className === 'CDPSession' && methodName === 'send';
for (const arg of argsDiff.extra) if (!isCdpSessionSend) {
text.push(`- Non-existing argument found: ${arg}`); const text = [
errors.push(text.join('\n')); `Method ${className}.${methodName}() fails to describe its parameters:`,
];
for (const arg of argsDiff.missing)
text.push(`- Argument not found: ${arg}`);
for (const arg of argsDiff.extra)
text.push(`- Non-existing argument found: ${arg}`);
errors.push(text.join('\n'));
}
} }
for (const arg of argsDiff.equal) for (const arg of argsDiff.equal)
@ -788,6 +796,13 @@ function compareDocumentations(actual, expected) {
expectedName: 'FrameWaitForFunctionOptions', expectedName: 'FrameWaitForFunctionOptions',
}, },
], ],
[
'Method BrowserContext.overridePermissions() permissions',
{
actualName: 'Array<string>',
expectedName: 'Array<Object>',
},
],
]); ]);
const expectedForSource = expectedNamingMismatches.get(source); const expectedForSource = expectedNamingMismatches.get(source);
@ -825,6 +840,16 @@ function compareDocumentations(actual, expected) {
* as they will likely be considered "wrong" by DocLint too. * as they will likely be considered "wrong" by DocLint too.
*/ */
if (namingMismatchIsExpected) return; if (namingMismatchIsExpected) return;
/* Some methods cause errors in the property checks for an unknown reason
* so we support a list of methods whose parameters are not checked.
*/
const skipPropertyChecksOnMethods = new Set([
'Method Page.deleteCookie() ...cookies',
'Method Page.setCookie() ...cookies',
]);
if (skipPropertyChecksOnMethods.has(source)) return;
const actualPropertiesMap = new Map( const actualPropertiesMap = new Map(
actual.properties.map((property) => [property.name, property.type]) actual.properties.map((property) => [property.name, property.type])
); );

View File

@ -1,276 +0,0 @@
// @ts-check
const path = require('path');
const puppeteer = require('../..');
const { execSync } = require('child_process');
const fetchAndGenerateProtocolDefinitions = () =>
puppeteer
.launch({
pipe: false,
executablePath: process.env.BINARY,
})
.then(async (browser) => {
const origin = browser
.wsEndpoint()
.match(/ws:\/\/([0-9A-Za-z:\.]*)\//)[1];
const page = await browser.newPage();
await page.goto(`http://${origin}/json/protocol`);
const json = JSON.parse(
await page.evaluate(() => document.documentElement.innerText)
);
const version = await browser.version();
await browser.close();
const output = `// This is generated from /utils/protocol-types-generator/index.js
type binary = string;
declare module Protocol {${json.domains
.map(
(domain) => `${
domain.description
? `
/**
* ${domain.description}
*/`
: ''
}
export module ${domain.domain} {${(domain.types || [])
.map(
(type) =>
`${
type.description
? `
/**
* ${type.description}
*/`
: ''
}${
type.properties
? `
export interface ${type.id} {${(type.properties || [])
.map(
(property) => `${
property.description
? `
/**
* ${property.description}
*/`
: ''
}
${property.name}${property.optional ? '?' : ''}: ${typeOfProperty(
property
)};`
)
.join(``)}
}`
: `
export type ${type.id} = ${typeOfProperty(type)};`
}`
)
.join('')}
${(domain.events || [])
.map(
(event) =>
`${
event.description
? `
/**
* ${event.description}
*/`
: ''
}${
event.parameters
? `
export type ${event.name}Payload = {${event.parameters
.map(
(parameter) => `${
parameter.description
? `
/**
* ${parameter.description}
*/`
: ''
}
${parameter.name}${parameter.optional ? '?' : ''}: ${typeOfProperty(
parameter
)};`
)
.join(``)}
}`
: `
export type ${event.name}Payload = void;`
}`
)
.join('')}
${(domain.commands || [])
.map(
(command) => `${
command.description
? `
/**
* ${command.description}
*/`
: ''
}
export type ${command.name}Parameters = {${(command.parameters || [])
.map(
(parameter) => `${
parameter.description
? `
/**
* ${parameter.description}
*/`
: ''
}
${parameter.name}${parameter.optional ? '?' : ''}: ${typeOfProperty(
parameter
)};`
)
.join(``)}
}
export type ${command.name}ReturnValue = {${(command.returns || [])
.map(
(retVal) => `${
retVal.description
? `
/**
* ${retVal.description}
*/`
: ''
}
${retVal.name}${retVal.optional ? '?' : ''}: ${typeOfProperty(
retVal
)};`
)
.join(``)}
}`
)
.join('')}
}
`
)
.join('')}
export interface Events {${json.domains
.map((domain) =>
(domain.events || [])
.map(
(event) => `
"${domain.domain}.${event.name}": ${domain.domain}.${event.name}Payload;`
)
.join('')
)
.join('')}
}
export interface CommandParameters {${json.domains
.map((domain) =>
(domain.commands || [])
.map(
(command) => `
"${domain.domain}.${command.name}": ${domain.domain}.${command.name}Parameters;`
)
.join('')
)
.join('')}
}
export interface CommandReturnValues {${json.domains
.map((domain) =>
(domain.commands || [])
.map(
(command) => `
"${domain.domain}.${command.name}": ${domain.domain}.${command.name}ReturnValue;`
)
.join('')
)
.join('')}
}
}
export default Protocol;
`;
return { output, version };
});
const protocolOutputPath = path.join(__dirname, '../../src/protocol.d.ts');
const relativeProtocolOutputPath = path.relative(
process.cwd(),
protocolOutputPath
);
const writeOutputToDisk = ({ output, version }) => {
require('fs').writeFileSync(protocolOutputPath, output);
console.log(
`Wrote protocol.d.ts for ${version} to ${relativeProtocolOutputPath}`
);
console.log(`You should commit the changes.`);
};
const lastCommitMessage = () => {
return execSync('git log --no-merges -n 1', { encoding: 'utf8' });
};
const cli = async () => {
const scriptToRun = process.argv[2];
const changeExpected = lastCommitMessage().includes('EXPECTED_PROTOCOL_DIFF');
if (scriptToRun === 'update') {
writeOutputToDisk(await fetchAndGenerateProtocolDefinitions());
} else if (scriptToRun === 'compare') {
const { output } = await fetchAndGenerateProtocolDefinitions();
const outputOnDisk = require('fs').readFileSync(protocolOutputPath, {
encoding: 'utf8',
});
if (output === outputOnDisk) {
console.log(`Success: ${relativeProtocolOutputPath} is up to date.`);
} else if (changeExpected) {
console.log(`Warning: ${relativeProtocolOutputPath} is out of date`);
console.log(
' continuing because EXPECTED_PROTOCOL_DIFF was found in the last commit message.'
);
} else {
console.log(`Error: ${relativeProtocolOutputPath} is out of date.`);
console.log(
'You should run `npm run update-protocol-d-ts` and commit the changes.'
);
process.exit(1);
}
} else {
console.log(`Unknown protocol script ${scriptToRun}.`);
console.log(`Valid scripts are:
- update: fetch and update ${relativeProtocolOutputPath}
- compare: check ${relativeProtocolOutputPath} is up to date with the latest CDP.
`);
process.exit(1);
}
};
cli();
/**
* @typedef {Object} Property
* @property {string=} $ref
* @property {!Array=} enum
* @property {string=} type
* @property {!Property=} items
* @property {string=} description
*/
/**
* @param {!Property} property
* @param {string=} domain
*/
function typeOfProperty(property, domain) {
if (property.$ref)
return property.$ref.includes('.') || !domain
? property.$ref
: domain + '.' + property.$ref;
if (property.enum)
return property.enum.map((value) => JSON.stringify(value)).join('|');
switch (property.type) {
case 'array':
return typeOfProperty(property.items, domain) + '[]';
case 'integer':
return 'number';
}
return property.type;
}