diff --git a/src/Accessibility.ts b/src/Accessibility.ts index 4c4a7658..d9a9bf61 100644 --- a/src/Accessibility.ts +++ b/src/Accessibility.ts @@ -51,13 +51,13 @@ interface SerializedAXNode { } export class Accessibility { - _client: CDPSession; + private _client: CDPSession; constructor(client: CDPSession) { this._client = client; } - async snapshot( + public async snapshot( options: { interestingOnly?: boolean; root?: ElementHandle } = {} ): Promise { const { interestingOnly = true, root = null } = options; @@ -73,80 +73,74 @@ export class Accessibility { let needle = defaultRoot; if (backendNodeId) { needle = defaultRoot.find( - (node) => node._payload.backendDOMNodeId === backendNodeId + (node) => node.payload.backendDOMNodeId === backendNodeId ); if (!needle) return null; } - if (!interestingOnly) return serializeTree(needle)[0]; + if (!interestingOnly) return this.serializeTree(needle)[0]; const interestingNodes = new Set(); - collectInterestingNodes(interestingNodes, defaultRoot, false); + this.collectInterestingNodes(interestingNodes, defaultRoot, false); if (!interestingNodes.has(needle)) return null; - return serializeTree(needle, interestingNodes)[0]; + return this.serializeTree(needle, interestingNodes)[0]; + } + + private serializeTree( + node: AXNode, + whitelistedNodes?: Set + ): SerializedAXNode[] { + const children: SerializedAXNode[] = []; + for (const child of node.children) + children.push(...this.serializeTree(child, whitelistedNodes)); + + if (whitelistedNodes && !whitelistedNodes.has(node)) return children; + + const serializedNode = node.serialize(); + if (children.length) serializedNode.children = children; + return [serializedNode]; + } + + private collectInterestingNodes( + collection: Set, + node: AXNode, + insideControl: boolean + ): void { + if (node.isInteresting(insideControl)) collection.add(node); + if (node.isLeafNode()) return; + insideControl = insideControl || node.isControl(); + for (const child of node.children) + this.collectInterestingNodes(collection, child, insideControl); } } -/** - * @param {!Set} collection - * @param {!AXNode} node - * @param {boolean} insideControl - */ -function collectInterestingNodes( - collection: Set, - node: AXNode, - insideControl: boolean -): void { - if (node.isInteresting(insideControl)) collection.add(node); - if (node.isLeafNode()) return; - insideControl = insideControl || node.isControl(); - for (const child of node._children) - collectInterestingNodes(collection, child, insideControl); -} - -function serializeTree( - node: AXNode, - whitelistedNodes?: Set -): SerializedAXNode[] { - const children: SerializedAXNode[] = []; - for (const child of node._children) - children.push(...serializeTree(child, whitelistedNodes)); - - if (whitelistedNodes && !whitelistedNodes.has(node)) return children; - - const serializedNode = node.serialize(); - if (children.length) serializedNode.children = children; - return [serializedNode]; -} - class AXNode { - _payload: Protocol.Accessibility.AXNode; - _children: AXNode[] = []; - _richlyEditable = false; - _editable = false; - _focusable = false; - _expanded = false; - _hidden = false; - _name: string; - _role: string; - _cachedHasFocusableChild?: boolean; + public payload: Protocol.Accessibility.AXNode; + public children: AXNode[] = []; + + private _richlyEditable = false; + private _editable = false; + private _focusable = false; + private _hidden = false; + private _name: string; + private _role: string; + private _cachedHasFocusableChild?: boolean; constructor(payload: Protocol.Accessibility.AXNode) { - this._payload = payload; - this._name = this._payload.name ? this._payload.name.value : ''; - this._role = this._payload.role ? this._payload.role.value : 'Unknown'; + this.payload = payload; + this._name = this.payload.name ? this.payload.name.value : ''; + this._role = this.payload.role ? this.payload.role.value : 'Unknown'; - for (const property of this._payload.properties || []) { + for (const property of this.payload.properties || []) { if (property.name === 'editable') { this._richlyEditable = property.value.value === 'richtext'; this._editable = true; } if (property.name === 'focusable') this._focusable = property.value.value; - if (property.name === 'expanded') this._expanded = property.value.value; if (property.name === 'hidden') this._hidden = property.value.value; } } - _isPlainTextField(): boolean { + private _isPlainTextField(): boolean { if (this._richlyEditable) return false; if (this._editable) return true; return ( @@ -156,15 +150,15 @@ class AXNode { ); } - _isTextOnlyObject(): boolean { + private _isTextOnlyObject(): boolean { const role = this._role; return role === 'LineBreak' || role === 'text' || role === 'InlineTextBox'; } - _hasFocusableChild(): boolean { + private _hasFocusableChild(): boolean { if (this._cachedHasFocusableChild === undefined) { this._cachedHasFocusableChild = false; - for (const child of this._children) { + for (const child of this.children) { if (child._focusable || child._hasFocusableChild()) { this._cachedHasFocusableChild = true; break; @@ -174,17 +168,17 @@ class AXNode { return this._cachedHasFocusableChild; } - find(predicate: (x: AXNode) => boolean): AXNode | null { + public find(predicate: (x: AXNode) => boolean): AXNode | null { if (predicate(this)) return this; - for (const child of this._children) { + for (const child of this.children) { const result = child.find(predicate); if (result) return result; } return null; } - isLeafNode(): boolean { - if (!this._children.length) return true; + public isLeafNode(): boolean { + if (!this.children.length) return true; // These types of objects may have children that we use as internal // implementation details, but we want to expose them as leaves to platform @@ -217,7 +211,7 @@ class AXNode { return false; } - isControl(): boolean { + public isControl(): boolean { switch (this._role) { case 'button': case 'checkbox': @@ -245,11 +239,7 @@ class AXNode { } } - /** - * @param {boolean} insideControl - * @return {boolean} - */ - isInteresting(insideControl: boolean): boolean { + public isInteresting(insideControl: boolean): boolean { const role = this._role; if (role === 'Ignored' || this._hidden) return false; @@ -264,14 +254,14 @@ class AXNode { return this.isLeafNode() && !!this._name; } - serialize(): SerializedAXNode { + public serialize(): SerializedAXNode { 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); - if (this._payload.name) properties.set('name', this._payload.name.value); - if (this._payload.value) properties.set('value', this._payload.value.value); - if (this._payload.description) - properties.set('description', this._payload.description.value); + if (this.payload.name) properties.set('name', this.payload.name.value); + if (this.payload.value) properties.set('value', this.payload.value.value); + if (this.payload.description) + properties.set('description', this.payload.description.value); const node: SerializedAXNode = { role: this._role, @@ -378,14 +368,13 @@ class AXNode { return node; } - static createTree(payloads: Protocol.Accessibility.AXNode[]): AXNode { - /** @type {!Map} */ - const nodeById = new Map(); + public static createTree(payloads: Protocol.Accessibility.AXNode[]): AXNode { + const nodeById = new Map(); for (const payload of payloads) nodeById.set(payload.nodeId, new AXNode(payload)); for (const node of nodeById.values()) { - for (const childId of node._payload.childIds || []) - node._children.push(nodeById.get(childId)); + for (const childId of node.payload.childIds || []) + node.children.push(nodeById.get(childId)); } return nodeById.values().next().value; }