Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 /** | 4 /** |
| 5 * @unrestricted | 5 * @unrestricted |
| 6 */ | 6 */ |
| 7 WebInspector.AXTreePane = class extends WebInspector.AccessibilitySubPane { | 7 WebInspector.AXTreePane = class extends WebInspector.AccessibilitySubPane { |
| 8 constructor() { | 8 constructor() { |
| 9 super(WebInspector.UIString('Accessibility Tree')); | 9 super(WebInspector.UIString('Accessibility Tree')); |
| 10 | 10 |
| 11 this._treeOutline = this.createTreeOutline(); | 11 this._treeOutline = this.createTreeOutline(); |
| 12 | 12 |
| 13 this.element.classList.add('accessibility-computed'); | 13 this.element.classList.add('accessibility-computed'); |
| 14 | |
| 15 this._expandedNodes = new Set(); | |
| 14 } | 16 } |
| 15 | 17 |
| 16 /** | 18 /** |
| 17 * @param {!Array<!WebInspector.AccessibilityNode>} nodes | 19 * @param {?WebInspector.AccessibilityNode} axNode |
| 20 * @override | |
| 18 */ | 21 */ |
| 19 setAXNodeAndAncestors(nodes) { | 22 setAXNode(axNode) { |
| 20 this._nodes = nodes; | 23 this._axNode = axNode; |
| 21 | 24 |
| 22 var target = this.node().target(); | |
| 23 var treeOutline = this._treeOutline; | 25 var treeOutline = this._treeOutline; |
| 24 treeOutline.removeChildren(); | 26 treeOutline.removeChildren(); |
| 27 | |
| 28 // TODO(aboxhall): show no node UI | |
| 29 if (!axNode) | |
| 30 return; | |
| 31 | |
| 25 treeOutline.element.classList.remove('hidden'); | 32 treeOutline.element.classList.remove('hidden'); |
| 26 var previous = treeOutline.rootElement(); | 33 var previousTreeElement = treeOutline.rootElement(); |
| 27 while (nodes.length) { | 34 var inspectedNodeTreeElement = new WebInspector.AXNodeTreeElement(axNode, th is); |
| 28 var ancestor = nodes.pop(); | 35 inspectedNodeTreeElement.setInspected(true); |
| 29 var ancestorTreeElement = new WebInspector.AXNodeTreeElement(ancestor, tar get); | 36 |
| 30 previous.appendChild(ancestorTreeElement); | 37 var parent = axNode.parentNode(); |
| 31 previous.expand(); | 38 if (parent) { |
| 32 previous = ancestorTreeElement; | 39 this.setExpanded(parent.backendDOMNodeId(), false); |
| 40 | |
| 41 var chain = []; | |
| 42 var ancestor = parent.parentNode(); | |
| 43 while (ancestor) { | |
| 44 chain.unshift(ancestor); | |
| 45 ancestor = ancestor.parentNode(); | |
| 46 } | |
| 47 for (var ancestorNode of chain) { | |
| 48 var ancestorTreeElement = new WebInspector.AXNodeTreeElement(ancestorNod e, this); | |
| 49 previousTreeElement.appendChild(ancestorTreeElement); | |
| 50 previousTreeElement.expand(); | |
| 51 previousTreeElement = ancestorTreeElement; | |
| 52 } | |
| 53 var parentTreeElement = new WebInspector.AXNodeTreeParentElement(parent, i nspectedNodeTreeElement, this); | |
| 54 if (this.isExpanded(parent.backendDOMNodeId())) | |
| 55 parentTreeElement.appendSiblings(); | |
| 56 else | |
| 57 parentTreeElement.appendChild(inspectedNodeTreeElement); | |
| 58 previousTreeElement.appendChild(parentTreeElement); | |
| 59 previousTreeElement.expand(); | |
| 60 previousTreeElement = parentTreeElement; | |
| 61 } else { | |
| 62 previousTreeElement.appendChild(inspectedNodeTreeElement); | |
| 33 } | 63 } |
| 34 previous.selectable = true; | 64 |
| 35 previous.select(true /* omitFocus */); | 65 previousTreeElement.expand(); |
| 66 | |
| 67 for (var child of axNode.children()) { | |
| 68 var childTreeElement = new WebInspector.AXNodeTreeElement(child, this); | |
| 69 inspectedNodeTreeElement.appendChild(childTreeElement); | |
| 70 } | |
| 71 | |
| 72 inspectedNodeTreeElement.selectable = true; | |
| 73 inspectedNodeTreeElement.select(!this._selectedByUser /* omitFocus */, false ); | |
| 74 if (this.isExpanded(axNode.backendDOMNodeId())) | |
| 75 inspectedNodeTreeElement.expand(); | |
| 76 this.clearSelectedByUser(); | |
| 77 } | |
| 78 | |
| 79 /** | |
| 80 * @param {boolean} selectedByUser | |
| 81 */ | |
| 82 setSelectedByUser(selectedByUser) { | |
| 83 this._selectedByUser = true; | |
| 84 } | |
| 85 | |
| 86 clearSelectedByUser() { | |
| 87 delete this._selectedByUser; | |
| 88 } | |
| 89 | |
| 90 /** | |
| 91 * @return {!WebInspector.Target} | |
| 92 */ | |
| 93 target() { | |
| 94 return this.node().target(); | |
| 95 } | |
| 96 | |
| 97 /** | |
| 98 * @param {?number} backendDOMNodeId | |
| 99 * @param {boolean} expanded | |
| 100 */ | |
| 101 setExpanded(backendDOMNodeId, expanded) { | |
| 102 if (!backendDOMNodeId) | |
| 103 return; | |
| 104 if (expanded) | |
| 105 this._expandedNodes.add(backendDOMNodeId); | |
| 106 else | |
| 107 this._expandedNodes.delete(backendDOMNodeId); | |
| 108 } | |
| 109 | |
| 110 /** | |
| 111 * @param {?number} backendDOMNodeId | |
| 112 * @return {boolean} | |
| 113 */ | |
| 114 isExpanded(backendDOMNodeId) { | |
| 115 if (!backendDOMNodeId) | |
| 116 return false; | |
| 117 | |
| 118 return this._expandedNodes.has(backendDOMNodeId); | |
| 36 } | 119 } |
| 37 }; | 120 }; |
| 38 | 121 |
| 122 WebInspector.InspectNodeButton = class { | |
| 123 /** | |
| 124 * @param {!WebInspector.AccessibilityNode} axNode | |
| 125 * @param {!WebInspector.AXTreePane} treePane | |
| 126 */ | |
| 127 constructor(axNode, treePane) { | |
| 128 this._axNode = axNode; | |
| 129 this._treePane = treePane; | |
| 130 | |
| 131 this.element = createElementWithClass('button', 'inspect-dom-node'); | |
| 132 this.element.addEventListener('mousedown', this._handleMouseDown.bind(this)) ; | |
| 133 } | |
| 134 | |
| 135 /** | |
| 136 * @param {!Event} event | |
| 137 */ | |
| 138 _handleMouseDown(event) { | |
| 139 this._treePane.setSelectedByUser(true); | |
| 140 WebInspector.Revealer.reveal(this._axNode.deferredDOMNode()); | |
| 141 } | |
| 142 }; | |
| 143 | |
| 39 /** | 144 /** |
| 40 * @unrestricted | 145 * @unrestricted |
| 41 */ | 146 */ |
| 42 WebInspector.AXNodeTreeElement = class extends TreeElement { | 147 WebInspector.AXNodeTreeElement = class extends TreeElement { |
| 43 /** | 148 /** |
| 44 * @param {!WebInspector.AccessibilityNode} axNode | 149 * @param {!WebInspector.AccessibilityNode} axNode |
| 45 * @param {!WebInspector.Target} target | 150 * @param {!WebInspector.AXTreePane} treePane |
| 46 */ | 151 */ |
| 47 constructor(axNode, target) { | 152 constructor(axNode, treePane) { |
| 48 // Pass an empty title, the title gets made later in onattach. | 153 // Pass an empty title, the title gets made later in onattach. |
| 49 super(''); | 154 super(''); |
| 50 | 155 |
| 51 /** @type {!WebInspector.AccessibilityNode} */ | 156 /** @type {!WebInspector.AccessibilityNode} */ |
| 52 this._axNode = axNode; | 157 this._axNode = axNode; |
| 53 | 158 |
| 54 /** @type {!WebInspector.Target} */ | 159 /** @type {!WebInspector.AXTreePane} */ |
| 55 this._target = target; | 160 this._treePane = treePane; |
| 56 | 161 |
| 57 this.selectable = false; | 162 this.selectable = true; |
| 163 | |
| 164 this._inspectNodeButton = | |
| 165 new WebInspector.InspectNodeButton(axNode, treePane); | |
| 58 } | 166 } |
| 59 | 167 |
| 60 /** | 168 /** |
| 169 * @return {!WebInspector.AccessibilityNode} | |
| 170 */ | |
| 171 axNode() { | |
| 172 return this._axNode; | |
| 173 } | |
| 174 | |
| 175 /** | |
| 176 * @param {boolean} inspected | |
| 177 */ | |
| 178 setInspected(inspected) { | |
| 179 this._inspected = inspected; | |
| 180 this.listItemElement.classList.toggle('inspected', this._inspected); | |
| 181 } | |
| 182 | |
| 183 /** | |
| 184 * @override | |
| 185 * @return {boolean} | |
| 186 */ | |
| 187 onenter() { | |
| 188 this.inspectDOMNode(); | |
| 189 return true; | |
| 190 } | |
| 191 | |
| 192 /** | |
| 193 * @override | |
| 194 * @param {!Event} event | |
| 195 * @return {boolean} | |
| 196 */ | |
| 197 ondblclick(event) { | |
| 198 this.inspectDOMNode(); | |
| 199 return true; | |
| 200 } | |
| 201 | |
| 202 inspectDOMNode() { | |
| 203 this._treePane.setSelectedByUser(true); | |
| 204 WebInspector.Revealer.reveal(this._axNode.deferredDOMNode()); | |
| 205 } | |
| 206 | |
| 207 /** | |
| 61 * @override | 208 * @override |
| 62 */ | 209 */ |
| 63 onattach() { | 210 onattach() { |
| 64 this._update(); | 211 this._update(); |
| 65 } | 212 } |
| 66 | 213 |
| 67 _update() { | 214 _update() { |
| 68 this.listItemElement.removeChildren(); | 215 this.listItemElement.removeChildren(); |
| 69 | 216 |
| 70 if (this._axNode.ignored()) { | 217 if (this._axNode.ignored()) { |
| 71 this._appendIgnoredNodeElement(); | 218 this._appendIgnoredNodeElement(); |
| 72 } else { | 219 } else { |
| 73 this._appendRoleElement(this._axNode.role()); | 220 this._appendRoleElement(this._axNode.role()); |
| 74 if ('name' in this._axNode && this._axNode.name().value) { | 221 if (this._axNode && this._axNode.name().value) { |
|
dgozman
2016/11/04 23:22:45
this._axNode is always there.
aboxhall
2016/11/07 19:54:43
Done.
| |
| 75 this.listItemElement.createChild('span', 'separator').textContent = '\u0 0A0'; | 222 this.listItemElement.createChild('span', 'separator').textContent = '\u0 0A0'; |
| 76 this._appendNameElement(/** @type {string} */ (this._axNode.name().value )); | 223 this._appendNameElement(/** @type {string} */ (this._axNode.name().value )); |
| 77 } | 224 } |
| 78 } | 225 } |
| 226 | |
| 227 if (this._axNode.hasOnlyUnloadedChildren()) { | |
| 228 this._hasOnlyUnloadedChildren = true; | |
|
dgozman
2016/11/04 23:22:45
Why not call this._axNode.hasOnlyUnloadedChildren(
aboxhall
2016/11/07 19:54:43
Done.
| |
| 229 this.listItemElement.classList.add('children-unloaded'); | |
| 230 this.setExpandable(true); | |
| 231 } else { | |
| 232 this.setExpandable(!!this._axNode.numChildren()); | |
| 233 } | |
| 234 | |
| 235 if (!this._axNode.isDOMNode()) | |
| 236 this.listItemElement.classList.add('no-dom-node'); | |
| 237 this.listItemElement.appendChild(this._inspectNodeButton.element); | |
| 79 } | 238 } |
| 80 | 239 |
| 81 /** | 240 /** |
| 241 * @override | |
| 242 */ | |
| 243 expand() { | |
| 244 if (this._hasOnlyUnloadedChildren) | |
| 245 return; | |
| 246 | |
| 247 this._treePane.setExpanded(this._axNode.backendDOMNodeId(), true); | |
| 248 super.expand(); | |
| 249 } | |
| 250 | |
| 251 /** | |
| 252 * @override | |
| 253 */ | |
| 254 collapse() { | |
| 255 if (this._hasOnlyUnloadedChildren) | |
| 256 return; | |
| 257 | |
| 258 if (this._treePane) | |
| 259 this._treePane.setExpanded(this._axNode.backendDOMNodeId(), false); | |
| 260 super.collapse(); | |
| 261 } | |
| 262 | |
| 263 /** | |
| 82 * @param {string} name | 264 * @param {string} name |
| 83 */ | 265 */ |
| 84 _appendNameElement(name) { | 266 _appendNameElement(name) { |
| 85 var nameElement = createElement('span'); | 267 var nameElement = createElement('span'); |
| 86 nameElement.textContent = '"' + name + '"'; | 268 nameElement.textContent = '"' + name + '"'; |
| 87 nameElement.classList.add('ax-readable-string'); | 269 nameElement.classList.add('ax-readable-string'); |
| 88 this.listItemElement.appendChild(nameElement); | 270 this.listItemElement.appendChild(nameElement); |
| 89 } | 271 } |
| 90 | 272 |
| 91 /** | 273 /** |
| 92 * @param {?Protocol.Accessibility.AXValue} role | 274 * @param {?Protocol.Accessibility.AXValue} role |
| 93 */ | 275 */ |
| 94 _appendRoleElement(role) { | 276 _appendRoleElement(role) { |
| 95 if (!role) | 277 if (!role) |
| 96 return; | 278 return; |
| 97 | 279 |
| 98 var roleElement = createElementWithClass('span', 'monospace'); | 280 var roleElement = createElementWithClass('span', 'monospace'); |
| 99 roleElement.classList.add(WebInspector.AXNodeTreeElement.RoleStyles[role.typ e]); | 281 roleElement.classList.add(WebInspector.AXNodeTreeElement.RoleStyles[role.typ e]); |
| 100 roleElement.setTextContentTruncatedIfNeeded(role.value || ''); | 282 roleElement.setTextContentTruncatedIfNeeded(role.value || ''); |
| 101 | 283 |
| 102 this.listItemElement.appendChild(roleElement); | 284 this.listItemElement.appendChild(roleElement); |
| 103 } | 285 } |
| 104 | 286 |
| 105 _appendIgnoredNodeElement() { | 287 _appendIgnoredNodeElement() { |
| 106 var ignoredNodeElement = createElementWithClass('span', 'monospace'); | 288 var ignoredNodeElement = createElementWithClass('span', 'monospace'); |
| 107 ignoredNodeElement.textContent = WebInspector.UIString('Ignored'); | 289 ignoredNodeElement.textContent = WebInspector.UIString('Ignored'); |
| 108 ignoredNodeElement.classList.add('ax-tree-ignored-node'); | 290 ignoredNodeElement.classList.add('ax-tree-ignored-node'); |
| 109 this.listItemElement.appendChild(ignoredNodeElement); | 291 this.listItemElement.appendChild(ignoredNodeElement); |
| 110 } | 292 } |
| 293 | |
| 294 /** | |
| 295 * @param {boolean=} omitFocus | |
| 296 * @param {boolean=} selectedByUser | |
| 297 * @return {boolean} | |
| 298 * @override | |
| 299 */ | |
| 300 select(omitFocus, selectedByUser) { | |
| 301 this._ensureSelection(); | |
|
dgozman
2016/11/04 23:22:45
Isn't this a private method of superclass? Isn't i
aboxhall
2016/11/07 19:54:43
Nope, not called by select(): https://cs.chromium.
dgozman
2016/11/08 16:38:41
Actually, it's called right after onattach, so it
aboxhall
2016/11/08 18:02:18
Ok, the issue is that it was being called earlier
| |
| 302 | |
| 303 this._treePane.setSelectedByUser(!!selectedByUser); | |
| 304 | |
| 305 return super.select(omitFocus, selectedByUser); | |
| 306 } | |
| 111 }; | 307 }; |
| 112 | 308 |
| 113 /** @type {!Object<string, string>} */ | 309 /** @type {!Object<string, string>} */ |
| 114 WebInspector.AXNodeTreeElement.RoleStyles = { | 310 WebInspector.AXNodeTreeElement.RoleStyles = { |
| 115 internalRole: 'ax-internal-role', | 311 internalRole: 'ax-internal-role', |
| 116 role: 'ax-role', | 312 role: 'ax-role', |
| 117 }; | 313 }; |
| 314 | |
| 315 /** | |
| 316 * @unrestricted | |
| 317 */ | |
| 318 WebInspector.ExpandSiblingsButton = class { | |
| 319 /** | |
| 320 * @param {!WebInspector.AXNodeTreeParentElement} treeElement | |
| 321 * @param {number} numSiblings | |
| 322 */ | |
| 323 constructor(treeElement, numSiblings) { | |
| 324 this._treeElement = treeElement; | |
| 325 | |
| 326 this.element = createElementWithClass('button', 'expand-siblings'); | |
| 327 this.element.textContent = WebInspector.UIString( | |
| 328 '+ %d %s', numSiblings, numSiblings === 1 ? 'node' : 'nodes'); | |
|
dgozman
2016/11/04 23:22:45
We should only substitute user data, not the parts
aboxhall
2016/11/07 19:54:43
Done.
| |
| 329 this.element.addEventListener('mousedown', this._handleMouseDown.bind(this)) ; | |
| 330 } | |
| 331 | |
| 332 /** | |
| 333 * @param {!Event} event | |
| 334 */ | |
| 335 _handleMouseDown(event) { | |
| 336 this._treeElement.expandSiblings(); | |
| 337 event.consume(); | |
| 338 } | |
| 339 }; | |
| 340 | |
| 341 /** | |
| 342 * @unrestricted | |
| 343 */ | |
| 344 WebInspector.AXNodeTreeParentElement = class extends WebInspector.AXNodeTreeElem ent { | |
| 345 /** | |
| 346 * @param {!WebInspector.AccessibilityNode} axNode | |
| 347 * @param {!WebInspector.AXNodeTreeElement} inspectedNodeTreeElement | |
| 348 * @param {!WebInspector.AXTreePane} treePane | |
| 349 */ | |
| 350 constructor(axNode, inspectedNodeTreeElement, treePane) { | |
| 351 super(axNode, treePane); | |
| 352 | |
| 353 this._inspectedNodeTreeElement = inspectedNodeTreeElement; | |
| 354 var numSiblings = axNode.children().length - 1; | |
| 355 this._expandSiblingsButton = new WebInspector.ExpandSiblingsButton(this, num Siblings); | |
| 356 this._partiallyExpanded = false; | |
| 357 } | |
| 358 | |
| 359 /** | |
| 360 * @override | |
| 361 */ | |
| 362 onattach() { | |
| 363 super.onattach(); | |
| 364 if (this._treePane.isExpanded(this._axNode.backendDOMNodeId())) | |
| 365 this._listItemNode.classList.add('siblings-expanded'); | |
| 366 if (this._axNode.numChildren() > 1) { | |
| 367 this._listItemNode.insertBefore(this._expandSiblingsButton.element, | |
| 368 this._inspectNodeButton.element); | |
| 369 } | |
| 370 } | |
| 371 | |
| 372 /** | |
| 373 * @param {boolean} altKey | |
| 374 * @return {boolean} | |
| 375 * @override | |
| 376 */ | |
| 377 descendOrExpand(altKey) { | |
| 378 if (!this.expanded || !this._partiallyExpanded) | |
| 379 return TreeElement.prototype.descendOrExpand.call(this, altKey); | |
| 380 | |
| 381 this.expandSiblings(); | |
| 382 if (altKey) | |
| 383 this.expandRecursively(); | |
| 384 return true; | |
| 385 } | |
| 386 | |
| 387 /** | |
| 388 * @override | |
| 389 */ | |
| 390 expand() { | |
| 391 TreeElement.prototype.expand.call(this); | |
|
dgozman
2016/11/04 23:22:45
super.expand();
aboxhall
2016/11/07 19:54:43
Done.
| |
| 392 this._partiallyExpanded = true; | |
| 393 } | |
| 394 | |
| 395 expandSiblings() { | |
| 396 this._listItemNode.classList.add('siblings-expanded'); | |
| 397 this.appendSiblings(); | |
| 398 this.expanded = true; | |
| 399 this._partiallyExpanded = false; | |
| 400 this._treePane.setExpanded(this._axNode.backendDOMNodeId(), true); | |
| 401 } | |
| 402 | |
| 403 appendSiblings() { | |
| 404 var inspectedAXNode = this._inspectedNodeTreeElement.axNode(); | |
| 405 var nextIndex = 0; | |
| 406 var foundInspectedNode = false; | |
| 407 for (var sibling of this._axNode.children()) { | |
| 408 var siblingTreeElement = null; | |
| 409 if (sibling === inspectedAXNode) { | |
| 410 foundInspectedNode = true; | |
| 411 continue; | |
| 412 } | |
| 413 siblingTreeElement = new WebInspector.AXNodeTreeElement(sibling, this._tre ePane); | |
| 414 if (foundInspectedNode) | |
| 415 this.appendChild(siblingTreeElement); | |
| 416 else | |
| 417 this.insertChild(siblingTreeElement, nextIndex++); | |
| 418 } | |
| 419 } | |
| 420 }; | |
| OLD | NEW |