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/*
experimental/
lib/
src/externs.d.ts
src/protocol.d.ts
/index.d.ts
# 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.

View File

@ -63,9 +63,9 @@ jobs:
env:
- CHROMIUM=true
script:
- npm run compare-protocol-d-ts
- npm run lint
- 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
# and we don't want that leaking into other bots and causing issues.

View File

@ -331,7 +331,7 @@
* [target.worker()](#targetworker)
- [class: CDPSession](#class-cdpsession)
* [cdpSession.detach()](#cdpsessiondetach)
* [cdpSession.send(method[, params])](#cdpsessionsendmethod-params)
* [cdpSession.send(method[, ...paramArgs])](#cdpsessionsendmethod-paramargs)
- [class: Coverage](#class-coverage)
* [coverage.startCSSCoverage([options])](#coveragestartcsscoverageoptions)
* [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
to send messages.
#### cdpSession.send(method[, params])
#### cdpSession.send(method[, ...paramArgs])
- `method` <[string]> protocol method name
- `params` <[Object]> Optional method parameters
- `...paramArgs` <[Object]> Optional method parameters
- returns: <[Promise]<[Object]>>
### class: Coverage

View File

@ -41,5 +41,5 @@ await client.send('Animation.setPlaybackRate', {
| 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. |
| [send(method, params)](./puppeteer.cdpsession.send.md) | | |
| [send(method, paramArgs)](./puppeteer.cdpsession.send.md) | | |

View File

@ -7,7 +7,7 @@
<b>Signature:</b>
```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
@ -15,9 +15,9 @@ send<T extends keyof Protocol.CommandParameters>(method: T, params?: Protocol.Co
| Parameter | Type | Description |
| --- | --- | --- |
| method | T | |
| params | Protocol.CommandParameters\[T\] | |
| paramArgs | ProtocolMapping.Commands\[T\]\['paramsType'\] | |
<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>
```typescript
deleteCookie(...cookies: Protocol.Network.deleteCookiesParameters[]): Promise<void>;
deleteCookie(...cookies: Protocol.Network.DeleteCookiesRequest[]): Promise<void>;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| cookies | Protocol.Network.deleteCookiesParameters\[\] | |
| cookies | Protocol.Network.DeleteCookiesRequest\[\] | |
<b>Returns:</b>

View File

@ -24,16 +24,15 @@
"doc": "node utils/doclint/cli.js",
"clean-lib": "rm -rf lib",
"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-esm": "tsc --build tsconfig-esm.json && cp src/protocol.d.ts lib/esm",
"tsc-cjs": "tsc -p .",
"tsc-esm": "tsc --build tsconfig-esm.json",
"typecheck": "tsc -p . --noEmit",
"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",
"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`",
"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": [
"lib/",
@ -46,6 +45,7 @@
"license": "Apache-2.0",
"dependencies": {
"debug": "^4.1.0",
"devtools-protocol": "0.0.754670",
"extract-zip": "^2.0.0",
"https-proxy-agent": "^4.0.0",
"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 { 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.

View File

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

View File

@ -18,7 +18,8 @@ import { debug } from './Debug';
const debugProtocolSend = debug('puppeteer:protocol:SEND ►');
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 { EventEmitter } from './EventEmitter';
@ -77,10 +78,17 @@ export class Connection extends EventEmitter {
return this._url;
}
send<T extends keyof Protocol.CommandParameters>(
send<T extends keyof ProtocolMapping.Commands>(
method: T,
params?: Protocol.CommandParameters[T]
): Promise<Protocol.CommandReturnValues[T]> {
...paramArgs: ProtocolMapping.Commands[T]['paramsType']
): 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 });
return new Promise((resolve, reject) => {
this._callbacks.set(id, { resolve, reject, error: new Error(), method });
@ -232,10 +240,10 @@ export class CDPSession extends EventEmitter {
this._sessionId = sessionId;
}
send<T extends keyof Protocol.CommandParameters>(
send<T extends keyof ProtocolMapping.Commands>(
method: T,
params?: Protocol.CommandParameters[T]
): Promise<Protocol.CommandReturnValues[T]> {
...paramArgs: ProtocolMapping.Commands[T]['paramsType']
): Promise<ProtocolMapping.Commands[T]['returnType']> {
if (!this._connection)
return Promise.reject(
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({
sessionId: this._sessionId,
method,

View File

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

View File

@ -16,7 +16,7 @@
import { assert } from './assert';
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.

View File

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

View File

@ -20,7 +20,7 @@ import { createJSHandle, JSHandle, ElementHandle } from './JSHandle';
import { CDPSession } from './Connection';
import { DOMWorld } from './DOMWorld';
import { Frame } from './FrameManager';
import Protocol from '../protocol';
import { Protocol } from 'devtools-protocol';
import { EvaluateHandleFn, SerializableOrJSHandle } from './EvalTypes';
export const EVALUATION_SCRIPT_URL = '__puppeteer_evaluation_script__';
@ -301,7 +301,7 @@ export class ExecutionContext {
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'))
return { result: { type: 'undefined' } };
if (error.message.includes("Object couldn't be returned by value"))

View File

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

View File

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

View File

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

View File

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

View File

@ -22,7 +22,7 @@ import { CDPSession } from './Connection';
import { KeyInput } from './USKeyboardLayout';
import { FrameManager, Frame } from './FrameManager';
import { getQueryHandlerAndSelector } from './QueryHandler';
import Protocol from '../protocol';
import { Protocol } from 'devtools-protocol';
import {
EvaluateFn,
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
.send('DOM.getBoxModel', {
objectId: this._remoteObject.objectId,
})
.send('DOM.getBoxModel', params)
.catch((error) => debugError(error));
}

View File

@ -16,7 +16,7 @@
import { EventEmitter } from './EventEmitter';
import { assert } from './assert';
import { helper, debugError } from './helper';
import Protocol from '../protocol';
import { Protocol } from 'devtools-protocol';
import { CDPSession } from './Connection';
import { FrameManager } from './FrameManager';
import { HTTPRequest } from './HTTPRequest';
@ -53,7 +53,7 @@ export class NetworkManager extends EventEmitter {
_requestIdToRequest = new Map<string, HTTPRequest>();
_requestIdToRequestWillBeSentEvent = new Map<
string,
Protocol.Network.requestWillBeSentPayload
Protocol.Network.RequestWillBeSentEvent
>();
_extraHTTPHeaders: Record<string, string> = {};
_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.
if (
this._protocolRequestInterceptionEnabled &&
@ -201,7 +201,7 @@ export class NetworkManager extends EventEmitter {
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
* in an easily referrable way - we should look at exposing it.
*/
@ -225,7 +225,7 @@ export class NetworkManager extends EventEmitter {
.catch(debugError);
}
_onRequestPaused(event: Protocol.Fetch.requestPausedPayload): void {
_onRequestPaused(event: Protocol.Fetch.RequestPausedEvent): void {
if (
!this._userRequestInterceptionEnabled &&
this._protocolRequestInterceptionEnabled
@ -251,7 +251,7 @@ export class NetworkManager extends EventEmitter {
}
_onRequest(
event: Protocol.Network.requestWillBeSentPayload,
event: Protocol.Network.RequestWillBeSentEvent,
interceptionId?: string
): void {
let redirectChain = [];
@ -280,7 +280,7 @@ export class NetworkManager extends EventEmitter {
}
_onRequestServedFromCache(
event: Protocol.Network.requestServedFromCachePayload
event: Protocol.Network.RequestServedFromCacheEvent
): void {
const request = this._requestIdToRequest.get(event.requestId);
if (request) request._fromMemoryCache = true;
@ -302,7 +302,7 @@ export class NetworkManager extends EventEmitter {
this.emit(NetworkManagerEmittedEvents.RequestFinished, request);
}
_onResponseReceived(event: Protocol.Network.responseReceivedPayload): void {
_onResponseReceived(event: Protocol.Network.ResponseReceivedEvent): void {
const request = this._requestIdToRequest.get(event.requestId);
// FileUpload sends a response without a matching request.
if (!request) return;
@ -311,7 +311,7 @@ export class NetworkManager extends EventEmitter {
this.emit(NetworkManagerEmittedEvents.Response, response);
}
_onLoadingFinished(event: Protocol.Network.loadingFinishedPayload): void {
_onLoadingFinished(event: Protocol.Network.LoadingFinishedEvent): void {
const request = this._requestIdToRequest.get(event.requestId);
// For certain requestIds we never receive requestWillBeSent event.
// @see https://crbug.com/750469
@ -325,7 +325,7 @@ export class NetworkManager extends EventEmitter {
this.emit(NetworkManagerEmittedEvents.RequestFinished, request);
}
_onLoadingFailed(event: Protocol.Network.loadingFailedPayload): void {
_onLoadingFailed(event: Protocol.Network.LoadingFailedEvent): void {
const request = this._requestIdToRequest.get(event.requestId);
// For certain requestIds we never receive requestWillBeSent event.
// @see https://crbug.com/750469

View File

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

View File

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

View File

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

View File

@ -18,7 +18,7 @@ import { debugError } from './helper';
import { ExecutionContext } from './ExecutionContext';
import { JSHandle } from './JSHandle';
import { CDPSession } from './Connection';
import Protocol from '../protocol';
import { Protocol } from 'devtools-protocol';
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._client.send('Runtime.enable', {}).catch(debugError);
this._client.send('Runtime.enable').catch(debugError);
this._client.on('Runtime.consoleAPICalled', (event) =>
consoleAPICalled(
event.type,

View File

@ -18,7 +18,7 @@ import { debug } from './Debug';
import * as fs from 'fs';
import { CDPSession } from './Connection';
import { promisify } from 'util';
import Protocol from '../protocol';
import { Protocol } from 'devtools-protocol';
import { CommonEventEmitter } from './EventEmitter';
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'),
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();
if (symbol.valueDeclaration && symbol.valueDeclaration.dotDotDotToken) {
const innerType = serializeType(type.typeArguments[0], circular);
innerType.name = '...' + innerType.name;
return Documentation.Member.createProperty('...' + name, innerType);
try {
const innerType = serializeType(type.typeArguments[0], circular);
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(
name,

View File

@ -251,15 +251,23 @@ function compareDocumentations(actual, expected) {
const actualArgs = Array.from(actualMethod.args.keys());
const expectedArgs = Array.from(expectedMethod.args.keys());
const argsDiff = diff(actualArgs, expectedArgs);
if (argsDiff.extra.length || argsDiff.missing.length) {
const text = [
`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'));
/* Doclint cannot handle the parameter type of the CDPSession send method.
* so we just ignore it.
*/
const isCdpSessionSend =
className === 'CDPSession' && methodName === 'send';
if (!isCdpSessionSend) {
const text = [
`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)
@ -788,6 +796,13 @@ function compareDocumentations(actual, expected) {
expectedName: 'FrameWaitForFunctionOptions',
},
],
[
'Method BrowserContext.overridePermissions() permissions',
{
actualName: 'Array<string>',
expectedName: 'Array<Object>',
},
],
]);
const expectedForSource = expectedNamingMismatches.get(source);
@ -825,6 +840,16 @@ function compareDocumentations(actual, expected) {
* as they will likely be considered "wrong" by DocLint too.
*/
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(
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;
}