1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
7 * @extends {WebInspector.ThrottledWidget}
9 WebInspector.AccessibilitySidebarView = function()
11 WebInspector.ThrottledWidget.call(this);
12 this._computedTextSubPane = null;
13 this._axNodeSubPane = null;
15 this._sidebarPaneStack = null;
16 WebInspector.context.addFlavorChangeListener(WebInspector.DOMNode, this._pullNode, this);
20 WebInspector.AccessibilitySidebarView.prototype = {
22 * @return {?WebInspector.DOMNode}
32 * @return {!Promise.<?>}
37 * @param {?AccessibilityAgent.AXNode} accessibilityNode
38 * @this {WebInspector.AccessibilitySidebarView}
40 function accessibilityNodeCallback(accessibilityNode)
42 if (this._computedTextSubPane)
43 this._computedTextSubPane.setAXNode(accessibilityNode);
44 if (this._axNodeSubPane)
45 this._axNodeSubPane.setAXNode(accessibilityNode);
47 var node = this.node();
48 return WebInspector.AccessibilityModel.fromTarget(node.target()).getAXNode(node.id)
49 .then(accessibilityNodeCallback.bind(this))
57 WebInspector.ThrottledWidget.prototype.wasShown.call(this);
59 if (!this._sidebarPaneStack) {
60 this._computedTextSubPane = new WebInspector.AXComputedTextSubPane();
61 this._computedTextSubPane.setNode(this.node());
62 this._computedTextSubPane.show(this.element);
63 this._computedTextSubPane.expand();
65 this._axNodeSubPane = new WebInspector.AXNodeSubPane();
66 this._axNodeSubPane.setNode(this.node());
67 this._axNodeSubPane.show(this.element);
68 this._axNodeSubPane.expand();
70 this._sidebarPaneStack = new WebInspector.SidebarPaneStack();
71 this._sidebarPaneStack.element.classList.add("flex-auto");
72 this._sidebarPaneStack.show(this.element);
73 this._sidebarPaneStack.addPane(this._computedTextSubPane);
74 this._sidebarPaneStack.addPane(this._axNodeSubPane);
77 WebInspector.targetManager.addModelListener(WebInspector.DOMModel, WebInspector.DOMModel.Events.AttrModified, this._onAttrChange, this);
78 WebInspector.targetManager.addModelListener(WebInspector.DOMModel, WebInspector.DOMModel.Events.AttrRemoved, this._onAttrChange, this);
79 WebInspector.targetManager.addModelListener(WebInspector.DOMModel, WebInspector.DOMModel.Events.CharacterDataModified, this._onNodeChange, this);
80 WebInspector.targetManager.addModelListener(WebInspector.DOMModel, WebInspector.DOMModel.Events.ChildNodeCountUpdated, this._onNodeChange, this);
88 WebInspector.targetManager.removeModelListener(WebInspector.DOMModel, WebInspector.DOMModel.Events.AttrModified, this._onAttrChange, this);
89 WebInspector.targetManager.removeModelListener(WebInspector.DOMModel, WebInspector.DOMModel.Events.AttrRemoved, this._onAttrChange, this);
90 WebInspector.targetManager.removeModelListener(WebInspector.DOMModel, WebInspector.DOMModel.Events.CharacterDataModified, this._onNodeChange, this);
91 WebInspector.targetManager.removeModelListener(WebInspector.DOMModel, WebInspector.DOMModel.Events.ChildNodeCountUpdated, this._onNodeChange, this);
96 this._node = WebInspector.context.flavor(WebInspector.DOMNode);
97 if (this._computedTextSubPane)
98 this._computedTextSubPane.setNode(this._node);
99 if (this._axNodeSubPane)
100 this._axNodeSubPane.setNode(this._node);
105 * @param {!WebInspector.Event} event
107 _onAttrChange: function(event)
111 var node = event.data.node;
112 if (this.node() !== node)
118 * @param {!WebInspector.Event} event
120 _onNodeChange: function(event)
124 var node = event.data;
125 if (this.node() !== node)
131 __proto__: WebInspector.ThrottledWidget.prototype
136 * @extends {WebInspector.SidebarPane}
137 * @param {string} name
139 WebInspector.AccessibilitySubPane = function(name)
141 WebInspector.SidebarPane.call(this, name);
144 this.registerRequiredCSS("accessibility/accessibilityNode.css");
147 WebInspector.AccessibilitySubPane.prototype = {
149 * @param {?AccessibilityAgent.AXNode} axNode
152 setAXNode: function(axNode)
157 * @return {?WebInspector.DOMNode}
165 * @param {?WebInspector.DOMNode} node
167 setNode: function(node)
173 * @param {string} textContent
174 * @param {string=} className
177 createInfo: function(textContent, className)
179 var classNameOrDefault = className || "info";
180 var info = createElementWithClass("div", classNameOrDefault);
181 info.textContent = textContent;
182 this.element.appendChild(info);
187 * @param {string=} className
188 * @return {!TreeOutline}
190 createTreeOutline: function(className)
192 var treeOutline = new TreeOutlineInShadow(className);
193 treeOutline.registerRequiredCSS("accessibility/accessibilityNode.css");
194 treeOutline.registerRequiredCSS("components/objectValue.css");
195 treeOutline.element.classList.add("hidden");
196 this.element.appendChild(treeOutline.element);
200 __proto__: WebInspector.SidebarPane.prototype
205 * @extends {WebInspector.AccessibilitySubPane}
207 WebInspector.AXComputedTextSubPane = function()
209 WebInspector.AccessibilitySubPane.call(this, WebInspector.UIString("Computed Text"));
211 this._computedTextElement = this.element.createChild("div", "ax-computed-text hidden");
213 this._noTextInfo = this.createInfo(WebInspector.UIString("Node has no text alternative."));
214 this._treeOutline = this.createTreeOutline();
218 WebInspector.AXComputedTextSubPane.prototype = {
220 * @param {?AccessibilityAgent.AXNode} axNode
223 setAXNode: function(axNode)
225 if (this._axNode === axNode)
227 this._axNode = axNode;
229 var treeOutline = this._treeOutline;
230 treeOutline.removeChildren();
231 var target = this.node().target();
233 if (!axNode || axNode.ignored) {
234 this._computedTextElement.classList.add("hidden");
235 treeOutline.element.classList.add("hidden");
237 this._noTextInfo.classList.remove("hidden");
240 this._computedTextElement.removeChildren();
242 // TODO(aboxhall): include contents where appropriate (requires protocol change)
243 this._computedTextElement.classList.toggle("hidden", !axNode.name || !axNode.name.value);
244 if (axNode.name && axNode.name.value)
245 this._computedTextElement.createChild("div").textContent = axNode.name.value;
247 var foundProperty = false;
249 * @param {!AccessibilityAgent.AXProperty} property
251 function addProperty(property)
253 foundProperty = true;
254 treeOutline.appendChild(new WebInspector.AXNodePropertyTreeElement(property, target));
257 if ("value" in axNode && axNode.value.type === "string")
258 addProperty(/** @type {!AccessibilityAgent.AXProperty} */ ({name: "value", value: axNode.value}));
260 var propertiesArray = /** @type {!Array.<!AccessibilityAgent.AXProperty> } */ (axNode.properties);
261 for (var property of propertiesArray) {
262 if (property.name == AccessibilityAgent.AXWidgetAttributes.Valuetext) {
263 addProperty(property);
268 treeOutline.element.classList.toggle("hidden", !foundProperty)
269 this._noTextInfo.classList.toggle("hidden", !treeOutline.element.classList.contains("hidden") || !this._computedTextElement.classList.contains("hidden"));
272 __proto__: WebInspector.AccessibilitySubPane.prototype
277 * @extends {WebInspector.AccessibilitySubPane}
279 WebInspector.AXNodeSubPane = function()
281 WebInspector.AccessibilitySubPane.call(this, WebInspector.UIString("Accessibility Node"));
283 this._noNodeInfo = this.createInfo(WebInspector.UIString("No accessibility node"));
284 this._ignoredInfo = this.createInfo(WebInspector.UIString("Accessibility node not exposed"), "ax-ignored-info hidden");
286 this._treeOutline = this.createTreeOutline();
287 this._ignoredReasonsTree = this.createTreeOutline();
291 WebInspector.AXNodeSubPane.prototype = {
293 * @param {?AccessibilityAgent.AXNode} axNode
296 setAXNode: function(axNode)
298 if (this._axNode === axNode)
300 this._axNode = axNode;
302 var treeOutline = this._treeOutline;
303 treeOutline.removeChildren();
304 var ignoredReasons = this._ignoredReasonsTree;
305 ignoredReasons.removeChildren();
306 var target = this.node().target();
309 treeOutline.element.classList.add("hidden");
310 this._ignoredInfo.classList.add("hidden");
311 ignoredReasons.element.classList.add("hidden");
313 this._noNodeInfo.classList.remove("hidden");
314 this.element.classList.add("ax-ignored-node-pane");
317 } else if (axNode.ignored) {
318 this._noNodeInfo.classList.add("hidden");
319 treeOutline.element.classList.add("hidden");
320 this.element.classList.add("ax-ignored-node-pane");
322 this._ignoredInfo.classList.remove("hidden");
323 ignoredReasons.element.classList.remove("hidden");
325 * @param {!AccessibilityAgent.AXProperty} property
327 function addIgnoredReason(property)
329 ignoredReasons.appendChild(new WebInspector.AXNodeIgnoredReasonTreeElement(property, axNode, target));
331 var ignoredReasonsArray = /** @type {!Array.<!Object>} */(axNode.ignoredReasons);
332 for (var reason of ignoredReasonsArray)
333 addIgnoredReason(reason);
334 if (!ignoredReasons.firstChild())
335 ignoredReasons.element.classList.add("hidden");
338 this.element.classList.remove("ax-ignored-node-pane");
340 this._ignoredInfo.classList.add("hidden");
341 ignoredReasons.element.classList.add("hidden");
342 this._noNodeInfo.classList.add("hidden");
344 treeOutline.element.classList.remove("hidden");
347 * @param {!AccessibilityAgent.AXProperty} property
349 function addProperty(property)
351 treeOutline.appendChild(new WebInspector.AXNodePropertyTreeElement(property, target));
354 for (var propertyName of ["name", "description", "help", "value"]) {
355 if (propertyName in axNode) {
356 var defaultProperty = /** @type {!AccessibilityAgent.AXProperty} */ ({name: propertyName, value: axNode[propertyName]});
357 addProperty(defaultProperty);
361 var roleProperty = /** @type {!AccessibilityAgent.AXProperty} */ ({name: "role", value: axNode.role});
362 addProperty(roleProperty);
364 var propertyMap = {};
365 var propertiesArray = /** @type {!Array.<!AccessibilityAgent.AXProperty>} */ (axNode.properties);
366 for (var property of propertiesArray)
367 propertyMap[property.name] = property;
369 for (var propertySet of [AccessibilityAgent.AXWidgetAttributes, AccessibilityAgent.AXWidgetStates, AccessibilityAgent.AXGlobalStates, AccessibilityAgent.AXLiveRegionAttributes, AccessibilityAgent.AXRelationshipAttributes]) {
370 for (var propertyKey in propertySet) {
371 var property = propertySet[propertyKey];
372 if (property in propertyMap)
373 addProperty(propertyMap[property]);
378 __proto__: WebInspector.AccessibilitySubPane.prototype
383 * @extends {TreeElement}
384 * @param {!AccessibilityAgent.AXProperty} property
385 * @param {!WebInspector.Target} target
387 WebInspector.AXNodePropertyTreeElement = function(property, target)
389 this._property = property;
390 this._target = target;
392 // Pass an empty title, the title gets made later in onattach.
393 TreeElement.call(this, "");
394 this.toggleOnClick = true;
395 this.selectable = false;
398 WebInspector.AXNodePropertyTreeElement.prototype = {
410 this._nameElement = WebInspector.AXNodePropertyTreeElement.createNameElement(this._property.name);
412 var value = this._property.value;
413 if (value.type === "idref") {
414 this._valueElement = WebInspector.AXNodePropertyTreeElement.createRelationshipValueElement(value, this._target);
415 } else if (value.type === "idrefList") {
416 var relatedNodes = value.relatedNodeArrayValue;
417 var numNodes = relatedNodes.length;
418 var description = "(" + numNodes + (numNodes == 1 ? " node" : " nodes") + ")";
419 value.value = description;
420 for (var i = 0; i < relatedNodes.length; i++) {
421 var backendId = relatedNodes[i].backendNodeId;
422 var deferredNode = new WebInspector.DeferredDOMNode(this._target, relatedNodes[i].backendNodeId);
423 var child = new WebInspector.AXRelatedNodeTreeElement(deferredNode);
424 this.appendChild(child);
426 this._valueElement = WebInspector.AXNodePropertyTreeElement.createValueElement(value, this.listItemElement);
427 if (relatedNodes.length <= 3)
432 this._valueElement = WebInspector.AXNodePropertyTreeElement.createValueElement(value, this.listItemElement);
435 var separatorElement = createElementWithClass("span", "separator");
436 separatorElement.textContent = ": ";
438 this.listItemElement.removeChildren();
439 this.listItemElement.appendChildren(this._nameElement, separatorElement, this._valueElement);
442 __proto__: TreeElement.prototype
446 * @param {!TreeElement} treeNode
447 * @param {?AccessibilityAgent.AXNode} axNode
448 * @param {!WebInspector.Target} target
450 WebInspector.AXNodePropertyTreeElement.populateWithNode = function(treeNode, axNode, target)
455 * @param {?string} name
458 WebInspector.AXNodePropertyTreeElement.createNameElement = function(name)
460 var nameElement = createElement("span");
461 var AXAttributes = WebInspector.AccessibilityStrings.AXAttributes;
462 if (name in AXAttributes) {
463 nameElement.textContent = WebInspector.UIString(AXAttributes[name].name);
464 nameElement.title = AXAttributes[name].description;
465 nameElement.classList.add("ax-readable-name");
467 nameElement.textContent = name;
468 nameElement.classList.add("ax-name");
474 * @param {!AccessibilityAgent.AXValue} value
475 * @param {!WebInspector.Target} target
478 WebInspector.AXNodePropertyTreeElement.createRelationshipValueElement = function(value, target)
480 var deferredNode = new WebInspector.DeferredDOMNode(target, value.relatedNodeValue.backendNodeId);
481 var valueElement = createElement("span");
484 * @param {?WebInspector.DOMNode} node
486 function onNodeResolved(node)
488 valueElement.appendChild(WebInspector.DOMPresentationUtils.linkifyNodeReference(node));
490 deferredNode.resolve(onNodeResolved);
496 * @param {!AccessibilityAgent.AXValue} value
497 * @param {!Element} parentElement
500 WebInspector.AXNodePropertyTreeElement.createValueElement = function(value, parentElement)
502 var valueElement = createElementWithClass("span", "monospace");
503 var type = value.type;
507 if (type === "string") {
508 // Render \n as a nice unicode cr symbol.
510 valueText = value.value.replace(/\n/g, "\u21B5");
512 valueElement._originalTextContent = "\"" + value.value + "\"";
514 valueText = String(value.value);
517 if (type in WebInspector.AXNodePropertyTreeElement.TypeStyles)
518 valueElement.classList.add(WebInspector.AXNodePropertyTreeElement.TypeStyles[type]);
520 valueElement.setTextContentTruncatedIfNeeded(valueText || "");
522 valueElement.insertBefore(createTextNode(prefix), valueElement.firstChild);
524 valueElement.createTextChild(suffix);
526 valueElement.title = String(value.value) || "";
533 * @extends {TreeElement}
534 * @param {!WebInspector.DeferredDOMNode} deferredNode
536 WebInspector.AXRelatedNodeTreeElement = function(deferredNode)
538 this._deferredNode = deferredNode;
540 TreeElement.call(this, "");
543 WebInspector.AXRelatedNodeTreeElement.prototype = {
551 var valueElement = createElement("div");
552 this.listItemElement.appendChild(valueElement);
555 * @param {?WebInspector.DOMNode} node
557 function onNodeResolved(node)
559 valueElement.appendChild(WebInspector.DOMPresentationUtils.linkifyNodeReference(node));
561 this._deferredNode.resolve(onNodeResolved);
564 __proto__: TreeElement.prototype
567 /** @type {!Object<string, string>} */
568 WebInspector.AXNodePropertyTreeElement.TypeStyles = {
569 boolean: "object-value-boolean",
570 booleanOrUndefined: "object-value-boolean",
571 tristate: "object-value-boolean",
572 number: "object-value-number",
573 integer: "object-value-number",
574 string: "object-value-string",
576 internalRole: "ax-internal-role"
581 * @extends {TreeElement}
582 * @param {!AccessibilityAgent.AXProperty} property
583 * @param {?AccessibilityAgent.AXNode} axNode
584 * @param {!WebInspector.Target} target
586 WebInspector.AXNodeIgnoredReasonTreeElement = function(property, axNode, target)
588 this._property = property;
589 this._axNode = axNode;
590 this._target = target;
592 // Pass an empty title, the title gets made later in onattach.
593 TreeElement.call(this, "");
594 this.toggleOnClick = true;
595 this.selectable = false;
598 WebInspector.AXNodeIgnoredReasonTreeElement.prototype = {
604 this.listItemElement.removeChildren();
606 this._reasonElement = WebInspector.AXNodeIgnoredReasonTreeElement.createReasonElement(this._property.name, this._axNode);
607 this.listItemElement.appendChild(this._reasonElement);
609 var value = this._property.value;
610 if (value.type === "idref") {
611 this._valueElement = WebInspector.AXNodePropertyTreeElement.createRelationshipValueElement(value, this._target);
612 this.listItemElement.appendChild(this._valueElement);
616 __proto__: TreeElement.prototype
620 * @param {?string} reason
621 * @param {?AccessibilityAgent.AXNode} axNode
624 WebInspector.AXNodeIgnoredReasonTreeElement.createReasonElement = function(reason, axNode)
626 var reasonElement = null;
628 case "activeModalDialog":
629 reasonElement = WebInspector.formatLocalized("Element is hidden by active modal dialog: ", [], "");
631 case "ancestorDisallowsChild":
632 reasonElement = WebInspector.formatLocalized("Element is not permitted as child of ", [], "");
634 // http://www.w3.org/TR/wai-aria/roles#childrenArePresentational
635 case "ancestorIsLeafNode":
636 reasonElement = WebInspector.formatLocalized("Ancestor's children are all presentational: ", [], "");
639 var ariaHiddenSpan = createElement("span", "source-code").textContent = "aria-hidden";
640 reasonElement = WebInspector.formatLocalized("Element is %s.", [ ariaHiddenSpan ], "");
642 case "ariaHiddenRoot":
643 var ariaHiddenSpan = createElement("span", "source-code").textContent = "aria-hidden";
644 var trueSpan = createElement("span", "source-code").textContent = "true";
645 reasonElement = WebInspector.formatLocalized("%s is %s on ancestor: ", [ ariaHiddenSpan, trueSpan ], "");
648 reasonElement = WebInspector.formatLocalized("Element has empty alt text.", [], "");
651 reasonElement = WebInspector.formatLocalized("No text content.", [], "");
654 reasonElement = WebInspector.formatLocalized("Element is inert.", [], "");
656 case "inheritsPresentation":
657 reasonElement = WebInspector.formatLocalized("Element inherits presentational role from ", [], "");
659 case "labelContainer":
660 reasonElement = WebInspector.formatLocalized("Part of label element: ", [], "");
663 reasonElement = WebInspector.formatLocalized("Label for ", [], "");
666 reasonElement = WebInspector.formatLocalized("Element is not rendered.", [], "");
669 reasonElement = WebInspector.formatLocalized("Element is not visible.", [], "");
671 case "presentationalRole":
672 var rolePresentationSpan = createElement("span", "source-code").textContent = "role=" + axNode.role.value;
673 reasonElement = WebInspector.formatLocalized("Element has %s.", [ rolePresentationSpan ], "");
675 case "probablyPresentational":
676 reasonElement = WebInspector.formatLocalized("Element is presentational.", [], "");
678 case "staticTextUsedAsNameFor":
679 reasonElement = WebInspector.formatLocalized("Static text node is used as name for ", [], "");
681 case "uninteresting":
682 reasonElement = WebInspector.formatLocalized("Element not interesting for accessibility.", [], "")
686 reasonElement.classList.add("ax-reason");
687 return reasonElement;