Skip to content

Commit

Permalink
chore(rpc): implement input, a11y, console (#2722)
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelfeldman authored Jun 26, 2020
1 parent ab6a6c9 commit 71618a9
Show file tree
Hide file tree
Showing 17 changed files with 375 additions and 74 deletions.
45 changes: 6 additions & 39 deletions src/accessibility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,48 +14,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as dom from './dom';

export type SerializedAXNode = {
role: string,
name: string,
value?: string|number,
description?: string,

keyshortcuts?: string,
roledescription?: string,
valuetext?: string,

disabled?: boolean,
expanded?: boolean,
focused?: boolean,
modal?: boolean,
multiline?: boolean,
multiselectable?: boolean,
readonly?: boolean,
required?: boolean,
selected?: boolean,

checked?: boolean|'mixed',
pressed?: boolean|'mixed',

level?: number,
valuemin?: number,
valuemax?: number,

autocomplete?: string,
haspopup?: string,
invalid?: string,
orientation?: string,

children?: SerializedAXNode[]
};
import * as dom from './dom';
import * as types from './types';

export interface AXNode {
isInteresting(insideControl: boolean): boolean;
isLeafNode(): boolean;
isControl(): boolean;
serialize(): SerializedAXNode;
serialize(): types.SerializedAXNode;
children(): Iterable<AXNode>;
}

Expand All @@ -68,7 +35,7 @@ export class Accessibility {
async snapshot(options: {
interestingOnly?: boolean;
root?: dom.ElementHandle;
} = {}): Promise<SerializedAXNode | null> {
} = {}): Promise<types.SerializedAXNode | null> {
const {
interestingOnly = true,
root = null,
Expand Down Expand Up @@ -98,8 +65,8 @@ function collectInterestingNodes(collection: Set<AXNode>, node: AXNode, insideCo
collectInterestingNodes(collection, child, insideControl);
}

function serializeTree(node: AXNode, whitelistedNodes?: Set<AXNode>): SerializedAXNode[] {
const children: SerializedAXNode[] = [];
function serializeTree(node: AXNode, whitelistedNodes?: Set<AXNode>): types.SerializedAXNode[] {
const children: types.SerializedAXNode[] = [];
for (const child of node.children())
children.push(...serializeTree(child, whitelistedNodes));

Expand Down
17 changes: 9 additions & 8 deletions src/chromium/crAccessibility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { CRSession } from './crConnection';
import { Protocol } from './protocol';
import * as dom from '../dom';
import * as accessibility from '../accessibility';
import * as types from '../types';

export async function getAccessibilityTree(client: CRSession, needle?: dom.ElementHandle): Promise<{tree: accessibility.AXNode, needle: accessibility.AXNode | null}> {
const {nodes} = await client.send('Accessibility.getFullAXTree');
Expand Down Expand Up @@ -198,7 +199,7 @@ class CRAXNode implements accessibility.AXNode {
return this.isLeafNode() && !!this._name;
}

serialize(): accessibility.SerializedAXNode {
serialize(): types.SerializedAXNode {
const properties: Map<string, number | string | boolean> = new Map();
for (const property of this._payload.properties || [])
properties.set(property.name.toLowerCase(), property.value.value);
Expand All @@ -209,12 +210,12 @@ class CRAXNode implements accessibility.AXNode {
if (this._payload.description)
properties.set('description', this._payload.description.value);

const node: {[x in keyof accessibility.SerializedAXNode]: any} = {
const node: {[x in keyof types.SerializedAXNode]: any} = {
role: this._role,
name: this._payload.name ? (this._payload.name.value || '') : ''
};

const userStringProperties: Array<keyof accessibility.SerializedAXNode> = [
const userStringProperties: Array<keyof types.SerializedAXNode> = [
'value',
'description',
'keyshortcuts',
Expand All @@ -227,7 +228,7 @@ class CRAXNode implements accessibility.AXNode {
node[userStringProperty] = properties.get(userStringProperty);
}

const booleanProperties: Array<keyof accessibility.SerializedAXNode> = [
const booleanProperties: Array<keyof types.SerializedAXNode> = [
'disabled',
'expanded',
'focused',
Expand All @@ -249,7 +250,7 @@ class CRAXNode implements accessibility.AXNode {
node[booleanProperty] = value;
}

const tristateProperties: Array<keyof accessibility.SerializedAXNode> = [
const tristateProperties: Array<keyof types.SerializedAXNode> = [
'checked',
'pressed',
];
Expand All @@ -259,7 +260,7 @@ class CRAXNode implements accessibility.AXNode {
const value = properties.get(tristateProperty);
node[tristateProperty] = value === 'mixed' ? 'mixed' : value === 'true' ? true : false;
}
const numericalProperties: Array<keyof accessibility.SerializedAXNode> = [
const numericalProperties: Array<keyof types.SerializedAXNode> = [
'level',
'valuemax',
'valuemin',
Expand All @@ -269,7 +270,7 @@ class CRAXNode implements accessibility.AXNode {
continue;
node[numericalProperty] = properties.get(numericalProperty);
}
const tokenProperties: Array<keyof accessibility.SerializedAXNode> = [
const tokenProperties: Array<keyof types.SerializedAXNode> = [
'autocomplete',
'haspopup',
'invalid',
Expand All @@ -281,7 +282,7 @@ class CRAXNode implements accessibility.AXNode {
continue;
node[tokenProperty] = value;
}
return node as accessibility.SerializedAXNode;
return node as types.SerializedAXNode;
}

static createTree(client: CRSession, payloads: Protocol.Accessibility.AXNode[]): CRAXNode {
Expand Down
7 changes: 1 addition & 6 deletions src/console.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,7 @@

import * as js from './javascript';
import * as util from 'util';

export type ConsoleMessageLocation = {
url?: string,
lineNumber?: number,
columnNumber?: number,
};
import { ConsoleMessageLocation } from './types';

export class ConsoleMessage {
private _type: string;
Expand Down
15 changes: 8 additions & 7 deletions src/firefox/ffAccessibility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import * as accessibility from '../accessibility';
import { FFSession } from './ffConnection';
import { Protocol } from './protocol';
import * as dom from '../dom';
import * as types from '../types';

export async function getAccessibilityTree(session: FFSession, needle?: dom.ElementHandle): Promise<{tree: accessibility.AXNode, needle: accessibility.AXNode | null}> {
const objectId = needle ? needle._objectId : undefined;
Expand Down Expand Up @@ -198,12 +199,12 @@ class FFAXNode implements accessibility.AXNode {
return this.isLeafNode() && !!this._name.trim();
}

serialize(): accessibility.SerializedAXNode {
const node: {[x in keyof accessibility.SerializedAXNode]: any} = {
serialize(): types.SerializedAXNode {
const node: {[x in keyof types.SerializedAXNode]: any} = {
role: FFRoleToARIARole.get(this._role) || this._role,
name: this._name || ''
};
const userStringProperties: Array<keyof accessibility.SerializedAXNode & keyof Protocol.Accessibility.AXTree> = [
const userStringProperties: Array<keyof types.SerializedAXNode & keyof Protocol.Accessibility.AXTree> = [
'name',
'value',
'description',
Expand All @@ -216,7 +217,7 @@ class FFAXNode implements accessibility.AXNode {
continue;
node[userStringProperty] = this._payload[userStringProperty];
}
const booleanProperties: Array<keyof accessibility.SerializedAXNode & keyof Protocol.Accessibility.AXTree> = [
const booleanProperties: Array<keyof types.SerializedAXNode & keyof Protocol.Accessibility.AXTree> = [
'disabled',
'expanded',
'focused',
Expand All @@ -235,7 +236,7 @@ class FFAXNode implements accessibility.AXNode {
continue;
node[booleanProperty] = value;
}
const tristateProperties: Array<keyof accessibility.SerializedAXNode & keyof Protocol.Accessibility.AXTree> = [
const tristateProperties: Array<keyof types.SerializedAXNode & keyof Protocol.Accessibility.AXTree> = [
'checked',
'pressed',
];
Expand All @@ -245,15 +246,15 @@ class FFAXNode implements accessibility.AXNode {
const value = this._payload[tristateProperty];
node[tristateProperty] = value;
}
const numericalProperties: Array<keyof accessibility.SerializedAXNode & keyof Protocol.Accessibility.AXTree> = [
const numericalProperties: Array<keyof types.SerializedAXNode & keyof Protocol.Accessibility.AXTree> = [
'level'
];
for (const numericalProperty of numericalProperties) {
if (!(numericalProperty in this._payload))
continue;
node[numericalProperty] = this._payload[numericalProperty];
}
const tokenProperties: Array<keyof accessibility.SerializedAXNode & keyof Protocol.Accessibility.AXTree> = [
const tokenProperties: Array<keyof types.SerializedAXNode & keyof Protocol.Accessibility.AXTree> = [
'autocomplete',
'haspopup',
'invalid',
Expand Down
4 changes: 2 additions & 2 deletions src/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { TimeoutSettings } from './timeoutSettings';
import * as types from './types';
import { Events } from './events';
import { BrowserContext, BrowserContextBase } from './browserContext';
import { ConsoleMessage, ConsoleMessageLocation } from './console';
import { ConsoleMessage } from './console';
import * as accessibility from './accessibility';
import { EventEmitter } from 'events';
import { FileChooser } from './fileChooser';
Expand Down Expand Up @@ -281,7 +281,7 @@ export class Page extends EventEmitter {
await PageBinding.dispatch(this, payload, context);
}

_addConsoleMessage(type: string, args: js.JSHandle[], location: ConsoleMessageLocation, text?: string) {
_addConsoleMessage(type: string, args: js.JSHandle[], location: types.ConsoleMessageLocation, text?: string) {
const message = new ConsoleMessage(type, text, args, location);
const intercepted = this._frameManager.interceptConsoleMessage(message);
if (intercepted || !this.listenerCount(Events.Page.Console))
Expand Down
17 changes: 17 additions & 0 deletions src/rpc/channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export interface PageChannel extends Channel {
on(event: 'requestFinished', callback: (params: RequestChannel) => void): this;
on(event: 'requestFailed', callback: (params: RequestChannel) => void): this;
on(event: 'close', callback: () => void): this;
on(event: 'console', callback: (params: ConsoleMessageChannel) => void): this;

setDefaultNavigationTimeoutNoReply(params: { timeout: number }): void;
setDefaultTimeoutNoReply(params: { timeout: number }): Promise<void>;
Expand All @@ -82,6 +83,20 @@ export interface PageChannel extends Channel {
setNetworkInterceptionEnabled(params: { enabled: boolean }): Promise<void>;
screenshot(params: { options?: types.ScreenshotOptions }): Promise<Buffer>;
close(params: { options?: { runBeforeUnload?: boolean } }): Promise<void>;

// Input
keyboardDown(params: { key: string }): Promise<void>;
keyboardUp(params: { key: string }): Promise<void>;
keyboardInsertText(params: { text: string }): Promise<void>;
keyboardType(params: { text: string, options?: { delay?: number } }): Promise<void>;
keyboardPress(params: { key: string, options?: { delay?: number } }): Promise<void>;
mouseMove(params: { x: number, y: number, options?: { steps?: number } }): Promise<void>;
mouseDown(params: { options?: { button?: types.MouseButton, clickCount?: number } }): Promise<void>;
mouseUp(params: { options?: { button?: types.MouseButton, clickCount?: number } }): Promise<void>;
mouseClick(params: { x: number, y: number, options?: { delay?: number, button?: types.MouseButton, clickCount?: number } }): Promise<void>;

// A11Y
accessibilitySnapshot(params: { options: { interestingOnly?: boolean, root?: ElementHandleChannel } }): Promise<types.SerializedAXNode | null>;
}

export interface FrameChannel extends Channel {
Expand Down Expand Up @@ -173,3 +188,5 @@ export interface ResponseChannel extends Channel {
finished(): Promise<Error | null>;
}

export interface ConsoleMessageChannel extends Channel {
}
33 changes: 33 additions & 0 deletions src/rpc/client/accessibility.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { PageChannel } from '../channels';
import { ElementHandle } from './elementHandle';
import * as types from '../../types';

export class Accessibility {
private _channel: PageChannel;

constructor(channel: PageChannel) {
this._channel = channel;
}

snapshot(options: { interestingOnly?: boolean; root?: ElementHandle } = {}): Promise<types.SerializedAXNode | null> {
const root = options.root ? options.root._elementChannel : undefined;
return this._channel.accessibilitySnapshot({ options: { interestingOnly: options.interestingOnly, root } });
}
}
64 changes: 64 additions & 0 deletions src/rpc/client/console.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as util from 'util';
import { ConsoleMessageLocation } from '../../types';
import { JSHandle } from './jsHandle';
import { ConsoleMessageChannel, JSHandleChannel } from '../channels';
import { ChannelOwner } from './channelOwner';
import { Connection } from '../connection';

export class ConsoleMessage extends ChannelOwner<ConsoleMessageChannel> {
private _type: string = '';
private _text: string = '';
private _args: JSHandle[] = [];
private _location: ConsoleMessageLocation = {};

static from(request: ConsoleMessageChannel): ConsoleMessage {
return request._object;
}

constructor(connection: Connection, channel: ConsoleMessageChannel) {
super(connection, channel);
}

_initialize(params: { type: string, text: string, args: JSHandleChannel[], location: ConsoleMessageLocation }) {
this._type = params.type;
this._text = params.text;
this._args = params.args.map(JSHandle.from);
this._location = params.location;
}

type(): string {
return this._type;
}

text(): string {
return this._text;
}

args(): JSHandle[] {
return this._args;
}

location(): ConsoleMessageLocation {
return this._location;
}

[util.inspect.custom]() {
return this.text();
}
}
2 changes: 1 addition & 1 deletion src/rpc/client/elementHandle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { FuncOn, JSHandle, convertArg } from './jsHandle';
import { Connection } from '../connection';

export class ElementHandle<T extends Node = Node> extends JSHandle<T> {
private _elementChannel: ElementHandleChannel;
readonly _elementChannel: ElementHandleChannel;

static from(handle: ElementHandleChannel): ElementHandle {
return handle._object;
Expand Down
Loading

0 comments on commit 71618a9

Please sign in to comment.