diff --git a/package.json b/package.json index 8e54a52d7c..4ea265a4a8 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "@types/mime": "^2.0.0", "@types/node": "^8.10.34", "@types/pngjs": "^3.4.0", + "@types/proxy-from-env": "^1.0.0", "@types/rimraf": "^2.0.2", "@types/ws": "^6.0.1", "@typescript-eslint/eslint-plugin": "^2.6.1", diff --git a/src/accessibility.ts b/src/accessibility.ts index 26eaa866ea..f4ce8a4b9d 100644 --- a/src/accessibility.ts +++ b/src/accessibility.ts @@ -68,13 +68,13 @@ export class Accessibility { async snapshot(options: { interestingOnly?: boolean; root?: dom.ElementHandle | null; - } = {}): Promise { + } = {}): Promise { const { interestingOnly = true, root = null, } = options; const defaultRoot = await this._getAXTree(); - let needle = defaultRoot; + let needle: AXNode | null = defaultRoot; if (root) { needle = await defaultRoot.findElement(root); if (!needle) diff --git a/src/browserContext.ts b/src/browserContext.ts index b077d725e4..17f1cbac91 100644 --- a/src/browserContext.ts +++ b/src/browserContext.ts @@ -103,14 +103,14 @@ export class BrowserContext { if (geolocation) { geolocation.accuracy = geolocation.accuracy || 0; 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.`); - if (latitude < -90 || latitude > 90) + if (latitude !== undefined && (latitude < -90 || latitude > 90)) throw new Error(`Invalid latitude "${latitude}": precondition -90 <= LATITUDE <= 90 failed.`); if (accuracy < 0) throw new Error(`Invalid accuracy "${accuracy}": precondition 0 <= ACCURACY failed.`); } - this._options.geolocation = geolocation; + this._options.geolocation = geolocation || undefined; await this._delegate.setGeolocation(geolocation); } diff --git a/src/chromium/crAccessibility.ts b/src/chromium/crAccessibility.ts index d1729193d2..c582946ee8 100644 --- a/src/chromium/crAccessibility.ts +++ b/src/chromium/crAccessibility.ts @@ -207,7 +207,7 @@ class CRAXNode implements accessibility.AXNode { const node: {[x in keyof accessibility.SerializedAXNode]: any} = { role: this._role, - name: this._payload.name.value || '' + name: this._payload.name ? (this._payload.name.value || '') : '' }; const userStringProperties: Array = [ @@ -286,7 +286,7 @@ class CRAXNode implements accessibility.AXNode { nodeById.set(payload.nodeId, new CRAXNode(client, payload)); for (const node of nodeById.values()) { for (const childId of node._payload.childIds || []) - node._children.push(nodeById.get(childId)); + node._children.push(nodeById.get(childId)!); } return nodeById.values().next().value; } diff --git a/src/chromium/crBrowser.ts b/src/chromium/crBrowser.ts index 50199e55b2..b026bebe0e 100644 --- a/src/chromium/crBrowser.ts +++ b/src/chromium/crBrowser.ts @@ -46,7 +46,7 @@ export class CRBrowser extends platform.EventEmitter implements Browser { _targets = new Map(); private _tracingRecording = false; - private _tracingPath = ''; + private _tracingPath: string | null = ''; private _tracingClient: CRSession | undefined; static async connect(options: CRConnectOptions): Promise { @@ -79,20 +79,21 @@ export class CRBrowser extends platform.EventEmitter implements Browser { pages: async (): Promise => { const targets = this._allTargets().filter(target => target.browserContext() === context && target.type() === '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 => { 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'); - return target.page(); + const page = await target.page(); + return page!; }, close: async (): Promise => { assert(contextId, 'Non-incognito profiles cannot be closed!'); - await this._client.send('Target.disposeBrowserContext', {browserContextId: contextId || undefined}); - this._contexts.delete(contextId); + await this._client.send('Target.disposeBrowserContext', { browserContextId: contextId! }); + this._contexts.delete(contextId!); }, cookies: async (): Promise => { @@ -172,7 +173,7 @@ export class CRBrowser extends platform.EventEmitter implements Browser { async _targetCreated(event: Protocol.Target.targetCreatedPayload) { const targetInfo = event.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)); 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; }) { - const target = this._targets.get(event.targetId); + const target = this._targets.get(event.targetId)!; target._initializedCallback(false); this._targets.delete(event.targetId); target._didClose(); @@ -192,7 +193,7 @@ export class CRBrowser extends platform.EventEmitter implements Browser { } _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'); const previousURL = target.url(); const wasInitialized = target._isInitialized; @@ -242,7 +243,7 @@ export class CRBrowser extends platform.EventEmitter implements Browser { } browserTarget(): CRTarget { - return [...this._targets.values()].find(t => t.type() === 'browser'); + return [...this._targets.values()].find(t => t.type() === 'browser')!; } serviceWorker(target: CRTarget): Promise { @@ -280,10 +281,10 @@ export class CRBrowser extends platform.EventEmitter implements Browser { assert(this._tracingClient, 'Tracing was not started.'); let fulfill: (buffer: platform.BufferType) => void; const contentPromise = new Promise(x => fulfill = x); - this._tracingClient.once('Tracing.tracingComplete', event => { - readProtocolStream(this._tracingClient, event.stream, this._tracingPath).then(fulfill); + this._tracingClient!.once('Tracing.tracingComplete', event => { + readProtocolStream(this._tracingClient!, event.stream!, this._tracingPath).then(fulfill); }); - await this._tracingClient.send('Tracing.end'); + await this._tracingClient!.send('Tracing.end'); this._tracingRecording = false; return contentPromise; } @@ -324,5 +325,5 @@ export async function createTransport(options: CRConnectOptions): Promise { 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 { 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 { - _connection: CRConnection; + _connection: CRConnection | null; private _callbacks = new Map void, reject: (e: Error) => void, error: Error, method: string}>(); private _targetType: string; private _sessionId: string; @@ -128,6 +128,12 @@ export class CRSession extends platform.EventEmitter { this._connection = connection; this._targetType = targetType; this._sessionId = sessionId; + + this.on = super.on; + this.addListener = super.addListener; + this.off = super.removeListener; + this.removeListener = super.removeListener; + this.once = super.once; } send( @@ -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; }) { 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); if (object.error) callback.reject(createProtocolError(callback.error, callback.method, object)); diff --git a/src/chromium/crCoverage.ts b/src/chromium/crCoverage.ts index 2a97a6998f..87045080c5 100644 --- a/src/chromium/crCoverage.ts +++ b/src/chromium/crCoverage.ts @@ -65,7 +65,7 @@ class JSCoverage { _scriptSources: Map; _eventListeners: RegisteredListener[]; _resetOnNavigation: boolean; - _reportAnonymousScripts: boolean; + _reportAnonymousScripts = false; constructor(client: CRSession) { this._client = client; @@ -238,12 +238,12 @@ class CSSCoverage { }); } - const coverage = []; + const coverage: CoverageEntry[] = []; for (const styleSheetId of this._stylesheetURLs.keys()) { - const url = this._stylesheetURLs.get(styleSheetId); - const text = this._stylesheetSources.get(styleSheetId); + const url = this._stylesheetURLs.get(styleSheetId)!; + const text = this._stylesheetSources.get(styleSheetId)!; const ranges = convertToDisjointRanges(styleSheetIdToCoverage.get(styleSheetId) || []); - coverage.push({url, ranges, text}); + coverage.push({ url, ranges, text }); } return coverage; @@ -277,7 +277,7 @@ function convertToDisjointRanges(nestedRanges: { }); const hitCountStack = []; - const results = []; + const results: { start: number; end: number; }[] = []; let lastOffset = 0; // Run scanning line to intersect all ranges. for (const point of points) { diff --git a/src/chromium/crExecutionContext.ts b/src/chromium/crExecutionContext.ts index cb252e1674..1920d55675 100644 --- a/src/chromium/crExecutionContext.ts +++ b/src/chromium/crExecutionContext.ts @@ -132,8 +132,11 @@ export class CRExecutionContext implements js.ExecutionContextDelegate { } async getProperties(handle: js.JSHandle): Promise> { + const objectId = toRemoteObject(handle).objectId; + if (!objectId) + return new Map(); const response = await this._client.send('Runtime.getProperties', { - objectId: toRemoteObject(handle).objectId, + objectId, ownProperties: true }); const result = new Map(); diff --git a/src/chromium/crNetworkManager.ts b/src/chromium/crNetworkManager.ts index 88aab3b98b..74f36d5c5f 100644 --- a/src/chromium/crNetworkManager.ts +++ b/src/chromium/crNetworkManager.ts @@ -154,11 +154,13 @@ export class CRNetworkManager { requestId: event.requestId }).catch(debugError); } + if (!event.networkId) + return; const requestId = event.networkId; 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._requestIdToRequestWillBeSentEvent.delete(requestId); } else { @@ -186,7 +188,7 @@ export class CRNetworkManager { } _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 response = await this._client.send('Network.getResponseBody', { requestId: request._requestId }); return platform.Buffer.from(response.body, response.base64Encoded ? 'base64' : 'utf8'); @@ -199,7 +201,8 @@ export class CRNetworkManager { request.request._redirectChain.push(request.request); response._requestFinished(new Error('Response body is unavailable for redirect responses')); this._requestIdToRequest.delete(request._requestId); - this._attemptedAuthentications.delete(request._interceptionId); + if (request._interceptionId) + this._attemptedAuthentications.delete(request._interceptionId); this._page._frameManager.requestReceivedResponse(response); this._page._frameManager.requestFinished(request.request); } @@ -222,10 +225,12 @@ export class CRNetworkManager { // Under certain conditions we never get the Network.responseReceived // event from protocol. @see https://crbug.com/883475 - if (request.request.response()) - request.request.response()._requestFinished(); + const response = request.request.response(); + if (response) + response._requestFinished(); this._requestIdToRequest.delete(request._requestId); - this._attemptedAuthentications.delete(request._interceptionId); + if (request._interceptionId) + this._attemptedAuthentications.delete(request._interceptionId); this._page._frameManager.requestFinished(request.request); } @@ -239,32 +244,33 @@ export class CRNetworkManager { if (response) response._requestFinished(); this._requestIdToRequest.delete(request._requestId); - this._attemptedAuthentications.delete(request._interceptionId); + if (request._interceptionId) + this._attemptedAuthentications.delete(request._interceptionId); 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 { readonly request: network.Request; _requestId: string; - _interceptionId: string; - _documentId: string; + _interceptionId: string | null; + _documentId: string | undefined; 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._requestId = event.requestId; this._interceptionId = interceptionId; this._documentId = 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; } = {}) { await this._client.send('Fetch.continueRequest', { - requestId: this._interceptionId, + requestId: this._interceptionId!, headers: overrides.headers ? headersArray(overrides.headers) : undefined, }).catch(error => { // 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)); await this._client.send('Fetch.fulfillRequest', { - requestId: this._interceptionId, + requestId: this._interceptionId!, responseCode: response.status || 200, responsePhrase: network.STATUS_TEXTS[String(response.status || 200)], responseHeaders: headersArray(responseHeaders), @@ -303,7 +309,7 @@ class InterceptableRequest implements network.RequestDelegate { const errorReason = errorReasons[errorCode]; assert(errorReason, 'Unknown error code: ' + errorCode); await this._client.send('Fetch.failRequest', { - requestId: this._interceptionId, + requestId: this._interceptionId!, errorReason }).catch(error => { // In certain cases, protocol will return error if the request was already canceled diff --git a/src/chromium/crPage.ts b/src/chromium/crPage.ts index eb5f919912..955e35692b 100644 --- a/src/chromium/crPage.ts +++ b/src/chromium/crPage.ts @@ -16,6 +16,7 @@ */ import * as dom from '../dom'; +import * as js from '../javascript'; import * as frames from '../frames'; import { debugError, helper, RegisteredListener } from '../helper'; import * as network from '../network'; @@ -151,7 +152,7 @@ export class CRPage implements PageDelegate { } _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); if (!frameTree.childFrames) return; @@ -196,7 +197,7 @@ export class CRPage implements PageDelegate { } _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) return; if (contextPayload.auxData && contextPayload.auxData.type === 'isolated') @@ -240,7 +241,7 @@ export class CRPage implements PageDelegate { // @see https://github.com/GoogleChrome/puppeteer/issues/3865 return; } - const context = this._contextIdToContext.get(event.executionContextId); + const context = this._contextIdToContext.get(event.executionContextId)!; const values = event.args.map(arg => context._createHandle(arg)); this._page._addConsoleMessage(event.type, values, toConsoleMessageLocation(event.stackTrace)); } @@ -252,7 +253,7 @@ export class CRPage implements PageDelegate { } _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); } @@ -283,7 +284,7 @@ export class CRPage implements PageDelegate { } 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 handle = await this.adoptBackendNodeId(event.backendNodeId, utilityContext); this._page._onFileChooserOpened(handle); @@ -458,7 +459,7 @@ export class CRPage implements PageDelegate { return { width: layoutMetrics.layoutViewport.clientWidth, height: layoutMetrics.layoutViewport.clientHeight }; } - async setInputFiles(handle: dom.ElementHandle, files: types.FilePayload[]): Promise { + async setInputFiles(handle: dom.ElementHandle, files: types.FilePayload[]): Promise { 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; } diff --git a/src/chromium/crPdf.ts b/src/chromium/crPdf.ts index daab7f199e..d137849614 100644 --- a/src/chromium/crPdf.ts +++ b/src/chromium/crPdf.ts @@ -125,6 +125,6 @@ export class CRPDF { pageRanges, preferCSSPageSize }); - return await readProtocolStream(this._client, result.stream, path); + return await readProtocolStream(this._client, result.stream!, path); } } diff --git a/src/chromium/crProtocolHelper.ts b/src/chromium/crProtocolHelper.ts index c311663fb5..24cd0717ba 100644 --- a/src/chromium/crProtocolHelper.ts +++ b/src/chromium/crProtocolHelper.ts @@ -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 { let eof = false; - let fd; + let fd: number | undefined; if (path) fd = await platform.openFdAsync(path, 'w'); 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); bufs.push(buf); if (path) - await platform.writeFdAsync(fd, buf); + await platform.writeFdAsync(fd!, buf); } if (path) - await platform.closeFdAsync(fd); + await platform.closeFdAsync(fd!); await client.send('IO.close', {handle}); let resultBuffer = null; try { resultBuffer = platform.Buffer.concat(bufs); } finally { - return resultBuffer; + return resultBuffer!; } } diff --git a/src/chromium/crTarget.ts b/src/chromium/crTarget.ts index a6304b2b0b..8c58bb10f7 100644 --- a/src/chromium/crTarget.ts +++ b/src/chromium/crTarget.ts @@ -37,7 +37,7 @@ export class CRTarget { _crPage: CRPage | null = null; private _workerPromise: Promise | null = null; readonly _initializedPromise: Promise; - _initializedCallback: (value?: unknown) => void; + _initializedCallback: (success: boolean) => void = () => {}; _isInitialized: boolean; static fromPage(page: Page): CRTarget { @@ -135,7 +135,7 @@ export class CRTarget { const { openerId } = this._targetInfo; if (!openerId) return null; - return this._browser._targets.get(openerId); + return this._browser._targets.get(openerId)!; } createCDPSession(): Promise { diff --git a/src/chromium/crWorkers.ts b/src/chromium/crWorkers.ts index 7af956edae..24e68e744b 100644 --- a/src/chromium/crWorkers.ts +++ b/src/chromium/crWorkers.ts @@ -28,7 +28,7 @@ export class CRWorkers { if (event.targetInfo.type !== 'worker') return; 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); page._addWorker(event.sessionId, worker); 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. 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))); }); client.on('Target.detachedFromTarget', event => page._removeWorker(event.sessionId)); diff --git a/src/chromium/tsconfig.json b/src/chromium/tsconfig.json index beccd788d8..9b96b484ca 100644 --- a/src/chromium/tsconfig.json +++ b/src/chromium/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "noImplicitAny": true + "strict": true }, "extends": "../../tsconfig.json" } diff --git a/src/dom.ts b/src/dom.ts index 05a4ec04b7..3074b1b588 100644 --- a/src/dom.ts +++ b/src/dom.ts @@ -62,7 +62,7 @@ export class FrameExecutionContext extends js.ExecutionContext { return result; } - _createHandle(remoteObject: any): js.JSHandle | null { + _createHandle(remoteObject: any): js.JSHandle { if (this.frame._page._delegate.isElementHandle(remoteObject)) return new ElementHandle(this, remoteObject); return super._createHandle(remoteObject); @@ -88,7 +88,7 @@ export class FrameExecutionContext extends js.ExecutionContext { ); if (!handle.asElement()) await handle.dispose(); - return handle.asElement(); + return handle.asElement() as ElementHandle; } async _$array(selector: string, scope?: ElementHandle): Promise> { @@ -103,9 +103,9 @@ export class FrameExecutionContext extends js.ExecutionContext { const arrayHandle = await this._$array(selector, scope); const properties = await arrayHandle.getProperties(); await arrayHandle.dispose(); - const result = []; + const result: ElementHandle[] = []; for (const property of properties.values()) { - const elementHandle = property.asElement(); + const elementHandle = property.asElement() as ElementHandle; if (elementHandle) result.push(elementHandle); else @@ -121,6 +121,7 @@ export class ElementHandle extends js.JSHandle { constructor(context: FrameExecutionContext, remoteObject: any) { super(context, remoteObject); + this._context = context; this._page = context.frame._page; return helper.logPublicApiCalls('handle', this); } @@ -131,7 +132,7 @@ export class ElementHandle extends js.JSHandle { _evaluateInUtility: types.EvaluateOn = async (pageFunction, ...args) => { const utility = await this._context.frame._utilityContext(); - return utility.evaluate(pageFunction, this, ...args); + return utility.evaluate(pageFunction as any, this, ...args); } async ownerFrame(): Promise { @@ -242,10 +243,10 @@ export class ElementHandle extends js.JSHandle { const [box, border] = await Promise.all([ this.boundingBox(), 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 }; 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), ]); const point = { x: relativePoint.x, y: relativePoint.y }; @@ -317,7 +318,7 @@ export class ElementHandle extends js.JSHandle { const element = node as HTMLSelectElement; const options = Array.from(element.options); - element.value = undefined; + element.value = undefined as any; for (let index = 0; index < options.length; index++) { const option = options[index]; option.selected = optionsToSelect.some(optionToSelect => { @@ -382,6 +383,8 @@ export class ElementHandle extends js.JSHandle { const range = element.ownerDocument.createRange(); range.selectNodeContents(element); const selection = element.ownerDocument.defaultView.getSelection(); + if (!selection) + return 'Element belongs to invisible iframe.'; selection.removeAllRanges(); selection.addRange(range); element.focus(); @@ -414,12 +417,12 @@ export class ElementHandle extends js.JSHandle { } return item; })); - await this._page._delegate.setInputFiles(this, filePayloads); + await this._page._delegate.setInputFiles(this as any as ElementHandle, filePayloads); } async focus() { const errorMessage = await this._evaluateInUtility((element: Node) => { - if (!element['focus']) + if (!(element as any)['focus']) return 'Node is not an HTML or SVG element.'; (element as HTMLElement|SVGElement).focus(); return false; @@ -532,10 +535,10 @@ export function waitForFunctionTask(selector: string | undefined, pageFunction: return innerPredicate(...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) => { selector = normalizeSelector(selector); return context.evaluateHandle((injected: Injected, selector: string, visibility: types.Visibility, timeout: number) => { diff --git a/src/frames.ts b/src/frames.ts index 57c7e542fe..ae10b8ddc9 100644 --- a/src/frames.ts +++ b/src/frames.ts @@ -63,6 +63,7 @@ export class FrameManager { constructor(page: Page) { this._page = page; + this._mainFrame = undefined as any as Frame; } mainFrame(): Frame { @@ -86,7 +87,7 @@ export class FrameManager { } 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 (this._mainFrame) { // 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) { - const frame = this._frames.get(frameId); + const frame = this._frames.get(frameId)!; for (const child of frame.childFrames()) this._removeFramesRecursively(child); frame._url = url; @@ -185,9 +186,10 @@ export class FrameManager { requestStarted(request: network.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) - watcher._onNavigationRequest(request.frame(), request); + watcher._onNavigationRequest(frame, request); } this._page.emit(Events.Page.Request, request); } @@ -203,14 +205,15 @@ export class FrameManager { requestFailed(request: network.Request, canceled: boolean) { this._inflightRequestFinished(request); - if (request._documentId && request.frame()) { - const isCurrentDocument = request.frame()._lastDocumentId === request._documentId; + const frame = request.frame(); + if (request._documentId && frame) { + const isCurrentDocument = frame._lastDocumentId === request._documentId; if (!isCurrentDocument) { - let errorText = request.failure().errorText; + let errorText = request.failure()!.errorText; if (canceled) errorText += '; maybe frame was detached?'; 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); @@ -227,9 +230,9 @@ export class FrameManager { } private _inflightRequestFinished(request: network.Request) { - if (!request.frame() || request.url().endsWith('favicon.ico')) - return; const frame = request.frame(); + if (!frame || request.url().endsWith('favicon.ico')) + return; if (!frame._inflightRequests.has(request)) return; frame._inflightRequests.delete(request); @@ -240,9 +243,9 @@ export class FrameManager { } private _inflightRequestStarted(request: network.Request) { - if (!request.frame() || request.url().endsWith('favicon.ico')) - return; const frame = request.frame(); + if (!frame || request.url().endsWith('favicon.ico')) + return; frame._inflightRequests.add(request); if (frame._inflightRequests.size === 1) this._stopNetworkIdleTimer(frame, 'networkidle0'); @@ -260,7 +263,9 @@ export class FrameManager { } 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); } } @@ -270,12 +275,12 @@ export class Frame { readonly _firedLifecycleEvents: Set; _lastDocumentId: string; readonly _page: Page; - private _parentFrame: Frame; + private _parentFrame: Frame | null; _url = ''; private _detached = false; private _contextData = new Map(); private _childFrames = new Set(); - _name: string; + _name = ''; _inflightRequests = new Set(); readonly _networkIdleTimers = new Map(); @@ -318,10 +323,10 @@ export class Frame { ]); if (!error) { const promises = [watcher.timeoutOrTerminationPromise]; - if (navigateResult.newDocumentId) { - watcher.setExpectedDocumentId(navigateResult.newDocumentId, url); + if (navigateResult!.newDocumentId) { + watcher.setExpectedDocumentId(navigateResult!.newDocumentId, url); promises.push(watcher.newDocumentNavigationPromise); - } else if (navigateResult.isSameDocument) { + } else if (navigateResult!.isSameDocument) { promises.push(watcher.sameDocumentNavigationPromise); } else { promises.push(watcher.sameDocumentNavigationPromise, watcher.newDocumentNavigationPromise); @@ -361,7 +366,7 @@ export class Frame { _context(contextType: ContextType): Promise { if (this._detached) 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 { @@ -505,14 +510,13 @@ export class Frame { const context = await this._mainContext(); return this._raceWithCSPError(async () => { if (url !== null) - return (await context.evaluateHandle(addScriptUrl, url, type)).asElement(); + return (await context.evaluateHandle(addScriptUrl, url, type)).asElement()!; if (path !== null) { let contents = await platform.readFileAsync(path, 'utf8'); 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 { @@ -554,16 +558,15 @@ export class Frame { const context = await this._mainContext(); return this._raceWithCSPError(async () => { if (url !== null) - return (await context.evaluateHandle(addStyleUrl, url)).asElement(); + return (await context.evaluateHandle(addStyleUrl, url)).asElement()!; if (path !== null) { let contents = await platform.readFileAsync(path, 'utf8'); 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 { @@ -595,7 +598,7 @@ export class Frame { private async _raceWithCSPError(func: () => Promise): Promise { const listeners: RegisteredListener[] = []; - let result: dom.ElementHandle | undefined; + let result: dom.ElementHandle; let error: Error | undefined; let cspMessage: ConsoleMessage | undefined; const actionPromise = new Promise(async resolve => { @@ -620,7 +623,7 @@ export class Frame { throw new Error(cspMessage.text()); if (error) throw error; - return result; + return result!; } async click(selector: string, options?: WaitForOptions & ClickOptions) { @@ -683,17 +686,19 @@ export class Frame { return Promise.reject(new Error('Unsupported target type: ' + (typeof selectorOrFunctionOrTimeout))); } - private async _optionallyWaitForSelectorInUtilityContext(selector: string, options: WaitForOptions | undefined): Promise | null> { + private async _optionallyWaitForSelectorInUtilityContext(selector: string, options: WaitForOptions | undefined): Promise> { const { timeout = this._page._timeoutSettings.timeout(), waitFor = 'visible' } = (options || {}); - let handle: dom.ElementHandle | null; + let handle: dom.ElementHandle; if (waitFor !== 'nowait') { - handle = await this._waitForSelectorInUtilityContext(selector, waitFor, timeout); - if (!handle) + const maybeHandle = await this._waitForSelectorInUtilityContext(selector, waitFor, timeout); + if (!maybeHandle) throw new Error('No node found for selector: ' + selectorToString(selector, waitFor)); + handle = maybeHandle; } else { const context = await this._context('utility'); - handle = await context._$(selector); - assert(handle, 'No node found for selector: ' + selector); + const maybeHandle = await context._$(selector); + assert(maybeHandle, 'No node found for selector: ' + selector); + handle = maybeHandle!; } return handle; } @@ -742,7 +747,7 @@ export class Frame { } private _scheduleRerunnableTask(task: dom.Task, contextType: ContextType, timeout?: number, title?: string): Promise { - const data = this._contextData.get(contextType); + const data = this._contextData.get(contextType)!; const rerunnableTask = new RerunnableTask(data, task, timeout, title); data.rerunnableTasks.add(rerunnableTask); if (data.context) @@ -751,7 +756,7 @@ export class Frame { } private _setContext(contextType: ContextType, context: dom.FrameExecutionContext | null) { - const data = this._contextData.get(contextType); + const data = this._contextData.get(contextType)!; data.context = context; if (context) { data.contextResolveCallback.call(null, context); @@ -765,7 +770,7 @@ export class Frame { } _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 // connections so we might end up creating multiple isolated worlds. // We can use either. @@ -787,10 +792,10 @@ class RerunnableTask { private _contextData: ContextData; private _task: dom.Task; private _runCount: number; - private _resolve: (result: js.JSHandle) => void; - private _reject: (reason: Error) => void; - private _timeoutTimer: NodeJS.Timer; - private _terminated: boolean; + private _resolve: (result: js.JSHandle) => void = () => {}; + private _reject: (reason: Error) => void = () => {}; + private _timeoutTimer?: NodeJS.Timer; + private _terminated = false; constructor(data: ContextData, task: dom.Task, timeout?: number, title?: string) { this._contextData = data; @@ -834,7 +839,7 @@ class RerunnableTask { // If execution context has been already destroyed, `context.evaluate` will // throw an error - ignore this predicate run altogether. if (!error && await context.evaluate(s => !s, success).catch(e => true)) { - await success.dispose(); + await success!.dispose(); return; } @@ -851,13 +856,14 @@ class RerunnableTask { if (error) this._reject(error); else - this._resolve(success); + this._resolve(success!); this._doCleanup(); } _doCleanup() { - clearTimeout(this._timeoutTimer); + if (this._timeoutTimer) + clearTimeout(this._timeoutTimer); this._contextData.rerunnableTasks.delete(this); } } @@ -870,13 +876,13 @@ class LifecycleWatcher { private _expectedLifecycle: LifecycleEvent[]; private _frame: Frame; private _navigationRequest: network.Request | null = null; - private _sameDocumentNavigationCompleteCallback: () => void; - private _lifecycleCallback: () => void; - private _newDocumentNavigationCompleteCallback: () => void; - private _frameDetachedCallback: (err: Error) => void; - private _navigationAbortedCallback: (err: Error) => void; - private _maximumTimer: NodeJS.Timer; - private _hasSameDocumentNavigation: boolean; + private _sameDocumentNavigationCompleteCallback: () => void = () => {}; + private _lifecycleCallback: () => void = () => {}; + private _newDocumentNavigationCompleteCallback: () => void = () => {}; + private _frameDetachedCallback: (err: Error) => void = () => {}; + private _navigationAbortedCallback: (err: Error) => void = () => {}; + private _maximumTimer?: NodeJS.Timer; + private _hasSameDocumentNavigation = false; private _targetUrl: string | undefined; private _expectedDocumentId: string | undefined; private _urlMatch: types.URLMatch | undefined; @@ -971,7 +977,7 @@ class LifecycleWatcher { this._checkLifecycleComplete(); } - navigationResponse(): Promise { + async navigationResponse(): Promise { return this._navigationRequest ? this._navigationRequest._finalRequest._waitForFinished() : null; } @@ -1009,7 +1015,8 @@ class LifecycleWatcher { dispose() { this._frame._page._frameManager._lifecycleWatchers.delete(this); - clearTimeout(this._maximumTimer); + if (this._maximumTimer) + clearTimeout(this._maximumTimer); } } diff --git a/src/helper.ts b/src/helper.ts index 2a1f1f52ad..77e93fea57 100644 --- a/src/helper.ts +++ b/src/helper.ts @@ -46,10 +46,10 @@ class Helper { const method = Reflect.get(classType.prototype, methodName); if (methodName === 'constructor' || typeof methodName !== 'string' || methodName.startsWith('_') || typeof method !== 'function' || method.constructor.name !== 'AsyncFunction') continue; - Reflect.set(classType.prototype, methodName, function(...args: any[]) { + Reflect.set(classType.prototype, methodName, function(this: any, ...args: any[]) { const syncStack: any = {}; 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 clientStack = stack.substring(stack.indexOf('\n')); if (e instanceof Error && e.stack && !e.stack.includes(clientStack)) @@ -112,7 +112,9 @@ class Helper { predicate: Function, timeout: number, abortPromise: Promise): Promise { - let eventTimeout, resolveCallback, rejectCallback; + let eventTimeout: NodeJS.Timer; + let resolveCallback: (event: any) => void = () => {}; + let rejectCallback: (error: any) => void = () => {}; const promise = new Promise((resolve, reject) => { resolveCallback = resolve; rejectCallback = reject; diff --git a/src/injected/tsconfig.json b/src/injected/tsconfig.json index beccd788d8..9b96b484ca 100644 --- a/src/injected/tsconfig.json +++ b/src/injected/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "noImplicitAny": true + "strict": true }, "extends": "../../tsconfig.json" } diff --git a/src/injected/xpathSelectorEngine.ts b/src/injected/xpathSelectorEngine.ts index 3516a3d1d4..9506931229 100644 --- a/src/injected/xpathSelectorEngine.ts +++ b/src/injected/xpathSelectorEngine.ts @@ -23,9 +23,10 @@ export const XPathEngine: SelectorEngine = { name: 'xpath', create(root: SelectorRoot, targetElement: Element, type: SelectorType): string | undefined { - const document = root instanceof Document ? root : root.ownerDocument; - if (!document) + const maybeDocument = root instanceof Document ? root : root.ownerDocument; + if (!maybeDocument) return; + const document = maybeDocument!; const xpathCache = new Map(); if (type === 'notext') diff --git a/src/injected/zsSelectorEngine.ts b/src/injected/zsSelectorEngine.ts index cc08d01815..684c1f54d2 100644 --- a/src/injected/zsSelectorEngine.ts +++ b/src/injected/zsSelectorEngine.ts @@ -349,7 +349,12 @@ class Engine { for (let [element, boundary] of currentStep) { let next: (Element | SelectorRoot)[] = []; 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 === '>') { boundary = element; next = this._matchChildren(element, token, all); @@ -367,7 +372,7 @@ class Engine { } if (element === boundary) break; - element = parentOrRoot(element); + element = parentOrRoot(element)!; } } for (const nextElement of next) { diff --git a/src/input.ts b/src/input.ts index a746a33742..dd5864262c 100644 --- a/src/input.ts +++ b/src/input.ts @@ -137,10 +137,10 @@ export class Keyboard { } async type(text: string, options?: { delay?: number }) { - const delay = (options && options.delay) || null; + const delay = (options && options.delay) || undefined; for (const char of text) { if (keyboardLayout.keyDefinitions[char]) { - await this.press(char, {delay}); + await this.press(char, { delay }); } else { if (delay) await new Promise(f => setTimeout(f, delay)); diff --git a/src/javascript.ts b/src/javascript.ts index 02efdc272e..3b693e22dc 100644 --- a/src/javascript.ts +++ b/src/javascript.ts @@ -62,16 +62,16 @@ export class JSHandle { } evaluate: types.EvaluateOn = (pageFunction, ...args) => { - return this._context.evaluate(pageFunction, this, ...args); + return this._context.evaluate(pageFunction as any, this, ...args); } evaluateHandle: types.EvaluateHandleOn = (pageFunction, ...args) => { - return this._context.evaluateHandle(pageFunction, this, ...args); + return this._context.evaluateHandle(pageFunction as any, this, ...args); } async getProperty(propertyName: string): Promise { - const objectHandle = await this.evaluateHandle((object, propertyName) => { - const result = {__proto__: null}; + const objectHandle = await this.evaluateHandle((object: any, propertyName) => { + const result: any = {__proto__: null}; result[propertyName] = object[propertyName]; return result; }, propertyName); diff --git a/src/network.ts b/src/network.ts index 650fba8e2c..6949be1128 100644 --- a/src/network.ts +++ b/src/network.ts @@ -101,17 +101,17 @@ export class Request { private _url: string; private _resourceType: string; private _method: string; - private _postData: string; + private _postData: string | undefined; private _headers: Headers; - private _frame: frames.Frame; + private _frame: frames.Frame | null; private _waitForResponsePromise: Promise; - private _waitForResponsePromiseCallback: (value?: Response) => void; - private _waitForFinishedPromise: Promise; - private _waitForFinishedPromiseCallback: (value?: Response | undefined) => void; + private _waitForResponsePromiseCallback: (value: Response) => void = () => {}; + private _waitForFinishedPromise: Promise; + private _waitForFinishedPromiseCallback: (value: Response | null) => void = () => {}; private _interceptionHandled = false; - constructor(delegate: RequestDelegate | null, frame: frames.Frame | null, redirectChain: Request[], documentId: string, - url: string, resourceType: string, method: string, postData: string, headers: Headers) { + constructor(delegate: RequestDelegate | null, frame: frames.Frame | null, redirectChain: Request[], documentId: string | undefined, + url: string, resourceType: string, method: string, postData: string | undefined, headers: Headers) { this._delegate = delegate; this._frame = frame; this._redirectChain = redirectChain; @@ -130,7 +130,7 @@ export class Request { _setFailureText(failureText: string) { this._failureText = failureText; - this._waitForFinishedPromiseCallback(); + this._waitForFinishedPromiseCallback(null); } url(): string { @@ -157,7 +157,7 @@ export class Request { return this._response; } - async _waitForFinished(): Promise { + async _waitForFinished(): Promise { return this._waitForFinishedPromise; } @@ -200,7 +200,7 @@ export class Request { assert(this._delegate, 'Request Interception is not enabled!'); assert(!this._interceptionHandled, 'Request is already handled!'); 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. @@ -209,7 +209,7 @@ export class Request { assert(this._delegate, 'Request Interception is not enabled!'); assert(!this._interceptionHandled, 'Request is already handled!'); this._interceptionHandled = true; - await this._delegate.fulfill(response); + await this._delegate!.fulfill(response); } async continue(overrides: { headers?: { [key: string]: string } } = {}) { @@ -218,7 +218,7 @@ export class Request { return; assert(this._delegate, 'Request Interception is not enabled!'); assert(!this._interceptionHandled, 'Request is already handled!'); - await this._delegate.continue(overrides); + await this._delegate!.continue(overrides); } } diff --git a/src/page.ts b/src/page.ts index 97ea5de1b3..47bf3304e0 100644 --- a/src/page.ts +++ b/src/page.ts @@ -65,7 +65,7 @@ export interface PageDelegate { getOwnerFrame(handle: dom.ElementHandle): Promise; getContentQuads(handle: dom.ElementHandle): Promise; layoutViewport(): Promise<{ width: number, height: number }>; - setInputFiles(handle: dom.ElementHandle, files: types.FilePayload[]): Promise; + setInputFiles(handle: dom.ElementHandle, files: types.FilePayload[]): Promise; getBoundingBox(handle: dom.ElementHandle): Promise; getAccessibilityTree(): Promise; @@ -114,7 +114,9 @@ export class Page extends platform.EventEmitter { constructor(delegate: PageDelegate, browserContext: BrowserContext) { super(); this._delegate = delegate; + this._closedCallback = () => {}; this._closedPromise = new Promise(f => this._closedCallback = f); + this._disconnectedCallback = () => {}; this._disconnectedPromise = new Promise(f => this._disconnectedCallback = f); this._browserContext = browserContext; this._state = { @@ -160,7 +162,7 @@ export class Page extends platform.EventEmitter { } 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)) { await handle.dispose(); return; @@ -197,10 +199,10 @@ export class Page extends platform.EventEmitter { return this.mainFrame().waitForSelector(selector, options); } - async _createSelector(name: string, handle: dom.ElementHandle): Promise { + async _createSelector(name: string, handle: dom.ElementHandle): Promise { const mainContext = await this.mainFrame()._mainContext(); 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); } @@ -270,7 +272,7 @@ export class Page extends platform.EventEmitter { const {name, seq, args} = JSON.parse(payload); let expression = null; try { - const result = await this._pageBindings.get(name)(...args); + const result = await this._pageBindings.get(name)!(...args); expression = helper.evaluationString(deliverResult, name, seq, result); } catch (error) { if (error instanceof Error) @@ -493,7 +495,7 @@ export class Page extends platform.EventEmitter { return this.mainFrame().type(selector, text, options); } - async waitFor(selectorOrFunctionOrTimeout: (string | number | Function), options?: types.WaitForFunctionOptions & { visibility?: types.Visibility }, ...args: any[]): Promise { + async waitFor(selectorOrFunctionOrTimeout: (string | number | Function), options?: types.WaitForFunctionOptions & { visibility?: types.Visibility }, ...args: any[]): Promise { return this.mainFrame().waitFor(selectorOrFunctionOrTimeout, options, ...args); } @@ -531,10 +533,11 @@ export class Worker { private _url: string; private _executionContextPromise: Promise; private _executionContextCallback: (value?: js.ExecutionContext) => void; - _existingExecutionContext: js.ExecutionContext | null; + _existingExecutionContext: js.ExecutionContext | null = null; constructor(url: string) { this._url = url; + this._executionContextCallback = () => {}; this._executionContextPromise = new Promise(x => this._executionContextCallback = x); return helper.logPublicApiCalls('worker', this); } diff --git a/src/platform.ts b/src/platform.ts index adaf56f352..c3d67a76e7 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -23,9 +23,9 @@ export const isNode = typeof process === 'object' && !!process && typeof process export function promisify(nodeFunction: Function): Function { assert(isNode); - function promisified(...args) { + function promisified(...args: any[]) { return new Promise((resolve, reject) => { - function callback(err, ...result) { + function callback(err: any, ...result: any[]) { if (err) return reject(err); if (result.length === 1) @@ -200,7 +200,7 @@ export async function closeFdAsync(fd: number): Promise { return await promisify(nodeFS.close)(fd); } -export function getMimeType(file: string): string { +export function getMimeType(file: string): string | null { assertFileAccess(); return mime.getType(file); } @@ -228,7 +228,7 @@ export function pngToJpeg(buffer: Buffer): Buffer { function nodeFetch(url: string): Promise { let resolve: (url: string) => void; - let reject: (e: Error) => void; + let reject: (e: Error) => void = () => {}; const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); const endpointURL = new URL(url); diff --git a/src/screenshotter.ts b/src/screenshotter.ts index 296dc60721..297c076397 100644 --- a/src/screenshotter.ts +++ b/src/screenshotter.ts @@ -29,10 +29,10 @@ export class Screenshotter { this._page = page; const browserContext = page.browserContext(); - this._queue = browserContext[taskQueueSymbol]; + this._queue = (browserContext as any)[taskQueueSymbol]; if (!this._queue) { this._queue = new TaskQueue(); - browserContext[taskQueueSymbol] = this._queue; + (browserContext as any)[taskQueueSymbol] = this._queue; } } @@ -45,7 +45,7 @@ export class Screenshotter { if (!viewport) { viewportSize = await this._page.evaluate(() => { if (!document.body || !document.documentElement) - return null; + return; return { width: Math.max(document.body.offsetWidth, document.documentElement.offsetWidth), height: Math.max(document.body.offsetHeight, document.documentElement.offsetHeight), @@ -79,13 +79,13 @@ export class Screenshotter { 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 (viewport) await this._page.setViewport(viewport); else - await this._page._delegate.resetViewport(viewportSize); + await this._page._delegate.resetViewport(viewportSize!); } return result; }).catch(rewriteError); @@ -97,12 +97,15 @@ export class Screenshotter { return this._queue.postTask(async () => { let overridenViewport: types.Viewport | undefined; - let boundingBox = await this._page._delegate.getBoundingBoxForScreenshot(handle); - assert(boundingBox, 'Node is either not visible or not an HTMLElement'); + let maybeBoundingBox = await this._page._delegate.getBoundingBoxForScreenshot(handle); + assert(maybeBoundingBox, 'Node is either not visible or not an HTMLElement'); + let boundingBox = maybeBoundingBox!; assert(boundingBox.width !== 0, 'Node has 0 width.'); assert(boundingBox.height !== 0, 'Node has 0 height.'); 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 (boundingBox.width > viewport.width || boundingBox.height > viewport.height) { @@ -115,7 +118,9 @@ export class Screenshotter { } 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) @@ -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) return clip; const p1 = { x: Math.min(clip.x, viewport.width), y: Math.min(clip.y, viewport.height) }; diff --git a/src/server/browserFetcher.ts b/src/server/browserFetcher.ts index d424f02c89..9d049190fb 100644 --- a/src/server/browserFetcher.ts +++ b/src/server/browserFetcher.ts @@ -56,12 +56,12 @@ export class BrowserFetcher { canDownload(revision: string = this._preferredRevision): Promise { const url = this._params(this._platform, revision).downloadUrl; - let resolve; + let resolve: (result: boolean) => void = () => {}; const promise = new Promise(x => resolve = x); const request = httpRequest(url, 'HEAD', response => { resolve(response.statusCode === 200); }); - request.on('error', error => { + request.on('error', (error: any) => { console.error(error); resolve(false); }); @@ -92,8 +92,8 @@ export class BrowserFetcher { async localRevisions(): Promise { if (!await existsAsync(this._downloadsFolder)) return []; - const fileNames = await readdirAsync(this._downloadsFolder); - return fileNames.map(fileName => parseFolderPath(fileName)).filter(entry => entry && entry.platform === this._platform).map(entry => entry.revision); + const fileNames: string[] = await readdirAsync(this._downloadsFolder); + return fileNames.map(fileName => parseFolderPath(fileName)).filter(entry => entry && entry.platform === this._platform).map(entry => entry!.revision); } async remove(revision: string = this._preferredRevision) { @@ -123,8 +123,9 @@ function parseFolderPath(folderPath: string): { platform: string; revision: stri return {platform, revision}; } -function downloadFile(url: string, destinationPath: string, progressCallback: OnProgressCallback | null): Promise { - let fulfill, reject; +function downloadFile(url: string, destinationPath: string, progressCallback: OnProgressCallback | undefined): Promise { + let fulfill: () => void = () => {}; + let reject: (error: any) => void = () => {}; let downloadedBytes = 0; let totalBytes = 0; @@ -146,12 +147,12 @@ function downloadFile(url: string, destinationPath: string, progressCallback: On if (progressCallback) response.on('data', onData); }); - request.on('error', error => reject(error)); + request.on('error', (error: any) => reject(error)); return promise; - function onData(chunk) { + function onData(chunk: string) { 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) httpRequest(res.headers.location, method, response); else diff --git a/src/server/crPlaywright.ts b/src/server/crPlaywright.ts index c6860199c2..c46fb58507 100644 --- a/src/server/crPlaywright.ts +++ b/src/server/crPlaywright.ts @@ -144,7 +144,7 @@ export class CRPlaywright implements Playwright { const usePipe = chromeArguments.includes('--remote-debugging-pipe'); const { launchedProcess, gracefullyClose } = await launchProcess({ - executablePath: chromeExecutable, + executablePath: chromeExecutable!, args: chromeArguments, env, handleSIGINT, @@ -152,7 +152,7 @@ export class CRPlaywright implements Playwright { handleSIGHUP, dumpio, pipe: usePipe, - tempDir: temporaryUserDataDir, + tempDir: temporaryUserDataDir || undefined, attemptToGracefullyClose: async () => { if (!connectOptions) return Promise.reject(); @@ -246,9 +246,9 @@ export class CRPlaywright implements Playwright { ...defaultOptions, ...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 executablePath = ''; if (platform === 'linux') { diff --git a/src/server/ffPlaywright.ts b/src/server/ffPlaywright.ts index 17025eeeda..8eb84d12a6 100644 --- a/src/server/ffPlaywright.ts +++ b/src/server/ffPlaywright.ts @@ -152,7 +152,7 @@ export class FFPlaywright implements Playwright { handleSIGHUP, dumpio, pipe: false, - tempDir: temporaryProfileDir, + tempDir: temporaryProfileDir || undefined, attemptToGracefullyClose: async () => { if (!connectOptions) return Promise.reject(); @@ -232,9 +232,9 @@ export class FFPlaywright implements Playwright { ...defaultOptions, ...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 = ''; if (platform === 'linux') executablePath = path.join('firefox', 'firefox'); @@ -243,7 +243,7 @@ export class FFPlaywright implements Playwright { else if (platform === 'win32' || platform === 'win64') executablePath = path.join('firefox', 'firefox.exe'); return { - downloadUrl: util.format(downloadURLs[platform], options.host, revision), + downloadUrl: util.format((downloadURLs as any)[platform], options.host, revision), executablePath }; }); @@ -465,8 +465,8 @@ const DEFAULT_PREFERENCES = { async function createProfile(extraPrefs?: object): Promise { const profilePath = await mkdtempAsync(path.join(os.tmpdir(), 'playwright_dev_firefox_profile-')); - const prefsJS = []; - const userJS = []; + const prefsJS: string[] = []; + const userJS: string[] = []; const prefs = { ...DEFAULT_PREFERENCES, ...extraPrefs }; for (const [key, value] of Object.entries(prefs)) diff --git a/src/server/pipeTransport.ts b/src/server/pipeTransport.ts index 55919243e3..8f7bb61243 100644 --- a/src/server/pipeTransport.ts +++ b/src/server/pipeTransport.ts @@ -19,7 +19,7 @@ import { debugError, helper, RegisteredListener } from '../helper'; import { ConnectionTransport } from '../transport'; export class PipeTransport implements ConnectionTransport { - private _pipeWrite: NodeJS.WritableStream; + private _pipeWrite: NodeJS.WritableStream | null; private _pendingMessage = ''; private _eventListeners: RegisteredListener[]; onmessage?: (message: string) => void; @@ -36,13 +36,13 @@ export class PipeTransport implements ConnectionTransport { helper.addEventListener(pipeRead, 'error', debugError), helper.addEventListener(pipeWrite, 'error', debugError), ]; - this.onmessage = null; - this.onclose = null; + this.onmessage = undefined; + this.onclose = undefined; } send(message: string) { - this._pipeWrite.write(message); - this._pipeWrite.write('\0'); + this._pipeWrite!.write(message); + this._pipeWrite!.write('\0'); } _dispatch(buffer: Buffer) { diff --git a/src/server/processLauncher.ts b/src/server/processLauncher.ts index 9ea382578e..2020981c7a 100644 --- a/src/server/processLauncher.ts +++ b/src/server/processLauncher.ts @@ -28,7 +28,7 @@ const removeFolderAsync = platform.promisify(removeFolder); export type LaunchProcessOptions = { executablePath: string, args: string[], - env?: {[key: string]: string}, + env?: {[key: string]: string | undefined}, handleSIGINT?: boolean, handleSIGTERM?: boolean, @@ -133,7 +133,8 @@ export async function launchProcess(options: LaunchProcessOptions): Promise { if (!connectOptions) return Promise.reject(); @@ -191,13 +190,13 @@ export class WKPlaywright implements Playwright { ...defaultOptions, ...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 { downloadUrl: (platform === 'mac') ? - util.format(downloadURLs[platform], options.host, revision, getMacVersion()) : - util.format(downloadURLs[platform], options.host, revision), + util.format(downloadURLs[platform], options!.host, revision, getMacVersion()) : + util.format((downloadURLs as any)[platform], options!.host, revision), 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() { if (!cachedMacVersion) { const [major, minor] = execSync('sw_vers -productVersion').toString('utf8').trim().split('.'); diff --git a/src/transport.ts b/src/transport.ts index feec07e495..0659429164 100644 --- a/src/transport.ts +++ b/src/transport.ts @@ -65,7 +65,7 @@ export class SlowMoTransport { const message = this._incomingMessageQueue.shift(); try { if (this.onmessage) - this.onmessage(message); + this.onmessage(message!); } finally { this._scheduleQueueDispatch(); } @@ -77,8 +77,8 @@ export class SlowMoTransport { if (this.onclose) this.onclose(); this._closed = true; - this._delegate.onmessage = null; - this._delegate.onclose = null; + this._delegate.onmessage = undefined; + this._delegate.onclose = undefined; } send(s: string) { diff --git a/src/webkit/tsconfig.json b/src/webkit/tsconfig.json new file mode 100644 index 0000000000..9b96b484ca --- /dev/null +++ b/src/webkit/tsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "strict": true + }, + "extends": "../../tsconfig.json" +} diff --git a/src/webkit/wkBrowser.ts b/src/webkit/wkBrowser.ts index cee74a6445..21a767f4cf 100644 --- a/src/webkit/wkBrowser.ts +++ b/src/webkit/wkBrowser.ts @@ -125,20 +125,20 @@ export class WKBrowser extends platform.EventEmitter implements Browser { if (this._firstPageProxyCallback) { this._firstPageProxyCallback(); - this._firstPageProxyCallback = null; + this._firstPageProxyCallback = undefined; } } _onPageProxyDestroyed(event: Protocol.Browser.pageProxyDestroyedPayload) { const pageProxyId = event.pageProxyId; - const pageProxy = this._pageProxies.get(pageProxyId); + const pageProxy = this._pageProxies.get(pageProxyId)!; pageProxy.didClose(); pageProxy.dispose(); this._pageProxies.delete(pageProxyId); } _onPageProxyMessageReceived(event: PageProxyMessageReceivedPayload) { - const pageProxy = this._pageProxies.get(event.pageProxyId); + const pageProxy = this._pageProxies.get(event.pageProxyId)!; pageProxy.dispatchMessageToSession(event.message); } @@ -166,14 +166,14 @@ export class WKBrowser extends platform.EventEmitter implements Browser { newPage: async (): Promise => { 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(); }, close: async (): Promise => { assert(browserContextId, 'Non-incognito profiles cannot be closed!'); - await this._browserSession.send('Browser.deleteContext', { browserContextId }); - this._contexts.delete(browserContextId); + await this._browserSession.send('Browser.deleteContext', { browserContextId: browserContextId! }); + this._contexts.delete(browserContextId!); }, cookies: async (): Promise => { diff --git a/src/webkit/wkConnection.ts b/src/webkit/wkConnection.ts index 3556993c38..c89a639742 100644 --- a/src/webkit/wkConnection.ts +++ b/src/webkit/wkConnection.ts @@ -77,8 +77,8 @@ export class WKConnection { if (this._closed) return; this._closed = true; - this._transport.onmessage = null; - this._transport.onclose = null; + this._transport.onmessage = undefined; + this._transport.onclose = undefined; this.browserSession.dispose(); this._onDisconnect(); } @@ -90,10 +90,11 @@ export class WKConnection { } export class WKSession extends platform.EventEmitter { - connection?: WKConnection; + connection: WKConnection; errorText: string; readonly sessionId: string; + private _disposed = false; private readonly _rawSend: (message: any) => void; private readonly _callbacks = new Map void, reject: (e: Error) => void, error: Error, method: string}>(); @@ -109,13 +110,19 @@ export class WKSession extends platform.EventEmitter { this.sessionId = sessionId; this._rawSend = rawSend; this.errorText = errorText; + + this.on = super.on; + this.off = super.removeListener; + this.addListener = super.addListener; + this.removeListener = super.removeListener; + this.once = super.once; } send( method: T, params?: Protocol.CommandParameters[T] ): Promise { - if (!this.connection) + if (this._disposed) return Promise.reject(new Error(`Protocol error (${method}): ${this.errorText}`)); const id = this.connection.nextMessageId(); const messageObj = { id, method, params }; @@ -128,20 +135,20 @@ export class WKSession extends platform.EventEmitter { } isDisposed(): boolean { - return !this.connection; + return this._disposed; } dispose() { for (const callback of this._callbacks.values()) callback.reject(rewriteError(callback.error, `Protocol error (${callback.method}): ${this.errorText}`)); this._callbacks.clear(); - this.connection = undefined; + this._disposed = true; } dispatchMessage(object: any) { debugWrappedMessage('◀ RECV ' + JSON.stringify(object, null, 2)); 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); if (object.error) callback.reject(createProtocolError(callback.error, callback.method, object)); diff --git a/src/webkit/wkExecutionContext.ts b/src/webkit/wkExecutionContext.ts index b6fae930e5..0af625adbd 100644 --- a/src/webkit/wkExecutionContext.ts +++ b/src/webkit/wkExecutionContext.ts @@ -28,14 +28,13 @@ export class WKExecutionContext implements js.ExecutionContextDelegate { private _globalObjectId?: Promise; _session: WKSession; _contextId: number | undefined; - private _contextDestroyedCallback: () => void; + private _contextDestroyedCallback: () => void = () => {}; private _executionContextDestroyedPromise: Promise; _jsonStringifyObjectId: Protocol.Runtime.RemoteObjectId | undefined; constructor(client: WKSession, contextId: number | undefined) { this._session = client; this._contextId = contextId; - this._contextDestroyedCallback = null; this._executionContextDestroyedPromise = new Promise((resolve, reject) => { this._contextDestroyedCallback = resolve; }); @@ -59,7 +58,7 @@ export class WKExecutionContext implements js.ExecutionContextDelegate { if (response.result.type === 'object' && response.result.className === 'Promise') { return Promise.race([ this._executionContextDestroyedPromise.then(() => contextDestroyedResult), - this._awaitPromise(response.result.objectId), + this._awaitPromise(response.result.objectId!), ]); } return response; @@ -131,7 +130,7 @@ export class WKExecutionContext implements js.ExecutionContextDelegate { if (response.result.type === 'object' && response.result.className === 'Promise') { return Promise.race([ this._executionContextDestroyedPromise.then(() => contextDestroyedResult), - this._awaitPromise(response.result.objectId), + this._awaitPromise(response.result.objectId!), ]); } 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 e; }).then(response => { - return response.result.objectId; + return response.result.objectId!; }); } return this._globalObjectId; @@ -233,8 +232,11 @@ export class WKExecutionContext implements js.ExecutionContextDelegate { } async getProperties(handle: js.JSHandle): Promise> { + const objectId = toRemoteObject(handle).objectId; + if (!objectId) + return new Map(); const response = await this._session.send('Runtime.getProperties', { - objectId: toRemoteObject(handle).objectId, + objectId, ownProperties: true }); const result = new Map(); diff --git a/src/webkit/wkInput.ts b/src/webkit/wkInput.ts index 39487c1b9d..9123f498fa 100644 --- a/src/webkit/wkInput.ts +++ b/src/webkit/wkInput.ts @@ -36,7 +36,7 @@ function toModifiersMask(modifiers: Set): number { export class RawKeyboardImpl implements input.RawKeyboard { private readonly _pageProxySession: WKSession; - private _session: WKSession; + private _session?: WKSession; constructor(session: WKSession) { this._pageProxySession = session; @@ -83,7 +83,7 @@ export class RawKeyboardImpl implements input.RawKeyboard { } async sendText(text: string): Promise { - await this._session.send('Page.insertText', { text }); + await this._session!.send('Page.insertText', { text }); } } diff --git a/src/webkit/wkNetworkManager.ts b/src/webkit/wkNetworkManager.ts index ecfec31792..8c4686b263 100644 --- a/src/webkit/wkNetworkManager.ts +++ b/src/webkit/wkNetworkManager.ts @@ -35,6 +35,7 @@ export class WKNetworkManager { constructor(page: Page, pageProxySession: WKSession) { this._page = page; this._pageProxySession = pageProxySession; + this._session = undefined as any as WKSession; } 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 const isNavigationRequest = event.type === 'Document'; 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._page._frameManager.requestStarted(request.request); } _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 { @@ -141,8 +142,9 @@ export class WKNetworkManager { // Under certain conditions we never get the Network.responseReceived // event from protocol. @see https://crbug.com/883475 - if (request.request.response()) - request.request.response()._requestFinished(); + const response = request.request.response(); + if (response) + response._requestFinished(); this._requestIdToRequest.delete(request._requestId); this._page._frameManager.requestFinished(request.request); } @@ -192,7 +194,7 @@ class InterceptableRequest implements network.RequestDelegate { readonly request: network.Request; _requestId: string; _documentId: string | undefined; - _interceptedCallback: () => void; + _interceptedCallback: () => void = () => {}; private _interceptedPromise: Promise; constructor(session: WKSession, allowInterception: boolean, frame: frames.Frame | null, event: Protocol.Network.requestWillBeSentPayload, redirectChain: network.Request[], documentId: string | undefined) { diff --git a/src/webkit/wkPage.ts b/src/webkit/wkPage.ts index d3468020be..c6d374f229 100644 --- a/src/webkit/wkPage.ts +++ b/src/webkit/wkPage.ts @@ -59,6 +59,7 @@ export class WKPage implements PageDelegate { this._page = new Page(this, browserContext); this._networkManager = new WKNetworkManager(this._page, pageProxySession); this._workers = new WKWorkers(this._page); + this._session = undefined as any as WKSession; } async _initializePageProxySession() { @@ -173,7 +174,7 @@ export class WKPage implements PageDelegate { } _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); if (!frameTree.childFrames) return; @@ -233,8 +234,8 @@ export class WKPage implements PageDelegate { async _onConsoleMessage(event: Protocol.Console.messageAddedPayload) { const { type, level, text, parameters, url, line: lineNumber, column: columnNumber, source } = event.message; if (level === 'debug' && parameters && parameters[0].value === BINDING_CALL_MESSAGE) { - const parsedObjectId = JSON.parse(parameters[1].objectId); - const context = this._contextIdToContext.get(parsedObjectId.injectedScriptId); + const parsedObjectId = JSON.parse(parameters[1].objectId!); + const context = this._contextIdToContext.get(parsedObjectId.injectedScriptId)!; this._page._onBindingCalled(parameters[2].value, context); return; } @@ -245,7 +246,7 @@ export class WKPage implements PageDelegate { return; } - let derivedType: string = type; + let derivedType: string = type || ''; if (type === 'log') derivedType = level; else if (type === 'timing') @@ -256,13 +257,13 @@ export class WKPage implements PageDelegate { let context: dom.FrameExecutionContext | null = null; if (p.objectId) { const objectId = JSON.parse(p.objectId); - context = this._contextIdToContext.get(objectId.injectedScriptId); + context = this._contextIdToContext.get(objectId.injectedScriptId)!; } else { context = mainFrameContext; } 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) { @@ -276,7 +277,7 @@ export class WKPage implements PageDelegate { } 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()!; this._page._onFileChooserOpened(handle); } @@ -418,7 +419,7 @@ export class WKPage implements PageDelegate { async getContentFrame(handle: dom.ElementHandle): Promise { const nodeInfo = await this._session.send('DOM.describeNode', { - objectId: toRemoteObject(handle).objectId + objectId: toRemoteObject(handle).objectId! }); if (!nodeInfo.contentFrameId) return null; @@ -462,7 +463,7 @@ export class WKPage implements PageDelegate { async getContentQuads(handle: dom.ElementHandle): Promise { const result = await this._session.send('DOM.getContentQuads', { - objectId: toRemoteObject(handle).objectId + objectId: toRemoteObject(handle).objectId! }).catch(debugError); if (!result) return null; @@ -478,8 +479,8 @@ export class WKPage implements PageDelegate { return this._page.evaluate(() => ({ width: innerWidth, height: innerHeight })); } - async setInputFiles(handle: dom.ElementHandle, files: types.FilePayload[]): Promise { - const objectId = toRemoteObject(handle).objectId; + async setInputFiles(handle: dom.ElementHandle, files: types.FilePayload[]): Promise { + const objectId = toRemoteObject(handle).objectId!; await this._session.send('DOM.setInputFiles', { objectId, files }); } diff --git a/src/webkit/wkPageProxy.ts b/src/webkit/wkPageProxy.ts index c099acba29..2e2fb585a1 100644 --- a/src/webkit/wkPageProxy.ts +++ b/src/webkit/wkPageProxy.ts @@ -21,7 +21,7 @@ export class WKPageProxy { private _pagePromise: Promise | null = null; private _wkPage: WKPage | null = null; private readonly _firstTargetPromise: Promise; - private _firstTargetCallback: () => void; + private _firstTargetCallback?: () => void; private readonly _sessions = new Map(); private readonly _eventListeners: RegisteredListener[]; @@ -71,16 +71,15 @@ export class WKPageProxy { } onPopupCreated(popupPageProxy: WKPageProxy) { - if (!this._wkPage) + const wkPage = this._wkPage; + if (!wkPage || !wkPage._page.listenerCount(Events.Page.Popup)) return; - if (!this._wkPage._page.listenerCount(Events.Page.Popup)) - return; - popupPageProxy.page().then(page => this._wkPage._page.emit(Events.Page.Popup, page)); + popupPageProxy.page().then(page => wkPage._page.emit(Events.Page.Popup, page)); } private async _initializeWKPage(): Promise { await this._firstTargetPromise; - let session: WKSession; + let session: WKSession | undefined; for (const anySession of this._sessions.values()) { if (!(anySession as any)[provisionalMessagesSymbol]) { session = anySession; @@ -89,10 +88,10 @@ export class WKPageProxy { } assert(session, 'One non-provisional target session must exist'); this._wkPage = new WKPage(this._browserContext, this._pageProxySession); - this._wkPage.setSession(session); + this._wkPage.setSession(session!); await Promise.all([ this._wkPage._initializePageProxySession(), - this._wkPage._initializeSession(session, false), + this._wkPage._initializeSession(session!, false), ]); return this._wkPage._page; } @@ -110,7 +109,7 @@ export class WKPageProxy { this._sessions.set(targetInfo.targetId, session); if (this._firstTargetCallback) { this._firstTargetCallback(); - this._firstTargetCallback = null; + this._firstTargetCallback = undefined; } if (targetInfo.isProvisional) (session as any)[provisionalMessagesSymbol] = []; @@ -138,7 +137,7 @@ export class WKPageProxy { if (provisionalMessages) provisionalMessages.push(message); else - session.dispatchMessage(JSON.parse(message)); + session!.dispatchMessage(JSON.parse(message)); } private _onDidCommitProvisionalTarget(event: Protocol.Target.didCommitProvisionalTargetPayload) { @@ -148,12 +147,12 @@ export class WKPageProxy { const oldSession = this._sessions.get(oldTargetId); assert(oldSession, 'Unknown old target: ' + oldTargetId); // 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]; assert(provisionalMessages, 'Committing target must be provisional'); (newSession as any)[provisionalMessagesSymbol] = undefined; for (const message of provisionalMessages) - newSession.dispatchMessage(JSON.parse(message)); - this._wkPage.setSession(newSession); + newSession!.dispatchMessage(JSON.parse(message)); + this._wkPage!.setSession(newSession!); } } diff --git a/src/webkit/wkWorkers.ts b/src/webkit/wkWorkers.ts index 9b75586425..e2ed737752 100644 --- a/src/webkit/wkWorkers.ts +++ b/src/webkit/wkWorkers.ts @@ -61,11 +61,11 @@ export class WKWorkers { } }), 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)); }), 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(); this._workerSessions.delete(event.workerId); this._page._removeWorker(event.workerId); @@ -79,15 +79,15 @@ export class WKWorkers { async _onConsoleMessage(worker: Worker, event: Protocol.Console.messageAddedPayload) { const { type, level, text, parameters, url, line: lineNumber, column: columnNumber } = event.message; - let derivedType: string = type; + let derivedType: string = type || ''; if (type === 'log') derivedType = level; else if (type === 'timing') derivedType = 'timeEnd'; 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); } } diff --git a/test/chromium/tracing.spec.js b/test/chromium/tracing.spec.js index 288a7c9cac..1b9eac9117 100644 --- a/test/chromium/tracing.spec.js +++ b/test/chromium/tracing.spec.js @@ -48,7 +48,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p await browser.stopTracing(); 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}) => { 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'); const trace = await browser.stopTracing(); 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}) => { await browser.startTracing(page); @@ -87,7 +87,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p await browser.startTracing(page, {screenshots: true}); await page.goto(server.PREFIX + '/grid.html'); const trace = await browser.stopTracing(); - expect(trace.toString()).toContain('screenshot'); + expect(trace.toString()).toContain('screenshot', 'Does not contain screenshot'); }); }); };