chore: migrate src/NetworkManager to TypeScript (#5774)

This commit is contained in:
Jack Franklin 2020-04-30 11:15:27 +01:00 committed by GitHub
parent 862eea850e
commit 8654d630ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 179 additions and 301 deletions

View File

@ -20,7 +20,7 @@ import {Events} from './Events';
import {ExecutionContext, EVALUATION_SCRIPT_URL} from './ExecutionContext'; import {ExecutionContext, EVALUATION_SCRIPT_URL} from './ExecutionContext';
import {LifecycleWatcher, PuppeteerLifeCycleEvent} from './LifecycleWatcher'; import {LifecycleWatcher, PuppeteerLifeCycleEvent} from './LifecycleWatcher';
import {DOMWorld, WaitForSelectorOptions} from './DOMWorld'; import {DOMWorld, WaitForSelectorOptions} from './DOMWorld';
import {NetworkManager} from './NetworkManager'; import {NetworkManager, Response} from './NetworkManager';
import {TimeoutSettings} from './TimeoutSettings'; import {TimeoutSettings} from './TimeoutSettings';
import {CDPSession} from './Connection'; import {CDPSession} from './Connection';
import {JSHandle, ElementHandle} from './JSHandle'; import {JSHandle, ElementHandle} from './JSHandle';
@ -31,7 +31,7 @@ const UTILITY_WORLD_NAME = '__puppeteer_utility_world__';
export class FrameManager extends EventEmitter { export class FrameManager extends EventEmitter {
_client: CDPSession; _client: CDPSession;
_page: Puppeteer.Page; _page: Puppeteer.Page;
_networkManager: Puppeteer.NetworkManager; _networkManager: NetworkManager;
_timeoutSettings: TimeoutSettings; _timeoutSettings: TimeoutSettings;
_frames = new Map<string, Frame>(); _frames = new Map<string, Frame>();
_contextIdToContext = new Map<number, ExecutionContext>(); _contextIdToContext = new Map<number, ExecutionContext>();
@ -70,11 +70,11 @@ export class FrameManager extends EventEmitter {
]); ]);
} }
networkManager(): Puppeteer.NetworkManager { networkManager(): NetworkManager {
return this._networkManager; return this._networkManager;
} }
async navigateFrame(frame: Frame, url: string, options: {referer?: string; timeout?: number; waitUntil?: PuppeteerLifeCycleEvent|PuppeteerLifeCycleEvent[]} = {}): Promise<Puppeteer.Response | null> { async navigateFrame(frame: Frame, url: string, options: {referer?: string; timeout?: number; waitUntil?: PuppeteerLifeCycleEvent|PuppeteerLifeCycleEvent[]} = {}): Promise<Response | null> {
assertNoLegacyNavigationOptions(options); assertNoLegacyNavigationOptions(options);
const { const {
referer = this._networkManager.extraHTTPHeaders()['referer'], referer = this._networkManager.extraHTTPHeaders()['referer'],
@ -110,7 +110,7 @@ export class FrameManager extends EventEmitter {
} }
} }
async waitForFrameNavigation(frame: Frame, options: {timeout?: number; waitUntil?: PuppeteerLifeCycleEvent|PuppeteerLifeCycleEvent[]} = {}): Promise<Puppeteer.Response | null> { async waitForFrameNavigation(frame: Frame, options: {timeout?: number; waitUntil?: PuppeteerLifeCycleEvent|PuppeteerLifeCycleEvent[]} = {}): Promise<Response | null> {
assertNoLegacyNavigationOptions(options); assertNoLegacyNavigationOptions(options);
const { const {
waitUntil = ['load'], waitUntil = ['load'],
@ -333,11 +333,11 @@ export class Frame {
this._parentFrame._childFrames.add(this); this._parentFrame._childFrames.add(this);
} }
async goto(url: string, options: {referer?: string; timeout?: number; waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[]}): Promise<Puppeteer.Response | null> { async goto(url: string, options: {referer?: string; timeout?: number; waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[]}): Promise<Response | null> {
return await this._frameManager.navigateFrame(this, url, options); return await this._frameManager.navigateFrame(this, url, options);
} }
async waitForNavigation(options: {timeout?: number; waitUntil?: PuppeteerLifeCycleEvent|PuppeteerLifeCycleEvent[]}): Promise<Puppeteer.Response | null> { async waitForNavigation(options: {timeout?: number; waitUntil?: PuppeteerLifeCycleEvent|PuppeteerLifeCycleEvent[]}): Promise<Response | null> {
return await this._frameManager.waitForFrameNavigation(this, options); return await this._frameManager.waitForFrameNavigation(this, options);
} }

View File

@ -18,6 +18,7 @@ import {helper, assert, PuppeteerEventListener} from './helper';
import {Events} from './Events'; import {Events} from './Events';
import {TimeoutError} from './Errors'; import {TimeoutError} from './Errors';
import {FrameManager, Frame} from './FrameManager'; import {FrameManager, Frame} from './FrameManager';
import {Request, Response} from './NetworkManager';
export type PuppeteerLifeCycleEvent = 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'; export type PuppeteerLifeCycleEvent = 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2';
type ProtocolLifeCycleEvent = 'load' | 'DOMContentLoaded' | 'networkIdle' | 'networkAlmostIdle'; type ProtocolLifeCycleEvent = 'load' | 'DOMContentLoaded' | 'networkIdle' | 'networkAlmostIdle';
@ -35,7 +36,7 @@ export class LifecycleWatcher {
_frameManager: FrameManager; _frameManager: FrameManager;
_frame: Frame; _frame: Frame;
_timeout: number; _timeout: number;
_navigationRequest?: Puppeteer.Request; _navigationRequest?: Request;
_eventListeners: PuppeteerEventListener[]; _eventListeners: PuppeteerEventListener[];
_initialLoaderId: string; _initialLoaderId: string;
@ -72,7 +73,6 @@ export class LifecycleWatcher {
this._frame = frame; this._frame = frame;
this._initialLoaderId = frame._loaderId; this._initialLoaderId = frame._loaderId;
this._timeout = timeout; this._timeout = timeout;
/** @type {?Puppeteer.Request} */
this._navigationRequest = null; this._navigationRequest = null;
this._eventListeners = [ this._eventListeners = [
helper.addEventListener(frameManager._client, Events.CDPSession.Disconnected, () => this._terminate(new Error('Navigation failed because browser has disconnected!'))), helper.addEventListener(frameManager._client, Events.CDPSession.Disconnected, () => this._terminate(new Error('Navigation failed because browser has disconnected!'))),
@ -101,7 +101,7 @@ export class LifecycleWatcher {
this._checkLifecycleComplete(); this._checkLifecycleComplete();
} }
_onRequest(request: Puppeteer.Request): void { _onRequest(request: Request): void {
if (request.frame() !== this._frame || !request.isNavigationRequest()) if (request.frame() !== this._frame || !request.isNavigationRequest())
return; return;
this._navigationRequest = request; this._navigationRequest = request;
@ -115,7 +115,7 @@ export class LifecycleWatcher {
this._checkLifecycleComplete(); this._checkLifecycleComplete();
} }
navigationResponse(): Puppeteer.Response | null { navigationResponse(): Response | null {
return this._navigationRequest ? this._navigationRequest.response() : null; return this._navigationRequest ? this._navigationRequest.response() : null;
} }

View File

@ -13,44 +13,37 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
const EventEmitter = require('events'); import * as EventEmitter from 'events';
const {helper, assert, debugError} = require('./helper'); import {helper, assert, debugError} from './helper';
const {Events} = require('./Events'); import {Events} from './Events';
// CDPSession is used only as a typedef import {CDPSession} from './Connection';
// eslint-disable-next-line no-unused-vars import {FrameManager, Frame} from './FrameManager';
const {CDPSession} = require('./Connection');
// used only as a typedef
// eslint-disable-next-line no-unused-vars
const {Frame, FrameManager} = require('./FrameManager');
class NetworkManager extends EventEmitter { interface Credentials {
/** username: string;
* @param {!CDPSession} client password: string;
* @param {!FrameManager} frameManager }
*/
constructor(client, ignoreHTTPSErrors, frameManager) { export class NetworkManager extends EventEmitter {
_client: CDPSession;
_ignoreHTTPSErrors: boolean;
_frameManager: FrameManager;
_requestIdToRequest = new Map<string, Request>();
_requestIdToRequestWillBeSentEvent = new Map<string, Protocol.Network.requestWillBeSentPayload>();
_extraHTTPHeaders: Record<string, string> = {};
_offline = false;
_credentials?: Credentials = null;
_attemptedAuthentications = new Set<string>();
_userRequestInterceptionEnabled = false;
_protocolRequestInterceptionEnabled = false;
_userCacheDisabled = false;
_requestIdToInterceptionId = new Map<string, string>();
constructor(client: CDPSession, ignoreHTTPSErrors: boolean, frameManager: FrameManager) {
super(); super();
this._client = client; this._client = client;
this._ignoreHTTPSErrors = ignoreHTTPSErrors; this._ignoreHTTPSErrors = ignoreHTTPSErrors;
this._frameManager = frameManager; this._frameManager = frameManager;
/** @type {!Map<string, !Request>} */
this._requestIdToRequest = new Map();
/** @type {!Map<string, !Protocol.Network.requestWillBeSentPayload>} */
this._requestIdToRequestWillBeSentEvent = new Map();
/** @type {!Object<string, string>} */
this._extraHTTPHeaders = {};
this._offline = false;
/** @type {?{username: string, password: string}} */
this._credentials = null;
/** @type {!Set<string>} */
this._attemptedAuthentications = new Set();
this._userRequestInterceptionEnabled = false;
this._protocolRequestInterceptionEnabled = false;
this._userCacheDisabled = false;
/** @type {!Map<string, string>} */
this._requestIdToInterceptionId = new Map();
this._client.on('Fetch.requestPaused', this._onRequestPaused.bind(this)); this._client.on('Fetch.requestPaused', this._onRequestPaused.bind(this));
this._client.on('Fetch.authRequired', this._onAuthRequired.bind(this)); this._client.on('Fetch.authRequired', this._onAuthRequired.bind(this));
@ -61,24 +54,18 @@ class NetworkManager extends EventEmitter {
this._client.on('Network.loadingFailed', this._onLoadingFailed.bind(this)); this._client.on('Network.loadingFailed', this._onLoadingFailed.bind(this));
} }
async initialize() { async initialize(): Promise<void> {
await this._client.send('Network.enable'); await this._client.send('Network.enable');
if (this._ignoreHTTPSErrors) if (this._ignoreHTTPSErrors)
await this._client.send('Security.setIgnoreCertificateErrors', {ignore: true}); await this._client.send('Security.setIgnoreCertificateErrors', {ignore: true});
} }
/** async authenticate(credentials?: Credentials): Promise<void> {
* @param {?{username: string, password: string}} credentials
*/
async authenticate(credentials) {
this._credentials = credentials; this._credentials = credentials;
await this._updateProtocolRequestInterception(); await this._updateProtocolRequestInterception();
} }
/** async setExtraHTTPHeaders(extraHTTPHeaders: Record<string, string>): Promise<void> {
* @param {!Object<string, string>} extraHTTPHeaders
*/
async setExtraHTTPHeaders(extraHTTPHeaders) {
this._extraHTTPHeaders = {}; this._extraHTTPHeaders = {};
for (const key of Object.keys(extraHTTPHeaders)) { for (const key of Object.keys(extraHTTPHeaders)) {
const value = extraHTTPHeaders[key]; const value = extraHTTPHeaders[key];
@ -88,17 +75,11 @@ class NetworkManager extends EventEmitter {
await this._client.send('Network.setExtraHTTPHeaders', {headers: this._extraHTTPHeaders}); await this._client.send('Network.setExtraHTTPHeaders', {headers: this._extraHTTPHeaders});
} }
/** extraHTTPHeaders(): Record<string, string> {
* @return {!Object<string, string>}
*/
extraHTTPHeaders() {
return Object.assign({}, this._extraHTTPHeaders); return Object.assign({}, this._extraHTTPHeaders);
} }
/** async setOfflineMode(value: boolean): Promise<void> {
* @param {boolean} value
*/
async setOfflineMode(value) {
if (this._offline === value) if (this._offline === value)
return; return;
this._offline = value; this._offline = value;
@ -111,30 +92,21 @@ class NetworkManager extends EventEmitter {
}); });
} }
/** async setUserAgent(userAgent: string): Promise<void> {
* @param {string} userAgent
*/
async setUserAgent(userAgent) {
await this._client.send('Network.setUserAgentOverride', {userAgent}); await this._client.send('Network.setUserAgentOverride', {userAgent});
} }
/** async setCacheEnabled(enabled: boolean): Promise<void> {
* @param {boolean} enabled
*/
async setCacheEnabled(enabled) {
this._userCacheDisabled = !enabled; this._userCacheDisabled = !enabled;
await this._updateProtocolCacheDisabled(); await this._updateProtocolCacheDisabled();
} }
/** async setRequestInterception(value: boolean): Promise<void> {
* @param {boolean} value
*/
async setRequestInterception(value) {
this._userRequestInterceptionEnabled = value; this._userRequestInterceptionEnabled = value;
await this._updateProtocolRequestInterception(); await this._updateProtocolRequestInterception();
} }
async _updateProtocolRequestInterception() { async _updateProtocolRequestInterception(): Promise<void> {
const enabled = this._userRequestInterceptionEnabled || !!this._credentials; const enabled = this._userRequestInterceptionEnabled || !!this._credentials;
if (enabled === this._protocolRequestInterceptionEnabled) if (enabled === this._protocolRequestInterceptionEnabled)
return; return;
@ -155,16 +127,13 @@ class NetworkManager extends EventEmitter {
} }
} }
async _updateProtocolCacheDisabled() { async _updateProtocolCacheDisabled(): Promise<void> {
await this._client.send('Network.setCacheDisabled', { await this._client.send('Network.setCacheDisabled', {
cacheDisabled: this._userCacheDisabled || this._protocolRequestInterceptionEnabled cacheDisabled: this._userCacheDisabled || this._protocolRequestInterceptionEnabled
}); });
} }
/** _onRequestWillBeSent(event: Protocol.Network.requestWillBeSentPayload): void {
* @param {!Protocol.Network.requestWillBeSentPayload} event
*/
_onRequestWillBeSent(event) {
// Request interception doesn't happen for data URLs with Network Service. // Request interception doesn't happen for data URLs with Network Service.
if (this._protocolRequestInterceptionEnabled && !event.request.url.startsWith('data:')) { if (this._protocolRequestInterceptionEnabled && !event.request.url.startsWith('data:')) {
const requestId = event.requestId; const requestId = event.requestId;
@ -183,9 +152,12 @@ class NetworkManager extends EventEmitter {
/** /**
* @param {!Protocol.Fetch.authRequiredPayload} event * @param {!Protocol.Fetch.authRequiredPayload} event
*/ */
_onAuthRequired(event) { _onAuthRequired(event: Protocol.Fetch.authRequiredPayload): void {
/** @type {"Default"|"CancelAuth"|"ProvideCredentials"} */ /* TODO(jacktfranklin): This is defined in protocol.d.ts but not
let response = 'Default'; * in an easily referrable way - we should look at exposing it.
*/
type AuthResponse = 'Default'|'CancelAuth'|'ProvideCredentials';
let response: AuthResponse = 'Default';
if (this._attemptedAuthentications.has(event.requestId)) { if (this._attemptedAuthentications.has(event.requestId)) {
response = 'CancelAuth'; response = 'CancelAuth';
} else if (this._credentials) { } else if (this._credentials) {
@ -199,10 +171,7 @@ class NetworkManager extends EventEmitter {
}).catch(debugError); }).catch(debugError);
} }
/** _onRequestPaused(event: Protocol.Fetch.requestPausedPayload): void {
* @param {!Protocol.Fetch.requestPausedPayload} event
*/
_onRequestPaused(event) {
if (!this._userRequestInterceptionEnabled && this._protocolRequestInterceptionEnabled) { if (!this._userRequestInterceptionEnabled && this._protocolRequestInterceptionEnabled) {
this._client.send('Fetch.continueRequest', { this._client.send('Fetch.continueRequest', {
requestId: event.requestId requestId: event.requestId
@ -220,11 +189,7 @@ class NetworkManager extends EventEmitter {
} }
} }
/** _onRequest(event: Protocol.Network.requestWillBeSentPayload, interceptionId?: string): void {
* @param {!Protocol.Network.requestWillBeSentPayload} event
* @param {?string} interceptionId
*/
_onRequest(event, interceptionId) {
let redirectChain = []; let redirectChain = [];
if (event.redirectResponse) { if (event.redirectResponse) {
const request = this._requestIdToRequest.get(event.requestId); const request = this._requestIdToRequest.get(event.requestId);
@ -240,21 +205,13 @@ class NetworkManager extends EventEmitter {
this.emit(Events.NetworkManager.Request, request); this.emit(Events.NetworkManager.Request, request);
} }
_onRequestServedFromCache(event: Protocol.Network.requestServedFromCachePayload): void {
/**
* @param {!Protocol.Network.requestServedFromCachePayload} event
*/
_onRequestServedFromCache(event) {
const request = this._requestIdToRequest.get(event.requestId); const request = this._requestIdToRequest.get(event.requestId);
if (request) if (request)
request._fromMemoryCache = true; request._fromMemoryCache = true;
} }
/** _handleRequestRedirect(request: Request, responsePayload: Protocol.Network.Response): void {
* @param {!Request} request
* @param {!Protocol.Network.Response} responsePayload
*/
_handleRequestRedirect(request, responsePayload) {
const response = new Response(this._client, request, responsePayload); const response = new Response(this._client, request, responsePayload);
request._response = response; request._response = response;
request._redirectChain.push(request); request._redirectChain.push(request);
@ -265,10 +222,7 @@ class NetworkManager extends EventEmitter {
this.emit(Events.NetworkManager.RequestFinished, request); this.emit(Events.NetworkManager.RequestFinished, request);
} }
/** _onResponseReceived(event: Protocol.Network.responseReceivedPayload): void {
* @param {!Protocol.Network.responseReceivedPayload} event
*/
_onResponseReceived(event) {
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) if (!request)
@ -278,10 +232,7 @@ class NetworkManager extends EventEmitter {
this.emit(Events.NetworkManager.Response, response); this.emit(Events.NetworkManager.Response, response);
} }
/** _onLoadingFinished(event: Protocol.Network.loadingFinishedPayload): void {
* @param {!Protocol.Network.loadingFinishedPayload} event
*/
_onLoadingFinished(event) {
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
@ -297,10 +248,7 @@ class NetworkManager extends EventEmitter {
this.emit(Events.NetworkManager.RequestFinished, request); this.emit(Events.NetworkManager.RequestFinished, request);
} }
/** _onLoadingFailed(event: Protocol.Network.loadingFailedPayload): void {
* @param {!Protocol.Network.loadingFailedPayload} event
*/
_onLoadingFailed(event) {
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
@ -316,105 +264,83 @@ class NetworkManager extends EventEmitter {
} }
} }
class Request { export class Request {
/** _client: CDPSession;
* @param {!CDPSession} client _requestId: string;
* @param {?Frame} frame _isNavigationRequest: boolean;
* @param {string} interceptionId _interceptionId: string;
* @param {boolean} allowInterception _allowInterception: boolean;
* @param {!Protocol.Network.requestWillBeSentPayload} event _interceptionHandled = false;
* @param {!Array<!Request>} redirectChain _response: Response | null = null;
*/ _failureText = null;
constructor(client, frame, interceptionId, allowInterception, event, redirectChain) { _url: string;
_resourceType: string;
_method: string;
_postData?: string;
_headers: Record<string, string> = {};
_frame: Frame;
_redirectChain: Request[];
_fromMemoryCache = false;
constructor(client: CDPSession, frame: Frame, interceptionId: string, allowInterception: boolean, event: Protocol.Network.requestWillBeSentPayload, redirectChain: Request[]) {
this._client = client; this._client = client;
this._requestId = event.requestId; this._requestId = event.requestId;
this._isNavigationRequest = event.requestId === event.loaderId && event.type === 'Document'; this._isNavigationRequest = event.requestId === event.loaderId && event.type === 'Document';
this._interceptionId = interceptionId; this._interceptionId = interceptionId;
this._allowInterception = allowInterception; this._allowInterception = allowInterception;
this._interceptionHandled = false;
this._response = null;
this._failureText = null;
this._url = event.request.url; this._url = event.request.url;
this._resourceType = event.type.toLowerCase(); this._resourceType = event.type.toLowerCase();
this._method = event.request.method; this._method = event.request.method;
this._postData = event.request.postData; this._postData = event.request.postData;
this._headers = {};
this._frame = frame; this._frame = frame;
this._redirectChain = redirectChain; this._redirectChain = redirectChain;
for (const key of Object.keys(event.request.headers)) for (const key of Object.keys(event.request.headers))
this._headers[key.toLowerCase()] = event.request.headers[key]; this._headers[key.toLowerCase()] = event.request.headers[key];
this._fromMemoryCache = false;
} }
/** url(): string {
* @return {string}
*/
url() {
return this._url; return this._url;
} }
/** resourceType(): string {
* @return {string}
*/
resourceType() {
return this._resourceType; return this._resourceType;
} }
/** method(): string {
* @return {string}
*/
method() {
return this._method; return this._method;
} }
/** postData(): string | undefined {
* @return {string|undefined}
*/
postData() {
return this._postData; return this._postData;
} }
/** headers(): Record<string, string> {
* @return {!Object}
*/
headers() {
return this._headers; return this._headers;
} }
/** response(): Response | null {
* @return {?Response}
*/
response() {
return this._response; return this._response;
} }
/** frame(): Frame | null {
* @return {?Frame}
*/
frame() {
return this._frame; return this._frame;
} }
/** isNavigationRequest(): boolean {
* @return {boolean}
*/
isNavigationRequest() {
return this._isNavigationRequest; return this._isNavigationRequest;
} }
/** redirectChain(): Request[] {
* @return {!Array<!Request>}
*/
redirectChain() {
return this._redirectChain.slice(); return this._redirectChain.slice();
} }
/** /**
* @return {?{errorText: string}} * @return {?{errorText: string}}
*/ */
failure() { failure(): {errorText: string} | null {
if (!this._failureText) if (!this._failureText)
return null; return null;
return { return {
@ -422,10 +348,7 @@ class Request {
}; };
} }
/** async continue(overrides: {url?: string; method?: string; postData?: string; headers?: Record<string, string>} = {}): Promise<void> {
* @param {!{url?: string, method?:string, postData?: string, headers?: !Object}} overrides
*/
async continue(overrides = {}) {
// Request interception is not supported for data: urls. // Request interception is not supported for data: urls.
if (this._url.startsWith('data:')) if (this._url.startsWith('data:'))
return; return;
@ -451,10 +374,12 @@ class Request {
}); });
} }
/** async respond(response: {
* @param {!{status: number, headers: Object, contentType: string, body: (string|Buffer)}} response status: number;
*/ headers: Record<string, string>;
async respond(response) { contentType: string;
body: string|Buffer;
}): Promise<void> {
// Mocking responses for dataURL requests is not currently supported. // Mocking responses for dataURL requests is not currently supported.
if (this._url.startsWith('data:')) if (this._url.startsWith('data:'))
return; return;
@ -462,10 +387,9 @@ class Request {
assert(!this._interceptionHandled, 'Request is already handled!'); assert(!this._interceptionHandled, 'Request is already handled!');
this._interceptionHandled = true; this._interceptionHandled = true;
const responseBody = response.body && helper.isString(response.body) ? Buffer.from(/** @type {string} */(response.body)) : /** @type {?Buffer} */(response.body || null); const responseBody: Buffer | null = response.body && helper.isString(response.body) ? Buffer.from(response.body) : response.body as Buffer || null;
/** @type {!Object<string, string>} */ const responseHeaders: Record<string, string> = {};
const responseHeaders = {};
if (response.headers) { if (response.headers) {
for (const header of Object.keys(response.headers)) for (const header of Object.keys(response.headers))
responseHeaders[header.toLowerCase()] = response.headers[header]; responseHeaders[header.toLowerCase()] = response.headers[header];
@ -488,10 +412,7 @@ class Request {
}); });
} }
/** async abort(errorCode: ErrorCode = 'failed'): Promise<void> {
* @param {string=} errorCode
*/
async abort(errorCode = 'failed') {
// Request interception is not supported for data: urls. // Request interception is not supported for data: urls.
if (this._url.startsWith('data:')) if (this._url.startsWith('data:'))
return; return;
@ -511,7 +432,9 @@ class Request {
} }
} }
const errorReasons = { type ErrorCode = 'aborted' | 'accessdenied' | 'addressunreachable' | 'blockedbyclient' | 'blockedbyresponse' | 'connectionaborted' | 'connectionclosed' | 'connectionfailed' | 'connectionrefused' | 'connectionreset' | 'internetdisconnected' | 'namenotresolved' | 'timedout' | 'failed';
const errorReasons: Record<ErrorCode, Protocol.Network.ErrorReason> = {
'aborted': 'Aborted', 'aborted': 'Aborted',
'accessdenied': 'AccessDenied', 'accessdenied': 'AccessDenied',
'addressunreachable': 'AddressUnreachable', 'addressunreachable': 'AddressUnreachable',
@ -526,18 +449,31 @@ const errorReasons = {
'namenotresolved': 'NameNotResolved', 'namenotresolved': 'NameNotResolved',
'timedout': 'TimedOut', 'timedout': 'TimedOut',
'failed': 'Failed', 'failed': 'Failed',
}; } as const;
class Response { interface RemoteAddress {
/** ip: string;
* @param {!CDPSession} client port: number;
* @param {!Request} request }
* @param {!Protocol.Network.Response} responsePayload
*/ export class Response {
constructor(client, request, responsePayload) { _client: CDPSession;
_request: Request;
_contentPromise: Promise<Buffer> | null = null;
_bodyLoadedPromise: Promise<boolean>;
_bodyLoadedPromiseFulfill: (x: boolean) => void;
_remoteAddress: RemoteAddress;
_status: number;
_statusText: string;
_url: string;
_fromDiskCache: boolean;
_fromServiceWorker: boolean;
_headers: Record<string, string> = {};
_securityDetails: SecurityDetails | null;
constructor(client: CDPSession, request: Request, responsePayload: Protocol.Network.Response) {
this._client = client; this._client = client;
this._request = request; this._request = request;
this._contentPromise = null;
this._bodyLoadedPromise = new Promise(fulfill => { this._bodyLoadedPromise = new Promise(fulfill => {
this._bodyLoadedPromiseFulfill = fulfill; this._bodyLoadedPromiseFulfill = fulfill;
@ -552,65 +488,40 @@ class Response {
this._url = request.url(); this._url = request.url();
this._fromDiskCache = !!responsePayload.fromDiskCache; this._fromDiskCache = !!responsePayload.fromDiskCache;
this._fromServiceWorker = !!responsePayload.fromServiceWorker; this._fromServiceWorker = !!responsePayload.fromServiceWorker;
this._headers = {};
for (const key of Object.keys(responsePayload.headers)) for (const key of Object.keys(responsePayload.headers))
this._headers[key.toLowerCase()] = responsePayload.headers[key]; this._headers[key.toLowerCase()] = responsePayload.headers[key];
this._securityDetails = responsePayload.securityDetails ? new SecurityDetails(responsePayload.securityDetails) : null; this._securityDetails = responsePayload.securityDetails ? new SecurityDetails(responsePayload.securityDetails) : null;
} }
/** remoteAddress(): RemoteAddress {
* @return {{ip: string, port: number}}
*/
remoteAddress() {
return this._remoteAddress; return this._remoteAddress;
} }
/** url(): string {
* @return {string}
*/
url() {
return this._url; return this._url;
} }
/** ok(): boolean {
* @return {boolean}
*/
ok() {
return this._status === 0 || (this._status >= 200 && this._status <= 299); return this._status === 0 || (this._status >= 200 && this._status <= 299);
} }
/** status(): number {
* @return {number}
*/
status() {
return this._status; return this._status;
} }
/** statusText(): string {
* @return {string}
*/
statusText() {
return this._statusText; return this._statusText;
} }
/** headers(): Record<string, string> {
* @return {!Object}
*/
headers() {
return this._headers; return this._headers;
} }
/** securityDetails(): SecurityDetails | null {
* @return {?SecurityDetails}
*/
securityDetails() {
return this._securityDetails; return this._securityDetails;
} }
/** buffer(): Promise<Buffer> {
* @return {!Promise<!Buffer>}
*/
buffer() {
if (!this._contentPromise) { if (!this._contentPromise) {
this._contentPromise = this._bodyLoadedPromise.then(async error => { this._contentPromise = this._bodyLoadedPromise.then(async error => {
if (error) if (error)
@ -624,104 +535,70 @@ class Response {
return this._contentPromise; return this._contentPromise;
} }
/** async text(): Promise<string> {
* @return {!Promise<string>}
*/
async text() {
const content = await this.buffer(); const content = await this.buffer();
return content.toString('utf8'); return content.toString('utf8');
} }
/** async json(): Promise<any> {
* @return {!Promise<!Object>}
*/
async json() {
const content = await this.text(); const content = await this.text();
return JSON.parse(content); return JSON.parse(content);
} }
/** request(): Request {
* @return {!Request}
*/
request() {
return this._request; return this._request;
} }
/** fromCache(): boolean {
* @return {boolean}
*/
fromCache() {
return this._fromDiskCache || this._request._fromMemoryCache; return this._fromDiskCache || this._request._fromMemoryCache;
} }
/** fromServiceWorker(): boolean {
* @return {boolean}
*/
fromServiceWorker() {
return this._fromServiceWorker; return this._fromServiceWorker;
} }
/** frame(): Frame | null {
* @return {?Frame}
*/
frame() {
return this._request.frame(); return this._request.frame();
} }
} }
class SecurityDetails { export class SecurityDetails {
/** _subjectName: string;
* @param {!Protocol.Network.SecurityDetails} securityPayload _issuer: string;
*/ _validFrom: number;
constructor(securityPayload) { _validTo: number;
this._subjectName = securityPayload['subjectName']; _protocol: string;
this._issuer = securityPayload['issuer'];
this._validFrom = securityPayload['validFrom']; constructor(securityPayload: Protocol.Network.SecurityDetails) {
this._validTo = securityPayload['validTo']; this._subjectName = securityPayload.subjectName;
this._protocol = securityPayload['protocol']; this._issuer = securityPayload.issuer;
this._validFrom = securityPayload.validFrom;
this._validTo = securityPayload.validTo;
this._protocol = securityPayload.protocol;
} }
/** subjectName(): string {
* @return {string}
*/
subjectName() {
return this._subjectName; return this._subjectName;
} }
/** issuer(): string {
* @return {string}
*/
issuer() {
return this._issuer; return this._issuer;
} }
/** validFrom(): number {
* @return {number}
*/
validFrom() {
return this._validFrom; return this._validFrom;
} }
/** validTo(): number {
* @return {number}
*/
validTo() {
return this._validTo; return this._validTo;
} }
/** protocol(): string {
* @return {string}
*/
protocol() {
return this._protocol; return this._protocol;
} }
} }
/** function headersArray(headers: Record<string, string>): Array<{name: string; value: string}> {
* @param {Object<string, string>} headers
* @return {!Array<{name: string, value: string}>}
*/
function headersArray(headers) {
const result = []; const result = [];
for (const name in headers) { for (const name in headers) {
if (!Object.is(headers[name], undefined)) if (!Object.is(headers[name], undefined))
@ -795,6 +672,4 @@ const STATUS_TEXTS = {
'508': 'Loop Detected', '508': 'Loop Detected',
'510': 'Not Extended', '510': 'Not Extended',
'511': 'Network Authentication Required', '511': 'Network Authentication Required',
}; } as const;
module.exports = {Request, Response, NetworkManager, SecurityDetails};

View File

@ -40,6 +40,9 @@ const {Target} = require('./Target');
// Import used as typedef // Import used as typedef
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
const {createJSHandle, JSHandle, ElementHandle} = require('./JSHandle'); const {createJSHandle, JSHandle, ElementHandle} = require('./JSHandle');
// Import used as typedef
// eslint-disable-next-line no-unused-vars
const {Request: PuppeteerRequest, Response: PuppeteerResponse} = require('./NetworkManager');
const {Accessibility} = require('./Accessibility'); const {Accessibility} = require('./Accessibility');
const {TimeoutSettings} = require('./TimeoutSettings'); const {TimeoutSettings} = require('./TimeoutSettings');
@ -692,7 +695,7 @@ class Page extends EventEmitter {
/** /**
* @param {string} url * @param {string} url
* @param {!{referer?: string, timeout?: number, waitUntil?: !Puppeteer.PuppeteerLifeCycleEvent|!Array<!Puppeteer.PuppeteerLifeCycleEvent>}=} options * @param {!{referer?: string, timeout?: number, waitUntil?: !Puppeteer.PuppeteerLifeCycleEvent|!Array<!Puppeteer.PuppeteerLifeCycleEvent>}=} options
* @return {!Promise<?Puppeteer.Response>} * @return {!Promise<?PuppeteerResponse>}
*/ */
async goto(url, options) { async goto(url, options) {
return await this._frameManager.mainFrame().goto(url, options); return await this._frameManager.mainFrame().goto(url, options);
@ -700,7 +703,7 @@ class Page extends EventEmitter {
/** /**
* @param {!{timeout?: number, waitUntil?: !Puppeteer.PuppeteerLifeCycleEvent|!Array<!Puppeteer.PuppeteerLifeCycleEvent>}=} options * @param {!{timeout?: number, waitUntil?: !Puppeteer.PuppeteerLifeCycleEvent|!Array<!Puppeteer.PuppeteerLifeCycleEvent>}=} options
* @return {!Promise<?Puppeteer.Response>} * @return {!Promise<?PuppeteerResponse>}
*/ */
async reload(options) { async reload(options) {
const result = await Promise.all([ const result = await Promise.all([
@ -708,13 +711,13 @@ class Page extends EventEmitter {
this._client.send('Page.reload') this._client.send('Page.reload')
]); ]);
const response = /** @type Puppeteer.Response */ (result[0]); const response = /** @type PuppeteerResponse */ (result[0]);
return response; return response;
} }
/** /**
* @param {!{timeout?: number, waitUntil?: !Puppeteer.PuppeteerLifeCycleEvent|!Array<!Puppeteer.PuppeteerLifeCycleEvent>}=} options * @param {!{timeout?: number, waitUntil?: !Puppeteer.PuppeteerLifeCycleEvent|!Array<!Puppeteer.PuppeteerLifeCycleEvent>}=} options
* @return {!Promise<?Puppeteer.Response>} * @return {!Promise<?PuppeteerResponse>}
*/ */
async waitForNavigation(options = {}) { async waitForNavigation(options = {}) {
return await this._frameManager.mainFrame().waitForNavigation(options); return await this._frameManager.mainFrame().waitForNavigation(options);
@ -729,7 +732,7 @@ class Page extends EventEmitter {
/** /**
* @param {(string|Function)} urlOrPredicate * @param {(string|Function)} urlOrPredicate
* @param {!{timeout?: number}=} options * @param {!{timeout?: number}=} options
* @return {!Promise<!Puppeteer.Request>} * @return {!Promise<!PuppeteerRequest>}
*/ */
async waitForRequest(urlOrPredicate, options = {}) { async waitForRequest(urlOrPredicate, options = {}) {
const { const {
@ -747,7 +750,7 @@ class Page extends EventEmitter {
/** /**
* @param {(string|Function)} urlOrPredicate * @param {(string|Function)} urlOrPredicate
* @param {!{timeout?: number}=} options * @param {!{timeout?: number}=} options
* @return {!Promise<!Puppeteer.Response>} * @return {!Promise<!PuppeteerResponse>}
*/ */
async waitForResponse(urlOrPredicate, options = {}) { async waitForResponse(urlOrPredicate, options = {}) {
const { const {
@ -764,7 +767,7 @@ class Page extends EventEmitter {
/** /**
* @param {!{timeout?: number, waitUntil?: !Puppeteer.PuppeteerLifeCycleEvent|!Array<!Puppeteer.PuppeteerLifeCycleEvent>}=} options * @param {!{timeout?: number, waitUntil?: !Puppeteer.PuppeteerLifeCycleEvent|!Array<!Puppeteer.PuppeteerLifeCycleEvent>}=} options
* @return {!Promise<?Puppeteer.Response>} * @return {!Promise<?PuppeteerResponse>}
*/ */
async goBack(options) { async goBack(options) {
return this._go(-1, options); return this._go(-1, options);
@ -772,7 +775,7 @@ class Page extends EventEmitter {
/** /**
* @param {!{timeout?: number, waitUntil?: !Puppeteer.PuppeteerLifeCycleEvent|!Array<!Puppeteer.PuppeteerLifeCycleEvent>}=} options * @param {!{timeout?: number, waitUntil?: !Puppeteer.PuppeteerLifeCycleEvent|!Array<!Puppeteer.PuppeteerLifeCycleEvent>}=} options
* @return {!Promise<?Puppeteer.Response>} * @return {!Promise<?PuppeteerResponse>}
*/ */
async goForward(options) { async goForward(options) {
return this._go(+1, options); return this._go(+1, options);
@ -780,7 +783,7 @@ class Page extends EventEmitter {
/** /**
* @param {!{timeout?: number, waitUntil?: !Puppeteer.PuppeteerLifeCycleEvent|!Array<!Puppeteer.PuppeteerLifeCycleEvent>}=} options * @param {!{timeout?: number, waitUntil?: !Puppeteer.PuppeteerLifeCycleEvent|!Array<!Puppeteer.PuppeteerLifeCycleEvent>}=} options
* @return {!Promise<?Puppeteer.Response>} * @return {!Promise<?PuppeteerResponse>}
*/ */
async _go(delta, options) { async _go(delta, options) {
const history = await this._client.send('Page.getNavigationHistory'); const history = await this._client.send('Page.getNavigationHistory');
@ -791,7 +794,7 @@ class Page extends EventEmitter {
this.waitForNavigation(options), this.waitForNavigation(options),
this._client.send('Page.navigateToHistoryEntry', {entryId: entry.id}), this._client.send('Page.navigateToHistoryEntry', {entryId: entry.id}),
]); ]);
const response = /** @type Puppeteer.Response */ (result[0]); const response = /** @type PuppeteerResponse */ (result[0]);
return response; return response;
} }

4
src/externs.d.ts vendored
View File

@ -1,12 +1,8 @@
import {Page as RealPage} from './Page.js'; import {Page as RealPage} from './Page.js';
import { NetworkManager as RealNetworkManager, Request as RealRequest, Response as RealResponse } from './NetworkManager.js';
import * as child_process from 'child_process'; import * as child_process from 'child_process';
declare global { declare global {
module Puppeteer { module Puppeteer {
export class NetworkManager extends RealNetworkManager {}
export class Page extends RealPage { } export class Page extends RealPage { }
export class Response extends RealResponse { }
export class Request extends RealRequest { }
/* TODO(jacktfranklin@): once DOMWorld, Page, and FrameManager are in TS /* TODO(jacktfranklin@): once DOMWorld, Page, and FrameManager are in TS

View File

@ -307,6 +307,10 @@ function compareDocumentations(actual, expected) {
actualName: 'Object', actualName: 'Object',
expectedName: 'WaitForSelectorOptions' expectedName: 'WaitForSelectorOptions'
}], }],
['Method Request.abort() errorCode', {
actualName: 'string',
expectedName: 'ErrorCode'
}],
['Method Frame.goto() options.waitUntil', { ['Method Frame.goto() options.waitUntil', {
actualName: '"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|Array', actualName: '"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|Array',
expectedName: '"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|Array<PuppeteerLifeCycleEvent>' expectedName: '"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|Array<PuppeteerLifeCycleEvent>'