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