chore: migrate src/Accessibility to TypeScript (#5726)

This commit is contained in:
Jack Franklin 2020-04-23 15:35:03 +01:00 committed by GitHub
parent 930cc32baf
commit 8509f4660e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 79 additions and 151 deletions

View File

@ -16,61 +16,49 @@
// Used as a TypeDef // Used as a TypeDef
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
const {CDPSession} = require('./Connection'); import {CDPSession} from './Connection';
// Used as a TypeDef // Used as a TypeDef
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
const {ElementHandle} = require('./JSHandle'); import {ElementHandle} from './JSHandle';
/**
* @typedef {Object} SerializedAXNode
* @property {string} role
*
* @property {string=} name
* @property {string|number=} value
* @property {string=} description
*
* @property {string=} keyshortcuts
* @property {string=} roledescription
* @property {string=} valuetext
*
* @property {boolean=} disabled
* @property {boolean=} expanded
* @property {boolean=} focused
* @property {boolean=} modal
* @property {boolean=} multiline
* @property {boolean=} multiselectable
* @property {boolean=} readonly
* @property {boolean=} required
* @property {boolean=} selected
*
* @property {boolean|"mixed"=} checked
* @property {boolean|"mixed"=} pressed
*
* @property {number=} level
* @property {number=} valuemin
* @property {number=} valuemax
*
* @property {string=} autocomplete
* @property {string=} haspopup
* @property {string=} invalid
* @property {string=} orientation
*
* @property {Array<SerializedAXNode>=} children
*/
class Accessibility { interface SerializedAXNode {
/** role: string;
* @param {!CDPSession} client name?: string;
*/ value?: string|number;
constructor(client) { 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?: Array<SerializedAXNode>;
}
export class Accessibility {
_client: CDPSession;
constructor(client: CDPSession) {
this._client = client; this._client = client;
} }
/** async snapshot(options: {interestingOnly?: boolean; root?: ElementHandle} = {}): Promise<SerializedAXNode> {
* @param {{interestingOnly?: boolean, root?: ?ElementHandle}=} options
* @return {!Promise<!SerializedAXNode>}
*/
async snapshot(options = {}) {
const { const {
interestingOnly = true, interestingOnly = true,
root = null, root = null,
@ -91,8 +79,7 @@ class Accessibility {
if (!interestingOnly) if (!interestingOnly)
return serializeTree(needle)[0]; return serializeTree(needle)[0];
/** @type {!Set<!AXNode>} */ const interestingNodes = new Set<AXNode>();
const interestingNodes = new Set();
collectInterestingNodes(interestingNodes, defaultRoot, false); collectInterestingNodes(interestingNodes, defaultRoot, false);
if (!interestingNodes.has(needle)) if (!interestingNodes.has(needle))
return null; return null;
@ -105,7 +92,7 @@ class Accessibility {
* @param {!AXNode} node * @param {!AXNode} node
* @param {boolean} insideControl * @param {boolean} insideControl
*/ */
function collectInterestingNodes(collection, node, insideControl) { function collectInterestingNodes(collection: Set<AXNode>, node: AXNode, insideControl: boolean): void {
if (node.isInteresting(insideControl)) if (node.isInteresting(insideControl))
collection.add(node); collection.add(node);
if (node.isLeafNode()) if (node.isLeafNode())
@ -115,14 +102,8 @@ function collectInterestingNodes(collection, node, insideControl) {
collectInterestingNodes(collection, child, insideControl); collectInterestingNodes(collection, child, insideControl);
} }
/** function serializeTree(node: AXNode, whitelistedNodes?: Set<AXNode>): SerializedAXNode[] {
* @param {!AXNode} node const children: SerializedAXNode[] = [];
* @param {!Set<!AXNode>=} whitelistedNodes
* @return {!Array<!SerializedAXNode>}
*/
function serializeTree(node, whitelistedNodes) {
/** @type {!Array<!SerializedAXNode>} */
const children = [];
for (const child of node._children) for (const child of node._children)
children.push(...serializeTree(child, whitelistedNodes)); children.push(...serializeTree(child, whitelistedNodes));
@ -137,23 +118,21 @@ function serializeTree(node, whitelistedNodes) {
class AXNode { class AXNode {
/** _payload: Protocol.Accessibility.AXNode;
* @param {!Protocol.Accessibility.AXNode} payload _children: AXNode[] = [];
*/ _richlyEditable = false;
constructor(payload) { _editable = false;
_focusable = false;
_expanded = false;
_hidden = false;
_name: string;
_role: string;
_cachedHasFocusableChild?: boolean;
constructor(payload: Protocol.Accessibility.AXNode) {
this._payload = payload; this._payload = payload;
/** @type {!Array<!AXNode>} */
this._children = [];
this._richlyEditable = false;
this._editable = false;
this._focusable = false;
this._expanded = false;
this._hidden = false;
this._name = this._payload.name ? this._payload.name.value : ''; this._name = this._payload.name ? this._payload.name.value : '';
this._role = this._payload.role ? this._payload.role.value : 'Unknown'; this._role = this._payload.role ? this._payload.role.value : 'Unknown';
this._cachedHasFocusableChild;
for (const property of this._payload.properties || []) { for (const property of this._payload.properties || []) {
if (property.name === 'editable') { if (property.name === 'editable') {
@ -169,10 +148,7 @@ class AXNode {
} }
} }
/** _isPlainTextField(): boolean {
* @return {boolean}
*/
_isPlainTextField() {
if (this._richlyEditable) if (this._richlyEditable)
return false; return false;
if (this._editable) if (this._editable)
@ -180,19 +156,13 @@ class AXNode {
return this._role === 'textbox' || this._role === 'ComboBox' || this._role === 'searchbox'; return this._role === 'textbox' || this._role === 'ComboBox' || this._role === 'searchbox';
} }
/** _isTextOnlyObject(): boolean {
* @return {boolean}
*/
_isTextOnlyObject() {
const role = this._role; const role = this._role;
return (role === 'LineBreak' || role === 'text' || return (role === 'LineBreak' || role === 'text' ||
role === 'InlineTextBox'); role === 'InlineTextBox');
} }
/** _hasFocusableChild(): boolean {
* @return {boolean}
*/
_hasFocusableChild() {
if (this._cachedHasFocusableChild === undefined) { if (this._cachedHasFocusableChild === undefined) {
this._cachedHasFocusableChild = false; this._cachedHasFocusableChild = false;
for (const child of this._children) { for (const child of this._children) {
@ -205,11 +175,7 @@ class AXNode {
return this._cachedHasFocusableChild; return this._cachedHasFocusableChild;
} }
/** find(predicate: (x: AXNode) => boolean): AXNode | null {
* @param {function(AXNode):boolean} predicate
* @return {?AXNode}
*/
find(predicate) {
if (predicate(this)) if (predicate(this))
return this; return this;
for (const child of this._children) { for (const child of this._children) {
@ -220,10 +186,7 @@ class AXNode {
return null; return null;
} }
/** isLeafNode(): boolean {
* @return {boolean}
*/
isLeafNode() {
if (!this._children.length) if (!this._children.length)
return true; return true;
@ -262,10 +225,7 @@ class AXNode {
return false; return false;
} }
/** isControl(): boolean {
* @return {boolean}
*/
isControl() {
switch (this._role) { switch (this._role) {
case 'button': case 'button':
case 'checkbox': case 'checkbox':
@ -297,7 +257,7 @@ class AXNode {
* @param {boolean} insideControl * @param {boolean} insideControl
* @return {boolean} * @return {boolean}
*/ */
isInteresting(insideControl) { isInteresting(insideControl: boolean): boolean {
const role = this._role; const role = this._role;
if (role === 'Ignored' || this._hidden) if (role === 'Ignored' || this._hidden)
return false; return false;
@ -316,12 +276,8 @@ class AXNode {
return this.isLeafNode() && !!this._name; return this.isLeafNode() && !!this._name;
} }
/** serialize(): SerializedAXNode {
* @return {!SerializedAXNode} const properties = new Map<string, number|string|boolean>();
*/
serialize() {
/** @type {!Map<string, number|string|boolean>} */
const properties = new Map();
for (const property of this._payload.properties || []) for (const property of this._payload.properties || [])
properties.set(property.name.toLowerCase(), property.value.value); properties.set(property.name.toLowerCase(), property.value.value);
if (this._payload.name) if (this._payload.name)
@ -331,15 +287,13 @@ class AXNode {
if (this._payload.description) if (this._payload.description)
properties.set('description', this._payload.description.value); properties.set('description', this._payload.description.value);
/** @type {SerializedAXNode} */ const node: SerializedAXNode = {
const node = {
role: this._role role: this._role
}; };
/** @enum {'name'|'value'|'description'|'keyshortcuts'|'roledescription'|'valuetext'} */ type UserStringProperty = 'name'|'value'|'description'|'keyshortcuts'|'roledescription'|'valuetext';
let UserStringProperties; // eslint-disable-line no-unused-vars
/** @type {!Array<UserStringProperties>} */ const userStringProperties: UserStringProperty[] = [
const userStringProperties = [
'name', 'name',
'value', 'value',
'description', 'description',
@ -347,10 +301,7 @@ class AXNode {
'roledescription', 'roledescription',
'valuetext', 'valuetext',
]; ];
/** const getUserStringPropertyValue = (key: UserStringProperty): string => properties.get(key) as string;
* @param {UserStringProperties} key
*/
const getUserStringPropertyValue = key => /** @type string */(properties.get(key));
for (const userStringProperty of userStringProperties) { for (const userStringProperty of userStringProperties) {
if (!properties.has(userStringProperty)) if (!properties.has(userStringProperty))
@ -359,10 +310,8 @@ class AXNode {
node[userStringProperty] = getUserStringPropertyValue(userStringProperty); node[userStringProperty] = getUserStringPropertyValue(userStringProperty);
} }
/** @enum {'disabled'|'expanded'|'focused'|'modal'|'multiline'|'multiselectable'|'readonly'|'required'|'selected'} */ type BooleanProperty = 'disabled'|'expanded'|'focused'|'modal'|'multiline'|'multiselectable'|'readonly'|'required'|'selected';
let BooleanProperties; // eslint-disable-line no-unused-vars const booleanProperties: BooleanProperty[] = [
/** @type {!Array<BooleanProperties>} */
const booleanProperties = [
'disabled', 'disabled',
'expanded', 'expanded',
'focused', 'focused',
@ -373,10 +322,7 @@ class AXNode {
'required', 'required',
'selected', 'selected',
]; ];
/** const getBooleanPropertyValue = (key: BooleanProperty): boolean => properties.get(key) as boolean;
* @param {BooleanProperties} key
*/
const getBooleanPropertyValue = key => /** @type boolean */(properties.get(key));
for (const booleanProperty of booleanProperties) { for (const booleanProperty of booleanProperties) {
// WebArea's treat focus differently than other nodes. They report whether their frame has focus, // WebArea's treat focus differently than other nodes. They report whether their frame has focus,
@ -389,10 +335,8 @@ class AXNode {
node[booleanProperty] = getBooleanPropertyValue(booleanProperty); node[booleanProperty] = getBooleanPropertyValue(booleanProperty);
} }
/** @enum {'checked'|'pressed'} */ type TristateProperty = 'checked'|'pressed';
let TristateProperties; // eslint-disable-line no-unused-vars const tristateProperties: TristateProperty[] = [
/** @type {!Array<TristateProperties>} */
const tristateProperties = [
'checked', 'checked',
'pressed', 'pressed',
]; ];
@ -404,18 +348,13 @@ class AXNode {
} }
/** @enum {'level'|'valuemax'|'valuemin'} */ type NumbericalProperty = 'level'|'valuemax'|'valuemin';
let NumericalProperties; // eslint-disable-line no-unused-vars const numericalProperties: NumbericalProperty[] = [
/** @type {!Array<NumericalProperties>} */
const numericalProperties = [
'level', 'level',
'valuemax', 'valuemax',
'valuemin', 'valuemin',
]; ];
/** const getNumericalPropertyValue = (key: NumbericalProperty): number => properties.get(key) as number;
* @param {NumericalProperties} key
*/
const getNumericalPropertyValue = key => /** @type number */(properties.get(key));
for (const numericalProperty of numericalProperties) { for (const numericalProperty of numericalProperties) {
if (!properties.has(numericalProperty)) if (!properties.has(numericalProperty))
continue; continue;
@ -423,19 +362,14 @@ class AXNode {
} }
/** @enum {'autocomplete'|'haspopup'|'invalid'|'orientation'} */ type TokenProperty = 'autocomplete'|'haspopup'|'invalid'|'orientation';
let TokenProperties; // eslint-disable-line no-unused-vars const tokenProperties: TokenProperty[] = [
/** @type {!Array<TokenProperties>} */
const tokenProperties = [
'autocomplete', 'autocomplete',
'haspopup', 'haspopup',
'invalid', 'invalid',
'orientation', 'orientation',
]; ];
/** const getTokenPropertyValue = (key: TokenProperty): string => properties.get(key) as string;
* @param {TokenProperties} key
*/
const getTokenPropertyValue = key => /** @type string */(properties.get(key));
for (const tokenProperty of tokenProperties) { for (const tokenProperty of tokenProperties) {
const value = getTokenPropertyValue(tokenProperty); const value = getTokenPropertyValue(tokenProperty);
if (!value || value === 'false') if (!value || value === 'false')
@ -445,11 +379,7 @@ class AXNode {
return node; return node;
} }
/** static createTree(payloads: Protocol.Accessibility.AXNode[]): AXNode {
* @param {!Array<!Protocol.Accessibility.AXNode>} payloads
* @return {!AXNode}
*/
static createTree(payloads) {
/** @type {!Map<string, !AXNode>} */ /** @type {!Map<string, !AXNode>} */
const nodeById = new Map(); const nodeById = new Map();
for (const payload of payloads) for (const payload of payloads)
@ -461,5 +391,3 @@ class AXNode {
return nodeById.values().next().value; return nodeById.values().next().value;
} }
} }
module.exports = {Accessibility};

View File

@ -34,7 +34,7 @@ describeChromeOnly('headful tests', function() {
/* These tests fire up an actual browser so let's /* These tests fire up an actual browser so let's
* allow a higher timeout * allow a higher timeout
*/ */
this.timeout(10 * 1000); this.timeout(20 * 1000);
let headfulOptions; let headfulOptions;
let headlessOptions; let headlessOptions;