chore: migrate src/Accessibility to TypeScript (#5726)
This commit is contained in:
parent
930cc32baf
commit
8509f4660e
@ -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};
|
|
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user