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