Skip to content

Commit

Permalink
chore: private-ise src/Accessibility.ts (#5832)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jackfranklin committed May 7, 2020
1 parent ce09742 commit 5343c7a
Showing 1 changed file with 63 additions and 74 deletions.
137 changes: 63 additions & 74 deletions src/Accessibility.ts
Expand Up @@ -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;
Expand All @@ -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];
}
}

/**
* @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);
}
private serializeTree(
node: AXNode,
whitelistedNodes?: Set<AXNode>
): SerializedAXNode[] {
const children: SerializedAXNode[] = [];
for (const child of node.children)
children.push(...this.serializeTree(child, whitelistedNodes));

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;

if (whitelistedNodes && !whitelistedNodes.has(node)) return children;
const serializedNode = node.serialize();
if (children.length) serializedNode.children = children;
return [serializedNode];
}

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);
}
}

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 (
Expand All @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -217,7 +211,7 @@ class AXNode {
return false;
}

isControl(): boolean {
public isControl(): boolean {
switch (this._role) {
case 'button':
case 'checkbox':
Expand Down Expand Up @@ -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;

Expand All @@ -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,
Expand Down Expand Up @@ -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;
}
Expand Down

0 comments on commit 5343c7a

Please sign in to comment.