diff --git a/src/rpc/channels.ts b/src/rpc/channels.ts index 4e592985e2..1e3054deb9 100644 --- a/src/rpc/channels.ts +++ b/src/rpc/channels.ts @@ -45,16 +45,11 @@ export type SerializedArgument = { handles: Channel[], }; -export type AXNodeValue = string | number; - -export type AXNodeChecked = boolean | 'mixed'; - -export type AXNodePressed = boolean | 'mixed'; - export type AXNode = { role: string, name: string, - value?: AXNodeValue, + valueString?: string, + valueNumber?: number, description?: string, keyshortcuts?: string, roledescription?: string, @@ -68,8 +63,8 @@ export type AXNode = { readonly?: boolean, required?: boolean, selected?: boolean, - checked?: AXNodeChecked, - pressed?: AXNodePressed, + checked?: 'checked' | 'unchecked' | 'mixed', + pressed?: 'pressed' | 'released' | 'mixed', level?: number, valuemin?: number, valuemax?: number, @@ -80,15 +75,6 @@ export type AXNode = { children?: AXNode[], }; -export type WaitForFunctionPolling = number | 'raf'; - -export type Viewport = { - width: number, - height: number, -}; - -export type ViewportOrNull = null | Viewport; - export type SerializedError = { error?: { message: string, @@ -241,7 +227,11 @@ export type BrowserTypeLaunchPersistentContextParams = { }, downloadsPath?: string, slowMo?: number, - viewport?: ViewportOrNull, + noDefaultViewport?: boolean, + viewport?: { + width: number, + height: number, + }, ignoreHTTPSErrors?: boolean, javaScriptEnabled?: boolean, bypassCSP?: boolean, @@ -303,7 +293,11 @@ export type BrowserCloseEvent = {}; export type BrowserCloseParams = {}; export type BrowserCloseResult = void; export type BrowserNewContextParams = { - viewport?: ViewportOrNull, + noDefaultViewport?: boolean, + viewport?: { + width: number, + height: number, + }, ignoreHTTPSErrors?: boolean, javaScriptEnabled?: boolean, bypassCSP?: boolean, @@ -1094,7 +1088,7 @@ export type FrameWaitForFunctionParams = { isFunction: boolean, arg: SerializedArgument, timeout?: number, - polling?: WaitForFunctionPolling, + pollingInterval?: number, }; export type FrameWaitForFunctionResult = { handle: JSHandleChannel, diff --git a/src/rpc/client/accessibility.ts b/src/rpc/client/accessibility.ts index ad69d46aec..773ab4e1d3 100644 --- a/src/rpc/client/accessibility.ts +++ b/src/rpc/client/accessibility.ts @@ -18,6 +18,7 @@ import { PageChannel } from '../channels'; import { ElementHandle } from './elementHandle'; import * as types from '../../types'; +import { axNodeFromProtocol } from '../serializers'; export class Accessibility { private _channel: PageChannel; @@ -29,6 +30,6 @@ export class Accessibility { async snapshot(options: { interestingOnly?: boolean; root?: ElementHandle } = {}): Promise { const root = options.root ? options.root._elementChannel : undefined; const result = await this._channel.accessibilitySnapshot({ interestingOnly: options.interestingOnly, root }); - return result.rootAXNode || null; + return result.rootAXNode ? axNodeFromProtocol(result.rootAXNode) : null; } } diff --git a/src/rpc/client/browser.ts b/src/rpc/client/browser.ts index 6f6decf484..4bf9db4a5c 100644 --- a/src/rpc/client/browser.ts +++ b/src/rpc/client/browser.ts @@ -57,6 +57,8 @@ export class Browser extends ChannelOwner { return this._wrapApiCall('browser.newContext', async () => { const contextOptions: BrowserNewContextParams = { ...options, + viewport: options.viewport === null ? undefined : options.viewport, + noDefaultViewport: options.viewport === null, extraHTTPHeaders: options.extraHTTPHeaders ? headersObjectToArray(options.extraHTTPHeaders) : undefined, }; const context = BrowserContext.from((await this._channel.newContext(contextOptions)).context); diff --git a/src/rpc/client/browserType.ts b/src/rpc/client/browserType.ts index caa3198980..b0d026be52 100644 --- a/src/rpc/client/browserType.ts +++ b/src/rpc/client/browserType.ts @@ -77,6 +77,8 @@ export class BrowserType extends ChannelOwner { const persistentOptions: BrowserTypeLaunchPersistentContextParams = { ...options, + viewport: options.viewport === null ? undefined : options.viewport, + noDefaultViewport: options.viewport === null, ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : undefined, ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs), env: options.env ? envObjectToArray(options.env) : undefined, diff --git a/src/rpc/client/frame.ts b/src/rpc/client/frame.ts index ea1792b56f..b3f2a4ae8d 100644 --- a/src/rpc/client/frame.ts +++ b/src/rpc/client/frame.ts @@ -380,7 +380,13 @@ export class Frame extends ChannelOwner { async waitForFunction(pageFunction: Func1, arg?: any, options?: types.WaitForFunctionOptions): Promise>; async waitForFunction(pageFunction: Func1, arg: Arg, options: types.WaitForFunctionOptions = {}): Promise> { return this._wrapApiCall(this._apiName('waitForFunction'), async () => { - const result = await this._channel.waitForFunction({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg), ...options }); + const result = await this._channel.waitForFunction({ + ...options, + pollingInterval: options.polling === 'raf' ? undefined : options.polling, + expression: String(pageFunction), + isFunction: typeof pageFunction === 'function', + arg: serializeArgument(arg), + }); return JSHandle.from(result.handle) as SmartHandle; }); } diff --git a/src/rpc/protocol.pdl b/src/rpc/protocol.pdl index 13b95a107a..665c858de1 100644 --- a/src/rpc/protocol.pdl +++ b/src/rpc/protocol.pdl @@ -41,24 +41,11 @@ type SerializedArgument value: SerializedValue handles: Channel[] -union AXNodeValue - string - number - -union AXNodeChecked - boolean - enum - mixed - -union AXNodePressed - boolean - enum - mixed - type AXNode role: string name: string - value?: AXNodeValue + valueString?: string + valueNumber?: number description?: string keyshortcuts?: string roledescription?: string @@ -72,8 +59,14 @@ type AXNode readonly?: boolean required?: boolean selected?: boolean - checked?: AXNodeChecked - pressed?: AXNodePressed + checked?: enum + checked + unchecked + mixed + pressed?: enum + pressed + released + mixed level?: number valuemin?: number valuemax?: number @@ -83,19 +76,6 @@ type AXNode orientation?: string children?: AXNode[] -union WaitForFunctionPolling - number - enum - raf - -type Viewport - width: number - height: number - -union ViewportOrNull - null - Viewport - type SerializedError error?: object message: string @@ -222,7 +202,10 @@ interface BrowserType password?: string downloadsPath?: string slowMo?: number - viewport?: ViewportOrNull + noDefaultViewport?: boolean + viewport?: object + width: number + height: number ignoreHTTPSErrors?: boolean javaScriptEnabled?: boolean bypassCSP?: boolean @@ -270,7 +253,10 @@ interface Browser command newContext parameters - viewport?: ViewportOrNull + noDefaultViewport?: boolean + viewport?: object + width: number + height: number ignoreHTTPSErrors?: boolean javaScriptEnabled?: boolean bypassCSP?: boolean @@ -1016,7 +1002,8 @@ interface Frame isFunction: boolean arg: SerializedArgument timeout?: number - polling?: WaitForFunctionPolling + # When present, polls on interval. Otherwise, polls on raf. + pollingInterval?: number returns handle: JSHandle diff --git a/src/rpc/serializers.ts b/src/rpc/serializers.ts index 6e738c2845..e4349b6840 100644 --- a/src/rpc/serializers.ts +++ b/src/rpc/serializers.ts @@ -21,7 +21,7 @@ import * as util from 'util'; import { TimeoutError } from '../errors'; import * as types from '../types'; import { helper, assert } from '../helper'; -import { SerializedError } from './channels'; +import { SerializedError, AXNode } from './channels'; import { serializeAsCallArgument, parseEvaluationResultValue } from '../common/utilityScriptSerializers'; export function serializeError(e: any): SerializedError { @@ -143,3 +143,29 @@ export function envArrayToObject(env: types.EnvArray): types.Env { result[name] = value; return result; } + +export function axNodeToProtocol(axNode: types.SerializedAXNode): AXNode { + const result: AXNode = { + ...axNode, + valueNumber: typeof axNode.value === 'number' ? axNode.value : undefined, + valueString: typeof axNode.value === 'string' ? axNode.value : undefined, + checked: axNode.checked === true ? 'checked' : axNode.checked === false ? 'unchecked' : axNode.checked, + pressed: axNode.pressed === true ? 'pressed' : axNode.pressed === false ? 'released' : axNode.pressed, + children: axNode.children ? axNode.children.map(axNodeToProtocol) : undefined, + }; + delete (result as any).value; + return result; +} + +export function axNodeFromProtocol(axNode: AXNode): types.SerializedAXNode { + const result: types.SerializedAXNode = { + ...axNode, + value: axNode.valueNumber !== undefined ? axNode.valueNumber : axNode.valueString, + checked: axNode.checked === 'checked' ? true : axNode.checked === 'unchecked' ? false : axNode.checked, + pressed: axNode.pressed === 'pressed' ? true : axNode.pressed === 'released' ? false : axNode.pressed, + children: axNode.children ? axNode.children.map(axNodeFromProtocol) : undefined, + }; + delete (result as any).valueNumber; + delete (result as any).valueString; + return result; +} diff --git a/src/rpc/server/browserDispatcher.ts b/src/rpc/server/browserDispatcher.ts index a5f8aeb06a..caf7321092 100644 --- a/src/rpc/server/browserDispatcher.ts +++ b/src/rpc/server/browserDispatcher.ts @@ -37,6 +37,7 @@ export class BrowserDispatcher extends Dispatcher i async newContext(params: BrowserNewContextParams): Promise<{ context: BrowserContextChannel }> { const options = { ...params, + viewport: params.viewport || (params.noDefaultViewport ? null : undefined), extraHTTPHeaders: params.extraHTTPHeaders ? headersArrayToObject(params.extraHTTPHeaders) : undefined, }; return { context: new BrowserContextDispatcher(this._scope, await this._object.newContext(options) as BrowserContextBase) }; diff --git a/src/rpc/server/browserTypeDispatcher.ts b/src/rpc/server/browserTypeDispatcher.ts index 849183c1ad..9737c38594 100644 --- a/src/rpc/server/browserTypeDispatcher.ts +++ b/src/rpc/server/browserTypeDispatcher.ts @@ -46,6 +46,7 @@ export class BrowserTypeDispatcher extends Dispatcher { const options = { ...params, + viewport: params.viewport || (params.noDefaultViewport ? null : undefined), ignoreDefaultArgs: params.ignoreAllDefaultArgs ? true : params.ignoreDefaultArgs, env: params.env ? envArrayToObject(params.env) : undefined, extraHTTPHeaders: params.extraHTTPHeaders ? headersArrayToObject(params.extraHTTPHeaders) : undefined, diff --git a/src/rpc/server/frameDispatcher.ts b/src/rpc/server/frameDispatcher.ts index 601eb1997e..3a6d40f2e5 100644 --- a/src/rpc/server/frameDispatcher.ts +++ b/src/rpc/server/frameDispatcher.ts @@ -16,7 +16,7 @@ import { Frame, kAddLifecycleEvent, kRemoveLifecycleEvent, kNavigationEvent, NavigationEvent } from '../../frames'; import * as types from '../../types'; -import { ElementHandleChannel, FrameChannel, FrameInitializer, JSHandleChannel, ResponseChannel, SerializedArgument } from '../channels'; +import { ElementHandleChannel, FrameChannel, FrameInitializer, JSHandleChannel, ResponseChannel, SerializedArgument, FrameWaitForFunctionParams } from '../channels'; import { Dispatcher, DispatcherScope, lookupNullableDispatcher, existingDispatcher } from './dispatcher'; import { convertSelectOptionValues, ElementHandleDispatcher, createHandle, convertInputFiles } from './elementHandlerDispatcher'; import { parseArgument, serializeResult } from './jsHandleDispatcher'; @@ -176,8 +176,12 @@ export class FrameDispatcher extends Dispatcher impleme await this._frame.uncheck(params.selector, params); } - async waitForFunction(params: { expression: string, isFunction: boolean, arg: SerializedArgument } & types.WaitForFunctionOptions): Promise<{ handle: JSHandleChannel }> { - return { handle: createHandle(this._scope, await this._frame._waitForFunctionExpression(params.expression, params.isFunction, parseArgument(params.arg), params)) }; + async waitForFunction(params: FrameWaitForFunctionParams): Promise<{ handle: JSHandleChannel }> { + const options = { + ...params, + polling: params.pollingInterval === undefined ? 'raf' as const : params.pollingInterval + }; + return { handle: createHandle(this._scope, await this._frame._waitForFunctionExpression(params.expression, params.isFunction, parseArgument(params.arg), options)) }; } async title(): Promise<{ value: string }> { diff --git a/src/rpc/server/pageDispatcher.ts b/src/rpc/server/pageDispatcher.ts index 186c16b146..a0f68f59b5 100644 --- a/src/rpc/server/pageDispatcher.ts +++ b/src/rpc/server/pageDispatcher.ts @@ -20,9 +20,9 @@ import { Frame } from '../../frames'; import { Request } from '../../network'; import { Page, Worker } from '../../page'; import * as types from '../../types'; -import { BindingCallChannel, BindingCallInitializer, ElementHandleChannel, PageChannel, PageInitializer, ResponseChannel, WorkerInitializer, WorkerChannel, JSHandleChannel, Binary, SerializedArgument, PagePdfParams, SerializedError } from '../channels'; +import { BindingCallChannel, BindingCallInitializer, ElementHandleChannel, PageChannel, PageInitializer, ResponseChannel, WorkerInitializer, WorkerChannel, JSHandleChannel, Binary, SerializedArgument, PagePdfParams, SerializedError, PageAccessibilitySnapshotResult } from '../channels'; import { Dispatcher, DispatcherScope, lookupDispatcher, lookupNullableDispatcher } from './dispatcher'; -import { parseError, serializeError, headersArrayToObject } from '../serializers'; +import { parseError, serializeError, headersArrayToObject, axNodeToProtocol } from '../serializers'; import { ConsoleMessageDispatcher } from './consoleMessageDispatcher'; import { DialogDispatcher } from './dialogDispatcher'; import { DownloadDispatcher } from './downloadDispatcher'; @@ -180,12 +180,12 @@ export class PageDispatcher extends Dispatcher implements await this._page.mouse.click(params.x, params.y, params); } - async accessibilitySnapshot(params: { interestingOnly?: boolean, root?: ElementHandleChannel }): Promise<{ rootAXNode?: types.SerializedAXNode }> { + async accessibilitySnapshot(params: { interestingOnly?: boolean, root?: ElementHandleChannel }): Promise { const rootAXNode = await this._page.accessibility.snapshot({ interestingOnly: params.interestingOnly, root: params.root ? (params.root as ElementHandleDispatcher)._elementHandle : undefined }); - return { rootAXNode: rootAXNode || undefined }; + return { rootAXNode: rootAXNode ? axNodeToProtocol(rootAXNode) : undefined }; } async pdf(params: PagePdfParams): Promise<{ pdf: Binary }> {