From 2540805bf27e7a1e745b8626c7f914b9bb67875d Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Fri, 3 Jul 2020 18:04:08 -0700 Subject: [PATCH] chore(rpc): misc serializer improvements (#2832) --- src/chromium/crNetworkManager.ts | 14 ++------ src/firefox/ffNetworkManager.ts | 14 ++------ src/rpc/channels.ts | 13 ++++---- src/rpc/client/browserContext.ts | 2 +- src/rpc/client/connection.ts | 3 +- src/rpc/client/elementHandle.ts | 4 +-- src/rpc/client/frame.ts | 4 +-- src/rpc/client/input.ts | 2 +- src/rpc/client/jsHandle.ts | 15 +++------ src/rpc/client/network.ts | 7 +--- src/rpc/client/page.ts | 4 ++- src/rpc/serializers.ts | 38 +++++++++++++++++----- src/rpc/server/elementHandlerDispatcher.ts | 4 +-- src/rpc/server/frameDispatcher.ts | 4 +-- src/rpc/server/jsHandleDispatcher.ts | 5 +++ src/rpc/server/networkDispatchers.ts | 5 ++- src/rpc/transport.ts | 6 ++-- src/types.ts | 4 +-- src/webkit/wkInterceptableRequest.ts | 30 +++++------------ 19 files changed, 81 insertions(+), 97 deletions(-) diff --git a/src/chromium/crNetworkManager.ts b/src/chromium/crNetworkManager.ts index 2f7ad5cb49..93a6bd2b0c 100644 --- a/src/chromium/crNetworkManager.ts +++ b/src/chromium/crNetworkManager.ts @@ -362,15 +362,7 @@ class InterceptableRequest implements network.RouteDelegate { } async fulfill(response: types.NormalizedFulfillResponse) { - const responseBody = response.body && helper.isString(response.body) ? Buffer.from(response.body) : (response.body || null); - - const responseHeaders: { [s: string]: string; } = {}; - for (const header of Object.keys(response.headers)) - responseHeaders[header.toLowerCase()] = response.headers[header]; - if (response.contentType) - responseHeaders['content-type'] = response.contentType; - if (responseBody && !('content-length' in responseHeaders)) - responseHeaders['content-length'] = String(Buffer.byteLength(responseBody)); + const body = response.isBase64 ? response.body : Buffer.from(response.body).toString('base64'); // In certain cases, protocol will return error if the request was already canceled // or the page was closed. We should tolerate these errors. @@ -378,8 +370,8 @@ class InterceptableRequest implements network.RouteDelegate { requestId: this._interceptionId!, responseCode: response.status, responsePhrase: network.STATUS_TEXTS[String(response.status)], - responseHeaders: headersArray(responseHeaders), - body: responseBody ? responseBody.toString('base64') : undefined, + responseHeaders: headersArray(response.headers), + body, }); } diff --git a/src/firefox/ffNetworkManager.ts b/src/firefox/ffNetworkManager.ts index 9b25ea5d1f..a75483a052 100644 --- a/src/firefox/ffNetworkManager.ts +++ b/src/firefox/ffNetworkManager.ts @@ -173,22 +173,14 @@ class InterceptableRequest implements network.RouteDelegate { } async fulfill(response: types.NormalizedFulfillResponse) { - const responseBody = response.body && helper.isString(response.body) ? Buffer.from(response.body) : (response.body || null); - - const responseHeaders: { [s: string]: string; } = {}; - for (const header of Object.keys(response.headers)) - responseHeaders[header.toLowerCase()] = response.headers[header]; - if (response.contentType) - responseHeaders['content-type'] = response.contentType; - if (responseBody && !('content-length' in responseHeaders)) - responseHeaders['content-length'] = String(Buffer.byteLength(responseBody)); + const base64body = response.isBase64 ? response.body : Buffer.from(response.body).toString('base64'); await this._session.sendMayFail('Network.fulfillInterceptedRequest', { requestId: this._id, status: response.status, statusText: network.STATUS_TEXTS[String(response.status)] || '', - headers: headersArray(responseHeaders), - base64body: responseBody ? responseBody.toString('base64') : undefined, + headers: headersArray(response.headers), + base64body, }); } diff --git a/src/rpc/channels.ts b/src/rpc/channels.ts index 01d33c30c3..db071ce35d 100644 --- a/src/rpc/channels.ts +++ b/src/rpc/channels.ts @@ -148,8 +148,8 @@ export type PageInitializer = { export interface FrameChannel extends Channel { - $$eval(params: { selector: string; expression: string, isFunction: boolean, arg: any, isPage?: boolean }): Promise; - $eval(params: { selector: string; expression: string, isFunction: boolean, arg: any, isPage?: boolean }): Promise; + evalOnSelector(params: { selector: string; expression: string, isFunction: boolean, arg: any, isPage?: boolean }): Promise; + evalOnSelectorAll(params: { selector: string; expression: string, isFunction: boolean, arg: any, isPage?: boolean }): Promise; addScriptTag(params: { options: { url?: string | undefined, path?: string | undefined, content?: string | undefined, type?: string | undefined }, isPage?: boolean }): Promise; addStyleTag(params: { options: { url?: string | undefined, path?: string | undefined, content?: string | undefined }, isPage?: boolean }): Promise; check(params: { selector: string, options: types.TimeoutOptions & { force?: boolean } & { noWaitAfter?: boolean }, isPage?: boolean }): Promise; @@ -205,6 +205,7 @@ export interface JSHandleChannel extends Channel { evaluateExpression(params: { expression: string, isFunction: boolean, arg: any }): Promise; evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: any}): Promise; getPropertyList(): Promise<{ name: string, value: JSHandleChannel}[]>; + getProperty(params: { name: string }): Promise; jsonValue(): Promise; } export type JSHandleInitializer = { @@ -213,8 +214,8 @@ export type JSHandleInitializer = { export interface ElementHandleChannel extends JSHandleChannel { - $$evalExpression(params: { selector: string; expression: string, isFunction: boolean, arg: any }): Promise; - $evalExpression(params: { selector: string; expression: string, isFunction: boolean, arg: any }): Promise; + evalOnSelector(params: { selector: string; expression: string, isFunction: boolean, arg: any }): Promise; + evalOnSelectorAll(params: { selector: string; expression: string, isFunction: boolean, arg: any }): Promise; boundingBox(): Promise; check(params: { options?: types.TimeoutOptions & { force?: boolean } & { noWaitAfter?: boolean } }): Promise; click(params: { options?: types.PointerActionOptions & types.MouseClickOptions & types.TimeoutOptions & { force?: boolean } & { noWaitAfter?: boolean } }): Promise; @@ -264,8 +265,8 @@ export interface RouteChannel extends Channel { response: { status?: number, headers?: types.Headers, - contentType?: string, - body: Binary, + body: string, + isBase64: boolean, } }): Promise; } diff --git a/src/rpc/client/browserContext.ts b/src/rpc/client/browserContext.ts index 4e58b80fd4..fcfeed4d61 100644 --- a/src/rpc/client/browserContext.ts +++ b/src/rpc/client/browserContext.ts @@ -187,7 +187,7 @@ export class BrowserContext extends ChannelOwner extends JSHandle { async $eval(selector: string, pageFunction: FuncOn, arg: Arg): Promise; async $eval(selector: string, pageFunction: FuncOn, arg?: any): Promise; async $eval(selector: string, pageFunction: FuncOn, arg: Arg): Promise { - return parseResult(await this._elementChannel.$evalExpression({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) })); + return parseResult(await this._elementChannel.evalOnSelector({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) })); } async $$eval(selector: string, pageFunction: FuncOn, arg: Arg): Promise; async $$eval(selector: string, pageFunction: FuncOn, arg?: any): Promise; async $$eval(selector: string, pageFunction: FuncOn, arg: Arg): Promise { - return parseResult(await this._elementChannel.$$evalExpression({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) })); + return parseResult(await this._elementChannel.evalOnSelectorAll({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) })); } } diff --git a/src/rpc/client/frame.ts b/src/rpc/client/frame.ts index f962c6f358..931aa1f0df 100644 --- a/src/rpc/client/frame.ts +++ b/src/rpc/client/frame.ts @@ -105,14 +105,14 @@ export class Frame extends ChannelOwner { async $eval(selector: string, pageFunction: FuncOn, arg?: any): Promise; async $eval(selector: string, pageFunction: FuncOn, arg: Arg): Promise { assertMaxArguments(arguments.length, 3); - return await this._channel.$eval({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg), isPage: this._page!._isPageCall }); + return await this._channel.evalOnSelector({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg), isPage: this._page!._isPageCall }); } async $$eval(selector: string, pageFunction: FuncOn, arg: Arg): Promise; async $$eval(selector: string, pageFunction: FuncOn, arg?: any): Promise; async $$eval(selector: string, pageFunction: FuncOn, arg: Arg): Promise { assertMaxArguments(arguments.length, 3); - return await this._channel.$$eval({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg), isPage: this._page!._isPageCall }); + return await this._channel.evalOnSelectorAll({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg), isPage: this._page!._isPageCall }); } async $$(selector: string): Promise[]> { diff --git a/src/rpc/client/input.ts b/src/rpc/client/input.ts index a64cb97ae6..8519133f46 100644 --- a/src/rpc/client/input.ts +++ b/src/rpc/client/input.ts @@ -37,7 +37,7 @@ export class Keyboard { await this._channel.keyboardInsertText({ text }); } - async type(text: string, options?: { delay?: number }) { + async type(text: string, options: { delay?: number } = {}) { await this._channel.keyboardType({ text, options }); } diff --git a/src/rpc/client/jsHandle.ts b/src/rpc/client/jsHandle.ts index 29fecdd580..1fb3d0c571 100644 --- a/src/rpc/client/jsHandle.ts +++ b/src/rpc/client/jsHandle.ts @@ -62,20 +62,13 @@ export class JSHandle extends ChannelOwner(pageFunction: FuncOn, arg: Arg): Promise>; async evaluateHandle(pageFunction: FuncOn, arg?: any): Promise>; async evaluateHandle(pageFunction: FuncOn, arg: Arg): Promise> { - const handleChannel = await this._channel.evaluateExpressionHandle({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) }); + const handleChannel = await this._channel.evaluateExpressionHandle({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) }); return JSHandle.from(handleChannel) as SmartHandle; } - async getProperty(propertyName: string): Promise { - const objectHandle = await this.evaluateHandle((object: any, propertyName: string) => { - const result: any = {__proto__: null}; - result[propertyName] = object[propertyName]; - return result; - }, propertyName); - const properties = await objectHandle.getProperties(); - const result = properties.get(propertyName)!; - objectHandle.dispose(); - return result; + async getProperty(name: string): Promise { + const handleChannel = await this._channel.getProperty({ name }); + return JSHandle.from(handleChannel); } async getProperties(): Promise> { diff --git a/src/rpc/client/network.ts b/src/rpc/client/network.ts index 51fbb92fab..5f2e22e94a 100644 --- a/src/rpc/client/network.ts +++ b/src/rpc/client/network.ts @@ -152,12 +152,7 @@ export class Route extends ChannelOwner { async fulfill(response: types.FulfillResponse & { path?: string }) { const normalized = await normalizeFulfillParameters(response); - await this._channel.fulfill({ response: { - status: normalized.status, - headers: normalized.headers, - contentType: normalized.contentType, - body: (typeof normalized.body === 'string' ? Buffer.from(normalized.body) : normalized.body).toString('base64') - }}); + await this._channel.fulfill({ response: normalized }); } async continue(overrides: { method?: string; headers?: types.Headers; postData?: string } = {}) { diff --git a/src/rpc/client/page.ts b/src/rpc/client/page.ts index cef8bbf44e..ebc5a087b9 100644 --- a/src/rpc/client/page.ts +++ b/src/rpc/client/page.ts @@ -144,8 +144,10 @@ export class Page extends ChannelOwner { async _onBinding(bindingCall: BindingCall) { const func = this._bindings.get(bindingCall._initializer.name); - if (func) + if (func) { bindingCall.call(func); + return; + } this._browserContext!._onBinding(bindingCall); } diff --git a/src/rpc/serializers.ts b/src/rpc/serializers.ts index add2185f40..68185a1a02 100644 --- a/src/rpc/serializers.ts +++ b/src/rpc/serializers.ts @@ -20,6 +20,7 @@ import * as path from 'path'; import * as util from 'util'; import { TimeoutError } from '../errors'; import * as types from '../types'; +import { helper } from '../helper'; export function serializeError(e: any): types.Error { @@ -64,18 +65,37 @@ export async function normalizeFilePayloads(files: string | types.FilePayload | } export async function normalizeFulfillParameters(params: types.FulfillResponse & { path?: string }): Promise { + let body = ''; + let isBase64 = false; + let length = 0; if (params.path) { - return { - status: params.status || 200, - headers: params.headers || {}, - contentType: mime.getType(params.path) || 'application/octet-stream', - body: await util.promisify(fs.readFile)(params.path) - }; + const buffer = await util.promisify(fs.readFile)(params.path); + body = buffer.toString('base64'); + isBase64 = true; + length = buffer.length; + } else if (helper.isString(params.body)) { + body = params.body; + isBase64 = false; + length = Buffer.byteLength(body); + } else if (params.body) { + body = params.body.toString('base64'); + isBase64 = true; + length = params.body.length; } + const headers: { [s: string]: string; } = {}; + for (const header of Object.keys(params.headers || {})) + headers[header.toLowerCase()] = String(params.headers![header]); + if (params.contentType) + headers['content-type'] = String(params.contentType); + else if (params.path) + headers['content-type'] = mime.getType(params.path) || 'application/octet-stream'; + if (length && !('content-length' in headers)) + headers['content-length'] = String(length); + return { status: params.status || 200, - headers: params.headers || {}, - contentType: params.contentType, - body: params.body || '' + headers, + body, + isBase64 }; } diff --git a/src/rpc/server/elementHandlerDispatcher.ts b/src/rpc/server/elementHandlerDispatcher.ts index b2d6fb74a0..2adc083cf8 100644 --- a/src/rpc/server/elementHandlerDispatcher.ts +++ b/src/rpc/server/elementHandlerDispatcher.ts @@ -139,11 +139,11 @@ export class ElementHandleDispatcher extends JSHandleDispatcher implements Eleme return elements.map(e => new ElementHandleDispatcher(this._scope, e)); } - async $evalExpression(params: { selector: string, expression: string, isFunction: boolean, arg: any }): Promise { + async evalOnSelector(params: { selector: string, expression: string, isFunction: boolean, arg: any }): Promise { return serializeResult(await this._elementHandle._$evalExpression(params.selector, params.expression, params.isFunction, parseArgument(params.arg))); } - async $$evalExpression(params: { selector: string, expression: string, isFunction: boolean, arg: any }): Promise { + async evalOnSelectorAll(params: { selector: string, expression: string, isFunction: boolean, arg: any }): Promise { return serializeResult(await this._elementHandle._$$evalExpression(params.selector, params.expression, params.isFunction, parseArgument(params.arg))); } } diff --git a/src/rpc/server/frameDispatcher.ts b/src/rpc/server/frameDispatcher.ts index d2a5fa6021..b305a264c5 100644 --- a/src/rpc/server/frameDispatcher.ts +++ b/src/rpc/server/frameDispatcher.ts @@ -78,12 +78,12 @@ export class FrameDispatcher extends Dispatcher impleme return target.dispatchEvent(params.selector, params.type, parseArgument(params.eventInit), params.options); } - async $eval(params: { selector: string, expression: string, isFunction: boolean, arg: any, isPage?: boolean }): Promise { + async evalOnSelector(params: { selector: string, expression: string, isFunction: boolean, arg: any, isPage?: boolean }): Promise { const target = params.isPage ? this._frame._page : this._frame; return serializeResult(await target._$evalExpression(params.selector, params.expression, params.isFunction, parseArgument(params.arg))); } - async $$eval(params: { selector: string, expression: string, isFunction: boolean, arg: any, isPage?: boolean }): Promise { + async evalOnSelectorAll(params: { selector: string, expression: string, isFunction: boolean, arg: any, isPage?: boolean }): Promise { const target = params.isPage ? this._frame._page : this._frame; return serializeResult(await target._$$evalExpression(params.selector, params.expression, params.isFunction, parseArgument(params.arg))); } diff --git a/src/rpc/server/jsHandleDispatcher.ts b/src/rpc/server/jsHandleDispatcher.ts index c49c73f080..0f2ed9f186 100644 --- a/src/rpc/server/jsHandleDispatcher.ts +++ b/src/rpc/server/jsHandleDispatcher.ts @@ -38,6 +38,11 @@ export class JSHandleDispatcher extends Dispatcher { + const jsHandle = await this._object.getProperty(params.name); + return createHandle(this._scope, jsHandle); + } + async getPropertyList(): Promise<{ name: string, value: JSHandleChannel }[]> { const map = await this._object.getProperties(); const result = []; diff --git a/src/rpc/server/networkDispatchers.ts b/src/rpc/server/networkDispatchers.ts index f65f0f10e1..607cf2779b 100644 --- a/src/rpc/server/networkDispatchers.ts +++ b/src/rpc/server/networkDispatchers.ts @@ -84,13 +84,12 @@ export class RouteDispatcher extends Dispatcher impleme await this._object.continue(params.overrides); } - async fulfill(params: { response: { status?: number, headers?: types.Headers, contentType?: string, body: Binary } }): Promise { + async fulfill(params: { response: { status?: number, headers?: types.Headers, contentType?: string, body: string, isBase64: boolean } }): Promise { const { response } = params; await this._object.fulfill({ status: response.status, headers: response.headers, - contentType: response.contentType, - body: Buffer.from(response.body, 'base64'), + body: response.isBase64 ? Buffer.from(response.body, 'base64') : response.body, }); } diff --git a/src/rpc/transport.ts b/src/rpc/transport.ts index c28557099c..1835d1d111 100644 --- a/src/rpc/transport.ts +++ b/src/rpc/transport.ts @@ -33,10 +33,10 @@ export class Transport { this.onclose = undefined; } - send(message: any) { + send(message: string) { if (this._closed) throw new Error('Pipe has been closed'); - const data = Buffer.from(JSON.stringify(message), 'utf-8'); + const data = Buffer.from(message, 'utf-8'); const dataLength = Buffer.alloc(4); dataLength.writeUInt32LE(data.length, 0); this._pipeWrite.write(dataLength); @@ -70,7 +70,7 @@ export class Transport { this._bytesLeft = 0; this._waitForNextTask(() => { if (this.onmessage) - this.onmessage.call(null, JSON.parse(message.toString('utf-8'))); + this.onmessage(message.toString('utf-8')); }); } } diff --git a/src/types.ts b/src/types.ts index 48e366f3b3..03813af2b3 100644 --- a/src/types.ts +++ b/src/types.ts @@ -199,8 +199,8 @@ export type FulfillResponse = { export type NormalizedFulfillResponse = { status: number, headers: Headers, - contentType?: string, - body: string | Buffer, + body: string, + isBase64: boolean, }; export type NetworkCookie = { diff --git a/src/webkit/wkInterceptableRequest.ts b/src/webkit/wkInterceptableRequest.ts index f0fc3f7fcc..4e94768ed5 100644 --- a/src/webkit/wkInterceptableRequest.ts +++ b/src/webkit/wkInterceptableRequest.ts @@ -16,7 +16,7 @@ */ import * as frames from '../frames'; -import { assert, helper } from '../helper'; +import { assert } from '../helper'; import * as network from '../network'; import * as types from '../types'; import { Protocol } from './protocol'; @@ -69,34 +69,20 @@ export class WKInterceptableRequest implements network.RouteDelegate { async fulfill(response: types.NormalizedFulfillResponse) { await this._interceptedPromise; - const base64Encoded = !!response.body && !helper.isString(response.body); - const responseBody = response.body ? (base64Encoded ? response.body.toString('base64') : response.body as string) : ''; - - const responseHeaders: { [s: string]: string; } = {}; - for (const header of Object.keys(response.headers)) - responseHeaders[header.toLowerCase()] = String(response.headers[header]); - let mimeType = base64Encoded ? 'application/octet-stream' : 'text/plain'; - if (response.contentType) { - responseHeaders['content-type'] = response.contentType; - const index = response.contentType.indexOf(';'); - if (index !== -1) - mimeType = response.contentType.substring(0, index).trimEnd(); - else - mimeType = response.contentType.trim(); - } - if (responseBody && !('content-length' in responseHeaders)) - responseHeaders['content-length'] = String(Buffer.byteLength(responseBody)); - // In certain cases, protocol will return error if the request was already canceled // or the page was closed. We should tolerate these errors. + let mimeType = response.isBase64 ? 'application/octet-stream' : 'text/plain'; + const contentType = response.headers['content-type']; + if (contentType) + mimeType = contentType.split(';')[0].trim(); await this._session.sendMayFail('Network.interceptRequestWithResponse', { requestId: this._requestId, status: response.status, statusText: network.STATUS_TEXTS[String(response.status)], mimeType, - headers: responseHeaders, - base64Encoded, - content: responseBody + headers: response.headers, + base64Encoded: response.isBase64, + content: response.body }); }