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