chore: private-ise src/Accessibility.ts (#5832)
Now the first pass of migrating to TypeScript is complete I'm going through the src files one by one to tidy up the public/private interfaces. Puppeteer used an underscore convention to denote privacy but violates this in some places; we should be strict with TypeScript's `public` and `private` keywords instead. This means we'll get nice TS errors if you try to refer to a private method/variable, and means when we swap to generating our TS docs the tooling knows what method(s) are public and therefore need to be documented vs private internals that don't.
This commit is contained in:
parent
ce097426af
commit
5343c7a18f
@ -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<SerializedAXNode> {
|
||||
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<AXNode>();
|
||||
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<AXNode>
|
||||
): 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<AXNode>,
|
||||
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<!AXNode>} collection
|
||||
* @param {!AXNode} node
|
||||
* @param {boolean} insideControl
|
||||
*/
|
||||
function collectInterestingNodes(
|
||||
collection: Set<AXNode>,
|
||||
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<AXNode>
|
||||
): 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<string, number | string | boolean>();
|
||||
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<string, !AXNode>} */
|
||||
const nodeById = new Map();
|
||||
public static createTree(payloads: Protocol.Accessibility.AXNode[]): AXNode {
|
||||
const nodeById = new Map<string, AXNode>();
|
||||
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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user