Web Inspector: Layer summary should be bottom sticky
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / DOMNodeDetailsSidebarPanel.js
1 /*
2  * Copyright (C) 2013, 2014 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 WebInspector.DOMNodeDetailsSidebarPanel = function()
27 {
28     WebInspector.DOMDetailsSidebarPanel.call(this, "dom-node-details", WebInspector.UIString("Node"), WebInspector.UIString("Node"), "Images/NavigationItemAngleBrackets.svg", "2");
29
30     WebInspector.domTreeManager.addEventListener(WebInspector.DOMTreeManager.Event.AttributeModified, this._attributesChanged, this);
31     WebInspector.domTreeManager.addEventListener(WebInspector.DOMTreeManager.Event.AttributeRemoved, this._attributesChanged, this);
32
33     this.element.classList.add(WebInspector.DOMNodeDetailsSidebarPanel.StyleClassName);
34
35     this._identityNodeTypeRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Type"));
36     this._identityNodeNameRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Name"));
37     this._identityNodeValueRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Value"));
38
39     var identityGroup = new WebInspector.DetailsSectionGroup([this._identityNodeTypeRow, this._identityNodeNameRow, this._identityNodeValueRow]);
40     var identitySection = new WebInspector.DetailsSection("dom-node-identity", WebInspector.UIString("Identity"), [identityGroup]);
41
42     this._attributesDataGridRow = new WebInspector.DetailsSectionDataGridRow(null, WebInspector.UIString("No Attributes"));
43
44     var attributesGroup = new WebInspector.DetailsSectionGroup([this._attributesDataGridRow]);
45     var attributesSection = new WebInspector.DetailsSection("dom-node-attributes", WebInspector.UIString("Attributes"), [attributesGroup]);
46
47     this._propertiesRow = new WebInspector.DetailsSectionRow;
48
49     var propertiesGroup = new WebInspector.DetailsSectionGroup([this._propertiesRow]);
50     var propertiesSection = new WebInspector.DetailsSection("dom-node-properties", WebInspector.UIString("Properties"), [propertiesGroup]);
51
52     this._eventListenersSectionGroup = new WebInspector.DetailsSectionGroup;
53     var eventListenersSection = new WebInspector.DetailsSection("dom-node-event-listeners", WebInspector.UIString("Event Listeners"), [this._eventListenersSectionGroup]);    
54
55     this.contentElement.appendChild(identitySection.element);
56     this.contentElement.appendChild(attributesSection.element);
57     this.contentElement.appendChild(propertiesSection.element);
58     this.contentElement.appendChild(eventListenersSection.element);
59
60     if (this._accessibilitySupported()) {
61         this._accessibilityEmptyRow = new WebInspector.DetailsSectionRow(WebInspector.UIString("No Accessibility Information"));
62         this._accessibilityNodeActiveDescendantRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Shared Focus"));
63         this._accessibilityNodeBusyRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Busy"));
64         this._accessibilityNodeCheckedRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Checked"));
65         this._accessibilityNodeChildrenRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Children"));
66         this._accessibilityNodeControlsRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Controls"));
67         this._accessibilityNodeDisabledRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Disabled"));
68         this._accessibilityNodeExpandedRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Expanded"));
69         this._accessibilityNodeFlowsRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Flows"));
70         this._accessibilityNodeFocusedRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Focused"));
71         this._accessibilityNodeIgnoredRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Ignored"));
72         this._accessibilityNodeInvalidRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Invalid"));
73         this._accessibilityNodeLiveRegionStatusRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Live"));
74         this._accessibilityNodeMouseEventRow = new WebInspector.DetailsSectionSimpleRow("");
75         this._accessibilityNodeLabelRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Label"));
76         this._accessibilityNodeOwnsRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Owns"));
77         this._accessibilityNodeParentRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Parent"));
78         this._accessibilityNodePressedRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Pressed"));
79         this._accessibilityNodeReadonlyRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Readonly"));
80         this._accessibilityNodeRequiredRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Required"));
81         this._accessibilityNodeRoleRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Role"));
82         this._accessibilityNodeSelectedRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Selected"));
83         this._accessibilityNodeSelectedChildrenRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Selected Items"));
84     
85         this._accessibilityGroup = new WebInspector.DetailsSectionGroup([this._accessibilityEmptyRow]);
86         var accessibilitySection = new WebInspector.DetailsSection("dom-node-accessibility", WebInspector.UIString("Accessibility"), [this._accessibilityGroup]);    
87
88         this.contentElement.appendChild(accessibilitySection.element);
89     }
90 };
91
92 WebInspector.DOMNodeDetailsSidebarPanel.StyleClassName = "dom-node";
93 WebInspector.DOMNodeDetailsSidebarPanel.PropertiesObjectGroupName = "dom-node-details-sidebar-properties-object-group";
94
95 WebInspector.DOMNodeDetailsSidebarPanel.prototype = {
96     constructor: WebInspector.DOMNodeDetailsSidebarPanel,
97     __proto__: WebInspector.DOMDetailsSidebarPanel.prototype,
98
99     // Public
100
101     refresh: function()
102     {
103         var domNode = this.domNode;
104         if (!domNode)
105             return;
106
107         this._identityNodeTypeRow.value = this._nodeTypeDisplayName();
108         this._identityNodeNameRow.value = domNode.nodeNameInCorrectCase();
109         this._identityNodeValueRow.value = domNode.nodeValue();
110
111         this._refreshAttributes();
112         this._refreshProperties();
113         this._refreshEventListeners();
114         this._refreshAccessibility();
115     },
116
117     // Private
118
119     _accessibilitySupported: function()
120     {
121         return window.DOMAgent && DOMAgent.getAccessibilityPropertiesForNode;
122     },
123
124     _refreshAttributes: function()
125     {
126         this._attributesDataGridRow.dataGrid = this._createAttributesDataGrid();
127     },
128
129     _refreshProperties: function()
130     {
131         var domNode = this.domNode;
132         if (!domNode)
133             return;
134
135         RuntimeAgent.releaseObjectGroup(WebInspector.DOMNodeDetailsSidebarPanel.PropertiesObjectGroupName);
136         WebInspector.RemoteObject.resolveNode(domNode, WebInspector.DOMNodeDetailsSidebarPanel.PropertiesObjectGroupName, nodeResolved.bind(this));
137
138         function nodeResolved(object)
139         {
140             if (!object)
141                 return;
142
143             // Bail if the DOM node changed while we were waiting for the async response.
144             if (this.domNode !== domNode)
145                 return;
146
147             function collectPrototypes()
148             {
149                 // This builds an object with numeric properties. This is easier than dealing with arrays
150                 // with the way RemoteObject works. Start at 1 since we use parseInt later and parseInt
151                 // returns 0 for non-numeric strings make it ambiguous.
152                 var prototype = this;
153                 var result = [];
154                 var counter = 1;
155
156                 while (prototype) {
157                     result[counter++] = prototype;
158                     prototype = prototype.__proto__;
159                 }
160
161                 return result;
162             }
163
164             object.callFunction(collectPrototypes, undefined, nodePrototypesReady.bind(this));
165             object.release();
166         }
167
168         function nodePrototypesReady(object)
169         {
170             if (!object)
171                 return;
172
173             // Bail if the DOM node changed while we were waiting for the async response.
174             if (this.domNode !== domNode)
175                 return;
176
177             object.getOwnProperties(fillSection.bind(this));
178         }
179
180         function fillSection(prototypes)
181         {
182             if (!prototypes)
183                 return;
184
185             // Bail if the DOM node changed while we were waiting for the async response.
186             if (this.domNode !== domNode)
187                 return;
188
189             var element = this._propertiesRow.element;
190             element.removeChildren();
191
192             // Get array of prototype user-friendly names.
193             for (var i = 0; i < prototypes.length; ++i) {
194                 // The only values we care about are numeric, as assigned in collectPrototypes.
195                 if (!parseInt(prototypes[i].name, 10))
196                     continue;
197
198                 var prototype = prototypes[i].value;
199                 var title = prototype.description;
200                 if (title.match(/Prototype$/))
201                     title = title.replace(/Prototype$/, WebInspector.UIString(" (Prototype)"));
202                 else if (title === "Object")
203                     title = title + WebInspector.UIString(" (Prototype)");
204
205                 var propertiesSection = new WebInspector.ObjectPropertiesSection(prototype);
206
207                 var detailsSection = new WebInspector.DetailsSection(prototype.description.hash + "-prototype-properties", title, null, null, true);
208                 detailsSection.groups[0].rows = [new WebInspector.DetailsSectionPropertiesRow(propertiesSection)];
209
210                 element.appendChild(detailsSection.element);
211             }
212         }
213     },
214
215     _refreshEventListeners: function()
216     {
217         var domNode = this.domNode;
218         if (!domNode)
219             return;
220
221         domNode.eventListeners(eventListenersCallback.bind(this));
222
223         function eventListenersCallback(error, eventListeners)
224         {
225             if (error)
226                 return;
227
228             // Bail if the DOM node changed while we were waiting for the async response.
229             if (this.domNode !== domNode)
230                 return;
231
232             var eventListenerTypes = [];
233             var eventListenerSections = {};
234             for (var i = 0; i < eventListeners.length; ++i) {
235                 var eventListener = eventListeners[i];
236                 eventListener.node = WebInspector.domTreeManager.nodeForId(eventListener.nodeId);
237
238                 var type = eventListener.type;
239                 var section = eventListenerSections[type];
240                 if (!section) {
241                     section = new WebInspector.EventListenerSection(type, domNode.id);
242                     eventListenerSections[type] = section;
243                     eventListenerTypes.push(type);
244                 }
245
246                 section.addListener(eventListener);
247             }
248
249             if (!eventListenerTypes.length) {
250                 var emptyRow = new WebInspector.DetailsSectionRow(WebInspector.UIString("No Event Listeners"));
251                 emptyRow.showEmptyMessage();
252                 this._eventListenersSectionGroup.rows = [emptyRow];
253                 return;
254             }
255
256             eventListenerTypes.sort();
257
258             var rows = [];
259             for (var i = 0; i < eventListenerTypes.length; ++i)
260                 rows.push(eventListenerSections[eventListenerTypes[i]]);
261             this._eventListenersSectionGroup.rows = rows;
262         }
263     },
264
265     _refreshAccessibility: (function() {
266         var properties = {};
267         var domNode;
268
269         function booleanValueToLocalizedStringIfTrue(property) {
270             if (properties[property])
271                 return WebInspector.UIString("Yes");
272             return "";
273         }
274
275         function booleanValueToLocalizedStringIfPropertyDefined(property) {
276             if (properties[property] !== undefined) {
277                 if (properties[property])
278                     return WebInspector.UIString("Yes");
279                 else
280                     return WebInspector.UIString("No");
281             }
282             return "";
283         }
284
285         function linkForNodeId(nodeId) {
286             var link = null;
287             if (nodeId !== undefined && typeof nodeId === "number") {
288                 var node = WebInspector.domTreeManager.nodeForId(nodeId);
289                 if (node)
290                     link = WebInspector.linkifyAccessibilityNodeReference(node);
291             }
292             return link;
293         }
294
295         function linkListForNodeIds(nodeIds) {
296             var hasLinks = false;
297             var linkList = null;
298             if (nodeIds !== undefined) {
299                 linkList = document.createElement("ul");
300                 linkList.className = "node-link-list";    
301                 for (var nodeId of nodeIds) {
302                     var node = WebInspector.domTreeManager.nodeForId(nodeId);
303                     if (node) {
304                         var link = WebInspector.linkifyAccessibilityNodeReference(node);
305                         if (link) {
306                             hasLinks = true;
307                             var listitem = document.createElement("li");
308                             listitem.appendChild(link);
309                             linkList.appendChild(listitem);
310                         }
311                     }
312                 }
313             }
314             return hasLinks ? linkList : null;
315         }
316
317         function accessibilityPropertiesCallback(accessibilityProperties) {
318             if (this.domNode !== domNode)
319                 return;
320
321             // Make sure the current set of properties is available in the closure scope for the helper functions.
322             properties = accessibilityProperties;
323
324             if (accessibilityProperties && accessibilityProperties.exists) {
325
326                 var activeDescendantLink = linkForNodeId(accessibilityProperties.activeDescendantNodeId);
327                 var busy = booleanValueToLocalizedStringIfPropertyDefined("busy");
328
329                 var checked = "";
330                 if (accessibilityProperties.checked !== undefined) {
331                     if (accessibilityProperties.checked === DOMAgent.AccessibilityPropertiesChecked.True)
332                         checked = WebInspector.UIString("Yes");
333                     else if (accessibilityProperties.checked === DOMAgent.AccessibilityPropertiesChecked.Mixed)
334                         checked = WebInspector.UIString("Mixed");
335                     else // DOMAgent.AccessibilityPropertiesChecked.False
336                         checked = WebInspector.UIString("No");
337                 }
338
339                 // Accessibility tree children are not a 1:1 mapping with DOM tree children.
340                 var childNodeLinkList = linkListForNodeIds(accessibilityProperties.childNodeIds);
341                 
342                 var controlledNodeLinkList = linkListForNodeIds(accessibilityProperties.controlledNodeIds);
343                 var disabled = booleanValueToLocalizedStringIfTrue("disabled");
344                 var expanded = booleanValueToLocalizedStringIfPropertyDefined("expanded");
345                 var flowedNodeLinkList = linkListForNodeIds(accessibilityProperties.flowedNodeIds);
346                 var focused = booleanValueToLocalizedStringIfPropertyDefined("focused");
347                 
348                 var ignored = "";
349                 if (accessibilityProperties.ignored) {
350                     ignored = WebInspector.UIString("Yes");
351                     if (accessibilityProperties.hidden)
352                         ignored = WebInspector.UIString("%s (hidden)").format(ignored);
353                     else if (accessibilityProperties.ignoredByDefault)
354                         ignored = WebInspector.UIString("%s (default)").format(ignored);
355                 }
356
357                 var invalid = "";
358                 if (accessibilityProperties.invalid === DOMAgent.AccessibilityPropertiesInvalid.True)
359                     invalid = WebInspector.UIString("Yes");
360                 else if (accessibilityProperties.invalid === DOMAgent.AccessibilityPropertiesInvalid.Grammar)
361                     invalid = WebInspector.UIString("Grammar");
362                 else if (accessibilityProperties.invalid === DOMAgent.AccessibilityPropertiesInvalid.Spelling)
363                     invalid = WebInspector.UIString("Spelling");
364
365                 var label = accessibilityProperties.label;
366
367                 var liveRegionStatus = "";
368                 var liveRegionStatusNode = null;
369                 var liveRegionStatusToken = accessibilityProperties.liveRegionStatus;
370                 switch(liveRegionStatusToken) {
371                 case DOMAgent.AccessibilityPropertiesLiveRegionStatus.Assertive:
372                     liveRegionStatus = WebInspector.UIString("Assertive");
373                     break;
374                 case DOMAgent.AccessibilityPropertiesLiveRegionStatus.Polite:
375                     liveRegionStatus = WebInspector.UIString("Polite");
376                     break;
377                 default:
378                     liveRegionStatus = "";
379                 }
380                 if (liveRegionStatus) {
381                     var liveRegionRelevant = accessibilityProperties.liveRegionRelevant;
382                     // Append @aria-relevant values. E.g. "Live: Assertive (Additions, Text)".
383                     if (liveRegionRelevant && liveRegionRelevant.length) {
384                         // @aria-relevant="all" is exposed as ["additions","removals","text"], in order.
385                         // This order is controlled in WebCore and expected in WebInspectorUI.
386                         if (liveRegionRelevant.length === 3 
387                             && liveRegionRelevant[0] === DOMAgent.LiveRegionRelevant.Additions
388                             && liveRegionRelevant[1] === DOMAgent.LiveRegionRelevant.Removals
389                             && liveRegionRelevant[2] === DOMAgent.LiveRegionRelevant.Text)
390                             liveRegionRelevant = [WebInspector.UIString("All Changes")];
391                         else {
392                             // Reassign localized strings in place: ["additions","text"] becomes ["Additions","Text"].
393                             liveRegionRelevant = liveRegionRelevant.map(function(value) {
394                                 switch (value) {
395                                 case DOMAgent.LiveRegionRelevant.Additions:
396                                     return WebInspector.UIString("Additions");
397                                 case DOMAgent.LiveRegionRelevant.Removals:
398                                     return WebInspector.UIString("Removals");
399                                 case DOMAgent.LiveRegionRelevant.Text:
400                                     return WebInspector.UIString("Text");
401                                 default: // If WebCore sends a new unhandled value, display as a String.
402                                     return "\"" + value + "\"";
403                                 }
404                             });
405                         }
406                         liveRegionStatus += " (" + liveRegionRelevant.join(", ") + ")";
407                     }
408                     // Clarify @aria-atomic if necessary.
409                     if (accessibilityProperties.liveRegionAtomic) {
410                         liveRegionStatusNode = document.createElement("div");
411                         liveRegionStatusNode.className = "value-with-clarification";
412                         liveRegionStatusNode.setAttribute("role", "text");
413                         liveRegionStatusNode.appendChild(document.createTextNode(liveRegionStatus));
414                         var clarificationNode = document.createElement("div");
415                         clarificationNode.className = "clarification";
416                         clarificationNode.appendChild(document.createTextNode(WebInspector.UIString("Region announced in its entirety.")));
417                         liveRegionStatusNode.appendChild(clarificationNode);
418                     }
419                 }
420
421                 var mouseEventNodeId = accessibilityProperties.mouseEventNodeId;
422                 var mouseEventTextValue = "";
423                 var mouseEventNodeLink = null;
424                 if (mouseEventNodeId) {
425                     if (mouseEventNodeId === accessibilityProperties.nodeId)
426                         mouseEventTextValue = WebInspector.UIString("Yes");
427                     else
428                         mouseEventNodeLink = linkForNodeId(mouseEventNodeId);
429                 }
430
431                 var ownedNodeLinkList = linkListForNodeIds(accessibilityProperties.ownedNodeIds);
432
433                 // Accessibility tree parent is not a 1:1 mapping with the DOM tree parent.
434                 var parentNodeLink = linkForNodeId(accessibilityProperties.parentNodeId);
435
436                 var pressed = booleanValueToLocalizedStringIfPropertyDefined("pressed");
437                 var readonly = booleanValueToLocalizedStringIfTrue("readonly");
438                 var required = booleanValueToLocalizedStringIfPropertyDefined("required");
439
440                 var role = accessibilityProperties.role;
441                 if (role === "" || role === "unknown")
442                     role = WebInspector.UIString("No exact ARIA role match.");
443                 else if (role) {
444                     if (!domNode.getAttribute("role"))
445                         role = WebInspector.UIString("%s (default)").format(role);
446                     else if (domNode.getAttribute("role") !== role)
447                         role = WebInspector.UIString("%s (computed)").format(role);
448                 }
449
450                 var selected = booleanValueToLocalizedStringIfTrue("selected");
451                 var selectedChildNodeLinkList = linkListForNodeIds(accessibilityProperties.selectedChildNodeIds);
452
453                 // Assign all the properties to their respective views.
454                 this._accessibilityNodeActiveDescendantRow.value = activeDescendantLink || "";
455                 this._accessibilityNodeBusyRow.value = busy;
456                 this._accessibilityNodeCheckedRow.value = checked;
457                 this._accessibilityNodeChildrenRow.value = childNodeLinkList || "";
458                 this._accessibilityNodeControlsRow.value = controlledNodeLinkList || "";
459                 this._accessibilityNodeDisabledRow.value = disabled;
460                 this._accessibilityNodeExpandedRow.value = expanded;
461                 this._accessibilityNodeFlowsRow.value = flowedNodeLinkList || "";
462                 this._accessibilityNodeFocusedRow.value = focused;
463                 this._accessibilityNodeIgnoredRow.value = ignored;
464                 this._accessibilityNodeInvalidRow.value = invalid;
465                 this._accessibilityNodeLabelRow.value = label;
466                 this._accessibilityNodeLiveRegionStatusRow.value = liveRegionStatusNode || liveRegionStatus;
467                 
468                 // Row label changes based on whether the value is a delegate node link.
469                 this._accessibilityNodeMouseEventRow.label = mouseEventNodeLink ? WebInspector.UIString("Click Listener") : WebInspector.UIString("Clickable");
470                 this._accessibilityNodeMouseEventRow.value = mouseEventNodeLink || mouseEventTextValue;
471
472                 this._accessibilityNodeOwnsRow.value = ownedNodeLinkList || "";
473                 this._accessibilityNodeParentRow.value = parentNodeLink || "";
474                 this._accessibilityNodePressedRow.value = pressed;
475                 this._accessibilityNodeReadonlyRow.value = readonly;
476                 this._accessibilityNodeRequiredRow.value = required;
477                 this._accessibilityNodeRoleRow.value = role;
478                 this._accessibilityNodeSelectedRow.value = selected;
479
480                 this._accessibilityNodeSelectedChildrenRow.label = WebInspector.UIString("Selected Items");
481                 this._accessibilityNodeSelectedChildrenRow.value = selectedChildNodeLinkList || "";
482                 if (selectedChildNodeLinkList && accessibilityProperties.selectedChildNodeIds.length === 1)
483                     this._accessibilityNodeSelectedChildrenRow.label = WebInspector.UIString("Selected Item");                
484
485                 // Display order, not alphabetical as above.
486                 this._accessibilityGroup.rows = [
487                     // Global properties for all elements.
488                     this._accessibilityNodeIgnoredRow,
489                     this._accessibilityNodeRoleRow,
490                     this._accessibilityNodeLabelRow,
491                     this._accessibilityNodeParentRow,
492                     this._accessibilityNodeActiveDescendantRow,
493                     this._accessibilityNodeSelectedChildrenRow,
494                     this._accessibilityNodeChildrenRow,
495                     this._accessibilityNodeOwnsRow,
496                     this._accessibilityNodeControlsRow,
497                     this._accessibilityNodeFlowsRow,
498                     this._accessibilityNodeMouseEventRow,
499                     this._accessibilityNodeFocusedRow,
500                     this._accessibilityNodeBusyRow,
501                     this._accessibilityNodeLiveRegionStatusRow,
502
503                     // Properties exposed for all input-type elements.
504                     this._accessibilityNodeDisabledRow,
505                     this._accessibilityNodeInvalidRow,
506                     this._accessibilityNodeRequiredRow,
507
508                     // Role-specific properties.
509                     this._accessibilityNodeCheckedRow,
510                     this._accessibilityNodeExpandedRow,
511                     this._accessibilityNodePressedRow,
512                     this._accessibilityNodeReadonlyRow,
513                     this._accessibilityNodeSelectedRow
514                 ];
515
516                 this._accessibilityEmptyRow.hideEmptyMessage();
517
518             } else {
519                 this._accessibilityGroup.rows = [this._accessibilityEmptyRow];
520                 this._accessibilityEmptyRow.showEmptyMessage();
521             }
522         }
523
524         function refreshAX() {
525             if (!this._accessibilitySupported())
526                 return;
527
528             // Make sure the domNode is available in the closure scope.
529             domNode = this.domNode;
530             if (!domNode)
531                 return;
532
533             domNode.accessibilityProperties(accessibilityPropertiesCallback.bind(this));
534         }
535
536         return refreshAX;
537     }()),
538
539     _attributesChanged: function(event)
540     {
541         if (event.data.node !== this.domNode)
542             return;
543         this._refreshAttributes();
544         this._refreshAccessibility();
545     },
546
547     _nodeTypeDisplayName: function()
548     {
549         switch (this.domNode.nodeType()) {
550         case Node.ELEMENT_NODE:
551             return WebInspector.UIString("Element");
552         case Node.TEXT_NODE:
553             return WebInspector.UIString("Text Node");
554         case Node.COMMENT_NODE:
555             return WebInspector.UIString("Comment");
556         case Node.DOCUMENT_NODE:
557             return WebInspector.UIString("Document");
558         case Node.DOCUMENT_TYPE_NODE:
559             return WebInspector.UIString("Document Type");
560         case Node.DOCUMENT_FRAGMENT_NODE:
561             return WebInspector.UIString("Document Fragment");
562         case Node.CDATA_SECTION_NODE:
563             return WebInspector.UIString("Character Data");
564         case Node.PROCESSING_INSTRUCTION_NODE:
565             return WebInspector.UIString("Processing Instruction");
566         default:
567             console.error("Unknown DOM node type: ", this.domNode.nodeType());
568             return this.domNode.nodeType();
569         }
570     },
571
572     _createAttributesDataGrid: function()
573     {
574         var domNode = this.domNode;
575         if (!domNode || !domNode.hasAttributes())
576             return null;
577
578         var columns = {name: {title: WebInspector.UIString("Name"), width: "30%"}, value: {title: WebInspector.UIString("Value")}};
579         var dataGrid = new WebInspector.DataGrid(columns);
580
581         var attributes = domNode.attributes();
582         for (var i = 0; i < attributes.length; ++i) {
583             var attribute = attributes[i];
584
585             var node = new WebInspector.DataGridNode({name: attribute.name, value: attribute.value || ""}, false);
586             node.selectable = true;
587
588             dataGrid.appendChild(node);
589         }
590
591         return dataGrid;
592     }
593 };