blob: 8dd1b5d35c8ffc60e0fc135f8462e3e63251c22e [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @unrestricted
*/
Accessibility.AXTreePane = class extends Accessibility.AccessibilitySubPane {
constructor() {
super(Common.UIString('Accessibility Tree'));
this._treeOutline = this.createTreeOutline();
this.element.classList.add('accessibility-computed');
this._expandedNodes = new Set();
}
/**
* @param {?Accessibility.AccessibilityNode} axNode
* @override
*/
setAXNode(axNode) {
this._axNode = axNode;
var treeOutline = this._treeOutline;
treeOutline.removeChildren();
// TODO(aboxhall): show no node UI
if (!axNode)
return;
treeOutline.element.classList.remove('hidden');
var previousTreeElement = treeOutline.rootElement();
var inspectedNodeTreeElement = new Accessibility.AXNodeTreeElement(axNode, this);
inspectedNodeTreeElement.setInspected(true);
var parent = axNode.parentNode();
if (parent) {
this.setExpanded(parent.backendDOMNodeId(), false);
var chain = [];
var ancestor = parent.parentNode();
while (ancestor) {
chain.unshift(ancestor);
ancestor = ancestor.parentNode();
}
for (var ancestorNode of chain) {
var ancestorTreeElement = new Accessibility.AXNodeTreeElement(ancestorNode, this);
previousTreeElement.appendChild(ancestorTreeElement);
previousTreeElement.expand();
previousTreeElement = ancestorTreeElement;
}
var parentTreeElement = new Accessibility.AXNodeTreeParentElement(parent, inspectedNodeTreeElement, this);
previousTreeElement.appendChild(parentTreeElement);
if (this.isExpanded(parent.backendDOMNodeId()))
parentTreeElement.appendSiblings();
else
parentTreeElement.appendChild(inspectedNodeTreeElement);
previousTreeElement.expand();
previousTreeElement = parentTreeElement;
} else {
previousTreeElement.appendChild(inspectedNodeTreeElement);
}
previousTreeElement.expand();
for (var child of axNode.children()) {
var childTreeElement = new Accessibility.AXNodeTreeElement(child, this);
inspectedNodeTreeElement.appendChild(childTreeElement);
}
inspectedNodeTreeElement.selectable = true;
inspectedNodeTreeElement.select(!this._selectedByUser /* omitFocus */, false);
if (this.isExpanded(axNode.backendDOMNodeId()))
inspectedNodeTreeElement.expand();
this.clearSelectedByUser();
}
/**
* @param {boolean} selectedByUser
*/
setSelectedByUser(selectedByUser) {
this._selectedByUser = true;
}
clearSelectedByUser() {
delete this._selectedByUser;
}
/**
* @return {!SDK.Target}
*/
target() {
return this.node().target();
}
/**
* @param {?number} backendDOMNodeId
* @param {boolean} expanded
*/
setExpanded(backendDOMNodeId, expanded) {
if (!backendDOMNodeId)
return;
if (expanded)
this._expandedNodes.add(backendDOMNodeId);
else
this._expandedNodes.delete(backendDOMNodeId);
}
/**
* @param {?number} backendDOMNodeId
* @return {boolean}
*/
isExpanded(backendDOMNodeId) {
if (!backendDOMNodeId)
return false;
return this._expandedNodes.has(backendDOMNodeId);
}
};
Accessibility.InspectNodeButton = class {
/**
* @param {!Accessibility.AccessibilityNode} axNode
* @param {!Accessibility.AXTreePane} treePane
*/
constructor(axNode, treePane) {
this._axNode = axNode;
this._treePane = treePane;
this.element = UI.Icon.create('smallicon-arrow-in-circle', 'inspect-dom-node');
this.element.addEventListener('mousedown', this._handleMouseDown.bind(this));
}
/**
* @param {!Event} event
*/
_handleMouseDown(event) {
this._treePane.setSelectedByUser(true);
Common.Revealer.reveal(this._axNode.deferredDOMNode());
}
};
/**
* @unrestricted
*/
Accessibility.AXNodeTreeElement = class extends TreeElement {
/**
* @param {!Accessibility.AccessibilityNode} axNode
* @param {!Accessibility.AXTreePane} treePane
*/
constructor(axNode, treePane) {
// Pass an empty title, the title gets made later in onattach.
super('');
/** @type {!Accessibility.AccessibilityNode} */
this._axNode = axNode;
/** @type {!Accessibility.AXTreePane} */
this._treePane = treePane;
this.selectable = true;
this._inspectNodeButton = new Accessibility.InspectNodeButton(axNode, treePane);
}
/**
* @return {!Accessibility.AccessibilityNode}
*/
axNode() {
return this._axNode;
}
/**
* @param {boolean} inspected
*/
setInspected(inspected) {
this._inspected = inspected;
this.listItemElement.classList.toggle('inspected', this._inspected);
}
/**
* @override
* @return {boolean}
*/
onenter() {
this.inspectDOMNode();
return true;
}
/**
* @override
* @param {!Event} event
* @return {boolean}
*/
ondblclick(event) {
this.inspectDOMNode();
return true;
}
inspectDOMNode() {
this._treePane.setSelectedByUser(true);
Common.Revealer.reveal(this._axNode.deferredDOMNode());
}
/**
* @override
*/
onattach() {
this._update();
}
_update() {
this.listItemElement.removeChildren();
if (this._axNode.ignored()) {
this._appendIgnoredNodeElement();
} else {
this._appendRoleElement(this._axNode.role());
if (this._axNode.name().value) {
this.listItemElement.createChild('span', 'separator').textContent = '\u00A0';
this._appendNameElement(/** @type {string} */ (this._axNode.name().value));
}
}
if (this._axNode.hasOnlyUnloadedChildren()) {
this.listItemElement.classList.add('children-unloaded');
this.setExpandable(true);
} else {
this.setExpandable(!!this._axNode.numChildren());
}
if (!this._axNode.isDOMNode())
this.listItemElement.classList.add('no-dom-node');
this.listItemElement.appendChild(this._inspectNodeButton.element);
}
/**
* @override
*/
expand() {
if (!this._axNode || this._axNode.hasOnlyUnloadedChildren())
return;
this._treePane.setExpanded(this._axNode.backendDOMNodeId(), true);
super.expand();
}
/**
* @override
*/
collapse() {
if (!this._axNode || this._axNode.hasOnlyUnloadedChildren())
return;
if (this._treePane)
this._treePane.setExpanded(this._axNode.backendDOMNodeId(), false);
super.collapse();
}
/**
* @param {string} name
*/
_appendNameElement(name) {
var nameElement = createElement('span');
nameElement.textContent = '"' + name + '"';
nameElement.classList.add('ax-readable-string');
this.listItemElement.appendChild(nameElement);
}
/**
* @param {?Protocol.Accessibility.AXValue} role
*/
_appendRoleElement(role) {
if (!role)
return;
var roleElement = createElementWithClass('span', 'monospace');
roleElement.classList.add(Accessibility.AXNodeTreeElement.RoleStyles[role.type]);
roleElement.setTextContentTruncatedIfNeeded(role.value || '');
this.listItemElement.appendChild(roleElement);
}
_appendIgnoredNodeElement() {
var ignoredNodeElement = createElementWithClass('span', 'monospace');
ignoredNodeElement.textContent = Common.UIString('Ignored');
ignoredNodeElement.classList.add('ax-tree-ignored-node');
this.listItemElement.appendChild(ignoredNodeElement);
}
/**
* @param {boolean=} omitFocus
* @param {boolean=} selectedByUser
* @return {boolean}
* @override
*/
select(omitFocus, selectedByUser) {
this._treePane.setSelectedByUser(!!selectedByUser);
return super.select(omitFocus, selectedByUser);
}
};
/** @type {!Object<string, string>} */
Accessibility.AXNodeTreeElement.RoleStyles = {
internalRole: 'ax-internal-role',
role: 'ax-role',
};
/**
* @unrestricted
*/
Accessibility.ExpandSiblingsButton = class {
/**
* @param {!Accessibility.AXNodeTreeParentElement} treeElement
* @param {number} numSiblings
*/
constructor(treeElement, numSiblings) {
this._treeElement = treeElement;
this.element = createElementWithClass('button', 'expand-siblings');
this.element.textContent = Common.UIString((numSiblings === 1 ? '+ %d node' : '+ %d nodes'), numSiblings);
this.element.addEventListener('mousedown', this._handleMouseDown.bind(this));
}
/**
* @param {!Event} event
*/
_handleMouseDown(event) {
this._treeElement.expandSiblings();
event.consume();
}
};
/**
* @unrestricted
*/
Accessibility.AXNodeTreeParentElement = class extends Accessibility.AXNodeTreeElement {
/**
* @param {!Accessibility.AccessibilityNode} axNode
* @param {!Accessibility.AXNodeTreeElement} inspectedNodeTreeElement
* @param {!Accessibility.AXTreePane} treePane
*/
constructor(axNode, inspectedNodeTreeElement, treePane) {
super(axNode, treePane);
this._inspectedNodeTreeElement = inspectedNodeTreeElement;
var numSiblings = axNode.children().length - 1;
this._expandSiblingsButton = new Accessibility.ExpandSiblingsButton(this, numSiblings);
this._partiallyExpanded = false;
}
/**
* @override
*/
onattach() {
super.onattach();
if (this._treePane.isExpanded(this._axNode.backendDOMNodeId()))
this._listItemNode.classList.add('siblings-expanded');
if (this._axNode.numChildren() > 1)
this._listItemNode.insertBefore(this._expandSiblingsButton.element, this._inspectNodeButton.element);
}
/**
* @param {boolean} altKey
* @return {boolean}
* @override
*/
descendOrExpand(altKey) {
if (!this.expanded || !this._partiallyExpanded)
return super.descendOrExpand(altKey);
this.expandSiblings();
if (altKey)
this.expandRecursively();
return true;
}
/**
* @override
*/
expand() {
super.expand();
this._partiallyExpanded = true;
}
expandSiblings() {
this._listItemNode.classList.add('siblings-expanded');
this.appendSiblings();
this.expanded = true;
this._partiallyExpanded = false;
this._treePane.setExpanded(this._axNode.backendDOMNodeId(), true);
}
appendSiblings() {
var inspectedAXNode = this._inspectedNodeTreeElement.axNode();
var nextIndex = 0;
var foundInspectedNode = false;
for (var sibling of this._axNode.children()) {
var siblingTreeElement = null;
if (sibling === inspectedAXNode) {
foundInspectedNode = true;
continue;
}
siblingTreeElement = new Accessibility.AXNodeTreeElement(sibling, this._treePane);
if (foundInspectedNode)
this.appendChild(siblingTreeElement);
else
this.insertChild(siblingTreeElement, nextIndex++);
}
}
};