Web Inspector: In a DataGrid, store value of columnIdentifier to DOM node representin...
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / DataGrid.js
1 /*
2  * Copyright (C) 2008, 2013 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. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.         IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 /**
27  * @constructor
28  * @extends {WebInspector.Object}
29  * @param {function(WebInspector.DataGridNode, number, string, string)=} editCallback
30  * @param {function(WebInspector.DataGridNode)=} deleteCallback
31  */
32 WebInspector.DataGrid = function(columns, editCallback, deleteCallback)
33 {
34     this.element = document.createElement("div");
35     this.element.className = "data-grid";
36     this.element.tabIndex = 0;
37     this.element.addEventListener("keydown", this._keyDown.bind(this), false);
38     this.element.copyHandler = this;
39
40     this._headerTable = document.createElement("table");
41     this._headerTable.className = "header";
42     this._headerTableHeaders = {};
43
44     this._dataTable = document.createElement("table");
45     this._dataTable.className = "data";
46
47     this._dataTable.addEventListener("mousedown", this._mouseDownInDataTable.bind(this));
48     this._dataTable.addEventListener("click", this._clickInDataTable.bind(this));
49
50     this._dataTable.addEventListener("contextmenu", this._contextMenuInDataTable.bind(this), true);
51
52     // FIXME: Add a createCallback which is different from editCallback and has different
53     // behavior when creating a new node.
54     if (editCallback) {
55         this._dataTable.addEventListener("dblclick", this._ondblclick.bind(this), false);
56         this._editCallback = editCallback;
57     }
58     if (deleteCallback)
59         this._deleteCallback = deleteCallback;
60
61     this.aligned = {};
62     this.groups = {};
63     this._hiddenColumns = {};
64
65     this._scrollContainer = document.createElement("div");
66     this._scrollContainer.className = "data-container";
67     this._scrollContainer.appendChild(this._dataTable);
68
69     this.element.appendChild(this._headerTable);
70     this.element.appendChild(this._scrollContainer);
71
72     var headerRow = document.createElement("tr");
73     var columnGroup = document.createElement("colgroup");
74     this._columnCount = 0;
75
76     for (var columnIdentifier in columns) {
77         var column = columns[columnIdentifier];
78         if (column.disclosure)
79             this.disclosureColumnIdentifier = columnIdentifier;
80
81         var col = document.createElement("col");
82         if (column.width)
83             col.style.width = column.width;
84         column.element = col;
85         columnGroup.appendChild(col);
86
87         var cell = document.createElement("th");
88         cell.className = columnIdentifier + "-column";
89         cell.columnIdentifier = columnIdentifier;
90         if (column.aligned)
91             cell.classList.add(column.aligned);
92         this._headerTableHeaders[columnIdentifier] = cell;
93
94         var div = document.createElement("div");
95         if (column.titleDOMFragment)
96             div.appendChild(column.titleDOMFragment);
97         else
98             div.textContent = column.title || "";
99         cell.appendChild(div);
100
101         if (column.sort) {
102             cell.classList.add("sort-" + column.sort);
103             this._sortColumnCell = cell;
104         }
105
106         if (column.sortable) {
107             cell.addEventListener("click", this._clickInHeaderCell.bind(this), false);
108             cell.classList.add("sortable");
109         }
110
111         if (column.aligned)
112             this.aligned[columnIdentifier] = column.aligned;
113
114         if (column.group) {
115             this.groups[columnIdentifier] = column.group;
116             cell.classList.add("column-group-" + column.group);
117         }
118
119         if (column.collapsesGroup) {
120             console.assert(column.group !== column.collapsesGroup);
121
122             var divider = document.createElement("div");
123             divider.className = "divider";
124             cell.appendChild(divider);
125
126             var collapseDiv = document.createElement("div");
127             collapseDiv.className = "collapser-button";
128             collapseDiv.title = this._collapserButtonCollapseColumnsToolTip();
129             collapseDiv.addEventListener("mouseover", this._mouseoverColumnCollapser.bind(this));
130             collapseDiv.addEventListener("mouseout", this._mouseoutColumnCollapser.bind(this));
131             collapseDiv.addEventListener("click", this._clickInColumnCollapser.bind(this));
132             cell.appendChild(collapseDiv);
133
134             cell.collapsesGroup = column.collapsesGroup;
135             cell.classList.add("collapser");
136         }
137
138         headerRow.appendChild(cell);
139
140         ++this._columnCount;
141     }
142
143     columnGroup.span = this._columnCount;
144
145     this._headerTableColumnGroup = columnGroup;
146     this._headerTable.appendChild(this._headerTableColumnGroup);
147     this.headerTableBody.appendChild(headerRow);
148
149     var fillerRow = document.createElement("tr");
150     fillerRow.className = "filler";
151
152     for (var columnIdentifier in columns) {
153         var column = columns[columnIdentifier];
154         var td = document.createElement("td");
155         td.className = columnIdentifier + "-column";
156         td.__columnIdentifier = columnIdentifier;
157         var group = this.groups[columnIdentifier];
158         if (group)
159             td.classList.add("column-group-" + group);
160         fillerRow.appendChild(td);
161     }
162
163     this._dataTableColumnGroup = columnGroup.cloneNode(true);
164     this._dataTable.appendChild(this._dataTableColumnGroup);
165     this.dataTableBody.appendChild(fillerRow);
166
167     this.columns = columns || {};
168     this._columnsArray = [];
169
170     for (var columnIdentifier in columns) {
171         columns[columnIdentifier].ordinal = this._columnsArray.length;
172         columns[columnIdentifier].identifier = columnIdentifier;
173         this._columnsArray.push(columns[columnIdentifier]);
174     }
175
176     for (var i = 0; i < this._columnsArray.length; ++i)
177         this._columnsArray[i].bodyElement = this._dataTableColumnGroup.children[i];
178
179     this.children = [];
180     this.selectedNode = null;
181     this.expandNodesWhenArrowing = false;
182     this.root = true;
183     this.hasChildren = false;
184     this.expanded = true;
185     this.revealed = true;
186     this.selected = false;
187     this.dataGrid = this;
188     this.indentWidth = 15;
189     this.resizers = [];
190     this._columnWidthsInitialized = false;
191
192     for (var columnIdentifier in columns) {
193         if (columns[columnIdentifier].hidden)
194             this._hideColumn(columnIdentifier);
195     }
196
197     this._generateSortIndicatorImagesIfNeeded();
198 }
199
200 WebInspector.DataGrid.Event = {
201     DidLayout: "datagrid-did-layout",
202     SortChanged: "datagrid-sort-changed",
203     SelectedNodeChanged: "datagrid-selected-node-changed",
204     ExpandedNode: "datagrid-expanded-node",
205     CollapsedNode: "datagrid-collapsed-node"
206 };
207
208 /**
209  * @param {Array.<string>} columnNames
210  * @param {Array.<string>} values
211  */
212 WebInspector.DataGrid.createSortableDataGrid = function(columnNames, values)
213 {
214     var numColumns = columnNames.length;
215     if (!numColumns)
216         return null;
217
218     var columns = {};
219
220     for (var i = 0; i < columnNames.length; ++i) {
221         var column = {};
222         column.width = columnNames[i].length;
223         column.title = columnNames[i];
224         column.sortable = true;
225
226         columns[columnNames[i]] = column;
227     }
228
229     var nodes = [];
230     for (var i = 0; i < values.length / numColumns; ++i) {
231         var data = {};
232         for (var j = 0; j < columnNames.length; ++j)
233             data[columnNames[j]] = values[numColumns * i + j];
234
235         var node = new WebInspector.DataGridNode(data, false);
236         node.selectable = false;
237         nodes.push(node);
238     }
239
240     var dataGrid = new WebInspector.DataGrid(columns);
241     var length = nodes.length;
242     for (var i = 0; i < length; ++i)
243         dataGrid.appendChild(nodes[i]);
244
245     dataGrid.addEventListener(WebInspector.DataGrid.Event.SortChanged, sortDataGrid, this);
246
247     function sortDataGrid()
248     {
249         var nodes = dataGrid.children.slice();
250         var sortColumnIdentifier = dataGrid.sortColumnIdentifier;
251         var sortDirection = dataGrid.sortOrder === "ascending" ? 1 : -1;
252         var columnIsNumeric = true;
253
254         for (var i = 0; i < nodes.length; i++) {
255             if (isNaN(Number(nodes[i].data[sortColumnIdentifier] || "")))
256                 columnIsNumeric = false;
257         }
258
259         function comparator(dataGridNode1, dataGridNode2)
260         {
261             var item1 = dataGridNode1.data[sortColumnIdentifier] || "";
262             var item2 = dataGridNode2.data[sortColumnIdentifier] || "";
263
264             var comparison;
265             if (columnIsNumeric) {
266                 // Sort numbers based on comparing their values rather than a lexicographical comparison.
267                 var number1 = parseFloat(item1);
268                 var number2 = parseFloat(item2);
269                 comparison = number1 < number2 ? -1 : (number1 > number2 ? 1 : 0);
270             } else
271                 comparison = item1 < item2 ? -1 : (item1 > item2 ? 1 : 0);
272
273             return sortDirection * comparison;
274         }
275
276         nodes.sort(comparator);
277         dataGrid.removeChildren();
278         for (var i = 0; i < nodes.length; i++)
279             dataGrid.appendChild(nodes[i]);
280     }
281     return dataGrid;
282 }
283
284 WebInspector.DataGrid.prototype = {
285     get refreshCallback()
286     {
287         return this._refreshCallback;
288     },
289
290     set refreshCallback(refreshCallback)
291     {
292         this._refreshCallback = refreshCallback;
293     },
294
295     _ondblclick: function(event)
296     {
297         if (this._editing || this._editingNode)
298             return;
299
300         this._startEditing(event.target);
301     },
302
303     _startEditingColumnOfDataGridNode: function(node, column)
304     {
305         this._editing = true;
306         this._editingNode = node;
307         this._editingNode.select();
308
309         var element = this._editingNode._element.children[column];
310         WebInspector.startEditing(element, this._startEditingConfig(element));
311         window.getSelection().setBaseAndExtent(element, 0, element, 1);
312     },
313
314     _startEditing: function(target)
315     {
316         var element = target.enclosingNodeOrSelfWithNodeName("td");
317         if (!element)
318             return;
319
320         this._editingNode = this.dataGridNodeFromNode(target);
321         if (!this._editingNode) {
322             if (!this.creationNode)
323                 return;
324             this._editingNode = this.creationNode;
325         }
326
327         // Force editing the 1st column when editing the creation node
328         if (this._editingNode.isCreationNode)
329             return this._startEditingColumnOfDataGridNode(this._editingNode, 0);
330
331         this._editing = true;
332         WebInspector.startEditing(element, this._startEditingConfig(element));
333
334         window.getSelection().setBaseAndExtent(element, 0, element, 1);
335     },
336
337     _startEditingConfig: function(element)
338     {
339         return new WebInspector.EditingConfig(this._editingCommitted.bind(this), this._editingCancelled.bind(this), element.textContent);
340     },
341
342     _editingCommitted: function(element, newText, oldText, context, moveDirection)
343     {
344         // FIXME: We need more column identifiers here throughout this function.
345         // Not needed yet since only editable DataGrid is DOM Storage, which is Key - Value.
346
347         var columnIdentifier = element.__columnIdentifier;
348
349         var textBeforeEditing = this._editingNode.data[columnIdentifier] || "";
350         var currentEditingNode = this._editingNode;
351
352         function moveToNextIfNeeded(wasChange) {
353             if (!moveDirection)
354                 return;
355
356             if (moveDirection === "forward") {
357                 if (currentEditingNode.isCreationNode && columnIdentifier === 0 && !wasChange)
358                     return;
359
360                 if (columnIdentifier === 0)
361                     return this._startEditingColumnOfDataGridNode(currentEditingNode, 1);
362
363                 var nextDataGridNode = currentEditingNode.traverseNextNode(true, null, true);
364                 if (nextDataGridNode)
365                     return this._startEditingColumnOfDataGridNode(nextDataGridNode, 0);
366                 if (currentEditingNode.isCreationNode && wasChange) {
367                     this.addCreationNode(false);
368                     return this._startEditingColumnOfDataGridNode(this.creationNode, 0);
369                 }
370                 return;
371             }
372
373             if (moveDirection === "backward") {
374                 if (columnIdentifier === 1)
375                     return this._startEditingColumnOfDataGridNode(currentEditingNode, 0);
376                     var nextDataGridNode = currentEditingNode.traversePreviousNode(true, null, true);
377
378                 if (nextDataGridNode)
379                     return this._startEditingColumnOfDataGridNode(nextDataGridNode, 1);
380                 return;
381             }
382         }
383
384         if (textBeforeEditing == newText) {
385             this._editingCancelled(element);
386             moveToNextIfNeeded.call(this, false);
387             return;
388         }
389
390         // Update the text in the datagrid that we typed
391         this._editingNode.data[columnIdentifier] = newText;
392
393         // Make the callback - expects an editing node (table row), the column number that is being edited,
394         // the text that used to be there, and the new text.
395         this._editCallback(this._editingNode, columnIdentifier, textBeforeEditing, newText);
396
397         if (this._editingNode.isCreationNode)
398             this.addCreationNode(false);
399
400         this._editingCancelled(element);
401         moveToNextIfNeeded.call(this, true);
402     },
403
404     _editingCancelled: function(element)
405     {
406         delete this._editing;
407         this._editingNode = null;
408     },
409
410     get sortColumnIdentifier()
411     {
412         if (!this._sortColumnCell)
413             return null;
414         return this._sortColumnCell.columnIdentifier;
415     },
416
417     get sortOrder()
418     {
419         if (!this._sortColumnCell || this._sortColumnCell.classList.contains("sort-ascending"))
420             return "ascending";
421         if (this._sortColumnCell.classList.contains("sort-descending"))
422             return "descending";
423         return null;
424     },
425
426     get headerTableBody()
427     {
428         if ("_headerTableBody" in this)
429             return this._headerTableBody;
430
431         this._headerTableBody = this._headerTable.getElementsByTagName("tbody")[0];
432         if (!this._headerTableBody) {
433             this._headerTableBody = this.element.ownerDocument.createElement("tbody");
434             this._headerTable.insertBefore(this._headerTableBody, this._headerTable.tFoot);
435         }
436
437         return this._headerTableBody;
438     },
439
440     get dataTableBody()
441     {
442         if ("_dataTableBody" in this)
443             return this._dataTableBody;
444
445         this._dataTableBody = this._dataTable.getElementsByTagName("tbody")[0];
446         if (!this._dataTableBody) {
447             this._dataTableBody = this.element.ownerDocument.createElement("tbody");
448             this._dataTable.insertBefore(this._dataTableBody, this._dataTable.tFoot);
449         }
450
451         return this._dataTableBody;
452     },
453
454     /**
455      * @param {number=} maxDescentLevel
456      */
457     autoSizeColumns: function(minPercent, maxPercent, maxDescentLevel)
458     {
459         if (minPercent)
460             minPercent = Math.min(minPercent, Math.floor(100 / this._columnCount));
461         var widths = {};
462         var columns = this.columns;
463         for (var columnIdentifier in columns)
464             widths[columnIdentifier] = (columns[columnIdentifier].title || "").length;
465
466         var children = maxDescentLevel ? this._enumerateChildren(this, [], maxDescentLevel + 1) : this.children;
467         for (var i = 0; i < children.length; ++i) {
468             var node = children[i];
469             for (var columnIdentifier in columns) {
470                 var text = node.data[columnIdentifier] || "";
471                 if (text.length > widths[columnIdentifier])
472                     widths[columnIdentifier] = text.length;
473             }
474         }
475
476         var totalColumnWidths = 0;
477         for (var columnIdentifier in columns)
478             totalColumnWidths += widths[columnIdentifier];
479
480         var recoupPercent = 0;
481         for (var columnIdentifier in columns) {
482             var width = Math.round(100 * widths[columnIdentifier] / totalColumnWidths);
483             if (minPercent && width < minPercent) {
484                 recoupPercent += (minPercent - width);
485                 width = minPercent;
486             } else if (maxPercent && width > maxPercent) {
487                 recoupPercent -= (width - maxPercent);
488                 width = maxPercent;
489             }
490             widths[columnIdentifier] = width;
491         }
492
493         while (minPercent && recoupPercent > 0) {
494             for (var columnIdentifier in columns) {
495                 if (widths[columnIdentifier] > minPercent) {
496                     --widths[columnIdentifier];
497                     --recoupPercent;
498                     if (!recoupPercent)
499                         break;
500                 }
501             }
502         }
503
504         while (maxPercent && recoupPercent < 0) {
505             for (var columnIdentifier in columns) {
506                 if (widths[columnIdentifier] < maxPercent) {
507                     ++widths[columnIdentifier];
508                     ++recoupPercent;
509                     if (!recoupPercent)
510                         break;
511                 }
512             }
513         }
514
515         for (var columnIdentifier in columns)
516             columns[columnIdentifier].element.style.width = widths[columnIdentifier] + "%";
517         this._columnWidthsInitialized = false;
518         this.updateLayout();
519     },
520
521     _enumerateChildren: function(rootNode, result, maxLevel)
522     {
523         if (!rootNode.root)
524             result.push(rootNode);
525         if (!maxLevel)
526             return;
527         for (var i = 0; i < rootNode.children.length; ++i)
528             this._enumerateChildren(rootNode.children[i], result, maxLevel - 1);
529         return result;
530     },
531
532     // Updates the widths of the table, including the positions of the column
533     // resizers.
534     //
535     // IMPORTANT: This function MUST be called once after the element of the
536     // DataGrid is attached to its parent element and every subsequent time the
537     // width of the parent element is changed in order to make it possible to
538     // resize the columns.
539     //
540     // If this function is not called after the DataGrid is attached to its
541     // parent element, then the DataGrid's columns will not be resizable.
542     updateLayout: function()
543     {
544         // Do not attempt to use offsetes if we're not attached to the document tree yet.
545         if (!this._columnWidthsInitialized && this.element.offsetWidth) {
546             // Give all the columns initial widths now so that during a resize,
547             // when the two columns that get resized get a percent value for
548             // their widths, all the other columns already have percent values
549             // for their widths.
550             var headerTableColumns = this._headerTableColumnGroup.children;
551             var tableWidth = this._dataTable.offsetWidth;
552             var numColumns = headerTableColumns.length;
553             for (var i = 0; i < numColumns; i++) {
554                 var headerCell = this.headerTableBody.rows[0].cells[i]
555                 if (this._isColumnVisible(headerCell.columnIdentifier)) {
556                     var columnWidth = headerCell.offsetWidth;
557                     var percentWidth = ((columnWidth / tableWidth) * 100) + "%";
558                     this._headerTableColumnGroup.children[i].style.width = percentWidth;
559                     this._dataTableColumnGroup.children[i].style.width = percentWidth;
560                 } else {
561                     this._headerTableColumnGroup.children[i].style.width = 0;
562                     this._dataTableColumnGroup.children[i].style.width = 0;
563                 }
564             }
565
566             this._columnWidthsInitialized = true;
567         }
568
569         this._positionResizers();
570         this.dispatchEventToListeners(WebInspector.DataGrid.Event.DidLayout);
571     },
572
573     columnWidthsMap: function()
574     {
575         var result = {};
576         for (var columnIdentifier in this.columns) {
577             var column = this.columns[columnIdentifier];
578             var width = this._headerTableColumnGroup.children[column.ordinal].style.width;
579             result[columnIdentifier] = parseFloat(width);
580         }
581         return result;
582     },
583
584     applyColumnWidthsMap: function(columnWidthsMap)
585     {
586         for (var columnIdentifier in this.columns) {
587             var column = this.columns[columnIdentifier];
588             var width = (columnWidthsMap[columnIdentifier] || 0) + "%";
589             this._headerTableColumnGroup.children[column.ordinal].style.width = width;
590             this._dataTableColumnGroup.children[column.ordinal].style.width = width;
591         }
592
593         this.updateLayout();
594     },
595
596     _isColumnVisible: function(columnIdentifier)
597     {
598         return !(columnIdentifier in this._hiddenColumns);
599     },
600
601     _showColumn: function(columnIdentifier)
602     {
603         delete this._hiddenColumns[columnIdentifier];
604     },
605
606     _hideColumn: function(columnIdentifier)
607     {
608         this._hiddenColumns[columnIdentifier] = true;
609
610         var column = this.columns[columnIdentifier];
611         var columnElement = column.element;
612         columnElement.style.width = 0;
613
614         var columnBodyElement = column.bodyElement;
615         columnBodyElement.style.width = 0;
616
617         this._columnWidthsInitialized = false;
618     },
619
620     get scrollContainer()
621     {
622         return this._scrollContainer;
623     },
624
625     isScrolledToLastRow: function()
626     {
627         return this._scrollContainer.isScrolledToBottom();
628     },
629
630     scrollToLastRow: function()
631     {
632         this._scrollContainer.scrollTop = this._scrollContainer.scrollHeight - this._scrollContainer.offsetHeight;
633     },
634
635     _positionResizers: function()
636     {
637         var headerTableColumns = this._headerTableColumnGroup.children;
638         var numColumns = headerTableColumns.length;
639         var left = 0;
640         var previousResizer = null;
641
642         // Make n - 1 resizers for n columns.
643         for (var i = 0; i < numColumns - 1; i++) {
644             var resizer = this.resizers[i];
645
646             if (!resizer) {
647                 // This is the first call to updateWidth, so the resizers need
648                 // to be created.
649                 resizer = document.createElement("div");
650                 resizer.classList.add("data-grid-resizer");
651                 // This resizer is associated with the column to its right.
652                 resizer.addEventListener("mousedown", this._startResizerDragging.bind(this), false);
653                 this.element.appendChild(resizer);
654                 this.resizers[i] = resizer;
655             }
656
657             // Get the width of the cell in the first (and only) row of the
658             // header table in order to determine the width of the column, since
659             // it is not possible to query a column for its width.
660             left += this.headerTableBody.rows[0].cells[i].offsetWidth;
661
662             var columnIsVisible = this._isColumnVisible(this._columnsArray[i].identifier);
663             if (columnIsVisible) {
664                 resizer.style.removeProperty("display");
665                 resizer.style.left = left + "px";
666                 resizer.leftNeighboringColumnID = i;
667                 if (previousResizer)
668                     previousResizer.rightNeighboringColumnID = i;
669                 previousResizer = resizer;
670             } else {
671                 resizer.style.setProperty("display", "none");
672                 resizer.leftNeighboringColumnID = 0;
673                 resizer.rightNeighboringColumnID = 0;
674             }
675         }
676         if (previousResizer)
677             previousResizer.rightNeighboringColumnID = numColumns - 1;
678     },
679
680     addCreationNode: function(hasChildren)
681     {
682         if (this.creationNode)
683             this.creationNode.makeNormal();
684
685         var emptyData = {};
686         for (var column in this.columns)
687             emptyData[column] = '';
688         this.creationNode = new WebInspector.CreationDataGridNode(emptyData, hasChildren);
689         this.appendChild(this.creationNode);
690     },
691
692     appendChild: function(child)
693     {
694         this.insertChild(child, this.children.length);
695     },
696
697     insertChild: function(child, index)
698     {
699         if (!child)
700             throw("insertChild: Node can't be undefined or null.");
701         if (child.parent === this)
702             throw("insertChild: Node is already a child of this node.");
703
704         if (child.parent)
705             child.parent.removeChild(child);
706
707         this.children.splice(index, 0, child);
708         this.hasChildren = true;
709
710         child.parent = this;
711         child.dataGrid = this.dataGrid;
712         child._recalculateSiblings(index);
713
714         delete child._depth;
715         delete child._revealed;
716         delete child._attached;
717         child._shouldRefreshChildren = true;
718
719         var current = child.children[0];
720         while (current) {
721             current.dataGrid = this.dataGrid;
722             delete current._depth;
723             delete current._revealed;
724             delete current._attached;
725             current._shouldRefreshChildren = true;
726             current = current.traverseNextNode(false, child, true);
727         }
728
729         if (this.expanded)
730             child._attach();
731     },
732
733     removeChild: function(child)
734     {
735         if (!child)
736             throw("removeChild: Node can't be undefined or null.");
737         if (child.parent !== this)
738             throw("removeChild: Node is not a child of this node.");
739
740         child.deselect();
741         child._detach();
742
743         this.children.remove(child, true);
744
745         if (child.previousSibling)
746             child.previousSibling.nextSibling = child.nextSibling;
747         if (child.nextSibling)
748             child.nextSibling.previousSibling = child.previousSibling;
749
750         child.dataGrid = null;
751         child.parent = null;
752         child.nextSibling = null;
753         child.previousSibling = null;
754
755         if (this.children.length <= 0)
756             this.hasChildren = false;
757
758         if (this.creationNode === child)
759             delete this.creationNode;
760     },
761
762     removeChildren: function()
763     {
764         for (var i = 0; i < this.children.length; ++i) {
765             var child = this.children[i];
766             child.deselect();
767             child._detach();
768
769             child.dataGrid = null;
770             child.parent = null;
771             child.nextSibling = null;
772             child.previousSibling = null;
773         }
774
775         this.children = [];
776         this.hasChildren = false;
777     },
778
779     removeChildrenRecursive: function()
780     {
781         var childrenToRemove = this.children;
782
783         var child = this.children[0];
784         while (child) {
785             if (child.children.length)
786                 childrenToRemove = childrenToRemove.concat(child.children);
787             child = child.traverseNextNode(false, this, true);
788         }
789
790         for (var i = 0; i < childrenToRemove.length; ++i) {
791             child = childrenToRemove[i];
792             child.deselect();
793             child._detach();
794
795             child.children = [];
796             child.dataGrid = null;
797             child.parent = null;
798             child.nextSibling = null;
799             child.previousSibling = null;
800         }
801
802         this.children = [];
803     },
804
805     sortNodes: function(comparator, reverseMode)
806     {
807         function comparatorWrapper(a, b)
808         {
809             if (a._dataGridNode._data.summaryRow)
810                 return 1;
811             if (b._dataGridNode._data.summaryRow)
812                 return -1;
813
814             var aDataGirdNode = a._dataGridNode;
815             var bDataGirdNode = b._dataGridNode;
816             return reverseMode ? comparator(bDataGirdNode, aDataGirdNode) : comparator(aDataGirdNode, bDataGirdNode);
817         }
818
819         var tbody = this.dataTableBody;
820         var tbodyParent = tbody.parentElement;
821         tbodyParent.removeChild(tbody);
822
823         var childNodes = tbody.childNodes;
824         var fillerRow = childNodes.lastValue;
825
826         var sortedRows = Array.prototype.slice.call(childNodes, 0, childNodes.length - 1);
827         sortedRows.sort(comparatorWrapper);
828         var sortedRowsLength = sortedRows.length;
829
830         tbody.removeChildren();
831         var previousSiblingNode = null;
832         for (var i = 0; i < sortedRowsLength; ++i) {
833             var row = sortedRows[i];
834             var node = row._dataGridNode;
835             node.previousSibling = previousSiblingNode;
836             if (previousSiblingNode)
837                 previousSiblingNode.nextSibling = node;
838             tbody.appendChild(row);
839             previousSiblingNode = node;
840         }
841         if (previousSiblingNode)
842             previousSiblingNode.nextSibling = null;
843
844         tbody.appendChild(fillerRow);
845         tbodyParent.appendChild(tbody);
846     },
847
848     _keyDown: function(event)
849     {
850         if (!this.selectedNode || event.shiftKey || event.metaKey || event.ctrlKey || this._editing)
851             return;
852
853         var handled = false;
854         var nextSelectedNode;
855         if (event.keyIdentifier === "Up" && !event.altKey) {
856             nextSelectedNode = this.selectedNode.traversePreviousNode(true);
857             while (nextSelectedNode && !nextSelectedNode.selectable)
858                 nextSelectedNode = nextSelectedNode.traversePreviousNode(true);
859             handled = nextSelectedNode ? true : false;
860         } else if (event.keyIdentifier === "Down" && !event.altKey) {
861             nextSelectedNode = this.selectedNode.traverseNextNode(true);
862             while (nextSelectedNode && !nextSelectedNode.selectable)
863                 nextSelectedNode = nextSelectedNode.traverseNextNode(true);
864             handled = nextSelectedNode ? true : false;
865         } else if (event.keyIdentifier === "Left") {
866             if (this.selectedNode.expanded) {
867                 if (event.altKey)
868                     this.selectedNode.collapseRecursively();
869                 else
870                     this.selectedNode.collapse();
871                 handled = true;
872             } else if (this.selectedNode.parent && !this.selectedNode.parent.root) {
873                 handled = true;
874                 if (this.selectedNode.parent.selectable) {
875                     nextSelectedNode = this.selectedNode.parent;
876                     handled = nextSelectedNode ? true : false;
877                 } else if (this.selectedNode.parent)
878                     this.selectedNode.parent.collapse();
879             }
880         } else if (event.keyIdentifier === "Right") {
881             if (!this.selectedNode.revealed) {
882                 this.selectedNode.reveal();
883                 handled = true;
884             } else if (this.selectedNode.hasChildren) {
885                 handled = true;
886                 if (this.selectedNode.expanded) {
887                     nextSelectedNode = this.selectedNode.children[0];
888                     handled = nextSelectedNode ? true : false;
889                 } else {
890                     if (event.altKey)
891                         this.selectedNode.expandRecursively();
892                     else
893                         this.selectedNode.expand();
894                 }
895             }
896         } else if (event.keyCode === 8 || event.keyCode === 46) {
897             if (this._deleteCallback) {
898                 handled = true;
899                 this._deleteCallback(this.selectedNode);
900             }
901         } else if (isEnterKey(event)) {
902             if (this._editCallback) {
903                 handled = true;
904                 this._startEditing(this.selectedNode._element.children[0]);
905             }
906         }
907
908         if (nextSelectedNode) {
909             nextSelectedNode.reveal();
910             nextSelectedNode.select();
911         }
912
913         if (handled) {
914             event.preventDefault();
915             event.stopPropagation();
916         }
917     },
918
919     expand: function()
920     {
921         // This is the root, do nothing.
922     },
923
924     collapse: function()
925     {
926         // This is the root, do nothing.
927     },
928
929     reveal: function()
930     {
931         // This is the root, do nothing.
932     },
933
934     revealAndSelect: function()
935     {
936         // This is the root, do nothing.
937     },
938
939     dataGridNodeFromNode: function(target)
940     {
941         var rowElement = target.enclosingNodeOrSelfWithNodeName("tr");
942         return rowElement && rowElement._dataGridNode;
943     },
944
945     dataGridNodeFromPoint: function(x, y)
946     {
947         var node = this._dataTable.ownerDocument.elementFromPoint(x, y);
948         var rowElement = node.enclosingNodeOrSelfWithNodeName("tr");
949         return rowElement && rowElement._dataGridNode;
950     },
951
952     _clickInHeaderCell: function(event)
953     {
954         var cell = event.target.enclosingNodeOrSelfWithNodeName("th");
955         if (!cell || !cell.columnIdentifier || !cell.classList.contains("sortable"))
956             return;
957
958         var sortOrder = this.sortOrder;
959
960         if (this._sortColumnCell)
961             this._sortColumnCell.removeMatchingStyleClasses("sort-\\w+");
962
963         if (cell == this._sortColumnCell) {
964             if (sortOrder === "ascending")
965                 sortOrder = "descending";
966             else
967                 sortOrder = "ascending";
968         }
969
970         this._sortColumnCell = cell;
971
972         cell.classList.add("sort-" + sortOrder);
973     
974         this.dispatchEventToListeners(WebInspector.DataGrid.Event.SortChanged);
975     },
976
977     _mouseoverColumnCollapser: function(event)
978     {
979         var cell = event.target.enclosingNodeOrSelfWithNodeName("th");
980         if (!cell || !cell.collapsesGroup)
981             return;
982
983         cell.classList.add("mouse-over-collapser");
984     },
985
986     _mouseoutColumnCollapser: function(event)
987     {
988         var cell = event.target.enclosingNodeOrSelfWithNodeName("th");
989         if (!cell || !cell.collapsesGroup)
990             return;
991
992         cell.classList.remove("mouse-over-collapser");
993     },
994
995     _clickInColumnCollapser: function(event)
996     {
997         var cell = event.target.enclosingNodeOrSelfWithNodeName("th");
998         if (!cell || !cell.collapsesGroup)
999             return;
1000
1001         this._collapseColumnGroupWithCell(cell);
1002
1003         event.stopPropagation();
1004         event.preventDefault();
1005     },
1006
1007     collapseColumnGroup: function(columnGroup)
1008     {
1009         var collapserColumn = null;
1010         for (var columnIdentifier in this.columns) {
1011             var column = this.columns[columnIdentifier];
1012             if (column.collapsesGroup == columnGroup) {
1013                 collapserColumn = column;
1014                 break;
1015             }
1016         }
1017
1018         console.assert(collapserColumn);
1019         if (!collapserColumn)
1020             return;
1021
1022         var cell = this._headerTableHeaders[collapserColumn.identifier];
1023         this._collapseColumnGroupWithCell(cell);
1024     },
1025
1026     _collapseColumnGroupWithCell: function(cell)
1027     {
1028         var columnsWillCollapse = cell.classList.toggle("collapsed");
1029
1030         this.willToggleColumnGroup(cell.collapsesGroup, columnsWillCollapse);
1031
1032         var showOrHide = columnsWillCollapse ? this._hideColumn : this._showColumn;
1033         for (var columnIdentifier in this.columns) {
1034             var column = this.columns[columnIdentifier];
1035             if (column.group === cell.collapsesGroup)
1036                 showOrHide.call(this, columnIdentifier);
1037         }
1038
1039         var collapserButton = cell.querySelector(".collapser-button");
1040         if (collapserButton)
1041             collapserButton.title = columnsWillCollapse ? this._collapserButtonExpandColumnsToolTip() : this._collapserButtonCollapseColumnsToolTip();
1042
1043         this.didToggleColumnGroup(cell.collapsesGroup, columnsWillCollapse);
1044     },
1045
1046     _collapserButtonCollapseColumnsToolTip: function()
1047     {
1048         return WebInspector.UIString("Collapse columns");
1049     },
1050
1051     _collapserButtonExpandColumnsToolTip: function()
1052     {
1053         return WebInspector.UIString("Expand columns");
1054     },
1055
1056     willToggleColumnGroup: function(columnGroup, willCollapse)
1057     {
1058         // Implemented by subclasses if needed.
1059     },
1060
1061     didToggleColumnGroup: function(columnGroup, didCollapse)
1062     {
1063         // Implemented by subclasses if needed.
1064     },
1065
1066     isColumnSortColumn: function(columnIdentifier)
1067     {
1068         return this._sortColumnCell === this._headerTableHeaders[columnIdentifier];
1069     },
1070
1071     markColumnAsSortedBy: function(columnIdentifier, sortOrder)
1072     {
1073         if (this._sortColumnCell)
1074             this._sortColumnCell.removeMatchingStyleClasses("sort-\\w+");
1075         this._sortColumnCell = this._headerTableHeaders[columnIdentifier];
1076         this._sortColumnCell.classList.add("sort-" + sortOrder);
1077     },
1078
1079     headerTableHeader: function(columnIdentifier)
1080     {
1081         return this._headerTableHeaders[columnIdentifier];
1082     },
1083
1084     _generateSortIndicatorImagesIfNeeded: function()
1085     {
1086         if (WebInspector.DataGrid._generatedSortIndicatorImages)
1087             return;
1088
1089         WebInspector.DataGrid._generatedSortIndicatorImages = true;
1090
1091         var specifications = {};
1092         specifications["arrow"] = {
1093             fillColor: [81, 81, 81],
1094             shadowColor: [255, 255, 255, 0.5],
1095             shadowOffsetX: 0,
1096             shadowOffsetY: 1,
1097             shadowBlur: 0
1098         };
1099
1100         generateColoredImagesForCSS("Images/SortIndicatorDownArrow.svg", specifications, 9, 8, "data-grid-sort-indicator-down-");
1101         generateColoredImagesForCSS("Images/SortIndicatorUpArrow.svg", specifications, 9, 8, "data-grid-sort-indicator-up-");
1102     },
1103
1104     _mouseDownInDataTable: function(event)
1105     {
1106         var gridNode = this.dataGridNodeFromNode(event.target);
1107         if (!gridNode || !gridNode.selectable)
1108             return;
1109
1110         if (gridNode.isEventWithinDisclosureTriangle(event))
1111             return;
1112
1113         if (event.metaKey) {
1114             if (gridNode.selected)
1115                 gridNode.deselect();
1116             else
1117                 gridNode.select();
1118         } else
1119             gridNode.select();
1120     },
1121
1122     _contextMenuInDataTable: function(event)
1123     {
1124         var contextMenu = new WebInspector.ContextMenu(event);
1125
1126         var gridNode = this.dataGridNodeFromNode(event.target);
1127         if (this.dataGrid._refreshCallback && (!gridNode || gridNode !== this.creationNode))
1128             contextMenu.appendItem(WebInspector.UIString("Refresh"), this._refreshCallback.bind(this));
1129
1130         if (gridNode && gridNode.selectable && !gridNode.isEventWithinDisclosureTriangle(event)) {
1131             contextMenu.appendItem(WebInspector.UIString("Copy Row"), this._copyRow.bind(this, event.target));
1132
1133             if (this.dataGrid._editCallback) {
1134                 if (gridNode === this.creationNode)
1135                     contextMenu.appendItem(WebInspector.UIString("Add New"), this._startEditing.bind(this, event.target));
1136                 else {
1137                     var element = event.target.enclosingNodeOrSelfWithNodeName("td");
1138                     var columnIdentifier = element.__columnIdentifier;
1139                     var columnTitle = this.dataGrid.columns[columnIdentifier].title;
1140                     contextMenu.appendItem(WebInspector.UIString("Edit ā€œ%sā€").format(columnTitle), this._startEditing.bind(this, event.target));
1141                 }
1142             }
1143             if (this.dataGrid._deleteCallback && gridNode !== this.creationNode)
1144                 contextMenu.appendItem(WebInspector.UIString("Delete"), this._deleteCallback.bind(this, gridNode));
1145         }
1146
1147         contextMenu.show();
1148     },
1149
1150     _clickInDataTable: function(event)
1151     {
1152         var gridNode = this.dataGridNodeFromNode(event.target);
1153         if (!gridNode || !gridNode.hasChildren)
1154             return;
1155
1156         if (!gridNode.isEventWithinDisclosureTriangle(event))
1157             return;
1158
1159         if (gridNode.expanded) {
1160             if (event.altKey)
1161                 gridNode.collapseRecursively();
1162             else
1163                 gridNode.collapse();
1164         } else {
1165             if (event.altKey)
1166                 gridNode.expandRecursively();
1167             else
1168                 gridNode.expand();
1169         }
1170     },
1171
1172     _copyTextForDataGridNode: function(node)
1173     {
1174         var fields = [];
1175         for (var columnIdentifier in node.dataGrid.columns)
1176             fields.push(node.data[columnIdentifier] || "");
1177
1178         var tabSeparatedValues = fields.join("\t");
1179         return tabSeparatedValues;
1180     },
1181
1182     handleBeforeCopyEvent: function(event)
1183     {
1184         if (this.selectedNode && window.getSelection().isCollapsed)
1185             event.preventDefault();
1186     },
1187
1188     handleCopyEvent: function(event)
1189     {
1190         if (!this.selectedNode || !window.getSelection().isCollapsed)
1191             return;
1192
1193         var copyText = this._copyTextForDataGridNode(this.selectedNode);
1194         event.clipboardData.setData("text/plain", copyText);
1195         event.stopPropagation();
1196         event.preventDefault();
1197     },
1198
1199     _copyRow: function(target)
1200     {
1201         var gridNode = this.dataGridNodeFromNode(target);
1202         if (!gridNode)
1203             return;
1204
1205         var copyText = this._copyTextForDataGridNode(gridNode);
1206         InspectorFrontendHost.copyText(copyText);
1207     },
1208
1209     get resizeMethod()
1210     {
1211         if (typeof this._resizeMethod === "undefined")
1212             return WebInspector.DataGrid.ResizeMethod.Nearest;
1213         return this._resizeMethod;
1214     },
1215
1216     set resizeMethod(method)
1217     {
1218         this._resizeMethod = method;
1219     },
1220
1221     _startResizerDragging: function(event)
1222     {
1223         if (event.button !== 0 || event.ctrlKey)
1224             return;
1225
1226         this._currentResizer = event.target;
1227         if (!this._currentResizer.rightNeighboringColumnID)
1228             return;
1229
1230         WebInspector.elementDragStart(this._currentResizer, this._resizerDragging.bind(this),
1231             this._endResizerDragging.bind(this), event, "col-resize");
1232     },
1233
1234     _resizerDragging: function(event)
1235     {
1236         if (event.button !== 0)
1237             return;
1238
1239         var resizer = this._currentResizer;
1240         if (!resizer)
1241             return;
1242
1243         // Constrain the dragpoint to be within the containing div of the
1244         // datagrid.
1245         var dragPoint = event.clientX - this.element.totalOffsetLeft;
1246         // Constrain the dragpoint to be within the space made up by the
1247         // column directly to the left and the column directly to the right.
1248         var leftCellIndex = resizer.leftNeighboringColumnID;
1249         var rightCellIndex = resizer.rightNeighboringColumnID;
1250         var firstRowCells = this.headerTableBody.rows[0].cells;
1251         var leftEdgeOfPreviousColumn = 0;
1252         for (var i = 0; i < leftCellIndex; i++)
1253             leftEdgeOfPreviousColumn += firstRowCells[i].offsetWidth;
1254
1255         // Differences for other resize methods
1256         if (this.resizeMethod == WebInspector.DataGrid.ResizeMethod.Last) {
1257             rightCellIndex = this.resizers.length;
1258         } else if (this.resizeMethod == WebInspector.DataGrid.ResizeMethod.First) {
1259             leftEdgeOfPreviousColumn += firstRowCells[leftCellIndex].offsetWidth - firstRowCells[0].offsetWidth;
1260             leftCellIndex = 0;
1261         }
1262
1263         var rightEdgeOfNextColumn = leftEdgeOfPreviousColumn + firstRowCells[leftCellIndex].offsetWidth + firstRowCells[rightCellIndex].offsetWidth;
1264
1265         // Give each column some padding so that they don't disappear.
1266         var leftMinimum = leftEdgeOfPreviousColumn + this.ColumnResizePadding;
1267         var rightMaximum = rightEdgeOfNextColumn - this.ColumnResizePadding;
1268
1269         dragPoint = Number.constrain(dragPoint, leftMinimum, rightMaximum);
1270
1271         resizer.style.left = (dragPoint - this.CenterResizerOverBorderAdjustment) + "px";
1272
1273         var percentLeftColumn = (((dragPoint - leftEdgeOfPreviousColumn) / this._dataTable.offsetWidth) * 100) + "%";
1274         this._headerTableColumnGroup.children[leftCellIndex].style.width = percentLeftColumn;
1275         this._dataTableColumnGroup.children[leftCellIndex].style.width = percentLeftColumn;
1276
1277         var percentRightColumn = (((rightEdgeOfNextColumn - dragPoint) / this._dataTable.offsetWidth) * 100) + "%";
1278         this._headerTableColumnGroup.children[rightCellIndex].style.width =  percentRightColumn;
1279         this._dataTableColumnGroup.children[rightCellIndex].style.width = percentRightColumn;
1280
1281         this._positionResizers();
1282         event.preventDefault();
1283         this.dispatchEventToListeners(WebInspector.DataGrid.Event.DidLayout);
1284     },
1285
1286     _endResizerDragging: function(event)
1287     {
1288         if (event.button !== 0)
1289             return;
1290
1291         WebInspector.elementDragEnd(event);
1292         this._currentResizer = null;
1293         this.dispatchEventToListeners(WebInspector.DataGrid.Event.DidLayout);
1294     },
1295
1296     ColumnResizePadding: 10,
1297
1298     CenterResizerOverBorderAdjustment: 3,
1299 }
1300
1301 WebInspector.DataGrid.ResizeMethod = {
1302     Nearest: "nearest",
1303     First: "first",
1304     Last: "last"
1305 };
1306
1307 WebInspector.DataGrid.prototype.__proto__ = WebInspector.Object.prototype;
1308
1309 /**
1310  * @constructor
1311  * @extends {WebInspector.Object}
1312  * @param {boolean=} hasChildren
1313  */
1314 WebInspector.DataGridNode = function(data, hasChildren)
1315 {
1316     this._expanded = false;
1317     this._selected = false;
1318     this._shouldRefreshChildren = true;
1319     this._data = data || {};
1320     this.hasChildren = hasChildren || false;
1321     this.children = [];
1322     this.dataGrid = null;
1323     this.parent = null;
1324     this.previousSibling = null;
1325     this.nextSibling = null;
1326     this.disclosureToggleWidth = 10;
1327 }
1328
1329 WebInspector.DataGridNode.prototype = {
1330     get selectable()
1331     {
1332         return !this._element || !this._element.classList.contains("hidden");
1333     },
1334
1335     get element()
1336     {
1337         if (this._element)
1338             return this._element;
1339
1340         if (!this.dataGrid)
1341             return null;
1342
1343         this._element = document.createElement("tr");
1344         this._element._dataGridNode = this;
1345
1346         if (this.hasChildren)
1347             this._element.classList.add("parent");
1348         if (this.expanded)
1349             this._element.classList.add("expanded");
1350         if (this.selected)
1351             this._element.classList.add("selected");
1352         if (this.revealed)
1353             this._element.classList.add("revealed");
1354
1355         this.createCells();
1356         return this._element;
1357     },
1358
1359     createCells: function()
1360     {
1361         for (var columnIdentifier in this.dataGrid.columns) {
1362             var cell = this.createCell(columnIdentifier);
1363             this._element.appendChild(cell);
1364         }
1365     },
1366
1367     get data()
1368     {
1369         return this._data;
1370     },
1371
1372     set data(x)
1373     {
1374         this._data = x || {};
1375         this.refresh();
1376     },
1377
1378     get revealed()
1379     {
1380         if ("_revealed" in this)
1381             return this._revealed;
1382
1383         var currentAncestor = this.parent;
1384         while (currentAncestor && !currentAncestor.root) {
1385             if (!currentAncestor.expanded) {
1386                 this._revealed = false;
1387                 return false;
1388             }
1389
1390             currentAncestor = currentAncestor.parent;
1391         }
1392
1393         this._revealed = true;
1394         return true;
1395     },
1396
1397     set hasChildren(x)
1398     {
1399         if (this._hasChildren === x)
1400             return;
1401
1402         this._hasChildren = x;
1403
1404         if (!this._element)
1405             return;
1406
1407         if (this._hasChildren)
1408         {
1409             this._element.classList.add("parent");
1410             if (this.expanded)
1411                 this._element.classList.add("expanded");
1412         }
1413         else
1414         {
1415             this._element.classList.remove("parent");
1416             this._element.classList.remove("expanded");
1417         }
1418     },
1419
1420     get hasChildren()
1421     {
1422         return this._hasChildren;
1423     },
1424
1425     set revealed(x)
1426     {
1427         if (this._revealed === x)
1428             return;
1429
1430         this._revealed = x;
1431
1432         if (this._element) {
1433             if (this._revealed)
1434                 this._element.classList.add("revealed");
1435             else
1436                 this._element.classList.remove("revealed");
1437         }
1438
1439         for (var i = 0; i < this.children.length; ++i)
1440             this.children[i].revealed = x && this.expanded;
1441     },
1442
1443     get depth()
1444     {
1445         if ("_depth" in this)
1446             return this._depth;
1447         if (this.parent && !this.parent.root)
1448             this._depth = this.parent.depth + 1;
1449         else
1450             this._depth = 0;
1451         return this._depth;
1452     },
1453
1454     get leftPadding()
1455     {
1456         if (typeof(this._leftPadding) === "number")
1457             return this._leftPadding;
1458         
1459         this._leftPadding = this.depth * this.dataGrid.indentWidth;
1460         return this._leftPadding;
1461     },
1462
1463     get shouldRefreshChildren()
1464     {
1465         return this._shouldRefreshChildren;
1466     },
1467
1468     set shouldRefreshChildren(x)
1469     {
1470         this._shouldRefreshChildren = x;
1471         if (x && this.expanded)
1472             this.expand();
1473     },
1474
1475     get selected()
1476     {
1477         return this._selected;
1478     },
1479
1480     set selected(x)
1481     {
1482         if (x)
1483             this.select();
1484         else
1485             this.deselect();
1486     },
1487
1488     get expanded()
1489     {
1490         return this._expanded;
1491     },
1492
1493     set expanded(x)
1494     {
1495         if (x)
1496             this.expand();
1497         else
1498             this.collapse();
1499     },
1500
1501     refresh: function()
1502     {
1503         if (!this._element || !this.dataGrid)
1504             return;
1505
1506         this._element.removeChildren();
1507         this.createCells();
1508     },
1509
1510     updateLayout: function()
1511     {
1512         // Implemented by subclasses if needed.
1513     },
1514
1515     createCell: function(columnIdentifier)
1516     {
1517         var cell = document.createElement("td");
1518         cell.className = columnIdentifier + "-column";
1519         cell.__columnIdentifier = columnIdentifier;
1520
1521         var alignment = this.dataGrid.aligned[columnIdentifier];
1522         if (alignment)
1523             cell.classList.add(alignment);
1524
1525         var group = this.dataGrid.groups[columnIdentifier];
1526         if (group)
1527             cell.classList.add("column-group-" + group);
1528
1529         var div = document.createElement("div");
1530         var content = this.createCellContent(columnIdentifier, cell);
1531         div.appendChild(content instanceof Node ? content : document.createTextNode(content));
1532         cell.appendChild(div);
1533
1534         if (columnIdentifier === this.dataGrid.disclosureColumnIdentifier) {
1535             cell.classList.add("disclosure");
1536             if (this.leftPadding)
1537                 cell.style.setProperty("padding-left", this.leftPadding + "px");
1538         }
1539
1540         return cell;
1541     },
1542
1543     createCellContent: function(columnIdentifier)
1544     {
1545         return this.data[columnIdentifier] || "\u200b"; // Zero width space to keep the cell from collapsing.
1546     },
1547
1548     elementWithColumnIdentifier: function(columnIdentifier)
1549     {
1550         var index = Object.keys(this.dataGrid.columns).indexOf(columnIdentifier);
1551         if (index === -1)
1552             return null;
1553
1554         return this._element.children[index];
1555     },
1556
1557     // Share these functions with DataGrid. They are written to work with a DataGridNode this object.
1558     appendChild: WebInspector.DataGrid.prototype.appendChild,
1559     insertChild: WebInspector.DataGrid.prototype.insertChild,
1560     removeChild: WebInspector.DataGrid.prototype.removeChild,
1561     removeChildren: WebInspector.DataGrid.prototype.removeChildren,
1562     removeChildrenRecursive: WebInspector.DataGrid.prototype.removeChildrenRecursive,
1563
1564     _recalculateSiblings: function(myIndex)
1565     {
1566         if (!this.parent)
1567             return;
1568
1569         var previousChild = (myIndex > 0 ? this.parent.children[myIndex - 1] : null);
1570
1571         if (previousChild) {
1572             previousChild.nextSibling = this;
1573             this.previousSibling = previousChild;
1574         } else
1575             this.previousSibling = null;
1576
1577         var nextChild = this.parent.children[myIndex + 1];
1578
1579         if (nextChild) {
1580             nextChild.previousSibling = this;
1581             this.nextSibling = nextChild;
1582         } else
1583             this.nextSibling = null;
1584     },
1585
1586     collapse: function()
1587     {
1588         if (this._element)
1589             this._element.classList.remove("expanded");
1590
1591         this._expanded = false;
1592
1593         for (var i = 0; i < this.children.length; ++i)
1594             this.children[i].revealed = false;
1595
1596         this.dispatchEventToListeners("collapsed");
1597
1598         if (this.dataGrid)
1599             this.dataGrid.dispatchEventToListeners(WebInspector.DataGrid.Event.CollapsedNode, {dataGridNode: this});
1600     },
1601
1602     collapseRecursively: function()
1603     {
1604         var item = this;
1605         while (item) {
1606             if (item.expanded)
1607                 item.collapse();
1608             item = item.traverseNextNode(false, this, true);
1609         }
1610     },
1611
1612     expand: function()
1613     {
1614         if (!this.hasChildren || this.expanded)
1615             return;
1616
1617         if (this.revealed && !this._shouldRefreshChildren)
1618             for (var i = 0; i < this.children.length; ++i)
1619                 this.children[i].revealed = true;
1620
1621         if (this._shouldRefreshChildren) {
1622             for (var i = 0; i < this.children.length; ++i)
1623                 this.children[i]._detach();
1624
1625             this.dispatchEventToListeners("populate");
1626
1627             if (this._attached) {
1628                 for (var i = 0; i < this.children.length; ++i) {
1629                     var child = this.children[i];
1630                     if (this.revealed)
1631                         child.revealed = true;
1632                     child._attach();
1633                 }
1634             }
1635
1636             delete this._shouldRefreshChildren;
1637         }
1638
1639         if (this._element)
1640             this._element.classList.add("expanded");
1641
1642         this._expanded = true;
1643
1644         this.dispatchEventToListeners("expanded");
1645
1646         if (this.dataGrid)
1647             this.dataGrid.dispatchEventToListeners(WebInspector.DataGrid.Event.ExpandedNode, {dataGridNode: this});
1648     },
1649
1650     expandRecursively: function()
1651     {
1652         var item = this;
1653         while (item) {
1654             item.expand();
1655             item = item.traverseNextNode(false, this);
1656         }
1657     },
1658
1659     reveal: function()
1660     {
1661         var currentAncestor = this.parent;
1662         while (currentAncestor && !currentAncestor.root) {
1663             if (!currentAncestor.expanded)
1664                 currentAncestor.expand();
1665             currentAncestor = currentAncestor.parent;
1666         }
1667
1668         this.element.scrollIntoViewIfNeeded(false);
1669
1670         this.dispatchEventToListeners("revealed");
1671     },
1672
1673     /**
1674      * @param {boolean=} supressSelectedEvent
1675      */
1676     select: function(supressSelectedEvent)
1677     {
1678         if (!this.dataGrid || !this.selectable || this.selected)
1679             return;
1680
1681         if (this.dataGrid.selectedNode)
1682             this.dataGrid.selectedNode.deselect();
1683
1684         this._selected = true;
1685         this.dataGrid.selectedNode = this;
1686
1687         if (this._element)
1688             this._element.classList.add("selected");
1689
1690         if (!supressSelectedEvent)
1691             this.dataGrid.dispatchEventToListeners(WebInspector.DataGrid.Event.SelectedNodeChanged);
1692     },
1693
1694     revealAndSelect: function()
1695     {
1696         this.reveal();
1697         this.select();
1698     },
1699
1700     /**
1701      * @param {boolean=} supressDeselectedEvent
1702      */
1703     deselect: function(supressDeselectedEvent)
1704     {
1705         if (!this.dataGrid || this.dataGrid.selectedNode !== this || !this.selected)
1706             return;
1707
1708         this._selected = false;
1709         this.dataGrid.selectedNode = null;
1710
1711         if (this._element)
1712             this._element.classList.remove("selected");
1713
1714         if (!supressDeselectedEvent)
1715             this.dataGrid.dispatchEventToListeners(WebInspector.DataGrid.Event.SelectedNodeChanged);
1716     },
1717
1718     traverseNextNode: function(skipHidden, stayWithin, dontPopulate, info)
1719     {
1720         if (!dontPopulate && this.hasChildren)
1721             this.dispatchEventToListeners("populate");
1722
1723         if (info)
1724             info.depthChange = 0;
1725
1726         var node = (!skipHidden || this.revealed) ? this.children[0] : null;
1727         if (node && (!skipHidden || this.expanded)) {
1728             if (info)
1729                 info.depthChange = 1;
1730             return node;
1731         }
1732
1733         if (this === stayWithin)
1734             return null;
1735
1736         node = (!skipHidden || this.revealed) ? this.nextSibling : null;
1737         if (node)
1738             return node;
1739
1740         node = this;
1741         while (node && !node.root && !((!skipHidden || node.revealed) ? node.nextSibling : null) && node.parent !== stayWithin) {
1742             if (info)
1743                 info.depthChange -= 1;
1744             node = node.parent;
1745         }
1746
1747         if (!node)
1748             return null;
1749
1750         return (!skipHidden || node.revealed) ? node.nextSibling : null;
1751     },
1752
1753     traversePreviousNode: function(skipHidden, dontPopulate)
1754     {
1755         var node = (!skipHidden || this.revealed) ? this.previousSibling : null;
1756         if (!dontPopulate && node && node.hasChildren)
1757             node.dispatchEventToListeners("populate");
1758
1759         while (node && ((!skipHidden || (node.revealed && node.expanded)) ? node.children.lastValue : null)) {
1760             if (!dontPopulate && node.hasChildren)
1761                 node.dispatchEventToListeners("populate");
1762             node = ((!skipHidden || (node.revealed && node.expanded)) ? node.children.lastValue : null);
1763         }
1764
1765         if (node)
1766             return node;
1767
1768         if (!this.parent || this.parent.root)
1769             return null;
1770
1771         return this.parent;
1772     },
1773
1774     isEventWithinDisclosureTriangle: function(event)
1775     {
1776         if (!this.hasChildren)
1777             return false;
1778         var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
1779         if (!cell.classList.contains("disclosure"))
1780             return false;
1781         
1782         var left = cell.totalOffsetLeft + this.leftPadding;
1783         return event.pageX >= left && event.pageX <= left + this.disclosureToggleWidth;
1784     },
1785
1786     _attach: function()
1787     {
1788         if (!this.dataGrid || this._attached)
1789             return;
1790
1791         this._attached = true;
1792
1793         var nextElement = null;
1794
1795         var previousGridNode = this.traversePreviousNode(true, true);
1796         if (previousGridNode && previousGridNode.element.parentNode)
1797             nextElement = previousGridNode.element.nextSibling;
1798         else if (!previousGridNode)
1799             nextElement = this.dataGrid.dataTableBody.firstChild;
1800
1801         // If there is no next grid node, then append before the last child since the last child is the filler row.
1802         console.assert(this.dataGrid.dataTableBody.lastChild.classList.contains("filler"));
1803         if (!nextElement)
1804             nextElement = this.dataGrid.dataTableBody.lastChild;
1805
1806         this.dataGrid.dataTableBody.insertBefore(this.element, nextElement);
1807
1808         if (this.expanded)
1809             for (var i = 0; i < this.children.length; ++i)
1810                 this.children[i]._attach();
1811     },
1812
1813     _detach: function()
1814     {
1815         if (!this._attached)
1816             return;
1817
1818         this._attached = false;
1819
1820         if (this._element && this._element.parentNode)
1821             this._element.parentNode.removeChild(this._element);
1822
1823         for (var i = 0; i < this.children.length; ++i)
1824             this.children[i]._detach();
1825     },
1826
1827     savePosition: function()
1828     {
1829         if (this._savedPosition)
1830             return;
1831
1832         if (!this.parent)
1833             throw("savePosition: Node must have a parent.");
1834         this._savedPosition = {
1835             parent: this.parent,
1836             index: this.parent.children.indexOf(this)
1837         };
1838     },
1839
1840     restorePosition: function()
1841     {
1842         if (!this._savedPosition)
1843             return;
1844
1845         if (this.parent !== this._savedPosition.parent)
1846             this._savedPosition.parent.insertChild(this, this._savedPosition.index);
1847
1848         delete this._savedPosition;
1849     }
1850 }
1851
1852 WebInspector.DataGridNode.prototype.__proto__ = WebInspector.Object.prototype;
1853
1854 /**
1855  * @constructor
1856  * @extends {WebInspector.DataGridNode}
1857  */
1858 WebInspector.CreationDataGridNode = function(data, hasChildren)
1859 {
1860     WebInspector.DataGridNode.call(this, data, hasChildren);
1861     this.isCreationNode = true;
1862 }
1863
1864 WebInspector.CreationDataGridNode.prototype = {
1865     makeNormal: function()
1866     {
1867         delete this.isCreationNode;
1868         delete this.makeNormal;
1869     }
1870 }
1871
1872 WebInspector.CreationDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype;