chore: finish strict type checks across src (#482)

This commit is contained in:
Dmitry Gozman 2020-01-13 22:08:35 -08:00 committed by Pavel Feldman
parent dbc39a8816
commit fb1b3d9a89
16 changed files with 110 additions and 105 deletions

View File

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

View File

@ -14,8 +14,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as accessibility from '../accessibility';
import { FFSession } from './ffConnection';
import { Protocol } from './protocol';
export async function getAccessibilityTree(session: FFSession) : Promise<accessibility.AXNode> {
const { tree } = await session.send('Accessibility.getFullAXTree');
@ -33,13 +35,13 @@ class FFAXNode implements accessibility.AXNode {
private _role: string;
private _cachedHasFocusableChild: boolean|undefined;
constructor(payload) {
constructor(payload: Protocol.AXTree) {
this._payload = payload;
this._children = (payload.children || []).map(x => new FFAXNode(x));
this._editable = payload.editable;
this._editable = !!payload.editable;
this._richlyEditable = this._editable && (payload.tag !== 'textarea' && payload.tag !== 'input');
this._focusable = payload.focusable;
this._expanded = payload.expanded;
this._focusable = !!payload.focusable;
this._expanded = !!payload.expanded;
this._name = this._payload.name;
this._role = this._payload.role;
this._cachedHasFocusableChild;

View File

@ -26,6 +26,7 @@ import { ConnectionTransport, SlowMoTransport } from '../transport';
import { ConnectionEvents, FFConnection, FFSessionEvents } from './ffConnection';
import { FFPage } from './ffPage';
import * as platform from '../platform';
import { Protocol } from './protocol';
export type FFConnectOptions = {
slowMo?: number,
@ -124,12 +125,14 @@ export class FFBrowser extends platform.EventEmitter implements Browser {
return Array.from(this._targets.values());
}
async _onTargetCreated({targetId, url, browserContextId, openerId, type}) {
const context = browserContextId ? this._contexts.get(browserContextId) : this._defaultContext;
async _onTargetCreated(payload: Protocol.Target.targetCreatedPayload) {
const {targetId, url, browserContextId, openerId, type} = payload;
const context = browserContextId ? this._contexts.get(browserContextId)! : this._defaultContext;
const target = new Target(this._connection, this, context, targetId, type, url, openerId);
this._targets.set(targetId, target);
if (target.opener() && target.opener()._pagePromise) {
const openerPage = await target.opener()._pagePromise;
const opener = target.opener();
if (opener && opener._pagePromise) {
const openerPage = await opener._pagePromise;
if (openerPage.listenerCount(Events.Page.Popup)) {
const popupPage = await target.page();
openerPage.emit(Events.Page.Popup, popupPage);
@ -137,14 +140,16 @@ export class FFBrowser extends platform.EventEmitter implements Browser {
}
}
_onTargetDestroyed({targetId}) {
const target = this._targets.get(targetId);
_onTargetDestroyed(payload: Protocol.Target.targetDestroyedPayload) {
const {targetId} = payload;
const target = this._targets.get(targetId)!;
this._targets.delete(targetId);
target._didClose();
}
_onTargetInfoChanged({targetId, url}) {
const target = this._targets.get(targetId);
_onTargetInfoChanged(payload: Protocol.Target.targetInfoChangedPayload) {
const {targetId, url} = payload;
const target = this._targets.get(targetId)!;
target._url = url;
}
@ -168,14 +173,14 @@ export class FFBrowser extends platform.EventEmitter implements Browser {
const {targetId} = await this._connection.send('Target.newPage', {
browserContextId: browserContextId || undefined
});
const target = this._targets.get(targetId);
const target = this._targets.get(targetId)!;
return target.page();
},
close: async (): Promise<void> => {
assert(browserContextId, 'Non-incognito profiles cannot be closed!');
await this._connection.send('Target.removeBrowserContext', { browserContextId });
this._contexts.delete(browserContextId);
await this._connection.send('Target.removeBrowserContext', { browserContextId: browserContextId! });
this._contexts.delete(browserContextId!);
},
cookies: async (): Promise<network.NetworkCookie[]> => {
@ -196,7 +201,7 @@ export class FFBrowser extends platform.EventEmitter implements Browser {
},
setPermissions: async (origin: string, permissions: string[]): Promise<void> => {
const webPermissionToProtocol = new Map([
const webPermissionToProtocol = new Map<string, 'geo' | 'microphone' | 'camera' | 'desktop-notifications'>([
['geolocation', 'geo'],
['microphone', 'microphone'],
['camera', 'camera'],
@ -232,7 +237,7 @@ class Target {
private readonly _targetId: string;
private readonly _type: 'page' | 'browser';
_url: string;
private readonly _openerId: string;
private readonly _openerId: string | undefined;
constructor(connection: any, browser: FFBrowser, context: BrowserContext, targetId: string, type: 'page' | 'browser', url: string, openerId: string | undefined) {
this._browser = browser;
@ -250,7 +255,7 @@ class Target {
}
opener(): Target | null {
return this._openerId ? this._browser._targets.get(this._openerId) : null;
return this._openerId ? this._browser._targets.get(this._openerId)! : null;
}
type(): 'page' | 'browser' {
@ -266,7 +271,9 @@ class Target {
}
page(): Promise<Page> {
if (this._type === 'page' && !this._pagePromise) {
if (this._type !== 'page')
throw new Error(`Cannot create page for "${this._type}" target`);
if (!this._pagePromise) {
this._pagePromise = new Promise(async f => {
const session = await this._connection.createSession(this._targetId);
this._ffPage = new FFPage(session, this._context);
@ -291,5 +298,5 @@ export async function createTransport(options: FFConnectOptions): Promise<Connec
transport = options.transport;
else if (options.browserWSEndpoint)
transport = await platform.createWebSocketTransport(options.browserWSEndpoint);
return SlowMoTransport.wrap(transport, options.slowMo);
return SlowMoTransport.wrap(transport!, options.slowMo);
}

View File

@ -33,6 +33,12 @@ export class FFConnection extends platform.EventEmitter {
private _sessions: Map<string, FFSession>;
_closed: boolean;
on: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
addListener: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
off: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
removeListener: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
once: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
constructor(transport: ConnectionTransport) {
super();
this._transport = transport;
@ -43,17 +49,26 @@ export class FFConnection extends platform.EventEmitter {
this._transport.onclose = this._onClose.bind(this);
this._sessions = new Map();
this._closed = false;
this.on = super.on;
this.addListener = super.addListener;
this.off = super.removeListener;
this.removeListener = super.removeListener;
this.once = super.once;
}
static fromSession(session: FFSession): FFConnection {
return session._connection;
return session._connection!;
}
session(sessionId: string): FFSession | null {
return this._sessions.get(sessionId) || null;
}
send(method: string, params: object | undefined = {}): Promise<any> {
send<T extends keyof Protocol.CommandParameters>(
method: T,
params?: Protocol.CommandParameters[T]
): Promise<Protocol.CommandReturnValues[T]> {
const id = this._rawSend({method, params});
return new Promise((resolve, reject) => {
this._callbacks.set(id, {resolve, reject, error: new Error(), method});
@ -105,8 +120,8 @@ export class FFConnection extends platform.EventEmitter {
if (this._closed)
return;
this._closed = true;
this._transport.onmessage = null;
this._transport.onclose = null;
this._transport.onmessage = undefined;
this._transport.onclose = undefined;
for (const callback of this._callbacks.values())
callback.reject(rewriteError(callback.error, `Protocol error (${callback.method}): Target closed.`));
this._callbacks.clear();
@ -123,7 +138,7 @@ export class FFConnection extends platform.EventEmitter {
async createSession(targetId: string): Promise<FFSession> {
const {sessionId} = await this.send('Target.attachToTarget', {targetId});
return this._sessions.get(sessionId);
return this._sessions.get(sessionId)!;
}
}
@ -132,7 +147,7 @@ export const FFSessionEvents = {
};
export class FFSession extends platform.EventEmitter {
_connection: FFConnection;
_connection: FFConnection | null;
private _callbacks: Map<number, {resolve: Function, reject: Function, error: Error, method: string}>;
private _targetType: string;
private _sessionId: string;
@ -148,6 +163,12 @@ export class FFSession 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<T extends keyof Protocol.CommandParameters>(
@ -164,7 +185,7 @@ export class FFSession extends platform.EventEmitter {
_onMessage(object: { id?: number; method: string; params: object; 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));
@ -176,12 +197,6 @@ export class FFSession extends platform.EventEmitter {
}
}
async detach() {
if (!this._connection)
throw new Error(`Session already detached. Most likely the ${this._targetType} has been closed.`);
await this._connection.send('Target.detachFromTarget', {sessionId: this._sessionId});
}
_onClosed() {
for (const callback of this._callbacks.values())
callback.reject(rewriteError(callback.error, `Protocol error (${callback.method}): Target closed.`));

View File

@ -105,7 +105,7 @@ export class FFExecutionContext implements js.ExecutionContextDelegate {
checkException(payload.exceptionDetails);
return context._createHandle(payload.result);
function rewriteError(error) : never {
function rewriteError(error: Error) : never {
if (error.message.includes('Failed to find execution context with id') || error.message.includes('Execution context was destroyed!'))
throw new Error('Execution context was destroyed, most likely because of a navigation.');
throw error;
@ -146,10 +146,10 @@ export class FFExecutionContext implements js.ExecutionContextDelegate {
const simpleValue = await this._session.send('Runtime.callFunction', {
executionContextId: this._executionContextId,
returnByValue: true,
functionDeclaration: (e => e).toString(),
functionDeclaration: ((e: any) => e).toString(),
args: [this._toCallArgument(payload)],
});
return deserializeValue(simpleValue.result);
return deserializeValue(simpleValue.result!);
}
handleToString(handle: js.JSHandle, includeType: boolean): string {

View File

@ -38,6 +38,7 @@ function toButtonNumber(button: input.Button): number {
return 1;
if (button === 'right')
return 2;
return 0;
}
function toButtonsMask(buttons: Set<input.Button>): number {

View File

@ -21,6 +21,7 @@ import { Page } from '../page';
import * as network from '../network';
import * as frames from '../frames';
import * as platform from '../platform';
import { Protocol } from './protocol';
export class FFNetworkManager {
private _session: FFSession;
@ -46,11 +47,11 @@ export class FFNetworkManager {
helper.removeEventListeners(this._eventListeners);
}
async setRequestInterception(enabled) {
async setRequestInterception(enabled: boolean) {
await this._session.send('Network.setRequestInterception', {enabled});
}
_onRequestWillBeSent(event) {
_onRequestWillBeSent(event: Protocol.Network.requestWillBeSentPayload) {
const redirected = event.redirectedFrom ? this._requests.get(event.redirectedFrom) : null;
const frame = redirected ? redirected.request.frame() : (event.frameId ? this._page._frameManager.frame(event.frameId) : null);
if (!frame)
@ -66,11 +67,11 @@ export class FFNetworkManager {
this._page._frameManager.requestStarted(request.request);
}
_onResponseReceived(event) {
_onResponseReceived(event: Protocol.Network.responseReceivedPayload) {
const request = this._requests.get(event.requestId);
if (!request)
return;
const remoteAddress: network.RemoteAddress = { ip: event.remoteIPAddress, port: event.remotePort };
const remoteAddress: network.RemoteAddress = { ip: event.remoteIPAddress || '', port: event.remotePort || 0 };
const getResponseBody = async () => {
const response = await this._session.send('Network.getResponseBody', {
requestId: request._id
@ -86,11 +87,11 @@ export class FFNetworkManager {
this._page._frameManager.requestReceivedResponse(response);
}
_onRequestFinished(event) {
_onRequestFinished(event: Protocol.Network.requestFinishedPayload) {
const request = this._requests.get(event.requestId);
if (!request)
return;
const response = request.request.response();
const response = request.request.response()!;
// Keep redirected requests in the map for future reference in redirectChain.
const isRedirected = response.status() >= 300 && response.status() <= 399;
if (isRedirected) {
@ -102,19 +103,20 @@ export class FFNetworkManager {
this._page._frameManager.requestFinished(request.request);
}
_onRequestFailed(event) {
_onRequestFailed(event: Protocol.Network.requestFailedPayload) {
const request = this._requests.get(event.requestId);
if (!request)
return;
this._requests.delete(request._id);
if (request.request.response())
request.request.response()._requestFinished();
const response = request.request.response();
if (response)
response._requestFinished();
request.request._setFailureText(event.errorCode);
this._page._frameManager.requestFailed(request.request, event.errorCode === 'NS_BINDING_ABORTED');
}
}
const causeToResourceType = {
const causeToResourceType: {[key: string]: string} = {
TYPE_INVALID: 'other',
TYPE_OTHER: 'other',
TYPE_SCRIPT: 'script',
@ -145,7 +147,7 @@ class InterceptableRequest implements network.RequestDelegate {
_id: string;
private _session: FFSession;
constructor(session: FFSession, frame: frames.Frame, redirectChain: network.Request[], payload: any) {
constructor(session: FFSession, frame: frames.Frame, redirectChain: network.Request[], payload: Protocol.Network.requestWillBeSentPayload) {
this._id = payload.requestId;
this._session = session;

View File

@ -87,7 +87,8 @@ export class FFPage implements PageDelegate {
await Promise.all(promises);
}
_onExecutionContextCreated({executionContextId, auxData}) {
_onExecutionContextCreated(payload: Protocol.Runtime.executionContextCreatedPayload) {
const {executionContextId, auxData} = payload;
const frame = this._page._frameManager.frame(auxData ? auxData.frameId : null);
if (!frame)
return;
@ -98,7 +99,8 @@ export class FFPage implements PageDelegate {
this._contextIdToContext.set(executionContextId, context);
}
_onExecutionContextDestroyed({executionContextId}) {
_onExecutionContextDestroyed(payload: Protocol.Runtime.executionContextDestroyedPayload) {
const {executionContextId} = payload;
const context = this._contextIdToContext.get(executionContextId);
if (!context)
return;
@ -110,7 +112,7 @@ export class FFPage implements PageDelegate {
}
_onNavigationAborted(params: Protocol.Page.navigationAbortedPayload) {
const frame = this._page._frameManager.frame(params.frameId);
const frame = this._page._frameManager.frame(params.frameId)!;
for (const watcher of this._page._frameManager._lifecycleWatchers)
watcher._onAbortedNewDocumentNavigation(frame, params.navigationId, params.errorText);
}
@ -131,7 +133,8 @@ export class FFPage implements PageDelegate {
this._page._frameManager.frameDetached(params.frameId);
}
_onEventFired({frameId, name}) {
_onEventFired(payload: Protocol.Page.eventFiredPayload) {
const {frameId, name} = payload;
if (name === 'load')
this._page._frameManager.frameLifecycleEvent(frameId, 'load');
if (name === 'DOMContentLoaded')
@ -144,8 +147,9 @@ export class FFPage implements PageDelegate {
this._page.emit(Events.Page.PageError, error);
}
_onConsole({type, args, executionContextId, location}) {
const context = this._contextIdToContext.get(executionContextId);
_onConsole(payload: Protocol.Runtime.consolePayload) {
const {type, args, executionContextId, location} = payload;
const context = this._contextIdToContext.get(executionContextId)!;
this._page._addConsoleMessage(type, args.map(arg => context._createHandle(arg)), location);
}
@ -160,12 +164,13 @@ export class FFPage implements PageDelegate {
}
_onBindingCalled(event: Protocol.Page.bindingCalledPayload) {
const context = this._contextIdToContext.get(event.executionContextId);
const context = this._contextIdToContext.get(event.executionContextId)!;
this._page._onBindingCalled(event.payload, context);
}
async _onFileChooserOpened({executionContextId, element}) {
const context = this._contextIdToContext.get(executionContextId);
async _onFileChooserOpened(payload: Protocol.Page.fileChooserOpenedPayload) {
const {executionContextId, element} = payload;
const context = this._contextIdToContext.get(executionContextId)!;
const handle = context._createHandle(element).asElement()!;
this._page._onFileChooserOpened(handle);
}
@ -184,7 +189,7 @@ export class FFPage implements PageDelegate {
async navigateFrame(frame: frames.Frame, url: string, referer: string | undefined): Promise<frames.GotoResult> {
const response = await this._session.send('Page.navigate', { url, referer, frameId: frame._id });
return { newDocumentId: response.navigationId, isSameDocument: !response.navigationId };
return { newDocumentId: response.navigationId || undefined, isSameDocument: !response.navigationId };
}
needsLifecycleResetOnSetContent(): boolean {
@ -292,7 +297,7 @@ export class FFPage implements PageDelegate {
async getContentFrame(handle: dom.ElementHandle): Promise<frames.Frame | null> {
const { frameId } = await this._session.send('Page.contentFrame', {
frameId: handle._context.frame._id,
objectId: toRemoteObject(handle).objectId,
objectId: toRemoteObject(handle).objectId!,
});
if (!frameId)
return null;
@ -329,7 +334,7 @@ export class FFPage implements PageDelegate {
async getContentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null> {
const result = await this._session.send('Page.getContentQuads', {
frameId: handle._context.frame._id,
objectId: toRemoteObject(handle).objectId,
objectId: toRemoteObject(handle).objectId!,
}).catch(debugError);
if (!result)
return null;
@ -340,7 +345,7 @@ export class FFPage implements PageDelegate {
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> {
await handle.evaluate(dom.setFileInputFunction, files);
}

View File

@ -69,7 +69,7 @@ class Helper {
const value = target[key];
if (typeof key === 'string' && key.startsWith('_') || typeof value !== 'function')
return value;
return function(...args: any) {
return function(this: any, ...args: any) {
if (args.length)
log(`${className}.${key} %o`, args);
else

View File

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

View File

@ -108,7 +108,7 @@ export class Screenshotter {
const viewport = this._page.viewport();
if (!this._page._delegate.canScreenshotOutsideViewport()) {
if (!viewport) {
viewportSize = await this._page.evaluate(() => {
const maybeViewportSize = await this._page.evaluate(() => {
if (!document.body || !document.documentElement)
return;
return {
@ -116,8 +116,9 @@ export class Screenshotter {
height: Math.max(document.body.offsetHeight, document.documentElement.offsetHeight),
};
});
if (!viewportSize)
if (!maybeViewportSize)
throw new Error(kScreenshotDuringNavigationError);
viewportSize = maybeViewportSize!;
} else {
viewportSize = viewport;
}
@ -137,7 +138,7 @@ export class Screenshotter {
if (!overridenViewport)
rewrittenOptions.clip = boundingBox;
const result = await this._screenshot(format, rewrittenOptions, overridenViewport || viewport);
const result = await this._screenshot(format, rewrittenOptions, (overridenViewport || viewport)!);
if (overridenViewport) {
if (viewport)

View File

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

View File

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

View File

@ -101,12 +101,8 @@ class WKAXNode implements accessibility.AXNode {
role: this._payload.role,
name: this._payload.name || '',
};
type AXPropertyOfType<Type> = {
[Key in keyof Protocol.Page.AXNode]:
Protocol.Page.AXNode[Key] extends Type ? Key : never
}[keyof Protocol.Page.AXNode];
const userStringProperties: AXPropertyOfType<string>[] = [
const userStringProperties: string[] = [
'value',
'description',
'keyshortcuts',
@ -116,10 +112,10 @@ class WKAXNode implements accessibility.AXNode {
for (const userStringProperty of userStringProperties) {
if (!(userStringProperty in this._payload))
continue;
node[userStringProperty] = this._payload[userStringProperty];
(node as any)[userStringProperty] = (this._payload as any)[userStringProperty];
}
const booleanProperties: AXPropertyOfType<boolean>[] = [
const booleanProperties: string[] = [
'disabled',
'expanded',
'focused',
@ -135,10 +131,10 @@ class WKAXNode implements accessibility.AXNode {
// not whether focus is specifically on the root node.
if (booleanProperty === 'focused' && (this._payload.role === 'WebArea' || this._payload.role === 'ScrollArea'))
continue;
const value = this._payload[booleanProperty];
const value = (this._payload as any)[booleanProperty];
if (!value)
continue;
node[booleanProperty] = value;
(node as any)[booleanProperty] = value;
}
const tristateProperties: ('checked'|'pressed')[] = [
@ -151,7 +147,7 @@ class WKAXNode implements accessibility.AXNode {
const value = this._payload[tristateProperty];
node[tristateProperty] = value === 'mixed' ? 'mixed' : value === 'true' ? true : false;
}
const numericalProperties: AXPropertyOfType<number>[] = [
const numericalProperties: string[] = [
'level',
'valuemax',
'valuemin',
@ -159,18 +155,18 @@ class WKAXNode implements accessibility.AXNode {
for (const numericalProperty of numericalProperties) {
if (!(numericalProperty in this._payload))
continue;
node[numericalProperty] = this._payload[numericalProperty];
(node as any)[numericalProperty] = (this._payload as any)[numericalProperty];
}
const tokenProperties: AXPropertyOfType<string>[] = [
const tokenProperties: string[] = [
'autocomplete',
'haspopup',
'invalid',
];
for (const tokenProperty of tokenProperties) {
const value = this._payload[tokenProperty];
const value = (this._payload as any)[tokenProperty];
if (!value || value === 'false')
continue;
node[tokenProperty] = value;
(node as any)[tokenProperty] = value;
}
const orientationIsApplicable = new Set([

View File

@ -6,7 +6,7 @@
"sourceMap": true,
"rootDir": "./src",
"outDir": "./lib",
"strictBindCallApply": true,
"strict": true,
"declaration": true
},
"compileOnSave": true,

View File

@ -9,7 +9,7 @@ async function generateChromiunProtocol(revision) {
const outputPath = path.join(__dirname, '..', '..', 'src', 'chromium', 'protocol.ts');
if (revision.local && fs.existsSync(outputPath))
return;
const playwright = await require('../../chromium');
const playwright = await require('../../index').chromium;
const browserServer = await playwright.launchServer({executablePath: revision.executablePath});
const origin = browserServer.wsEndpoint().match(/ws:\/\/([0-9A-Za-z:\.]*)\//)[1];
const page = await (await browserServer.connect()).defaultContext().newPage();
@ -211,7 +211,7 @@ function firefoxTypeToString(type, indent=' ') {
if (type['$type'] === 'array')
return firefoxTypeToString(type['$items'], indent) + '[]';
if (type['$type'] === 'enum')
return type['$values'].map(v => JSON.stringify(v)).join('|');
return '(' + type['$values'].map(v => JSON.stringify(v)).join('|') + ')';
return type['$type'];
}