chore: more strict type checking (#466)

This commit is contained in:
Dmitry Gozman 2020-01-13 13:33:25 -08:00 committed by Pavel Feldman
parent d19f10ef42
commit a1d1f26fb7
45 changed files with 345 additions and 277 deletions

View file

@ -51,6 +51,7 @@
"@types/mime": "^2.0.0", "@types/mime": "^2.0.0",
"@types/node": "^8.10.34", "@types/node": "^8.10.34",
"@types/pngjs": "^3.4.0", "@types/pngjs": "^3.4.0",
"@types/proxy-from-env": "^1.0.0",
"@types/rimraf": "^2.0.2", "@types/rimraf": "^2.0.2",
"@types/ws": "^6.0.1", "@types/ws": "^6.0.1",
"@typescript-eslint/eslint-plugin": "^2.6.1", "@typescript-eslint/eslint-plugin": "^2.6.1",

View file

@ -68,13 +68,13 @@ export class Accessibility {
async snapshot(options: { async snapshot(options: {
interestingOnly?: boolean; interestingOnly?: boolean;
root?: dom.ElementHandle | null; root?: dom.ElementHandle | null;
} = {}): Promise<SerializedAXNode> { } = {}): Promise<SerializedAXNode | null> {
const { const {
interestingOnly = true, interestingOnly = true,
root = null, root = null,
} = options; } = options;
const defaultRoot = await this._getAXTree(); const defaultRoot = await this._getAXTree();
let needle = defaultRoot; let needle: AXNode | null = defaultRoot;
if (root) { if (root) {
needle = await defaultRoot.findElement(root); needle = await defaultRoot.findElement(root);
if (!needle) if (!needle)

View file

@ -103,14 +103,14 @@ export class BrowserContext {
if (geolocation) { if (geolocation) {
geolocation.accuracy = geolocation.accuracy || 0; geolocation.accuracy = geolocation.accuracy || 0;
const { longitude, latitude, accuracy } = geolocation; const { longitude, latitude, accuracy } = geolocation;
if (longitude < -180 || longitude > 180) if (longitude !== undefined && (longitude < -180 || longitude > 180))
throw new Error(`Invalid longitude "${longitude}": precondition -180 <= LONGITUDE <= 180 failed.`); throw new Error(`Invalid longitude "${longitude}": precondition -180 <= LONGITUDE <= 180 failed.`);
if (latitude < -90 || latitude > 90) if (latitude !== undefined && (latitude < -90 || latitude > 90))
throw new Error(`Invalid latitude "${latitude}": precondition -90 <= LATITUDE <= 90 failed.`); throw new Error(`Invalid latitude "${latitude}": precondition -90 <= LATITUDE <= 90 failed.`);
if (accuracy < 0) if (accuracy < 0)
throw new Error(`Invalid accuracy "${accuracy}": precondition 0 <= ACCURACY failed.`); throw new Error(`Invalid accuracy "${accuracy}": precondition 0 <= ACCURACY failed.`);
} }
this._options.geolocation = geolocation; this._options.geolocation = geolocation || undefined;
await this._delegate.setGeolocation(geolocation); await this._delegate.setGeolocation(geolocation);
} }

View file

@ -207,7 +207,7 @@ class CRAXNode implements accessibility.AXNode {
const node: {[x in keyof accessibility.SerializedAXNode]: any} = { const node: {[x in keyof accessibility.SerializedAXNode]: any} = {
role: this._role, role: this._role,
name: this._payload.name.value || '' name: this._payload.name ? (this._payload.name.value || '') : ''
}; };
const userStringProperties: Array<keyof accessibility.SerializedAXNode> = [ const userStringProperties: Array<keyof accessibility.SerializedAXNode> = [
@ -286,7 +286,7 @@ class CRAXNode implements accessibility.AXNode {
nodeById.set(payload.nodeId, new CRAXNode(client, payload)); nodeById.set(payload.nodeId, new CRAXNode(client, payload));
for (const node of nodeById.values()) { for (const node of nodeById.values()) {
for (const childId of node._payload.childIds || []) for (const childId of node._payload.childIds || [])
node._children.push(nodeById.get(childId)); node._children.push(nodeById.get(childId)!);
} }
return nodeById.values().next().value; return nodeById.values().next().value;
} }

View file

@ -46,7 +46,7 @@ export class CRBrowser extends platform.EventEmitter implements Browser {
_targets = new Map<string, CRTarget>(); _targets = new Map<string, CRTarget>();
private _tracingRecording = false; private _tracingRecording = false;
private _tracingPath = ''; private _tracingPath: string | null = '';
private _tracingClient: CRSession | undefined; private _tracingClient: CRSession | undefined;
static async connect(options: CRConnectOptions): Promise<CRBrowser> { static async connect(options: CRConnectOptions): Promise<CRBrowser> {
@ -79,20 +79,21 @@ export class CRBrowser extends platform.EventEmitter implements Browser {
pages: async (): Promise<Page[]> => { pages: async (): Promise<Page[]> => {
const targets = this._allTargets().filter(target => target.browserContext() === context && target.type() === 'page'); const targets = this._allTargets().filter(target => target.browserContext() === context && target.type() === 'page');
const pages = await Promise.all(targets.map(target => target.page())); const pages = await Promise.all(targets.map(target => target.page()));
return pages.filter(page => !!page); return pages.filter(page => !!page) as Page[];
}, },
newPage: async (): Promise<Page> => { newPage: async (): Promise<Page> => {
const { targetId } = await this._client.send('Target.createTarget', { url: 'about:blank', browserContextId: contextId || undefined }); const { targetId } = await this._client.send('Target.createTarget', { url: 'about:blank', browserContextId: contextId || undefined });
const target = this._targets.get(targetId); const target = this._targets.get(targetId)!;
assert(await target._initializedPromise, 'Failed to create target for page'); assert(await target._initializedPromise, 'Failed to create target for page');
return target.page(); const page = await target.page();
return page!;
}, },
close: async (): Promise<void> => { close: async (): Promise<void> => {
assert(contextId, 'Non-incognito profiles cannot be closed!'); assert(contextId, 'Non-incognito profiles cannot be closed!');
await this._client.send('Target.disposeBrowserContext', {browserContextId: contextId || undefined}); await this._client.send('Target.disposeBrowserContext', { browserContextId: contextId! });
this._contexts.delete(contextId); this._contexts.delete(contextId!);
}, },
cookies: async (): Promise<network.NetworkCookie[]> => { cookies: async (): Promise<network.NetworkCookie[]> => {
@ -172,7 +173,7 @@ export class CRBrowser extends platform.EventEmitter implements Browser {
async _targetCreated(event: Protocol.Target.targetCreatedPayload) { async _targetCreated(event: Protocol.Target.targetCreatedPayload) {
const targetInfo = event.targetInfo; const targetInfo = event.targetInfo;
const {browserContextId} = targetInfo; const {browserContextId} = targetInfo;
const context = (browserContextId && this._contexts.has(browserContextId)) ? this._contexts.get(browserContextId) : this._defaultContext; const context = (browserContextId && this._contexts.has(browserContextId)) ? this._contexts.get(browserContextId)! : this._defaultContext;
const target = new CRTarget(this, targetInfo, context, () => this._connection.createSession(targetInfo)); const target = new CRTarget(this, targetInfo, context, () => this._connection.createSession(targetInfo));
assert(!this._targets.has(event.targetInfo.targetId), 'Target should not exist before targetCreated'); assert(!this._targets.has(event.targetInfo.targetId), 'Target should not exist before targetCreated');
@ -183,7 +184,7 @@ export class CRBrowser extends platform.EventEmitter implements Browser {
} }
async _targetDestroyed(event: { targetId: string; }) { async _targetDestroyed(event: { targetId: string; }) {
const target = this._targets.get(event.targetId); const target = this._targets.get(event.targetId)!;
target._initializedCallback(false); target._initializedCallback(false);
this._targets.delete(event.targetId); this._targets.delete(event.targetId);
target._didClose(); target._didClose();
@ -192,7 +193,7 @@ export class CRBrowser extends platform.EventEmitter implements Browser {
} }
_targetInfoChanged(event: Protocol.Target.targetInfoChangedPayload) { _targetInfoChanged(event: Protocol.Target.targetInfoChangedPayload) {
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');
const previousURL = target.url(); const previousURL = target.url();
const wasInitialized = target._isInitialized; const wasInitialized = target._isInitialized;
@ -242,7 +243,7 @@ export class CRBrowser extends platform.EventEmitter implements Browser {
} }
browserTarget(): CRTarget { browserTarget(): CRTarget {
return [...this._targets.values()].find(t => t.type() === 'browser'); return [...this._targets.values()].find(t => t.type() === 'browser')!;
} }
serviceWorker(target: CRTarget): Promise<Worker | null> { serviceWorker(target: CRTarget): Promise<Worker | null> {
@ -280,10 +281,10 @@ export class CRBrowser extends platform.EventEmitter implements Browser {
assert(this._tracingClient, 'Tracing was not started.'); assert(this._tracingClient, 'Tracing was not started.');
let fulfill: (buffer: platform.BufferType) => void; let fulfill: (buffer: platform.BufferType) => void;
const contentPromise = new Promise<platform.BufferType>(x => fulfill = x); const contentPromise = new Promise<platform.BufferType>(x => fulfill = x);
this._tracingClient.once('Tracing.tracingComplete', event => { this._tracingClient!.once('Tracing.tracingComplete', event => {
readProtocolStream(this._tracingClient, event.stream, this._tracingPath).then(fulfill); readProtocolStream(this._tracingClient!, event.stream!, this._tracingPath).then(fulfill);
}); });
await this._tracingClient.send('Tracing.end'); await this._tracingClient!.send('Tracing.end');
this._tracingRecording = false; this._tracingRecording = false;
return contentPromise; return contentPromise;
} }
@ -324,5 +325,5 @@ export async function createTransport(options: CRConnectOptions): Promise<Connec
} }
transport = await platform.createWebSocketTransport(connectionURL); transport = await platform.createWebSocketTransport(connectionURL);
} }
return SlowMoTransport.wrap(transport, options.slowMo); return SlowMoTransport.wrap(transport!, options.slowMo);
} }

View file

@ -43,7 +43,7 @@ export class CRConnection extends platform.EventEmitter {
} }
static fromSession(session: CRSession): CRConnection { static fromSession(session: CRSession): CRConnection {
return session._connection; return session._connection!;
} }
session(sessionId: string): CRSession | null { session(sessionId: string): CRSession | null {
@ -84,8 +84,8 @@ export class CRConnection extends platform.EventEmitter {
if (this._closed) if (this._closed)
return; return;
this._closed = true; this._closed = true;
this._transport.onmessage = null; this._transport.onmessage = undefined;
this._transport.onclose = null; this._transport.onclose = undefined;
for (const session of this._sessions.values()) for (const session of this._sessions.values())
session._onClosed(); session._onClosed();
this._sessions.clear(); this._sessions.clear();
@ -99,12 +99,12 @@ export class CRConnection extends platform.EventEmitter {
async createSession(targetInfo: Protocol.Target.TargetInfo): Promise<CRSession> { async createSession(targetInfo: Protocol.Target.TargetInfo): Promise<CRSession> {
const { sessionId } = await this.rootSession.send('Target.attachToTarget', { targetId: targetInfo.targetId, flatten: true }); const { sessionId } = await this.rootSession.send('Target.attachToTarget', { targetId: targetInfo.targetId, flatten: true });
return this._sessions.get(sessionId); return this._sessions.get(sessionId)!;
} }
async createBrowserSession(): Promise<CRSession> { async createBrowserSession(): Promise<CRSession> {
const { sessionId } = await this.rootSession.send('Target.attachToBrowserTarget'); const { sessionId } = await this.rootSession.send('Target.attachToBrowserTarget');
return this._sessions.get(sessionId); return this._sessions.get(sessionId)!;
} }
} }
@ -113,7 +113,7 @@ export const CRSessionEvents = {
}; };
export class CRSession extends platform.EventEmitter { export class CRSession extends platform.EventEmitter {
_connection: CRConnection; _connection: CRConnection | null;
private _callbacks = new Map<number, {resolve:(o: any) => void, reject: (e: Error) => void, error: Error, method: string}>(); private _callbacks = new Map<number, {resolve:(o: any) => void, reject: (e: Error) => void, error: Error, method: string}>();
private _targetType: string; private _targetType: string;
private _sessionId: string; private _sessionId: string;
@ -128,6 +128,12 @@ export class CRSession extends platform.EventEmitter {
this._connection = connection; this._connection = connection;
this._targetType = targetType; this._targetType = targetType;
this._sessionId = sessionId; this._sessionId = sessionId;
this.on = super.on;
this.addListener = super.addListener;
this.off = super.removeListener;
this.removeListener = super.removeListener;
this.once = super.once;
} }
send<T extends keyof Protocol.CommandParameters>( send<T extends keyof Protocol.CommandParameters>(
@ -144,7 +150,7 @@ export class CRSession extends platform.EventEmitter {
_onMessage(object: { id?: number; method: string; params: any; error: { message: string; data: any; }; result?: any; }) { _onMessage(object: { id?: number; method: string; params: any; error: { message: string; data: any; }; result?: any; }) {
if (object.id && this._callbacks.has(object.id)) { if (object.id && this._callbacks.has(object.id)) {
const callback = this._callbacks.get(object.id); const callback = this._callbacks.get(object.id)!;
this._callbacks.delete(object.id); this._callbacks.delete(object.id);
if (object.error) if (object.error)
callback.reject(createProtocolError(callback.error, callback.method, object)); callback.reject(createProtocolError(callback.error, callback.method, object));

View file

@ -65,7 +65,7 @@ class JSCoverage {
_scriptSources: Map<string, string>; _scriptSources: Map<string, string>;
_eventListeners: RegisteredListener[]; _eventListeners: RegisteredListener[];
_resetOnNavigation: boolean; _resetOnNavigation: boolean;
_reportAnonymousScripts: boolean; _reportAnonymousScripts = false;
constructor(client: CRSession) { constructor(client: CRSession) {
this._client = client; this._client = client;
@ -238,12 +238,12 @@ class CSSCoverage {
}); });
} }
const coverage = []; const coverage: CoverageEntry[] = [];
for (const styleSheetId of this._stylesheetURLs.keys()) { for (const styleSheetId of this._stylesheetURLs.keys()) {
const url = this._stylesheetURLs.get(styleSheetId); const url = this._stylesheetURLs.get(styleSheetId)!;
const text = this._stylesheetSources.get(styleSheetId); const text = this._stylesheetSources.get(styleSheetId)!;
const ranges = convertToDisjointRanges(styleSheetIdToCoverage.get(styleSheetId) || []); const ranges = convertToDisjointRanges(styleSheetIdToCoverage.get(styleSheetId) || []);
coverage.push({url, ranges, text}); coverage.push({ url, ranges, text });
} }
return coverage; return coverage;
@ -277,7 +277,7 @@ function convertToDisjointRanges(nestedRanges: {
}); });
const hitCountStack = []; const hitCountStack = [];
const results = []; const results: { start: number; end: number; }[] = [];
let lastOffset = 0; let lastOffset = 0;
// Run scanning line to intersect all ranges. // Run scanning line to intersect all ranges.
for (const point of points) { for (const point of points) {

View file

@ -132,8 +132,11 @@ export class CRExecutionContext implements js.ExecutionContextDelegate {
} }
async getProperties(handle: js.JSHandle): Promise<Map<string, js.JSHandle>> { async getProperties(handle: js.JSHandle): Promise<Map<string, js.JSHandle>> {
const objectId = toRemoteObject(handle).objectId;
if (!objectId)
return new Map();
const response = await this._client.send('Runtime.getProperties', { const response = await this._client.send('Runtime.getProperties', {
objectId: toRemoteObject(handle).objectId, objectId,
ownProperties: true ownProperties: true
}); });
const result = new Map(); const result = new Map();

View file

@ -154,11 +154,13 @@ export class CRNetworkManager {
requestId: event.requestId requestId: event.requestId
}).catch(debugError); }).catch(debugError);
} }
if (!event.networkId)
return;
const requestId = event.networkId; const requestId = event.networkId;
const interceptionId = event.requestId; const interceptionId = event.requestId;
if (requestId && this._requestIdToRequestWillBeSentEvent.has(requestId)) {
const requestWillBeSentEvent = this._requestIdToRequestWillBeSentEvent.get(requestId); const requestWillBeSentEvent = this._requestIdToRequestWillBeSentEvent.get(requestId);
if (requestWillBeSentEvent) {
this._onRequest(requestWillBeSentEvent, interceptionId); this._onRequest(requestWillBeSentEvent, interceptionId);
this._requestIdToRequestWillBeSentEvent.delete(requestId); this._requestIdToRequestWillBeSentEvent.delete(requestId);
} else { } else {
@ -186,7 +188,7 @@ export class CRNetworkManager {
} }
_createResponse(request: InterceptableRequest, responsePayload: Protocol.Network.Response): network.Response { _createResponse(request: InterceptableRequest, responsePayload: Protocol.Network.Response): network.Response {
const remoteAddress: network.RemoteAddress = { ip: responsePayload.remoteIPAddress, port: responsePayload.remotePort }; const remoteAddress: network.RemoteAddress = { ip: responsePayload.remoteIPAddress || '', port: responsePayload.remotePort || 0 };
const getResponseBody = async () => { const getResponseBody = async () => {
const response = await this._client.send('Network.getResponseBody', { requestId: request._requestId }); const response = await this._client.send('Network.getResponseBody', { requestId: request._requestId });
return platform.Buffer.from(response.body, response.base64Encoded ? 'base64' : 'utf8'); return platform.Buffer.from(response.body, response.base64Encoded ? 'base64' : 'utf8');
@ -199,6 +201,7 @@ export class CRNetworkManager {
request.request._redirectChain.push(request.request); request.request._redirectChain.push(request.request);
response._requestFinished(new Error('Response body is unavailable for redirect responses')); response._requestFinished(new Error('Response body is unavailable for redirect responses'));
this._requestIdToRequest.delete(request._requestId); this._requestIdToRequest.delete(request._requestId);
if (request._interceptionId)
this._attemptedAuthentications.delete(request._interceptionId); this._attemptedAuthentications.delete(request._interceptionId);
this._page._frameManager.requestReceivedResponse(response); this._page._frameManager.requestReceivedResponse(response);
this._page._frameManager.requestFinished(request.request); this._page._frameManager.requestFinished(request.request);
@ -222,9 +225,11 @@ export class CRNetworkManager {
// Under certain conditions we never get the Network.responseReceived // Under certain conditions we never get the Network.responseReceived
// event from protocol. @see https://crbug.com/883475 // event from protocol. @see https://crbug.com/883475
if (request.request.response()) const response = request.request.response();
request.request.response()._requestFinished(); if (response)
response._requestFinished();
this._requestIdToRequest.delete(request._requestId); this._requestIdToRequest.delete(request._requestId);
if (request._interceptionId)
this._attemptedAuthentications.delete(request._interceptionId); this._attemptedAuthentications.delete(request._interceptionId);
this._page._frameManager.requestFinished(request.request); this._page._frameManager.requestFinished(request.request);
} }
@ -239,32 +244,33 @@ export class CRNetworkManager {
if (response) if (response)
response._requestFinished(); response._requestFinished();
this._requestIdToRequest.delete(request._requestId); this._requestIdToRequest.delete(request._requestId);
if (request._interceptionId)
this._attemptedAuthentications.delete(request._interceptionId); this._attemptedAuthentications.delete(request._interceptionId);
request.request._setFailureText(event.errorText); request.request._setFailureText(event.errorText);
this._page._frameManager.requestFailed(request.request, event.canceled); this._page._frameManager.requestFailed(request.request, !!event.canceled);
} }
} }
class InterceptableRequest implements network.RequestDelegate { class InterceptableRequest implements network.RequestDelegate {
readonly request: network.Request; readonly request: network.Request;
_requestId: string; _requestId: string;
_interceptionId: string; _interceptionId: string | null;
_documentId: string; _documentId: string | undefined;
private _client: CRSession; private _client: CRSession;
constructor(client: CRSession, frame: frames.Frame | null, interceptionId: string, documentId: string | undefined, allowInterception: boolean, event: Protocol.Network.requestWillBeSentPayload, redirectChain: network.Request[]) { constructor(client: CRSession, frame: frames.Frame | null, interceptionId: string | null, documentId: string | undefined, allowInterception: boolean, event: Protocol.Network.requestWillBeSentPayload, redirectChain: network.Request[]) {
this._client = client; this._client = client;
this._requestId = event.requestId; this._requestId = event.requestId;
this._interceptionId = interceptionId; this._interceptionId = interceptionId;
this._documentId = documentId; this._documentId = documentId;
this.request = new network.Request(allowInterception ? this : null, frame, redirectChain, documentId, this.request = new network.Request(allowInterception ? this : null, frame, redirectChain, documentId,
event.request.url, event.type.toLowerCase(), event.request.method, event.request.postData, headersObject(event.request.headers)); event.request.url, (event.type || '').toLowerCase(), event.request.method, event.request.postData, headersObject(event.request.headers));
} }
async continue(overrides: { headers?: network.Headers; } = {}) { async continue(overrides: { headers?: network.Headers; } = {}) {
await this._client.send('Fetch.continueRequest', { await this._client.send('Fetch.continueRequest', {
requestId: this._interceptionId, requestId: this._interceptionId!,
headers: overrides.headers ? headersArray(overrides.headers) : undefined, headers: overrides.headers ? headersArray(overrides.headers) : undefined,
}).catch(error => { }).catch(error => {
// In certain cases, protocol will return error if the request was already canceled // In certain cases, protocol will return error if the request was already canceled
@ -287,7 +293,7 @@ class InterceptableRequest implements network.RequestDelegate {
responseHeaders['content-length'] = String(platform.Buffer.byteLength(responseBody)); responseHeaders['content-length'] = String(platform.Buffer.byteLength(responseBody));
await this._client.send('Fetch.fulfillRequest', { await this._client.send('Fetch.fulfillRequest', {
requestId: this._interceptionId, requestId: this._interceptionId!,
responseCode: response.status || 200, responseCode: response.status || 200,
responsePhrase: network.STATUS_TEXTS[String(response.status || 200)], responsePhrase: network.STATUS_TEXTS[String(response.status || 200)],
responseHeaders: headersArray(responseHeaders), responseHeaders: headersArray(responseHeaders),
@ -303,7 +309,7 @@ class InterceptableRequest implements network.RequestDelegate {
const errorReason = errorReasons[errorCode]; const errorReason = errorReasons[errorCode];
assert(errorReason, 'Unknown error code: ' + errorCode); assert(errorReason, 'Unknown error code: ' + errorCode);
await this._client.send('Fetch.failRequest', { await this._client.send('Fetch.failRequest', {
requestId: this._interceptionId, requestId: this._interceptionId!,
errorReason errorReason
}).catch(error => { }).catch(error => {
// In certain cases, protocol will return error if the request was already canceled // In certain cases, protocol will return error if the request was already canceled

View file

@ -16,6 +16,7 @@
*/ */
import * as dom from '../dom'; import * as dom from '../dom';
import * as js from '../javascript';
import * as frames from '../frames'; import * as frames from '../frames';
import { debugError, helper, RegisteredListener } from '../helper'; import { debugError, helper, RegisteredListener } from '../helper';
import * as network from '../network'; import * as network from '../network';
@ -151,7 +152,7 @@ export class CRPage implements PageDelegate {
} }
_handleFrameTree(frameTree: Protocol.Page.FrameTree) { _handleFrameTree(frameTree: Protocol.Page.FrameTree) {
this._onFrameAttached(frameTree.frame.id, frameTree.frame.parentId); this._onFrameAttached(frameTree.frame.id, frameTree.frame.parentId || null);
this._onFrameNavigated(frameTree.frame, true); this._onFrameNavigated(frameTree.frame, true);
if (!frameTree.childFrames) if (!frameTree.childFrames)
return; return;
@ -196,7 +197,7 @@ export class CRPage implements PageDelegate {
} }
_onExecutionContextCreated(contextPayload: Protocol.Runtime.ExecutionContextDescription) { _onExecutionContextCreated(contextPayload: Protocol.Runtime.ExecutionContextDescription) {
const frame = this._page._frameManager.frame(contextPayload.auxData ? contextPayload.auxData.frameId : null); const frame = contextPayload.auxData ? this._page._frameManager.frame(contextPayload.auxData.frameId) : null;
if (!frame) if (!frame)
return; return;
if (contextPayload.auxData && contextPayload.auxData.type === 'isolated') if (contextPayload.auxData && contextPayload.auxData.type === 'isolated')
@ -240,7 +241,7 @@ export class CRPage implements PageDelegate {
// @see https://github.com/GoogleChrome/puppeteer/issues/3865 // @see https://github.com/GoogleChrome/puppeteer/issues/3865
return; return;
} }
const context = this._contextIdToContext.get(event.executionContextId); const context = this._contextIdToContext.get(event.executionContextId)!;
const values = event.args.map(arg => context._createHandle(arg)); const values = event.args.map(arg => context._createHandle(arg));
this._page._addConsoleMessage(event.type, values, toConsoleMessageLocation(event.stackTrace)); this._page._addConsoleMessage(event.type, values, toConsoleMessageLocation(event.stackTrace));
} }
@ -252,7 +253,7 @@ export class CRPage implements PageDelegate {
} }
_onBindingCalled(event: Protocol.Runtime.bindingCalledPayload) { _onBindingCalled(event: Protocol.Runtime.bindingCalledPayload) {
const context = this._contextIdToContext.get(event.executionContextId); const context = this._contextIdToContext.get(event.executionContextId)!;
this._page._onBindingCalled(event.payload, context); this._page._onBindingCalled(event.payload, context);
} }
@ -283,7 +284,7 @@ export class CRPage implements PageDelegate {
} }
async _onFileChooserOpened(event: Protocol.Page.fileChooserOpenedPayload) { async _onFileChooserOpened(event: Protocol.Page.fileChooserOpenedPayload) {
const frame = this._page._frameManager.frame(event.frameId); const frame = this._page._frameManager.frame(event.frameId)!;
const utilityContext = await frame._utilityContext(); const utilityContext = await frame._utilityContext();
const handle = await this.adoptBackendNodeId(event.backendNodeId, utilityContext); const handle = await this.adoptBackendNodeId(event.backendNodeId, utilityContext);
this._page._onFileChooserOpened(handle); this._page._onFileChooserOpened(handle);
@ -458,7 +459,7 @@ export class CRPage implements PageDelegate {
return { width: layoutMetrics.layoutViewport.clientWidth, height: layoutMetrics.layoutViewport.clientHeight }; return { width: layoutMetrics.layoutViewport.clientWidth, height: layoutMetrics.layoutViewport.clientHeight };
} }
async setInputFiles(handle: dom.ElementHandle, files: types.FilePayload[]): Promise<void> { async setInputFiles(handle: dom.ElementHandle<HTMLInputElement>, files: types.FilePayload[]): Promise<void> {
await handle.evaluate(dom.setFileInputFunction, files); await handle.evaluate(dom.setFileInputFunction, files);
} }
@ -492,7 +493,7 @@ export class CRPage implements PageDelegate {
} }
} }
function toRemoteObject(handle: dom.ElementHandle): Protocol.Runtime.RemoteObject { function toRemoteObject(handle: js.JSHandle): Protocol.Runtime.RemoteObject {
return handle._remoteObject as Protocol.Runtime.RemoteObject; return handle._remoteObject as Protocol.Runtime.RemoteObject;
} }

View file

@ -125,6 +125,6 @@ export class CRPDF {
pageRanges, pageRanges,
preferCSSPageSize preferCSSPageSize
}); });
return await readProtocolStream(this._client, result.stream, path); return await readProtocolStream(this._client, result.stream!, path);
} }
} }

View file

@ -67,7 +67,7 @@ export async function releaseObject(client: CRSession, remoteObject: Protocol.Ru
export async function readProtocolStream(client: CRSession, handle: string, path: string | null): Promise<platform.BufferType> { export async function readProtocolStream(client: CRSession, handle: string, path: string | null): Promise<platform.BufferType> {
let eof = false; let eof = false;
let fd; let fd: number | undefined;
if (path) if (path)
fd = await platform.openFdAsync(path, 'w'); fd = await platform.openFdAsync(path, 'w');
const bufs = []; const bufs = [];
@ -77,16 +77,16 @@ export async function readProtocolStream(client: CRSession, handle: string, path
const buf = platform.Buffer.from(response.data, response.base64Encoded ? 'base64' : undefined); const buf = platform.Buffer.from(response.data, response.base64Encoded ? 'base64' : undefined);
bufs.push(buf); bufs.push(buf);
if (path) if (path)
await platform.writeFdAsync(fd, buf); await platform.writeFdAsync(fd!, buf);
} }
if (path) if (path)
await platform.closeFdAsync(fd); await platform.closeFdAsync(fd!);
await client.send('IO.close', {handle}); await client.send('IO.close', {handle});
let resultBuffer = null; let resultBuffer = null;
try { try {
resultBuffer = platform.Buffer.concat(bufs); resultBuffer = platform.Buffer.concat(bufs);
} finally { } finally {
return resultBuffer; return resultBuffer!;
} }
} }

View file

@ -37,7 +37,7 @@ export class CRTarget {
_crPage: CRPage | null = null; _crPage: CRPage | null = null;
private _workerPromise: Promise<Worker> | null = null; private _workerPromise: Promise<Worker> | null = null;
readonly _initializedPromise: Promise<boolean>; readonly _initializedPromise: Promise<boolean>;
_initializedCallback: (value?: unknown) => void; _initializedCallback: (success: boolean) => void = () => {};
_isInitialized: boolean; _isInitialized: boolean;
static fromPage(page: Page): CRTarget { static fromPage(page: Page): CRTarget {
@ -135,7 +135,7 @@ export class CRTarget {
const { openerId } = this._targetInfo; const { openerId } = this._targetInfo;
if (!openerId) if (!openerId)
return null; return null;
return this._browser._targets.get(openerId); return this._browser._targets.get(openerId)!;
} }
createCDPSession(): Promise<CRSession> { createCDPSession(): Promise<CRSession> {

View file

@ -28,7 +28,7 @@ export class CRWorkers {
if (event.targetInfo.type !== 'worker') if (event.targetInfo.type !== 'worker')
return; return;
const url = event.targetInfo.url; const url = event.targetInfo.url;
const session = CRConnection.fromSession(client).session(event.sessionId); const session = CRConnection.fromSession(client).session(event.sessionId)!;
const worker = new Worker(url); const worker = new Worker(url);
page._addWorker(event.sessionId, worker); page._addWorker(event.sessionId, worker);
session.once('Runtime.executionContextCreated', async event => { session.once('Runtime.executionContextCreated', async event => {
@ -36,7 +36,7 @@ export class CRWorkers {
}); });
// 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.
session.send('Runtime.enable', {}).catch(debugError); session.send('Runtime.enable', {}).catch(debugError);
session.on('Runtime.consoleAPICalled', event => page._addConsoleMessage(event.type, event.args.map(o => worker._existingExecutionContext._createHandle(o)), toConsoleMessageLocation(event.stackTrace))); session.on('Runtime.consoleAPICalled', event => page._addConsoleMessage(event.type, event.args.map(o => worker._existingExecutionContext!._createHandle(o)), toConsoleMessageLocation(event.stackTrace)));
session.on('Runtime.exceptionThrown', exception => page.emit(Events.Page.PageError, exceptionToError(exception.exceptionDetails))); session.on('Runtime.exceptionThrown', exception => page.emit(Events.Page.PageError, exceptionToError(exception.exceptionDetails)));
}); });
client.on('Target.detachedFromTarget', event => page._removeWorker(event.sessionId)); client.on('Target.detachedFromTarget', event => page._removeWorker(event.sessionId));

View file

@ -1,6 +1,6 @@
{ {
"compilerOptions": { "compilerOptions": {
"noImplicitAny": true "strict": true
}, },
"extends": "../../tsconfig.json" "extends": "../../tsconfig.json"
} }

View file

@ -62,7 +62,7 @@ export class FrameExecutionContext extends js.ExecutionContext {
return result; return result;
} }
_createHandle(remoteObject: any): js.JSHandle | null { _createHandle(remoteObject: any): js.JSHandle {
if (this.frame._page._delegate.isElementHandle(remoteObject)) if (this.frame._page._delegate.isElementHandle(remoteObject))
return new ElementHandle(this, remoteObject); return new ElementHandle(this, remoteObject);
return super._createHandle(remoteObject); return super._createHandle(remoteObject);
@ -88,7 +88,7 @@ export class FrameExecutionContext extends js.ExecutionContext {
); );
if (!handle.asElement()) if (!handle.asElement())
await handle.dispose(); await handle.dispose();
return handle.asElement(); return handle.asElement() as ElementHandle<Element>;
} }
async _$array(selector: string, scope?: ElementHandle): Promise<js.JSHandle<Element[]>> { async _$array(selector: string, scope?: ElementHandle): Promise<js.JSHandle<Element[]>> {
@ -103,9 +103,9 @@ export class FrameExecutionContext extends js.ExecutionContext {
const arrayHandle = await this._$array(selector, scope); const arrayHandle = await this._$array(selector, scope);
const properties = await arrayHandle.getProperties(); const properties = await arrayHandle.getProperties();
await arrayHandle.dispose(); await arrayHandle.dispose();
const result = []; const result: ElementHandle<Element>[] = [];
for (const property of properties.values()) { for (const property of properties.values()) {
const elementHandle = property.asElement(); const elementHandle = property.asElement() as ElementHandle<Element>;
if (elementHandle) if (elementHandle)
result.push(elementHandle); result.push(elementHandle);
else else
@ -121,6 +121,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
constructor(context: FrameExecutionContext, remoteObject: any) { constructor(context: FrameExecutionContext, remoteObject: any) {
super(context, remoteObject); super(context, remoteObject);
this._context = context;
this._page = context.frame._page; this._page = context.frame._page;
return helper.logPublicApiCalls('handle', this); return helper.logPublicApiCalls('handle', this);
} }
@ -131,7 +132,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
_evaluateInUtility: types.EvaluateOn<T> = async (pageFunction, ...args) => { _evaluateInUtility: types.EvaluateOn<T> = async (pageFunction, ...args) => {
const utility = await this._context.frame._utilityContext(); const utility = await this._context.frame._utilityContext();
return utility.evaluate(pageFunction, this, ...args); return utility.evaluate(pageFunction as any, this, ...args);
} }
async ownerFrame(): Promise<frames.Frame | null> { async ownerFrame(): Promise<frames.Frame | null> {
@ -242,10 +243,10 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
const [box, border] = await Promise.all([ const [box, border] = await Promise.all([
this.boundingBox(), this.boundingBox(),
this._evaluateInUtility((node: Node) => { this._evaluateInUtility((node: Node) => {
if (node.nodeType !== Node.ELEMENT_NODE) if (node.nodeType !== Node.ELEMENT_NODE || !node.ownerDocument || !node.ownerDocument.defaultView)
return { x: 0, y: 0 }; return { x: 0, y: 0 };
const style = node.ownerDocument.defaultView.getComputedStyle(node as Element); const style = node.ownerDocument.defaultView.getComputedStyle(node as Element);
return { x: parseInt(style.borderLeftWidth, 10), y: parseInt(style.borderTopWidth, 10) }; return { x: parseInt(style.borderLeftWidth || '', 10), y: parseInt(style.borderTopWidth || '', 10) };
}).catch(debugError), }).catch(debugError),
]); ]);
const point = { x: relativePoint.x, y: relativePoint.y }; const point = { x: relativePoint.x, y: relativePoint.y };
@ -317,7 +318,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
const element = node as HTMLSelectElement; const element = node as HTMLSelectElement;
const options = Array.from(element.options); const options = Array.from(element.options);
element.value = undefined; element.value = undefined as any;
for (let index = 0; index < options.length; index++) { for (let index = 0; index < options.length; index++) {
const option = options[index]; const option = options[index];
option.selected = optionsToSelect.some(optionToSelect => { option.selected = optionsToSelect.some(optionToSelect => {
@ -382,6 +383,8 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
const range = element.ownerDocument.createRange(); const range = element.ownerDocument.createRange();
range.selectNodeContents(element); range.selectNodeContents(element);
const selection = element.ownerDocument.defaultView.getSelection(); const selection = element.ownerDocument.defaultView.getSelection();
if (!selection)
return 'Element belongs to invisible iframe.';
selection.removeAllRanges(); selection.removeAllRanges();
selection.addRange(range); selection.addRange(range);
element.focus(); element.focus();
@ -414,12 +417,12 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
} }
return item; return item;
})); }));
await this._page._delegate.setInputFiles(this, filePayloads); await this._page._delegate.setInputFiles(this as any as ElementHandle<HTMLInputElement>, filePayloads);
} }
async focus() { async focus() {
const errorMessage = await this._evaluateInUtility((element: Node) => { const errorMessage = await this._evaluateInUtility((element: Node) => {
if (!element['focus']) if (!(element as any)['focus'])
return 'Node is not an HTML or SVG element.'; return 'Node is not an HTML or SVG element.';
(element as HTMLElement|SVGElement).focus(); (element as HTMLElement|SVGElement).focus();
return false; return false;
@ -532,10 +535,10 @@ export function waitForFunctionTask(selector: string | undefined, pageFunction:
return innerPredicate(...args); return innerPredicate(...args);
return innerPredicate(element, ...args); return innerPredicate(element, ...args);
} }
}, await context._injected(), selector, predicateBody, polling, options.timeout, ...args); }, await context._injected(), selector, predicateBody, polling, options.timeout || 0, ...args);
} }
export function waitForSelectorTask(selector: string, visibility: types.Visibility | undefined, timeout: number): Task { export function waitForSelectorTask(selector: string, visibility: types.Visibility, timeout: number): Task {
return async (context: FrameExecutionContext) => { return async (context: FrameExecutionContext) => {
selector = normalizeSelector(selector); selector = normalizeSelector(selector);
return context.evaluateHandle((injected: Injected, selector: string, visibility: types.Visibility, timeout: number) => { return context.evaluateHandle((injected: Injected, selector: string, visibility: types.Visibility, timeout: number) => {

View file

@ -63,6 +63,7 @@ export class FrameManager {
constructor(page: Page) { constructor(page: Page) {
this._page = page; this._page = page;
this._mainFrame = undefined as any as Frame;
} }
mainFrame(): Frame { mainFrame(): Frame {
@ -86,7 +87,7 @@ export class FrameManager {
} }
frameAttached(frameId: string, parentFrameId: string | null | undefined): Frame { frameAttached(frameId: string, parentFrameId: string | null | undefined): Frame {
const parentFrame = parentFrameId ? this._frames.get(parentFrameId) : null; const parentFrame = parentFrameId ? this._frames.get(parentFrameId)! : null;
if (!parentFrame) { if (!parentFrame) {
if (this._mainFrame) { if (this._mainFrame) {
// Update frame id to retain frame identity on cross-process navigation. // Update frame id to retain frame identity on cross-process navigation.
@ -108,7 +109,7 @@ export class FrameManager {
} }
frameCommittedNewDocumentNavigation(frameId: string, url: string, name: string, documentId: string, initial: boolean) { frameCommittedNewDocumentNavigation(frameId: string, url: string, name: string, documentId: string, initial: boolean) {
const frame = this._frames.get(frameId); const frame = this._frames.get(frameId)!;
for (const child of frame.childFrames()) for (const child of frame.childFrames())
this._removeFramesRecursively(child); this._removeFramesRecursively(child);
frame._url = url; frame._url = url;
@ -185,9 +186,10 @@ export class FrameManager {
requestStarted(request: network.Request) { requestStarted(request: network.Request) {
this._inflightRequestStarted(request); this._inflightRequestStarted(request);
if (request._documentId && request.frame() && !request.redirectChain().length) { const frame = request.frame();
if (request._documentId && frame && !request.redirectChain().length) {
for (const watcher of this._lifecycleWatchers) for (const watcher of this._lifecycleWatchers)
watcher._onNavigationRequest(request.frame(), request); watcher._onNavigationRequest(frame, request);
} }
this._page.emit(Events.Page.Request, request); this._page.emit(Events.Page.Request, request);
} }
@ -203,14 +205,15 @@ export class FrameManager {
requestFailed(request: network.Request, canceled: boolean) { requestFailed(request: network.Request, canceled: boolean) {
this._inflightRequestFinished(request); this._inflightRequestFinished(request);
if (request._documentId && request.frame()) { const frame = request.frame();
const isCurrentDocument = request.frame()._lastDocumentId === request._documentId; if (request._documentId && frame) {
const isCurrentDocument = frame._lastDocumentId === request._documentId;
if (!isCurrentDocument) { if (!isCurrentDocument) {
let errorText = request.failure().errorText; let errorText = request.failure()!.errorText;
if (canceled) if (canceled)
errorText += '; maybe frame was detached?'; errorText += '; maybe frame was detached?';
for (const watcher of this._lifecycleWatchers) for (const watcher of this._lifecycleWatchers)
watcher._onAbortedNewDocumentNavigation(request.frame(), request._documentId, errorText); watcher._onAbortedNewDocumentNavigation(frame, request._documentId, errorText);
} }
} }
this._page.emit(Events.Page.RequestFailed, request); this._page.emit(Events.Page.RequestFailed, request);
@ -227,9 +230,9 @@ export class FrameManager {
} }
private _inflightRequestFinished(request: network.Request) { private _inflightRequestFinished(request: network.Request) {
if (!request.frame() || request.url().endsWith('favicon.ico'))
return;
const frame = request.frame(); const frame = request.frame();
if (!frame || request.url().endsWith('favicon.ico'))
return;
if (!frame._inflightRequests.has(request)) if (!frame._inflightRequests.has(request))
return; return;
frame._inflightRequests.delete(request); frame._inflightRequests.delete(request);
@ -240,9 +243,9 @@ export class FrameManager {
} }
private _inflightRequestStarted(request: network.Request) { private _inflightRequestStarted(request: network.Request) {
if (!request.frame() || request.url().endsWith('favicon.ico'))
return;
const frame = request.frame(); const frame = request.frame();
if (!frame || request.url().endsWith('favicon.ico'))
return;
frame._inflightRequests.add(request); frame._inflightRequests.add(request);
if (frame._inflightRequests.size === 1) if (frame._inflightRequests.size === 1)
this._stopNetworkIdleTimer(frame, 'networkidle0'); this._stopNetworkIdleTimer(frame, 'networkidle0');
@ -260,7 +263,9 @@ export class FrameManager {
} }
private _stopNetworkIdleTimer(frame: Frame, event: LifecycleEvent) { private _stopNetworkIdleTimer(frame: Frame, event: LifecycleEvent) {
clearTimeout(frame._networkIdleTimers.get(event)); const timeoutId = frame._networkIdleTimers.get(event);
if (timeoutId)
clearTimeout(timeoutId);
frame._networkIdleTimers.delete(event); frame._networkIdleTimers.delete(event);
} }
} }
@ -270,12 +275,12 @@ export class Frame {
readonly _firedLifecycleEvents: Set<LifecycleEvent>; readonly _firedLifecycleEvents: Set<LifecycleEvent>;
_lastDocumentId: string; _lastDocumentId: string;
readonly _page: Page; readonly _page: Page;
private _parentFrame: Frame; private _parentFrame: Frame | null;
_url = ''; _url = '';
private _detached = false; private _detached = false;
private _contextData = new Map<ContextType, ContextData>(); private _contextData = new Map<ContextType, ContextData>();
private _childFrames = new Set<Frame>(); private _childFrames = new Set<Frame>();
_name: string; _name = '';
_inflightRequests = new Set<network.Request>(); _inflightRequests = new Set<network.Request>();
readonly _networkIdleTimers = new Map<LifecycleEvent, NodeJS.Timer>(); readonly _networkIdleTimers = new Map<LifecycleEvent, NodeJS.Timer>();
@ -318,10 +323,10 @@ export class Frame {
]); ]);
if (!error) { if (!error) {
const promises = [watcher.timeoutOrTerminationPromise]; const promises = [watcher.timeoutOrTerminationPromise];
if (navigateResult.newDocumentId) { if (navigateResult!.newDocumentId) {
watcher.setExpectedDocumentId(navigateResult.newDocumentId, url); watcher.setExpectedDocumentId(navigateResult!.newDocumentId, url);
promises.push(watcher.newDocumentNavigationPromise); promises.push(watcher.newDocumentNavigationPromise);
} else if (navigateResult.isSameDocument) { } else if (navigateResult!.isSameDocument) {
promises.push(watcher.sameDocumentNavigationPromise); promises.push(watcher.sameDocumentNavigationPromise);
} else { } else {
promises.push(watcher.sameDocumentNavigationPromise, watcher.newDocumentNavigationPromise); promises.push(watcher.sameDocumentNavigationPromise, watcher.newDocumentNavigationPromise);
@ -361,7 +366,7 @@ export class Frame {
_context(contextType: ContextType): Promise<dom.FrameExecutionContext> { _context(contextType: ContextType): Promise<dom.FrameExecutionContext> {
if (this._detached) if (this._detached)
throw new Error(`Execution Context is not available in detached frame "${this.url()}" (are you trying to evaluate?)`); throw new Error(`Execution Context is not available in detached frame "${this.url()}" (are you trying to evaluate?)`);
return this._contextData.get(contextType).contextPromise; return this._contextData.get(contextType)!.contextPromise;
} }
_mainContext(): Promise<dom.FrameExecutionContext> { _mainContext(): Promise<dom.FrameExecutionContext> {
@ -505,14 +510,13 @@ export class Frame {
const context = await this._mainContext(); const context = await this._mainContext();
return this._raceWithCSPError(async () => { return this._raceWithCSPError(async () => {
if (url !== null) if (url !== null)
return (await context.evaluateHandle(addScriptUrl, url, type)).asElement(); return (await context.evaluateHandle(addScriptUrl, url, type)).asElement()!;
if (path !== null) { if (path !== null) {
let contents = await platform.readFileAsync(path, 'utf8'); let contents = await platform.readFileAsync(path, 'utf8');
contents += '//# sourceURL=' + path.replace(/\n/g, ''); contents += '//# sourceURL=' + path.replace(/\n/g, '');
return (await context.evaluateHandle(addScriptContent, contents, type)).asElement(); return (await context.evaluateHandle(addScriptContent, contents, type)).asElement()!;
} }
if (content !== null) return (await context.evaluateHandle(addScriptContent, content!, type)).asElement()!;
return (await context.evaluateHandle(addScriptContent, content, type)).asElement();
}); });
async function addScriptUrl(url: string, type: string): Promise<HTMLElement> { async function addScriptUrl(url: string, type: string): Promise<HTMLElement> {
@ -554,16 +558,15 @@ export class Frame {
const context = await this._mainContext(); const context = await this._mainContext();
return this._raceWithCSPError(async () => { return this._raceWithCSPError(async () => {
if (url !== null) if (url !== null)
return (await context.evaluateHandle(addStyleUrl, url)).asElement(); return (await context.evaluateHandle(addStyleUrl, url)).asElement()!;
if (path !== null) { if (path !== null) {
let contents = await platform.readFileAsync(path, 'utf8'); let contents = await platform.readFileAsync(path, 'utf8');
contents += '/*# sourceURL=' + path.replace(/\n/g, '') + '*/'; contents += '/*# sourceURL=' + path.replace(/\n/g, '') + '*/';
return (await context.evaluateHandle(addStyleContent, contents)).asElement(); return (await context.evaluateHandle(addStyleContent, contents)).asElement()!;
} }
if (content !== null) return (await context.evaluateHandle(addStyleContent, content!)).asElement()!;
return (await context.evaluateHandle(addStyleContent, content)).asElement();
}); });
async function addStyleUrl(url: string): Promise<HTMLElement> { async function addStyleUrl(url: string): Promise<HTMLElement> {
@ -595,7 +598,7 @@ export class Frame {
private async _raceWithCSPError(func: () => Promise<dom.ElementHandle>): Promise<dom.ElementHandle> { private async _raceWithCSPError(func: () => Promise<dom.ElementHandle>): Promise<dom.ElementHandle> {
const listeners: RegisteredListener[] = []; const listeners: RegisteredListener[] = [];
let result: dom.ElementHandle | undefined; let result: dom.ElementHandle;
let error: Error | undefined; let error: Error | undefined;
let cspMessage: ConsoleMessage | undefined; let cspMessage: ConsoleMessage | undefined;
const actionPromise = new Promise<dom.ElementHandle>(async resolve => { const actionPromise = new Promise<dom.ElementHandle>(async resolve => {
@ -620,7 +623,7 @@ export class Frame {
throw new Error(cspMessage.text()); throw new Error(cspMessage.text());
if (error) if (error)
throw error; throw error;
return result; return result!;
} }
async click(selector: string, options?: WaitForOptions & ClickOptions) { async click(selector: string, options?: WaitForOptions & ClickOptions) {
@ -683,17 +686,19 @@ export class Frame {
return Promise.reject(new Error('Unsupported target type: ' + (typeof selectorOrFunctionOrTimeout))); return Promise.reject(new Error('Unsupported target type: ' + (typeof selectorOrFunctionOrTimeout)));
} }
private async _optionallyWaitForSelectorInUtilityContext(selector: string, options: WaitForOptions | undefined): Promise<dom.ElementHandle<Element> | null> { private async _optionallyWaitForSelectorInUtilityContext(selector: string, options: WaitForOptions | undefined): Promise<dom.ElementHandle<Element>> {
const { timeout = this._page._timeoutSettings.timeout(), waitFor = 'visible' } = (options || {}); const { timeout = this._page._timeoutSettings.timeout(), waitFor = 'visible' } = (options || {});
let handle: dom.ElementHandle<Element> | null; let handle: dom.ElementHandle<Element>;
if (waitFor !== 'nowait') { if (waitFor !== 'nowait') {
handle = await this._waitForSelectorInUtilityContext(selector, waitFor, timeout); const maybeHandle = await this._waitForSelectorInUtilityContext(selector, waitFor, timeout);
if (!handle) if (!maybeHandle)
throw new Error('No node found for selector: ' + selectorToString(selector, waitFor)); throw new Error('No node found for selector: ' + selectorToString(selector, waitFor));
handle = maybeHandle;
} else { } else {
const context = await this._context('utility'); const context = await this._context('utility');
handle = await context._$(selector); const maybeHandle = await context._$(selector);
assert(handle, 'No node found for selector: ' + selector); assert(maybeHandle, 'No node found for selector: ' + selector);
handle = maybeHandle!;
} }
return handle; return handle;
} }
@ -742,7 +747,7 @@ export class Frame {
} }
private _scheduleRerunnableTask(task: dom.Task, contextType: ContextType, timeout?: number, title?: string): Promise<js.JSHandle> { private _scheduleRerunnableTask(task: dom.Task, contextType: ContextType, timeout?: number, title?: string): Promise<js.JSHandle> {
const data = this._contextData.get(contextType); const data = this._contextData.get(contextType)!;
const rerunnableTask = new RerunnableTask(data, task, timeout, title); const rerunnableTask = new RerunnableTask(data, task, timeout, title);
data.rerunnableTasks.add(rerunnableTask); data.rerunnableTasks.add(rerunnableTask);
if (data.context) if (data.context)
@ -751,7 +756,7 @@ export class Frame {
} }
private _setContext(contextType: ContextType, context: dom.FrameExecutionContext | null) { private _setContext(contextType: ContextType, context: dom.FrameExecutionContext | null) {
const data = this._contextData.get(contextType); const data = this._contextData.get(contextType)!;
data.context = context; data.context = context;
if (context) { if (context) {
data.contextResolveCallback.call(null, context); data.contextResolveCallback.call(null, context);
@ -765,7 +770,7 @@ export class Frame {
} }
_contextCreated(contextType: ContextType, context: dom.FrameExecutionContext) { _contextCreated(contextType: ContextType, context: dom.FrameExecutionContext) {
const data = this._contextData.get(contextType); const data = this._contextData.get(contextType)!;
// In case of multiple sessions to the same target, there's a race between // In case of multiple sessions to the same target, there's a race between
// connections so we might end up creating multiple isolated worlds. // connections so we might end up creating multiple isolated worlds.
// We can use either. // We can use either.
@ -787,10 +792,10 @@ class RerunnableTask {
private _contextData: ContextData; private _contextData: ContextData;
private _task: dom.Task; private _task: dom.Task;
private _runCount: number; private _runCount: number;
private _resolve: (result: js.JSHandle) => void; private _resolve: (result: js.JSHandle) => void = () => {};
private _reject: (reason: Error) => void; private _reject: (reason: Error) => void = () => {};
private _timeoutTimer: NodeJS.Timer; private _timeoutTimer?: NodeJS.Timer;
private _terminated: boolean; private _terminated = false;
constructor(data: ContextData, task: dom.Task, timeout?: number, title?: string) { constructor(data: ContextData, task: dom.Task, timeout?: number, title?: string) {
this._contextData = data; this._contextData = data;
@ -834,7 +839,7 @@ class RerunnableTask {
// If execution context has been already destroyed, `context.evaluate` will // If execution context has been already destroyed, `context.evaluate` will
// throw an error - ignore this predicate run altogether. // throw an error - ignore this predicate run altogether.
if (!error && await context.evaluate(s => !s, success).catch(e => true)) { if (!error && await context.evaluate(s => !s, success).catch(e => true)) {
await success.dispose(); await success!.dispose();
return; return;
} }
@ -851,12 +856,13 @@ class RerunnableTask {
if (error) if (error)
this._reject(error); this._reject(error);
else else
this._resolve(success); this._resolve(success!);
this._doCleanup(); this._doCleanup();
} }
_doCleanup() { _doCleanup() {
if (this._timeoutTimer)
clearTimeout(this._timeoutTimer); clearTimeout(this._timeoutTimer);
this._contextData.rerunnableTasks.delete(this); this._contextData.rerunnableTasks.delete(this);
} }
@ -870,13 +876,13 @@ class LifecycleWatcher {
private _expectedLifecycle: LifecycleEvent[]; private _expectedLifecycle: LifecycleEvent[];
private _frame: Frame; private _frame: Frame;
private _navigationRequest: network.Request | null = null; private _navigationRequest: network.Request | null = null;
private _sameDocumentNavigationCompleteCallback: () => void; private _sameDocumentNavigationCompleteCallback: () => void = () => {};
private _lifecycleCallback: () => void; private _lifecycleCallback: () => void = () => {};
private _newDocumentNavigationCompleteCallback: () => void; private _newDocumentNavigationCompleteCallback: () => void = () => {};
private _frameDetachedCallback: (err: Error) => void; private _frameDetachedCallback: (err: Error) => void = () => {};
private _navigationAbortedCallback: (err: Error) => void; private _navigationAbortedCallback: (err: Error) => void = () => {};
private _maximumTimer: NodeJS.Timer; private _maximumTimer?: NodeJS.Timer;
private _hasSameDocumentNavigation: boolean; private _hasSameDocumentNavigation = false;
private _targetUrl: string | undefined; private _targetUrl: string | undefined;
private _expectedDocumentId: string | undefined; private _expectedDocumentId: string | undefined;
private _urlMatch: types.URLMatch | undefined; private _urlMatch: types.URLMatch | undefined;
@ -971,7 +977,7 @@ class LifecycleWatcher {
this._checkLifecycleComplete(); this._checkLifecycleComplete();
} }
navigationResponse(): Promise<network.Response | null> { async navigationResponse(): Promise<network.Response | null> {
return this._navigationRequest ? this._navigationRequest._finalRequest._waitForFinished() : null; return this._navigationRequest ? this._navigationRequest._finalRequest._waitForFinished() : null;
} }
@ -1009,6 +1015,7 @@ class LifecycleWatcher {
dispose() { dispose() {
this._frame._page._frameManager._lifecycleWatchers.delete(this); this._frame._page._frameManager._lifecycleWatchers.delete(this);
if (this._maximumTimer)
clearTimeout(this._maximumTimer); clearTimeout(this._maximumTimer);
} }
} }

View file

@ -46,10 +46,10 @@ class Helper {
const method = Reflect.get(classType.prototype, methodName); const method = Reflect.get(classType.prototype, methodName);
if (methodName === 'constructor' || typeof methodName !== 'string' || methodName.startsWith('_') || typeof method !== 'function' || method.constructor.name !== 'AsyncFunction') if (methodName === 'constructor' || typeof methodName !== 'string' || methodName.startsWith('_') || typeof method !== 'function' || method.constructor.name !== 'AsyncFunction')
continue; continue;
Reflect.set(classType.prototype, methodName, function(...args: any[]) { Reflect.set(classType.prototype, methodName, function(this: any, ...args: any[]) {
const syncStack: any = {}; const syncStack: any = {};
Error.captureStackTrace(syncStack); Error.captureStackTrace(syncStack);
return method.call(this, ...args).catch(e => { return method.call(this, ...args).catch((e: any) => {
const stack = syncStack.stack.substring(syncStack.stack.indexOf('\n') + 1); const stack = syncStack.stack.substring(syncStack.stack.indexOf('\n') + 1);
const clientStack = stack.substring(stack.indexOf('\n')); const clientStack = stack.substring(stack.indexOf('\n'));
if (e instanceof Error && e.stack && !e.stack.includes(clientStack)) if (e instanceof Error && e.stack && !e.stack.includes(clientStack))
@ -112,7 +112,9 @@ class Helper {
predicate: Function, predicate: Function,
timeout: number, timeout: number,
abortPromise: Promise<Error>): Promise<any> { abortPromise: Promise<Error>): Promise<any> {
let eventTimeout, resolveCallback, rejectCallback; let eventTimeout: NodeJS.Timer;
let resolveCallback: (event: any) => void = () => {};
let rejectCallback: (error: any) => void = () => {};
const promise = new Promise((resolve, reject) => { const promise = new Promise((resolve, reject) => {
resolveCallback = resolve; resolveCallback = resolve;
rejectCallback = reject; rejectCallback = reject;

View file

@ -1,6 +1,6 @@
{ {
"compilerOptions": { "compilerOptions": {
"noImplicitAny": true "strict": true
}, },
"extends": "../../tsconfig.json" "extends": "../../tsconfig.json"
} }

View file

@ -23,9 +23,10 @@ export const XPathEngine: SelectorEngine = {
name: 'xpath', name: 'xpath',
create(root: SelectorRoot, targetElement: Element, type: SelectorType): string | undefined { create(root: SelectorRoot, targetElement: Element, type: SelectorType): string | undefined {
const document = root instanceof Document ? root : root.ownerDocument; const maybeDocument = root instanceof Document ? root : root.ownerDocument;
if (!document) if (!maybeDocument)
return; return;
const document = maybeDocument!;
const xpathCache = new Map<string, Element[]>(); const xpathCache = new Map<string, Element[]>();
if (type === 'notext') if (type === 'notext')

View file

@ -349,7 +349,12 @@ class Engine {
for (let [element, boundary] of currentStep) { for (let [element, boundary] of currentStep) {
let next: (Element | SelectorRoot)[] = []; let next: (Element | SelectorRoot)[] = [];
if (token.combinator === '^') { if (token.combinator === '^') {
next = element === boundary ? [] : (parentOrRoot(element) ? [parentOrRoot(element)] : []); if (element === boundary) {
next = [];
} else {
const parent = parentOrRoot(element);
next = parent ? [parent] : [];
}
} else if (token.combinator === '>') { } else if (token.combinator === '>') {
boundary = element; boundary = element;
next = this._matchChildren(element, token, all); next = this._matchChildren(element, token, all);
@ -367,7 +372,7 @@ class Engine {
} }
if (element === boundary) if (element === boundary)
break; break;
element = parentOrRoot(element); element = parentOrRoot(element)!;
} }
} }
for (const nextElement of next) { for (const nextElement of next) {

View file

@ -137,10 +137,10 @@ export class Keyboard {
} }
async type(text: string, options?: { delay?: number }) { async type(text: string, options?: { delay?: number }) {
const delay = (options && options.delay) || null; const delay = (options && options.delay) || undefined;
for (const char of text) { for (const char of text) {
if (keyboardLayout.keyDefinitions[char]) { if (keyboardLayout.keyDefinitions[char]) {
await this.press(char, {delay}); await this.press(char, { delay });
} else { } else {
if (delay) if (delay)
await new Promise(f => setTimeout(f, delay)); await new Promise(f => setTimeout(f, delay));

View file

@ -62,16 +62,16 @@ export class JSHandle<T = any> {
} }
evaluate: types.EvaluateOn<T> = (pageFunction, ...args) => { evaluate: types.EvaluateOn<T> = (pageFunction, ...args) => {
return this._context.evaluate(pageFunction, this, ...args); return this._context.evaluate(pageFunction as any, this, ...args);
} }
evaluateHandle: types.EvaluateHandleOn<T> = (pageFunction, ...args) => { evaluateHandle: types.EvaluateHandleOn<T> = (pageFunction, ...args) => {
return this._context.evaluateHandle(pageFunction, this, ...args); return this._context.evaluateHandle(pageFunction as any, this, ...args);
} }
async getProperty(propertyName: string): Promise<JSHandle | null> { async getProperty(propertyName: string): Promise<JSHandle | null> {
const objectHandle = await this.evaluateHandle((object, propertyName) => { const objectHandle = await this.evaluateHandle((object: any, propertyName) => {
const result = {__proto__: null}; const result: any = {__proto__: null};
result[propertyName] = object[propertyName]; result[propertyName] = object[propertyName];
return result; return result;
}, propertyName); }, propertyName);

View file

@ -101,17 +101,17 @@ export class Request {
private _url: string; private _url: string;
private _resourceType: string; private _resourceType: string;
private _method: string; private _method: string;
private _postData: string; private _postData: string | undefined;
private _headers: Headers; private _headers: Headers;
private _frame: frames.Frame; private _frame: frames.Frame | null;
private _waitForResponsePromise: Promise<Response>; private _waitForResponsePromise: Promise<Response>;
private _waitForResponsePromiseCallback: (value?: Response) => void; private _waitForResponsePromiseCallback: (value: Response) => void = () => {};
private _waitForFinishedPromise: Promise<Response | undefined>; private _waitForFinishedPromise: Promise<Response | null>;
private _waitForFinishedPromiseCallback: (value?: Response | undefined) => void; private _waitForFinishedPromiseCallback: (value: Response | null) => void = () => {};
private _interceptionHandled = false; private _interceptionHandled = false;
constructor(delegate: RequestDelegate | null, frame: frames.Frame | null, redirectChain: Request[], documentId: string, constructor(delegate: RequestDelegate | null, frame: frames.Frame | null, redirectChain: Request[], documentId: string | undefined,
url: string, resourceType: string, method: string, postData: string, headers: Headers) { url: string, resourceType: string, method: string, postData: string | undefined, headers: Headers) {
this._delegate = delegate; this._delegate = delegate;
this._frame = frame; this._frame = frame;
this._redirectChain = redirectChain; this._redirectChain = redirectChain;
@ -130,7 +130,7 @@ export class Request {
_setFailureText(failureText: string) { _setFailureText(failureText: string) {
this._failureText = failureText; this._failureText = failureText;
this._waitForFinishedPromiseCallback(); this._waitForFinishedPromiseCallback(null);
} }
url(): string { url(): string {
@ -157,7 +157,7 @@ export class Request {
return this._response; return this._response;
} }
async _waitForFinished(): Promise<Response | undefined> { async _waitForFinished(): Promise<Response | null> {
return this._waitForFinishedPromise; return this._waitForFinishedPromise;
} }
@ -200,7 +200,7 @@ export class Request {
assert(this._delegate, 'Request Interception is not enabled!'); assert(this._delegate, 'Request Interception is not enabled!');
assert(!this._interceptionHandled, 'Request is already handled!'); assert(!this._interceptionHandled, 'Request is already handled!');
this._interceptionHandled = true; this._interceptionHandled = true;
await this._delegate.abort(errorCode); await this._delegate!.abort(errorCode);
} }
async fulfill(response: { status: number; headers: Headers; contentType: string; body: (string | platform.BufferType); }) { // Mocking responses for dataURL requests is not currently supported. async fulfill(response: { status: number; headers: Headers; contentType: string; body: (string | platform.BufferType); }) { // Mocking responses for dataURL requests is not currently supported.
@ -209,7 +209,7 @@ export class Request {
assert(this._delegate, 'Request Interception is not enabled!'); assert(this._delegate, 'Request Interception is not enabled!');
assert(!this._interceptionHandled, 'Request is already handled!'); assert(!this._interceptionHandled, 'Request is already handled!');
this._interceptionHandled = true; this._interceptionHandled = true;
await this._delegate.fulfill(response); await this._delegate!.fulfill(response);
} }
async continue(overrides: { headers?: { [key: string]: string } } = {}) { async continue(overrides: { headers?: { [key: string]: string } } = {}) {
@ -218,7 +218,7 @@ export class Request {
return; return;
assert(this._delegate, 'Request Interception is not enabled!'); assert(this._delegate, 'Request Interception is not enabled!');
assert(!this._interceptionHandled, 'Request is already handled!'); assert(!this._interceptionHandled, 'Request is already handled!');
await this._delegate.continue(overrides); await this._delegate!.continue(overrides);
} }
} }

View file

@ -65,7 +65,7 @@ export interface PageDelegate {
getOwnerFrame(handle: dom.ElementHandle): Promise<frames.Frame | null>; getOwnerFrame(handle: dom.ElementHandle): Promise<frames.Frame | null>;
getContentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null>; getContentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null>;
layoutViewport(): Promise<{ width: number, height: number }>; layoutViewport(): Promise<{ width: number, height: number }>;
setInputFiles(handle: dom.ElementHandle, files: types.FilePayload[]): Promise<void>; setInputFiles(handle: dom.ElementHandle<HTMLInputElement>, files: types.FilePayload[]): Promise<void>;
getBoundingBox(handle: dom.ElementHandle): Promise<types.Rect | null>; getBoundingBox(handle: dom.ElementHandle): Promise<types.Rect | null>;
getAccessibilityTree(): Promise<accessibility.AXNode>; getAccessibilityTree(): Promise<accessibility.AXNode>;
@ -114,7 +114,9 @@ export class Page extends platform.EventEmitter {
constructor(delegate: PageDelegate, browserContext: BrowserContext) { constructor(delegate: PageDelegate, browserContext: BrowserContext) {
super(); super();
this._delegate = delegate; this._delegate = delegate;
this._closedCallback = () => {};
this._closedPromise = new Promise(f => this._closedCallback = f); this._closedPromise = new Promise(f => this._closedCallback = f);
this._disconnectedCallback = () => {};
this._disconnectedPromise = new Promise(f => this._disconnectedCallback = f); this._disconnectedPromise = new Promise(f => this._disconnectedCallback = f);
this._browserContext = browserContext; this._browserContext = browserContext;
this._state = { this._state = {
@ -160,7 +162,7 @@ export class Page extends platform.EventEmitter {
} }
async _onFileChooserOpened(handle: dom.ElementHandle) { async _onFileChooserOpened(handle: dom.ElementHandle) {
const multiple = await handle.evaluate((element: HTMLInputElement) => !!element.multiple); const multiple = await handle.evaluate(element => !!(element as HTMLInputElement).multiple);
if (!this.listenerCount(Events.Page.FileChooser)) { if (!this.listenerCount(Events.Page.FileChooser)) {
await handle.dispose(); await handle.dispose();
return; return;
@ -197,10 +199,10 @@ export class Page extends platform.EventEmitter {
return this.mainFrame().waitForSelector(selector, options); return this.mainFrame().waitForSelector(selector, options);
} }
async _createSelector(name: string, handle: dom.ElementHandle<Element>): Promise<string> { async _createSelector(name: string, handle: dom.ElementHandle<Element>): Promise<string | undefined> {
const mainContext = await this.mainFrame()._mainContext(); const mainContext = await this.mainFrame()._mainContext();
return mainContext.evaluate((injected: Injected, target: Element, name: string) => { return mainContext.evaluate((injected: Injected, target: Element, name: string) => {
return injected.engines.get(name).create(document.documentElement, target); return injected.engines.get(name)!.create(document.documentElement, target);
}, await mainContext._injected(), handle, name); }, await mainContext._injected(), handle, name);
} }
@ -270,7 +272,7 @@ export class Page extends platform.EventEmitter {
const {name, seq, args} = JSON.parse(payload); const {name, seq, args} = JSON.parse(payload);
let expression = null; let expression = null;
try { try {
const result = await this._pageBindings.get(name)(...args); const result = await this._pageBindings.get(name)!(...args);
expression = helper.evaluationString(deliverResult, name, seq, result); expression = helper.evaluationString(deliverResult, name, seq, result);
} catch (error) { } catch (error) {
if (error instanceof Error) if (error instanceof Error)
@ -493,7 +495,7 @@ export class Page extends platform.EventEmitter {
return this.mainFrame().type(selector, text, options); return this.mainFrame().type(selector, text, options);
} }
async waitFor(selectorOrFunctionOrTimeout: (string | number | Function), options?: types.WaitForFunctionOptions & { visibility?: types.Visibility }, ...args: any[]): Promise<js.JSHandle> { async waitFor(selectorOrFunctionOrTimeout: (string | number | Function), options?: types.WaitForFunctionOptions & { visibility?: types.Visibility }, ...args: any[]): Promise<js.JSHandle | null> {
return this.mainFrame().waitFor(selectorOrFunctionOrTimeout, options, ...args); return this.mainFrame().waitFor(selectorOrFunctionOrTimeout, options, ...args);
} }
@ -531,10 +533,11 @@ export class Worker {
private _url: string; private _url: string;
private _executionContextPromise: Promise<js.ExecutionContext>; private _executionContextPromise: Promise<js.ExecutionContext>;
private _executionContextCallback: (value?: js.ExecutionContext) => void; private _executionContextCallback: (value?: js.ExecutionContext) => void;
_existingExecutionContext: js.ExecutionContext | null; _existingExecutionContext: js.ExecutionContext | null = null;
constructor(url: string) { constructor(url: string) {
this._url = url; this._url = url;
this._executionContextCallback = () => {};
this._executionContextPromise = new Promise(x => this._executionContextCallback = x); this._executionContextPromise = new Promise(x => this._executionContextCallback = x);
return helper.logPublicApiCalls('worker', this); return helper.logPublicApiCalls('worker', this);
} }

View file

@ -23,9 +23,9 @@ export const isNode = typeof process === 'object' && !!process && typeof process
export function promisify(nodeFunction: Function): Function { export function promisify(nodeFunction: Function): Function {
assert(isNode); assert(isNode);
function promisified(...args) { function promisified(...args: any[]) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
function callback(err, ...result) { function callback(err: any, ...result: any[]) {
if (err) if (err)
return reject(err); return reject(err);
if (result.length === 1) if (result.length === 1)
@ -200,7 +200,7 @@ export async function closeFdAsync(fd: number): Promise<void> {
return await promisify(nodeFS.close)(fd); return await promisify(nodeFS.close)(fd);
} }
export function getMimeType(file: string): string { export function getMimeType(file: string): string | null {
assertFileAccess(); assertFileAccess();
return mime.getType(file); return mime.getType(file);
} }
@ -228,7 +228,7 @@ export function pngToJpeg(buffer: Buffer): Buffer {
function nodeFetch(url: string): Promise<string> { function nodeFetch(url: string): Promise<string> {
let resolve: (url: string) => void; let resolve: (url: string) => void;
let reject: (e: Error) => void; let reject: (e: Error) => void = () => {};
const promise = new Promise<string>((res, rej) => { resolve = res; reject = rej; }); const promise = new Promise<string>((res, rej) => { resolve = res; reject = rej; });
const endpointURL = new URL(url); const endpointURL = new URL(url);

View file

@ -29,10 +29,10 @@ export class Screenshotter {
this._page = page; this._page = page;
const browserContext = page.browserContext(); const browserContext = page.browserContext();
this._queue = browserContext[taskQueueSymbol]; this._queue = (browserContext as any)[taskQueueSymbol];
if (!this._queue) { if (!this._queue) {
this._queue = new TaskQueue(); this._queue = new TaskQueue();
browserContext[taskQueueSymbol] = this._queue; (browserContext as any)[taskQueueSymbol] = this._queue;
} }
} }
@ -45,7 +45,7 @@ export class Screenshotter {
if (!viewport) { if (!viewport) {
viewportSize = await this._page.evaluate(() => { viewportSize = await this._page.evaluate(() => {
if (!document.body || !document.documentElement) if (!document.body || !document.documentElement)
return null; return;
return { return {
width: Math.max(document.body.offsetWidth, document.documentElement.offsetWidth), width: Math.max(document.body.offsetWidth, document.documentElement.offsetWidth),
height: Math.max(document.body.offsetHeight, document.documentElement.offsetHeight), height: Math.max(document.body.offsetHeight, document.documentElement.offsetHeight),
@ -79,13 +79,13 @@ export class Screenshotter {
options.clip = trimClipToViewport(viewport, options.clip); options.clip = trimClipToViewport(viewport, options.clip);
} }
const result = await this._screenshot(format, options, overridenViewport || viewport); const result = await this._screenshot(format, options, (overridenViewport || viewport)!);
if (overridenViewport) { if (overridenViewport) {
if (viewport) if (viewport)
await this._page.setViewport(viewport); await this._page.setViewport(viewport);
else else
await this._page._delegate.resetViewport(viewportSize); await this._page._delegate.resetViewport(viewportSize!);
} }
return result; return result;
}).catch(rewriteError); }).catch(rewriteError);
@ -97,12 +97,15 @@ export class Screenshotter {
return this._queue.postTask(async () => { return this._queue.postTask(async () => {
let overridenViewport: types.Viewport | undefined; let overridenViewport: types.Viewport | undefined;
let boundingBox = await this._page._delegate.getBoundingBoxForScreenshot(handle); let maybeBoundingBox = await this._page._delegate.getBoundingBoxForScreenshot(handle);
assert(boundingBox, 'Node is either not visible or not an HTMLElement'); assert(maybeBoundingBox, 'Node is either not visible or not an HTMLElement');
let boundingBox = maybeBoundingBox!;
assert(boundingBox.width !== 0, 'Node has 0 width.'); assert(boundingBox.width !== 0, 'Node has 0 width.');
assert(boundingBox.height !== 0, 'Node has 0 height.'); assert(boundingBox.height !== 0, 'Node has 0 height.');
boundingBox = enclosingIntRect(boundingBox); boundingBox = enclosingIntRect(boundingBox);
const viewport = this._page.viewport();
// TODO: viewport may be null here.
const viewport = this._page.viewport()!;
if (!this._page._delegate.canScreenshotOutsideViewport()) { if (!this._page._delegate.canScreenshotOutsideViewport()) {
if (boundingBox.width > viewport.width || boundingBox.height > viewport.height) { if (boundingBox.width > viewport.width || boundingBox.height > viewport.height) {
@ -115,7 +118,9 @@ export class Screenshotter {
} }
await handle.scrollIntoViewIfNeeded(); await handle.scrollIntoViewIfNeeded();
boundingBox = enclosingIntRect(await this._page._delegate.getBoundingBoxForScreenshot(handle)); maybeBoundingBox = await this._page._delegate.getBoundingBoxForScreenshot(handle);
assert(maybeBoundingBox, 'Node is either not visible or not an HTMLElement');
boundingBox = enclosingIntRect(maybeBoundingBox!);
} }
if (!overridenViewport) if (!overridenViewport)
@ -159,7 +164,7 @@ class TaskQueue {
} }
} }
function trimClipToViewport(viewport: types.Viewport | null, clip: types.Rect | null): types.Rect | null { function trimClipToViewport(viewport: types.Viewport | null, clip: types.Rect | undefined): types.Rect | undefined {
if (!clip || !viewport) if (!clip || !viewport)
return clip; return clip;
const p1 = { x: Math.min(clip.x, viewport.width), y: Math.min(clip.y, viewport.height) }; const p1 = { x: Math.min(clip.x, viewport.width), y: Math.min(clip.y, viewport.height) };

View file

@ -56,12 +56,12 @@ export class BrowserFetcher {
canDownload(revision: string = this._preferredRevision): Promise<boolean> { canDownload(revision: string = this._preferredRevision): Promise<boolean> {
const url = this._params(this._platform, revision).downloadUrl; const url = this._params(this._platform, revision).downloadUrl;
let resolve; let resolve: (result: boolean) => void = () => {};
const promise = new Promise<boolean>(x => resolve = x); const promise = new Promise<boolean>(x => resolve = x);
const request = httpRequest(url, 'HEAD', response => { const request = httpRequest(url, 'HEAD', response => {
resolve(response.statusCode === 200); resolve(response.statusCode === 200);
}); });
request.on('error', error => { request.on('error', (error: any) => {
console.error(error); console.error(error);
resolve(false); resolve(false);
}); });
@ -92,8 +92,8 @@ export class BrowserFetcher {
async localRevisions(): Promise<string[]> { async localRevisions(): Promise<string[]> {
if (!await existsAsync(this._downloadsFolder)) if (!await existsAsync(this._downloadsFolder))
return []; return [];
const fileNames = await readdirAsync(this._downloadsFolder); const fileNames: string[] = await readdirAsync(this._downloadsFolder);
return fileNames.map(fileName => parseFolderPath(fileName)).filter(entry => entry && entry.platform === this._platform).map(entry => entry.revision); return fileNames.map(fileName => parseFolderPath(fileName)).filter(entry => entry && entry.platform === this._platform).map(entry => entry!.revision);
} }
async remove(revision: string = this._preferredRevision) { async remove(revision: string = this._preferredRevision) {
@ -123,8 +123,9 @@ function parseFolderPath(folderPath: string): { platform: string; revision: stri
return {platform, revision}; return {platform, revision};
} }
function downloadFile(url: string, destinationPath: string, progressCallback: OnProgressCallback | null): Promise<any> { function downloadFile(url: string, destinationPath: string, progressCallback: OnProgressCallback | undefined): Promise<any> {
let fulfill, reject; let fulfill: () => void = () => {};
let reject: (error: any) => void = () => {};
let downloadedBytes = 0; let downloadedBytes = 0;
let totalBytes = 0; let totalBytes = 0;
@ -146,12 +147,12 @@ function downloadFile(url: string, destinationPath: string, progressCallback: On
if (progressCallback) if (progressCallback)
response.on('data', onData); response.on('data', onData);
}); });
request.on('error', error => reject(error)); request.on('error', (error: any) => reject(error));
return promise; return promise;
function onData(chunk) { function onData(chunk: string) {
downloadedBytes += chunk.length; downloadedBytes += chunk.length;
progressCallback(downloadedBytes, totalBytes); progressCallback!(downloadedBytes, totalBytes);
} }
} }
@ -186,7 +187,7 @@ function httpRequest(url: string, method: string, response: (r: any) => void) {
} }
} }
const requestCallback = res => { const requestCallback = (res: any) => {
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location)
httpRequest(res.headers.location, method, response); httpRequest(res.headers.location, method, response);
else else

View file

@ -144,7 +144,7 @@ export class CRPlaywright implements Playwright {
const usePipe = chromeArguments.includes('--remote-debugging-pipe'); const usePipe = chromeArguments.includes('--remote-debugging-pipe');
const { launchedProcess, gracefullyClose } = await launchProcess({ const { launchedProcess, gracefullyClose } = await launchProcess({
executablePath: chromeExecutable, executablePath: chromeExecutable!,
args: chromeArguments, args: chromeArguments,
env, env,
handleSIGINT, handleSIGINT,
@ -152,7 +152,7 @@ export class CRPlaywright implements Playwright {
handleSIGHUP, handleSIGHUP,
dumpio, dumpio,
pipe: usePipe, pipe: usePipe,
tempDir: temporaryUserDataDir, tempDir: temporaryUserDataDir || undefined,
attemptToGracefullyClose: async () => { attemptToGracefullyClose: async () => {
if (!connectOptions) if (!connectOptions)
return Promise.reject(); return Promise.reject();
@ -246,9 +246,9 @@ export class CRPlaywright implements Playwright {
...defaultOptions, ...defaultOptions,
...options, ...options,
}; };
assert(!!(downloadURLs as any)[options.platform], 'Unsupported platform: ' + options.platform); assert(!!(downloadURLs as any)[options.platform!], 'Unsupported platform: ' + options.platform);
return new BrowserFetcher(options.path, options.platform, this._revision, (platform: string, revision: string) => { return new BrowserFetcher(options.path!, options.platform!, this._revision, (platform: string, revision: string) => {
let archiveName = ''; let archiveName = '';
let executablePath = ''; let executablePath = '';
if (platform === 'linux') { if (platform === 'linux') {

View file

@ -152,7 +152,7 @@ export class FFPlaywright implements Playwright {
handleSIGHUP, handleSIGHUP,
dumpio, dumpio,
pipe: false, pipe: false,
tempDir: temporaryProfileDir, tempDir: temporaryProfileDir || undefined,
attemptToGracefullyClose: async () => { attemptToGracefullyClose: async () => {
if (!connectOptions) if (!connectOptions)
return Promise.reject(); return Promise.reject();
@ -232,9 +232,9 @@ export class FFPlaywright implements Playwright {
...defaultOptions, ...defaultOptions,
...options, ...options,
}; };
assert(!!downloadURLs[options.platform], 'Unsupported platform: ' + options.platform); assert(!!(downloadURLs as any)[options.platform!], 'Unsupported platform: ' + options.platform);
return new BrowserFetcher(options.path, options.platform, this._revision, (platform: string, revision: string) => { return new BrowserFetcher(options.path!, options.platform!, this._revision, (platform: string, revision: string) => {
let executablePath = ''; let executablePath = '';
if (platform === 'linux') if (platform === 'linux')
executablePath = path.join('firefox', 'firefox'); executablePath = path.join('firefox', 'firefox');
@ -243,7 +243,7 @@ export class FFPlaywright implements Playwright {
else if (platform === 'win32' || platform === 'win64') else if (platform === 'win32' || platform === 'win64')
executablePath = path.join('firefox', 'firefox.exe'); executablePath = path.join('firefox', 'firefox.exe');
return { return {
downloadUrl: util.format(downloadURLs[platform], options.host, revision), downloadUrl: util.format((downloadURLs as any)[platform], options.host, revision),
executablePath executablePath
}; };
}); });
@ -465,8 +465,8 @@ const DEFAULT_PREFERENCES = {
async function createProfile(extraPrefs?: object): Promise<string> { async function createProfile(extraPrefs?: object): Promise<string> {
const profilePath = await mkdtempAsync(path.join(os.tmpdir(), 'playwright_dev_firefox_profile-')); const profilePath = await mkdtempAsync(path.join(os.tmpdir(), 'playwright_dev_firefox_profile-'));
const prefsJS = []; const prefsJS: string[] = [];
const userJS = []; const userJS: string[] = [];
const prefs = { ...DEFAULT_PREFERENCES, ...extraPrefs }; const prefs = { ...DEFAULT_PREFERENCES, ...extraPrefs };
for (const [key, value] of Object.entries(prefs)) for (const [key, value] of Object.entries(prefs))

View file

@ -19,7 +19,7 @@ import { debugError, helper, RegisteredListener } from '../helper';
import { ConnectionTransport } from '../transport'; import { ConnectionTransport } from '../transport';
export class PipeTransport implements ConnectionTransport { export class PipeTransport implements ConnectionTransport {
private _pipeWrite: NodeJS.WritableStream; private _pipeWrite: NodeJS.WritableStream | null;
private _pendingMessage = ''; private _pendingMessage = '';
private _eventListeners: RegisteredListener[]; private _eventListeners: RegisteredListener[];
onmessage?: (message: string) => void; onmessage?: (message: string) => void;
@ -36,13 +36,13 @@ export class PipeTransport implements ConnectionTransport {
helper.addEventListener(pipeRead, 'error', debugError), helper.addEventListener(pipeRead, 'error', debugError),
helper.addEventListener(pipeWrite, 'error', debugError), helper.addEventListener(pipeWrite, 'error', debugError),
]; ];
this.onmessage = null; this.onmessage = undefined;
this.onclose = null; this.onclose = undefined;
} }
send(message: string) { send(message: string) {
this._pipeWrite.write(message); this._pipeWrite!.write(message);
this._pipeWrite.write('\0'); this._pipeWrite!.write('\0');
} }
_dispatch(buffer: Buffer) { _dispatch(buffer: Buffer) {

View file

@ -28,7 +28,7 @@ const removeFolderAsync = platform.promisify(removeFolder);
export type LaunchProcessOptions = { export type LaunchProcessOptions = {
executablePath: string, executablePath: string,
args: string[], args: string[],
env?: {[key: string]: string}, env?: {[key: string]: string | undefined},
handleSIGINT?: boolean, handleSIGINT?: boolean,
handleSIGTERM?: boolean, handleSIGTERM?: boolean,
@ -133,6 +133,7 @@ export async function launchProcess(options: LaunchProcessOptions): Promise<Laun
} }
// Attempt to remove temporary profile directory to avoid littering. // Attempt to remove temporary profile directory to avoid littering.
try { try {
if (options.tempDir)
removeFolder.sync(options.tempDir); removeFolder.sync(options.tempDir);
} catch (e) { } } catch (e) { }
} }

6
src/server/tsconfig.json Normal file
View file

@ -0,0 +1,6 @@
{
"compilerOptions": {
"strict": true
},
"extends": "../../tsconfig.json"
}

View file

@ -122,7 +122,7 @@ export class WKPlaywright implements Playwright {
let connectOptions: WKConnectOptions | undefined = undefined; let connectOptions: WKConnectOptions | undefined = undefined;
const { launchedProcess, gracefullyClose } = await launchProcess({ const { launchedProcess, gracefullyClose } = await launchProcess({
executablePath: webkitExecutable, executablePath: webkitExecutable!,
args: webkitArguments, args: webkitArguments,
env, env,
handleSIGINT, handleSIGINT,
@ -130,7 +130,6 @@ export class WKPlaywright implements Playwright {
handleSIGHUP, handleSIGHUP,
dumpio, dumpio,
pipe: true, pipe: true,
tempDir: null,
attemptToGracefullyClose: async () => { attemptToGracefullyClose: async () => {
if (!connectOptions) if (!connectOptions)
return Promise.reject(); return Promise.reject();
@ -191,13 +190,13 @@ export class WKPlaywright implements Playwright {
...defaultOptions, ...defaultOptions,
...options, ...options,
}; };
assert(!!downloadURLs[options.platform], 'Unsupported platform: ' + options.platform); assert(!!(downloadURLs as any)[options.platform!], 'Unsupported platform: ' + options.platform);
return new BrowserFetcher(options.path, options.platform, this._revision, (platform: string, revision: string) => { return new BrowserFetcher(options.path!, options.platform!, this._revision, (platform: string, revision: string) => {
return { return {
downloadUrl: (platform === 'mac') ? downloadUrl: (platform === 'mac') ?
util.format(downloadURLs[platform], options.host, revision, getMacVersion()) : util.format(downloadURLs[platform], options!.host, revision, getMacVersion()) :
util.format(downloadURLs[platform], options.host, revision), util.format((downloadURLs as any)[platform], options!.host, revision),
executablePath: 'pw_run.sh', executablePath: 'pw_run.sh',
}; };
}); });
@ -211,9 +210,9 @@ export class WKPlaywright implements Playwright {
} }
} }
const DEFAULT_ARGS = []; const DEFAULT_ARGS: string[] = [];
let cachedMacVersion = undefined; let cachedMacVersion: string | undefined = undefined;
function getMacVersion() { function getMacVersion() {
if (!cachedMacVersion) { if (!cachedMacVersion) {
const [major, minor] = execSync('sw_vers -productVersion').toString('utf8').trim().split('.'); const [major, minor] = execSync('sw_vers -productVersion').toString('utf8').trim().split('.');

View file

@ -65,7 +65,7 @@ export class SlowMoTransport {
const message = this._incomingMessageQueue.shift(); const message = this._incomingMessageQueue.shift();
try { try {
if (this.onmessage) if (this.onmessage)
this.onmessage(message); this.onmessage(message!);
} finally { } finally {
this._scheduleQueueDispatch(); this._scheduleQueueDispatch();
} }
@ -77,8 +77,8 @@ export class SlowMoTransport {
if (this.onclose) if (this.onclose)
this.onclose(); this.onclose();
this._closed = true; this._closed = true;
this._delegate.onmessage = null; this._delegate.onmessage = undefined;
this._delegate.onclose = null; this._delegate.onclose = undefined;
} }
send(s: string) { send(s: string) {

6
src/webkit/tsconfig.json Normal file
View file

@ -0,0 +1,6 @@
{
"compilerOptions": {
"strict": true
},
"extends": "../../tsconfig.json"
}

View file

@ -125,20 +125,20 @@ export class WKBrowser extends platform.EventEmitter implements Browser {
if (this._firstPageProxyCallback) { if (this._firstPageProxyCallback) {
this._firstPageProxyCallback(); this._firstPageProxyCallback();
this._firstPageProxyCallback = null; this._firstPageProxyCallback = undefined;
} }
} }
_onPageProxyDestroyed(event: Protocol.Browser.pageProxyDestroyedPayload) { _onPageProxyDestroyed(event: Protocol.Browser.pageProxyDestroyedPayload) {
const pageProxyId = event.pageProxyId; const pageProxyId = event.pageProxyId;
const pageProxy = this._pageProxies.get(pageProxyId); const pageProxy = this._pageProxies.get(pageProxyId)!;
pageProxy.didClose(); pageProxy.didClose();
pageProxy.dispose(); pageProxy.dispose();
this._pageProxies.delete(pageProxyId); this._pageProxies.delete(pageProxyId);
} }
_onPageProxyMessageReceived(event: PageProxyMessageReceivedPayload) { _onPageProxyMessageReceived(event: PageProxyMessageReceivedPayload) {
const pageProxy = this._pageProxies.get(event.pageProxyId); const pageProxy = this._pageProxies.get(event.pageProxyId)!;
pageProxy.dispatchMessageToSession(event.message); pageProxy.dispatchMessageToSession(event.message);
} }
@ -166,14 +166,14 @@ export class WKBrowser extends platform.EventEmitter implements Browser {
newPage: async (): Promise<Page> => { newPage: async (): Promise<Page> => {
const { pageProxyId } = await this._browserSession.send('Browser.createPage', { browserContextId }); const { pageProxyId } = await this._browserSession.send('Browser.createPage', { browserContextId });
const pageProxy = this._pageProxies.get(pageProxyId); const pageProxy = this._pageProxies.get(pageProxyId)!;
return await pageProxy.page(); return await pageProxy.page();
}, },
close: async (): Promise<void> => { close: async (): Promise<void> => {
assert(browserContextId, 'Non-incognito profiles cannot be closed!'); assert(browserContextId, 'Non-incognito profiles cannot be closed!');
await this._browserSession.send('Browser.deleteContext', { browserContextId }); await this._browserSession.send('Browser.deleteContext', { browserContextId: browserContextId! });
this._contexts.delete(browserContextId); this._contexts.delete(browserContextId!);
}, },
cookies: async (): Promise<network.NetworkCookie[]> => { cookies: async (): Promise<network.NetworkCookie[]> => {

View file

@ -77,8 +77,8 @@ export class WKConnection {
if (this._closed) if (this._closed)
return; return;
this._closed = true; this._closed = true;
this._transport.onmessage = null; this._transport.onmessage = undefined;
this._transport.onclose = null; this._transport.onclose = undefined;
this.browserSession.dispose(); this.browserSession.dispose();
this._onDisconnect(); this._onDisconnect();
} }
@ -90,10 +90,11 @@ export class WKConnection {
} }
export class WKSession extends platform.EventEmitter { export class WKSession extends platform.EventEmitter {
connection?: WKConnection; connection: WKConnection;
errorText: string; errorText: string;
readonly sessionId: string; readonly sessionId: string;
private _disposed = false;
private readonly _rawSend: (message: any) => void; private readonly _rawSend: (message: any) => void;
private readonly _callbacks = new Map<number, {resolve:(o: any) => void, reject: (e: Error) => void, error: Error, method: string}>(); private readonly _callbacks = new Map<number, {resolve:(o: any) => void, reject: (e: Error) => void, error: Error, method: string}>();
@ -109,13 +110,19 @@ export class WKSession extends platform.EventEmitter {
this.sessionId = sessionId; this.sessionId = sessionId;
this._rawSend = rawSend; this._rawSend = rawSend;
this.errorText = errorText; this.errorText = errorText;
this.on = super.on;
this.off = super.removeListener;
this.addListener = super.addListener;
this.removeListener = super.removeListener;
this.once = super.once;
} }
send<T extends keyof Protocol.CommandParameters>( send<T extends keyof Protocol.CommandParameters>(
method: T, method: T,
params?: Protocol.CommandParameters[T] params?: Protocol.CommandParameters[T]
): Promise<Protocol.CommandReturnValues[T]> { ): Promise<Protocol.CommandReturnValues[T]> {
if (!this.connection) if (this._disposed)
return Promise.reject(new Error(`Protocol error (${method}): ${this.errorText}`)); return Promise.reject(new Error(`Protocol error (${method}): ${this.errorText}`));
const id = this.connection.nextMessageId(); const id = this.connection.nextMessageId();
const messageObj = { id, method, params }; const messageObj = { id, method, params };
@ -128,20 +135,20 @@ export class WKSession extends platform.EventEmitter {
} }
isDisposed(): boolean { isDisposed(): boolean {
return !this.connection; return this._disposed;
} }
dispose() { dispose() {
for (const callback of this._callbacks.values()) for (const callback of this._callbacks.values())
callback.reject(rewriteError(callback.error, `Protocol error (${callback.method}): ${this.errorText}`)); callback.reject(rewriteError(callback.error, `Protocol error (${callback.method}): ${this.errorText}`));
this._callbacks.clear(); this._callbacks.clear();
this.connection = undefined; this._disposed = true;
} }
dispatchMessage(object: any) { dispatchMessage(object: any) {
debugWrappedMessage('◀ RECV ' + JSON.stringify(object, null, 2)); debugWrappedMessage('◀ RECV ' + JSON.stringify(object, null, 2));
if (object.id && this._callbacks.has(object.id)) { if (object.id && this._callbacks.has(object.id)) {
const callback = this._callbacks.get(object.id); const callback = this._callbacks.get(object.id)!;
this._callbacks.delete(object.id); this._callbacks.delete(object.id);
if (object.error) if (object.error)
callback.reject(createProtocolError(callback.error, callback.method, object)); callback.reject(createProtocolError(callback.error, callback.method, object));

View file

@ -28,14 +28,13 @@ export class WKExecutionContext implements js.ExecutionContextDelegate {
private _globalObjectId?: Promise<string>; private _globalObjectId?: Promise<string>;
_session: WKSession; _session: WKSession;
_contextId: number | undefined; _contextId: number | undefined;
private _contextDestroyedCallback: () => void; private _contextDestroyedCallback: () => void = () => {};
private _executionContextDestroyedPromise: Promise<unknown>; private _executionContextDestroyedPromise: Promise<unknown>;
_jsonStringifyObjectId: Protocol.Runtime.RemoteObjectId | undefined; _jsonStringifyObjectId: Protocol.Runtime.RemoteObjectId | undefined;
constructor(client: WKSession, contextId: number | undefined) { constructor(client: WKSession, contextId: number | undefined) {
this._session = client; this._session = client;
this._contextId = contextId; this._contextId = contextId;
this._contextDestroyedCallback = null;
this._executionContextDestroyedPromise = new Promise((resolve, reject) => { this._executionContextDestroyedPromise = new Promise((resolve, reject) => {
this._contextDestroyedCallback = resolve; this._contextDestroyedCallback = resolve;
}); });
@ -59,7 +58,7 @@ export class WKExecutionContext implements js.ExecutionContextDelegate {
if (response.result.type === 'object' && response.result.className === 'Promise') { if (response.result.type === 'object' && response.result.className === 'Promise') {
return Promise.race([ return Promise.race([
this._executionContextDestroyedPromise.then(() => contextDestroyedResult), this._executionContextDestroyedPromise.then(() => contextDestroyedResult),
this._awaitPromise(response.result.objectId), this._awaitPromise(response.result.objectId!),
]); ]);
} }
return response; return response;
@ -131,7 +130,7 @@ export class WKExecutionContext implements js.ExecutionContextDelegate {
if (response.result.type === 'object' && response.result.className === 'Promise') { if (response.result.type === 'object' && response.result.className === 'Promise') {
return Promise.race([ return Promise.race([
this._executionContextDestroyedPromise.then(() => contextDestroyedResult), this._executionContextDestroyedPromise.then(() => contextDestroyedResult),
this._awaitPromise(response.result.objectId), this._awaitPromise(response.result.objectId!),
]); ]);
} }
return response; return response;
@ -198,7 +197,7 @@ export class WKExecutionContext implements js.ExecutionContextDelegate {
throw new Error('Execution context was destroyed, most likely because of a navigation.'); throw new Error('Execution context was destroyed, most likely because of a navigation.');
throw e; throw e;
}).then(response => { }).then(response => {
return response.result.objectId; return response.result.objectId!;
}); });
} }
return this._globalObjectId; return this._globalObjectId;
@ -233,8 +232,11 @@ export class WKExecutionContext implements js.ExecutionContextDelegate {
} }
async getProperties(handle: js.JSHandle): Promise<Map<string, js.JSHandle>> { async getProperties(handle: js.JSHandle): Promise<Map<string, js.JSHandle>> {
const objectId = toRemoteObject(handle).objectId;
if (!objectId)
return new Map();
const response = await this._session.send('Runtime.getProperties', { const response = await this._session.send('Runtime.getProperties', {
objectId: toRemoteObject(handle).objectId, objectId,
ownProperties: true ownProperties: true
}); });
const result = new Map(); const result = new Map();

View file

@ -36,7 +36,7 @@ function toModifiersMask(modifiers: Set<input.Modifier>): number {
export class RawKeyboardImpl implements input.RawKeyboard { export class RawKeyboardImpl implements input.RawKeyboard {
private readonly _pageProxySession: WKSession; private readonly _pageProxySession: WKSession;
private _session: WKSession; private _session?: WKSession;
constructor(session: WKSession) { constructor(session: WKSession) {
this._pageProxySession = session; this._pageProxySession = session;
@ -83,7 +83,7 @@ export class RawKeyboardImpl implements input.RawKeyboard {
} }
async sendText(text: string): Promise<void> { async sendText(text: string): Promise<void> {
await this._session.send('Page.insertText', { text }); await this._session!.send('Page.insertText', { text });
} }
} }

View file

@ -35,6 +35,7 @@ export class WKNetworkManager {
constructor(page: Page, pageProxySession: WKSession) { constructor(page: Page, pageProxySession: WKSession) {
this._page = page; this._page = page;
this._pageProxySession = pageProxySession; this._pageProxySession = pageProxySession;
this._session = undefined as any as WKSession;
} }
async initializePageProxySession(credentials: types.Credentials | null) { async initializePageProxySession(credentials: types.Credentials | null) {
@ -96,13 +97,13 @@ export class WKNetworkManager {
// TODO(einbinder) this will fail if we are an XHR document request // TODO(einbinder) this will fail if we are an XHR document request
const isNavigationRequest = event.type === 'Document'; const isNavigationRequest = event.type === 'Document';
const documentId = isNavigationRequest ? event.loaderId : undefined; const documentId = isNavigationRequest ? event.loaderId : undefined;
const request = new InterceptableRequest(this._session, this._page._state.interceptNetwork, frame, event, redirectChain, documentId); const request = new InterceptableRequest(this._session, !!this._page._state.interceptNetwork, frame, event, redirectChain, documentId);
this._requestIdToRequest.set(event.requestId, request); this._requestIdToRequest.set(event.requestId, request);
this._page._frameManager.requestStarted(request.request); this._page._frameManager.requestStarted(request.request);
} }
_onRequestIntercepted(event: Protocol.Network.requestInterceptedPayload) { _onRequestIntercepted(event: Protocol.Network.requestInterceptedPayload) {
this._requestIdToRequest.get(event.requestId)._interceptedCallback(); this._requestIdToRequest.get(event.requestId)!._interceptedCallback();
} }
_createResponse(request: InterceptableRequest, responsePayload: Protocol.Network.Response): network.Response { _createResponse(request: InterceptableRequest, responsePayload: Protocol.Network.Response): network.Response {
@ -141,8 +142,9 @@ export class WKNetworkManager {
// Under certain conditions we never get the Network.responseReceived // Under certain conditions we never get the Network.responseReceived
// event from protocol. @see https://crbug.com/883475 // event from protocol. @see https://crbug.com/883475
if (request.request.response()) const response = request.request.response();
request.request.response()._requestFinished(); if (response)
response._requestFinished();
this._requestIdToRequest.delete(request._requestId); this._requestIdToRequest.delete(request._requestId);
this._page._frameManager.requestFinished(request.request); this._page._frameManager.requestFinished(request.request);
} }
@ -192,7 +194,7 @@ class InterceptableRequest implements network.RequestDelegate {
readonly request: network.Request; readonly request: network.Request;
_requestId: string; _requestId: string;
_documentId: string | undefined; _documentId: string | undefined;
_interceptedCallback: () => void; _interceptedCallback: () => void = () => {};
private _interceptedPromise: Promise<unknown>; private _interceptedPromise: Promise<unknown>;
constructor(session: WKSession, allowInterception: boolean, frame: frames.Frame | null, event: Protocol.Network.requestWillBeSentPayload, redirectChain: network.Request[], documentId: string | undefined) { constructor(session: WKSession, allowInterception: boolean, frame: frames.Frame | null, event: Protocol.Network.requestWillBeSentPayload, redirectChain: network.Request[], documentId: string | undefined) {

View file

@ -59,6 +59,7 @@ export class WKPage implements PageDelegate {
this._page = new Page(this, browserContext); this._page = new Page(this, browserContext);
this._networkManager = new WKNetworkManager(this._page, pageProxySession); this._networkManager = new WKNetworkManager(this._page, pageProxySession);
this._workers = new WKWorkers(this._page); this._workers = new WKWorkers(this._page);
this._session = undefined as any as WKSession;
} }
async _initializePageProxySession() { async _initializePageProxySession() {
@ -173,7 +174,7 @@ export class WKPage implements PageDelegate {
} }
_handleFrameTree(frameTree: Protocol.Page.FrameResourceTree) { _handleFrameTree(frameTree: Protocol.Page.FrameResourceTree) {
this._onFrameAttached(frameTree.frame.id, frameTree.frame.parentId); this._onFrameAttached(frameTree.frame.id, frameTree.frame.parentId || null);
this._onFrameNavigated(frameTree.frame, true); this._onFrameNavigated(frameTree.frame, true);
if (!frameTree.childFrames) if (!frameTree.childFrames)
return; return;
@ -233,8 +234,8 @@ export class WKPage implements PageDelegate {
async _onConsoleMessage(event: Protocol.Console.messageAddedPayload) { async _onConsoleMessage(event: Protocol.Console.messageAddedPayload) {
const { type, level, text, parameters, url, line: lineNumber, column: columnNumber, source } = event.message; const { type, level, text, parameters, url, line: lineNumber, column: columnNumber, source } = event.message;
if (level === 'debug' && parameters && parameters[0].value === BINDING_CALL_MESSAGE) { if (level === 'debug' && parameters && parameters[0].value === BINDING_CALL_MESSAGE) {
const parsedObjectId = JSON.parse(parameters[1].objectId); const parsedObjectId = JSON.parse(parameters[1].objectId!);
const context = this._contextIdToContext.get(parsedObjectId.injectedScriptId); const context = this._contextIdToContext.get(parsedObjectId.injectedScriptId)!;
this._page._onBindingCalled(parameters[2].value, context); this._page._onBindingCalled(parameters[2].value, context);
return; return;
} }
@ -245,7 +246,7 @@ export class WKPage implements PageDelegate {
return; return;
} }
let derivedType: string = type; let derivedType: string = type || '';
if (type === 'log') if (type === 'log')
derivedType = level; derivedType = level;
else if (type === 'timing') else if (type === 'timing')
@ -256,13 +257,13 @@ export class WKPage implements PageDelegate {
let context: dom.FrameExecutionContext | null = null; let context: dom.FrameExecutionContext | null = null;
if (p.objectId) { if (p.objectId) {
const objectId = JSON.parse(p.objectId); const objectId = JSON.parse(p.objectId);
context = this._contextIdToContext.get(objectId.injectedScriptId); context = this._contextIdToContext.get(objectId.injectedScriptId)!;
} else { } else {
context = mainFrameContext; context = mainFrameContext;
} }
return context._createHandle(p); return context._createHandle(p);
}); });
this._page._addConsoleMessage(derivedType, handles, { url, lineNumber: lineNumber - 1, columnNumber: columnNumber - 1 }, handles.length ? undefined : text); this._page._addConsoleMessage(derivedType, handles, { url, lineNumber: (lineNumber || 1) - 1, columnNumber: (columnNumber || 1) - 1 }, handles.length ? undefined : text);
} }
_onDialog(event: Protocol.Dialog.javascriptDialogOpeningPayload) { _onDialog(event: Protocol.Dialog.javascriptDialogOpeningPayload) {
@ -276,7 +277,7 @@ export class WKPage implements PageDelegate {
} }
async _onFileChooserOpened(event: {frameId: Protocol.Network.FrameId, element: Protocol.Runtime.RemoteObject}) { async _onFileChooserOpened(event: {frameId: Protocol.Network.FrameId, element: Protocol.Runtime.RemoteObject}) {
const context = await this._page._frameManager.frame(event.frameId)._mainContext(); const context = await this._page._frameManager.frame(event.frameId)!._mainContext();
const handle = context._createHandle(event.element).asElement()!; const handle = context._createHandle(event.element).asElement()!;
this._page._onFileChooserOpened(handle); this._page._onFileChooserOpened(handle);
} }
@ -418,7 +419,7 @@ export class WKPage implements PageDelegate {
async getContentFrame(handle: dom.ElementHandle): Promise<frames.Frame | null> { async getContentFrame(handle: dom.ElementHandle): Promise<frames.Frame | null> {
const nodeInfo = await this._session.send('DOM.describeNode', { const nodeInfo = await this._session.send('DOM.describeNode', {
objectId: toRemoteObject(handle).objectId objectId: toRemoteObject(handle).objectId!
}); });
if (!nodeInfo.contentFrameId) if (!nodeInfo.contentFrameId)
return null; return null;
@ -462,7 +463,7 @@ export class WKPage implements PageDelegate {
async getContentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null> { async getContentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null> {
const result = await this._session.send('DOM.getContentQuads', { const result = await this._session.send('DOM.getContentQuads', {
objectId: toRemoteObject(handle).objectId objectId: toRemoteObject(handle).objectId!
}).catch(debugError); }).catch(debugError);
if (!result) if (!result)
return null; return null;
@ -478,8 +479,8 @@ export class WKPage implements PageDelegate {
return this._page.evaluate(() => ({ width: innerWidth, height: innerHeight })); return this._page.evaluate(() => ({ width: innerWidth, height: innerHeight }));
} }
async setInputFiles(handle: dom.ElementHandle, files: types.FilePayload[]): Promise<void> { async setInputFiles(handle: dom.ElementHandle<HTMLInputElement>, files: types.FilePayload[]): Promise<void> {
const objectId = toRemoteObject(handle).objectId; const objectId = toRemoteObject(handle).objectId!;
await this._session.send('DOM.setInputFiles', { objectId, files }); await this._session.send('DOM.setInputFiles', { objectId, files });
} }

View file

@ -21,7 +21,7 @@ export class WKPageProxy {
private _pagePromise: Promise<Page> | null = null; private _pagePromise: Promise<Page> | null = null;
private _wkPage: WKPage | null = null; private _wkPage: WKPage | null = null;
private readonly _firstTargetPromise: Promise<void>; private readonly _firstTargetPromise: Promise<void>;
private _firstTargetCallback: () => void; private _firstTargetCallback?: () => void;
private readonly _sessions = new Map<string, WKSession>(); private readonly _sessions = new Map<string, WKSession>();
private readonly _eventListeners: RegisteredListener[]; private readonly _eventListeners: RegisteredListener[];
@ -71,16 +71,15 @@ export class WKPageProxy {
} }
onPopupCreated(popupPageProxy: WKPageProxy) { onPopupCreated(popupPageProxy: WKPageProxy) {
if (!this._wkPage) const wkPage = this._wkPage;
if (!wkPage || !wkPage._page.listenerCount(Events.Page.Popup))
return; return;
if (!this._wkPage._page.listenerCount(Events.Page.Popup)) popupPageProxy.page().then(page => wkPage._page.emit(Events.Page.Popup, page));
return;
popupPageProxy.page().then(page => this._wkPage._page.emit(Events.Page.Popup, page));
} }
private async _initializeWKPage(): Promise<Page> { private async _initializeWKPage(): Promise<Page> {
await this._firstTargetPromise; await this._firstTargetPromise;
let session: WKSession; let session: WKSession | undefined;
for (const anySession of this._sessions.values()) { for (const anySession of this._sessions.values()) {
if (!(anySession as any)[provisionalMessagesSymbol]) { if (!(anySession as any)[provisionalMessagesSymbol]) {
session = anySession; session = anySession;
@ -89,10 +88,10 @@ export class WKPageProxy {
} }
assert(session, 'One non-provisional target session must exist'); assert(session, 'One non-provisional target session must exist');
this._wkPage = new WKPage(this._browserContext, this._pageProxySession); this._wkPage = new WKPage(this._browserContext, this._pageProxySession);
this._wkPage.setSession(session); this._wkPage.setSession(session!);
await Promise.all([ await Promise.all([
this._wkPage._initializePageProxySession(), this._wkPage._initializePageProxySession(),
this._wkPage._initializeSession(session, false), this._wkPage._initializeSession(session!, false),
]); ]);
return this._wkPage._page; return this._wkPage._page;
} }
@ -110,7 +109,7 @@ export class WKPageProxy {
this._sessions.set(targetInfo.targetId, session); this._sessions.set(targetInfo.targetId, session);
if (this._firstTargetCallback) { if (this._firstTargetCallback) {
this._firstTargetCallback(); this._firstTargetCallback();
this._firstTargetCallback = null; this._firstTargetCallback = undefined;
} }
if (targetInfo.isProvisional) if (targetInfo.isProvisional)
(session as any)[provisionalMessagesSymbol] = []; (session as any)[provisionalMessagesSymbol] = [];
@ -138,7 +137,7 @@ export class WKPageProxy {
if (provisionalMessages) if (provisionalMessages)
provisionalMessages.push(message); provisionalMessages.push(message);
else else
session.dispatchMessage(JSON.parse(message)); session!.dispatchMessage(JSON.parse(message));
} }
private _onDidCommitProvisionalTarget(event: Protocol.Target.didCommitProvisionalTargetPayload) { private _onDidCommitProvisionalTarget(event: Protocol.Target.didCommitProvisionalTargetPayload) {
@ -148,12 +147,12 @@ export class WKPageProxy {
const oldSession = this._sessions.get(oldTargetId); const oldSession = this._sessions.get(oldTargetId);
assert(oldSession, 'Unknown old target: ' + oldTargetId); assert(oldSession, 'Unknown old target: ' + oldTargetId);
// TODO: make some calls like screenshot catch swapped out error and retry. // TODO: make some calls like screenshot catch swapped out error and retry.
oldSession.errorText = 'Target was swapped out.'; oldSession!.errorText = 'Target was swapped out.';
const provisionalMessages = (newSession as any)[provisionalMessagesSymbol]; const provisionalMessages = (newSession as any)[provisionalMessagesSymbol];
assert(provisionalMessages, 'Committing target must be provisional'); assert(provisionalMessages, 'Committing target must be provisional');
(newSession as any)[provisionalMessagesSymbol] = undefined; (newSession as any)[provisionalMessagesSymbol] = undefined;
for (const message of provisionalMessages) for (const message of provisionalMessages)
newSession.dispatchMessage(JSON.parse(message)); newSession!.dispatchMessage(JSON.parse(message));
this._wkPage.setSession(newSession); this._wkPage!.setSession(newSession!);
} }
} }

View file

@ -61,11 +61,11 @@ export class WKWorkers {
} }
}), }),
helper.addEventListener(session, 'Worker.dispatchMessageFromWorker', (event: Protocol.Worker.dispatchMessageFromWorkerPayload) => { helper.addEventListener(session, 'Worker.dispatchMessageFromWorker', (event: Protocol.Worker.dispatchMessageFromWorkerPayload) => {
const workerSession = this._workerSessions.get(event.workerId); const workerSession = this._workerSessions.get(event.workerId)!;
workerSession.dispatchMessage(JSON.parse(event.message)); workerSession.dispatchMessage(JSON.parse(event.message));
}), }),
helper.addEventListener(session, 'Worker.workerTerminated', (event: Protocol.Worker.workerTerminatedPayload) => { helper.addEventListener(session, 'Worker.workerTerminated', (event: Protocol.Worker.workerTerminatedPayload) => {
const workerSession = this._workerSessions.get(event.workerId); const workerSession = this._workerSessions.get(event.workerId)!;
workerSession.dispose(); workerSession.dispose();
this._workerSessions.delete(event.workerId); this._workerSessions.delete(event.workerId);
this._page._removeWorker(event.workerId); this._page._removeWorker(event.workerId);
@ -79,15 +79,15 @@ export class WKWorkers {
async _onConsoleMessage(worker: Worker, event: Protocol.Console.messageAddedPayload) { async _onConsoleMessage(worker: Worker, event: Protocol.Console.messageAddedPayload) {
const { type, level, text, parameters, url, line: lineNumber, column: columnNumber } = event.message; const { type, level, text, parameters, url, line: lineNumber, column: columnNumber } = event.message;
let derivedType: string = type; let derivedType: string = type || '';
if (type === 'log') if (type === 'log')
derivedType = level; derivedType = level;
else if (type === 'timing') else if (type === 'timing')
derivedType = 'timeEnd'; derivedType = 'timeEnd';
const handles = (parameters || []).map(p => { const handles = (parameters || []).map(p => {
return worker._existingExecutionContext._createHandle(p); return worker._existingExecutionContext!._createHandle(p);
}); });
this._page._addConsoleMessage(derivedType, handles, { url, lineNumber: lineNumber - 1, columnNumber: columnNumber - 1 }, handles.length ? undefined : text); this._page._addConsoleMessage(derivedType, handles, { url, lineNumber: (lineNumber || 1) - 1, columnNumber: (columnNumber || 1) - 1 }, handles.length ? undefined : text);
} }
} }

View file

@ -48,7 +48,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
await browser.stopTracing(); await browser.stopTracing();
const traceJson = JSON.parse(fs.readFileSync(outputFile).toString()); const traceJson = JSON.parse(fs.readFileSync(outputFile).toString());
expect(traceJson.metadata['trace-config']).toContain('disabled-by-default-v8.cpu_profiler.hires'); expect(traceJson.metadata['trace-config']).toContain('disabled-by-default-v8.cpu_profiler.hires', 'Does not contain expected category');
}); });
it('should throw if tracing on two pages', async({browser, page, server, outputFile}) => { it('should throw if tracing on two pages', async({browser, page, server, outputFile}) => {
await browser.startTracing(page, {path: outputFile}); await browser.startTracing(page, {path: outputFile});
@ -64,7 +64,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
await page.goto(server.PREFIX + '/grid.html'); await page.goto(server.PREFIX + '/grid.html');
const trace = await browser.stopTracing(); const trace = await browser.stopTracing();
const buf = fs.readFileSync(outputFile); const buf = fs.readFileSync(outputFile);
expect(trace.toString()).toEqual(buf.toString()); expect(trace.toString()).toEqual(buf.toString(), 'Tracing buffer mismatch');
}); });
it('should work without options', async({browser, page, server, outputFile}) => { it('should work without options', async({browser, page, server, outputFile}) => {
await browser.startTracing(page); await browser.startTracing(page);
@ -87,7 +87,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
await browser.startTracing(page, {screenshots: true}); await browser.startTracing(page, {screenshots: true});
await page.goto(server.PREFIX + '/grid.html'); await page.goto(server.PREFIX + '/grid.html');
const trace = await browser.stopTracing(); const trace = await browser.stopTracing();
expect(trace.toString()).toContain('screenshot'); expect(trace.toString()).toContain('screenshot', 'Does not contain screenshot');
}); });
}); });
}; };