Initial patch.
[vuplus_webkit] / Source / WebCore / inspector / front-end / 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 WebInspector.DataGrid = function(columns, editCallback, deleteCallback)
27 {
28     this.element = document.createElement("div");
29     this.element.className = "data-grid";
30     this.element.tabIndex = 0;
31     this.element.addEventListener("keydown", this._keyDown.bind(this), false);
32
33     this._headerTable = document.createElement("table");
34     this._headerTable.className = "header";
35     this._headerTableHeaders = {};
36
37     this._dataTable = document.createElement("table");
38     this._dataTable.className = "data";
39
40     this._dataTable.addEventListener("mousedown", this._mouseDownInDataTable.bind(this), true);
41     this._dataTable.addEventListener("click", this._clickInDataTable.bind(this), true);
42
43     this._dataTable.addEventListener("contextmenu", this._contextMenuInDataTable.bind(this), true);
44
45     // FIXME: Add a createCallback which is different from editCallback and has different
46     // behavior when creating a new node.
47     if (editCallback) {
48         this._dataTable.addEventListener("dblclick", this._ondblclick.bind(this), false);
49         this._editCallback = editCallback;
50     }
51     if (deleteCallback)
52         this._deleteCallback = deleteCallback;
53
54     this.aligned = {};
55
56     this._scrollContainer = document.createElement("div");
57     this._scrollContainer.className = "data-container";
58     this._scrollContainer.appendChild(this._dataTable);
59
60     this.element.appendChild(this._headerTable);
61     this.element.appendChild(this._scrollContainer);
62
63     var headerRow = document.createElement("tr");
64     var columnGroup = document.createElement("colgroup");
65     this._columnCount = 0;
66
67     for (var columnIdentifier in columns) {
68         var column = columns[columnIdentifier];
69         if (column.disclosure)
70             this.disclosureColumnIdentifier = columnIdentifier;
71
72         var col = document.createElement("col");
73         if (column.width)
74             col.style.width = column.width;
75         column.element = col;
76         columnGroup.appendChild(col);
77
78         var cell = document.createElement("th");
79         cell.className = columnIdentifier + "-column";
80         cell.columnIdentifier = columnIdentifier;
81         this._headerTableHeaders[columnIdentifier] = cell;
82
83         var div = document.createElement("div");
84         if (column.titleDOMFragment)
85             div.appendChild(column.titleDOMFragment);
86         else
87             div.textContent = column.title;
88         cell.appendChild(div);
89
90         if (column.sort) {
91             cell.addStyleClass("sort-" + column.sort);
92             this._sortColumnCell = cell;
93         }
94
95         if (column.sortable) {
96             cell.addEventListener("click", this._clickInHeaderCell.bind(this), false);
97             cell.addStyleClass("sortable");
98         }
99
100         if (column.aligned)
101             this.aligned[columnIdentifier] = column.aligned;
102
103         headerRow.appendChild(cell);
104
105         ++this._columnCount;
106     }
107
108     columnGroup.span = this._columnCount;
109
110     var cell = document.createElement("th");
111     cell.className = "corner";
112     headerRow.appendChild(cell);
113
114     this._headerTableColumnGroup = columnGroup;
115     this._headerTable.appendChild(this._headerTableColumnGroup);
116     this.headerTableBody.appendChild(headerRow);
117
118     var fillerRow = document.createElement("tr");
119     fillerRow.className = "filler";
120
121     for (var columnIdentifier in columns) {
122         var column = columns[columnIdentifier];
123         var cell = document.createElement("td");
124         cell.className = columnIdentifier + "-column";
125         fillerRow.appendChild(cell);
126     }
127
128     this._dataTableColumnGroup = columnGroup.cloneNode(true);
129     this._dataTable.appendChild(this._dataTableColumnGroup);
130     this.dataTableBody.appendChild(fillerRow);
131
132     this.columns = columns || {};
133     this._columnsArray = [];
134     for (var columnIdentifier in columns) {
135         columns[columnIdentifier].ordinal = this._columnsArray.length;
136         columns[columnIdentifier].identifier = columnIdentifier;
137         this._columnsArray.push(columns[columnIdentifier]);
138     }
139
140     for (var i = 0; i < this._columnsArray.length; ++i)
141         this._columnsArray[i].bodyElement = this._dataTableColumnGroup.children[i];
142
143     this.children = [];
144     this.selectedNode = null;
145     this.expandNodesWhenArrowing = false;
146     this.root = true;
147     this.hasChildren = false;
148     this.expanded = true;
149     this.revealed = true;
150     this.selected = false;
151     this.dataGrid = this;
152     this.indentWidth = 15;
153     this.resizers = [];
154     this._columnWidthsInitialized = false;
155 }
156
157 WebInspector.DataGrid.prototype = {
158     get refreshCallback()
159     {
160         return this._refreshCallback;
161     },
162
163     set refreshCallback(refreshCallback)
164     {
165         this._refreshCallback = refreshCallback;
166     },
167
168     _ondblclick: function(event)
169     {
170         if (this._editing || this._editingNode)
171             return;
172
173         this._startEditing(event.target);
174     },
175
176     _startEditingColumnOfDataGridNode: function(node, column)
177     {
178         this._editing = true;
179         this._editingNode = node;
180         this._editingNode.select();
181
182         var element = this._editingNode._element.children[column];
183         WebInspector.startEditing(element, {
184             context: element.textContent,
185             commitHandler: this._editingCommitted.bind(this),
186             cancelHandler: this._editingCancelled.bind(this)
187         });
188         window.getSelection().setBaseAndExtent(element, 0, element, 1);
189     },
190
191     _startEditing: function(target)
192     {
193         var element = target.enclosingNodeOrSelfWithNodeName("td");
194         if (!element)
195             return;
196
197         this._editingNode = this.dataGridNodeFromNode(target);
198         if (!this._editingNode) {
199             if (!this.creationNode)
200                 return;
201             this._editingNode = this.creationNode;
202         }
203
204         // Force editing the 1st column when editing the creation node
205         if (this._editingNode.isCreationNode)
206             return this._startEditingColumnOfDataGridNode(this._editingNode, 0);
207
208         this._editing = true;
209         WebInspector.startEditing(element, {
210             context: element.textContent,
211             commitHandler: this._editingCommitted.bind(this),
212             cancelHandler: this._editingCancelled.bind(this)
213         });
214         window.getSelection().setBaseAndExtent(element, 0, element, 1);
215     },
216
217     _editingCommitted: function(element, newText, oldText, context, moveDirection)
218     {
219         // FIXME: We need more column identifiers here throughout this function.
220         // Not needed yet since only editable DataGrid is DOM Storage, which is Key - Value.
221
222         // FIXME: Better way to do this than regular expressions?
223         var columnIdentifier = parseInt(element.className.match(/\b(\d+)-column\b/)[1]);
224
225         var textBeforeEditing = this._editingNode.data[columnIdentifier];
226         var currentEditingNode = this._editingNode;
227
228         function moveToNextIfNeeded(wasChange) {
229             if (!moveDirection)
230                 return;
231
232             if (moveDirection === "forward") {
233                 if (currentEditingNode.isCreationNode && columnIdentifier === 0 && !wasChange)
234                     return;
235
236                 if (columnIdentifier === 0)
237                     return this._startEditingColumnOfDataGridNode(currentEditingNode, 1);
238
239                 var nextDataGridNode = currentEditingNode.traverseNextNode(true, null, true);
240                 if (nextDataGridNode)
241                     return this._startEditingColumnOfDataGridNode(nextDataGridNode, 0);
242                 if (currentEditingNode.isCreationNode && wasChange) {
243                     addCreationNode(false);
244                     return this._startEditingColumnOfDataGridNode(this.creationNode, 0);
245                 }
246                 return;
247             }
248
249             if (moveDirection === "backward") {
250                 if (columnIdentifier === 1)
251                     return this._startEditingColumnOfDataGridNode(currentEditingNode, 0);
252                     var nextDataGridNode = currentEditingNode.traversePreviousNode(true, null, true);
253
254                 if (nextDataGridNode)
255                     return this._startEditingColumnOfDataGridNode(nextDataGridNode, 1);
256                 return;
257             }
258         }
259
260         if (textBeforeEditing == newText) {
261             this._editingCancelled(element);
262             moveToNextIfNeeded.call(this, false);
263             return;
264         }
265
266         // Update the text in the datagrid that we typed
267         this._editingNode.data[columnIdentifier] = newText;
268
269         // Make the callback - expects an editing node (table row), the column number that is being edited,
270         // the text that used to be there, and the new text.
271         this._editCallback(this._editingNode, columnIdentifier, textBeforeEditing, newText);
272
273         if (this._editingNode.isCreationNode)
274             this.addCreationNode(false);
275
276         this._editingCancelled(element);
277         moveToNextIfNeeded.call(this, true);
278     },
279
280     _editingCancelled: function(element, context)
281     {
282         delete this._editing;
283         this._editingNode = null;
284     },
285
286     get sortColumnIdentifier()
287     {
288         if (!this._sortColumnCell)
289             return null;
290         return this._sortColumnCell.columnIdentifier;
291     },
292
293     get sortOrder()
294     {
295         if (!this._sortColumnCell || this._sortColumnCell.hasStyleClass("sort-ascending"))
296             return "ascending";
297         if (this._sortColumnCell.hasStyleClass("sort-descending"))
298             return "descending";
299         return null;
300     },
301
302     get headerTableBody()
303     {
304         if ("_headerTableBody" in this)
305             return this._headerTableBody;
306
307         this._headerTableBody = this._headerTable.getElementsByTagName("tbody")[0];
308         if (!this._headerTableBody) {
309             this._headerTableBody = this.element.ownerDocument.createElement("tbody");
310             this._headerTable.insertBefore(this._headerTableBody, this._headerTable.tFoot);
311         }
312
313         return this._headerTableBody;
314     },
315
316     get dataTableBody()
317     {
318         if ("_dataTableBody" in this)
319             return this._dataTableBody;
320
321         this._dataTableBody = this._dataTable.getElementsByTagName("tbody")[0];
322         if (!this._dataTableBody) {
323             this._dataTableBody = this.element.ownerDocument.createElement("tbody");
324             this._dataTable.insertBefore(this._dataTableBody, this._dataTable.tFoot);
325         }
326
327         return this._dataTableBody;
328     },
329
330     autoSizeColumns: function(minPercent, maxPercent, maxDescentLevel)
331     {
332         if (minPercent)
333             minPercent = Math.min(minPercent, Math.floor(100 / this._columnCount));
334         var widths = {};
335         var columns = this.columns;
336         for (var columnIdentifier in columns)
337             widths[columnIdentifier] = (columns[columnIdentifier].title || "").length;
338
339         var children = maxDescentLevel ? this._enumerateChildren(this, [], maxDescentLevel + 1) : this.children;
340         for (var i = 0; i < children.length; ++i) {
341             var node = children[i];
342             for (var columnIdentifier in columns) {
343                 var text = node.data[columnIdentifier] || "";
344                 if (text.length > widths[columnIdentifier])
345                     widths[columnIdentifier] = text.length;
346             }
347         }
348
349         var totalColumnWidths = 0;
350         for (var columnIdentifier in columns)
351             totalColumnWidths += widths[columnIdentifier];
352
353         var recoupPercent = 0;
354         for (var columnIdentifier in columns) {
355             var width = Math.round(100 * widths[columnIdentifier] / totalColumnWidths);
356             if (minPercent && width < minPercent) {
357                 recoupPercent += (minPercent - width);
358                 width = minPercent;
359             } else if (maxPercent && width > maxPercent) {
360                 recoupPercent -= (width - maxPercent);
361                 width = maxPercent;
362             }
363             widths[columnIdentifier] = width;
364         }
365
366         while (minPercent && recoupPercent > 0) {
367             for (var columnIdentifier in columns) {
368                 if (widths[columnIdentifier] > minPercent) {
369                     --widths[columnIdentifier];
370                     --recoupPercent;
371                     if (!recoupPercent)
372                         break;
373                 }
374             }
375         }
376
377         while (maxPercent && recoupPercent < 0) {
378             for (var columnIdentifier in columns) {
379                 if (widths[columnIdentifier] < maxPercent) {
380                     ++widths[columnIdentifier];
381                     ++recoupPercent;
382                     if (!recoupPercent)
383                         break;
384                 }
385             }
386         }
387
388         for (var columnIdentifier in columns)
389             columns[columnIdentifier].element.style.width = widths[columnIdentifier] + "%";
390         this._columnWidthsInitialized = false;
391         this.updateWidths();
392     },
393
394     _enumerateChildren: function(rootNode, result, maxLevel)
395     {
396         if (!rootNode.root)
397             result.push(rootNode);
398         if (!maxLevel)
399             return;
400         for (var i = 0; i < rootNode.children.length; ++i)
401             this._enumerateChildren(rootNode.children[i], result, maxLevel - 1);
402         return result;
403     },
404
405     // Updates the widths of the table, including the positions of the column
406     // resizers.
407     //
408     // IMPORTANT: This function MUST be called once after the element of the
409     // DataGrid is attached to its parent element and every subsequent time the
410     // width of the parent element is changed in order to make it possible to
411     // resize the columns.
412     //
413     // If this function is not called after the DataGrid is attached to its
414     // parent element, then the DataGrid's columns will not be resizable.
415     updateWidths: function()
416     {
417         var headerTableColumns = this._headerTableColumnGroup.children;
418
419         var tableWidth = this._dataTable.offsetWidth;
420         var numColumns = headerTableColumns.length;
421
422         // Do not attempt to use offsetes if we're not attached to the document tree yet.
423         if (!this._columnWidthsInitialized && this.element.offsetWidth) {
424             // Give all the columns initial widths now so that during a resize,
425             // when the two columns that get resized get a percent value for
426             // their widths, all the other columns already have percent values
427             // for their widths.
428             for (var i = 0; i < numColumns; i++) {
429                 var columnWidth = this.headerTableBody.rows[0].cells[i].offsetWidth;
430                 var percentWidth = ((columnWidth / tableWidth) * 100) + "%";
431                 this._headerTableColumnGroup.children[i].style.width = percentWidth;
432                 this._dataTableColumnGroup.children[i].style.width = percentWidth;
433             }
434             this._columnWidthsInitialized = true;
435         }
436         this._positionResizers();
437         this.dispatchEventToListeners("width changed");
438     },
439
440     columnWidthsMap: function()
441     {
442         var result = {};
443         for (var i = 0; i < this._columnsArray.length; ++i) {
444             var width = this._headerTableColumnGroup.children[i].style.width;
445             result[this._columnsArray[i].columnIdentifier] = parseFloat(width);
446         }
447         return result;
448     },
449
450     applyColumnWidthsMap: function(columnWidthsMap)
451     {
452         for (var columnIdentifier in this.columns) {
453             var column = this.columns[columnIdentifier];
454             var width = (columnWidthsMap[columnIdentifier] || 0) + "%";
455             this._headerTableColumnGroup.children[column.ordinal].style.width = width;
456             this._dataTableColumnGroup.children[column.ordinal].style.width = width;
457         }
458
459         // Normalize widths
460         delete this._columnWidthsInitialized;
461         this.updateWidths();
462     },
463
464     isColumnVisible: function(columnIdentifier)
465     {
466         var column = this.columns[columnIdentifier];
467         var columnElement = column.element;
468         return !columnElement.hidden;
469     },
470
471     showColumn: function(columnIdentifier)
472     {
473         var column = this.columns[columnIdentifier];
474         var columnElement = column.element;
475         if (!columnElement.hidden)
476             return;
477
478         columnElement.hidden = false;
479         columnElement.removeStyleClass("hidden");
480
481         var columnBodyElement = column.bodyElement;
482         columnBodyElement.hidden = false;
483         columnBodyElement.removeStyleClass("hidden");
484     },
485
486     hideColumn: function(columnIdentifier)
487     {
488         var column = this.columns[columnIdentifier];
489         var columnElement = column.element;
490         if (columnElement.hidden)
491             return;
492
493         var oldWidth = parseFloat(columnElement.style.width);
494
495         columnElement.hidden = true;
496         columnElement.addStyleClass("hidden");
497         columnElement.style.width = 0;
498
499         var columnBodyElement = column.bodyElement;
500         columnBodyElement.hidden = true;
501         columnBodyElement.addStyleClass("hidden");
502         columnBodyElement.style.width = 0;
503
504         this._columnWidthsInitialized = false;
505     },
506
507     get scrollContainer()
508     {
509         return this._scrollContainer;
510     },
511
512     isScrolledToLastRow: function()
513     {
514         return this._scrollContainer.isScrolledToBottom();
515     },
516
517     scrollToLastRow: function()
518     {
519         this._scrollContainer.scrollTop = this._scrollContainer.scrollHeight - this._scrollContainer.offsetHeight;
520     },
521
522     _positionResizers: function()
523     {
524         var headerTableColumns = this._headerTableColumnGroup.children;
525         var numColumns = headerTableColumns.length;
526         var left = 0;
527         var previousResizer = null;
528
529         // Make n - 1 resizers for n columns.
530         for (var i = 0; i < numColumns - 1; i++) {
531             var resizer = this.resizers[i];
532
533             if (!resizer) {
534                 // This is the first call to updateWidth, so the resizers need
535                 // to be created.
536                 resizer = document.createElement("div");
537                 resizer.addStyleClass("data-grid-resizer");
538                 // This resizer is associated with the column to its right.
539                 resizer.addEventListener("mousedown", this._startResizerDragging.bind(this), false);
540                 this.element.appendChild(resizer);
541                 this.resizers[i] = resizer;
542             }
543
544             // Get the width of the cell in the first (and only) row of the
545             // header table in order to determine the width of the column, since
546             // it is not possible to query a column for its width.
547             left += this.headerTableBody.rows[0].cells[i].offsetWidth;
548
549             var columnIsVisible = !this._headerTableColumnGroup.children[i].hidden;
550             if (columnIsVisible) {
551                 resizer.style.removeProperty("display");
552                 resizer.style.left = left + "px";
553                 resizer.leftNeighboringColumnID = i;
554                 if (previousResizer)
555                     previousResizer.rightNeighboringColumnID = i;
556                 previousResizer = resizer;
557             } else {
558                 resizer.style.setProperty("display", "none");
559                 resizer.leftNeighboringColumnID = 0;
560                 resizer.rightNeighboringColumnID = 0;
561             }
562         }
563         if (previousResizer)
564             previousResizer.rightNeighboringColumnID = numColumns - 1;
565     },
566
567     addCreationNode: function(hasChildren)
568     {
569         if (this.creationNode)
570             this.creationNode.makeNormal();
571
572         var emptyData = {};
573         for (var column in this.columns)
574             emptyData[column] = '';
575         this.creationNode = new WebInspector.CreationDataGridNode(emptyData, hasChildren);
576         this.appendChild(this.creationNode);
577     },
578
579     appendChild: function(child)
580     {
581         this.insertChild(child, this.children.length);
582     },
583
584     insertChild: function(child, index)
585     {
586         if (!child)
587             throw("insertChild: Node can't be undefined or null.");
588         if (child.parent === this)
589             throw("insertChild: Node is already a child of this node.");
590
591         if (child.parent)
592             child.parent.removeChild(child);
593
594         this.children.splice(index, 0, child);
595         this.hasChildren = true;
596
597         child.parent = this;
598         child.dataGrid = this.dataGrid;
599         child._recalculateSiblings(index);
600
601         delete child._depth;
602         delete child._revealed;
603         delete child._attached;
604         child._shouldRefreshChildren = true;
605
606         var current = child.children[0];
607         while (current) {
608             current.dataGrid = this.dataGrid;
609             delete current._depth;
610             delete current._revealed;
611             delete current._attached;
612             current._shouldRefreshChildren = true;
613             current = current.traverseNextNode(false, child, true);
614         }
615
616         if (this.expanded)
617             child._attach();
618     },
619
620     removeChild: function(child)
621     {
622         if (!child)
623             throw("removeChild: Node can't be undefined or null.");
624         if (child.parent !== this)
625             throw("removeChild: Node is not a child of this node.");
626
627         child.deselect();
628         child._detach();
629
630         this.children.remove(child, true);
631
632         if (child.previousSibling)
633             child.previousSibling.nextSibling = child.nextSibling;
634         if (child.nextSibling)
635             child.nextSibling.previousSibling = child.previousSibling;
636
637         child.dataGrid = null;
638         child.parent = null;
639         child.nextSibling = null;
640         child.previousSibling = null;
641
642         if (this.children.length <= 0)
643             this.hasChildren = false;
644     },
645
646     removeChildren: function()
647     {
648         for (var i = 0; i < this.children.length; ++i) {
649             var child = this.children[i];
650             child.deselect();
651             child._detach();
652
653             child.dataGrid = null;
654             child.parent = null;
655             child.nextSibling = null;
656             child.previousSibling = null;
657         }
658
659         this.children = [];
660         this.hasChildren = false;
661     },
662
663     removeChildrenRecursive: function()
664     {
665         var childrenToRemove = this.children;
666
667         var child = this.children[0];
668         while (child) {
669             if (child.children.length)
670                 childrenToRemove = childrenToRemove.concat(child.children);
671             child = child.traverseNextNode(false, this, true);
672         }
673
674         for (var i = 0; i < childrenToRemove.length; ++i) {
675             var child = childrenToRemove[i];
676             child.deselect();
677             child._detach();
678
679             child.children = [];
680             child.dataGrid = null;
681             child.parent = null;
682             child.nextSibling = null;
683             child.previousSibling = null;
684         }
685
686         this.children = [];
687     },
688
689     sortNodes: function(comparator, reverseMode)
690     {
691         function comparatorWrapper(a, b)
692         {
693             if (a._dataGridNode._data.summaryRow)
694                 return 1;
695             if (b._dataGridNode._data.summaryRow)
696                 return -1;
697
698             var aDataGirdNode = a._dataGridNode;
699             var bDataGirdNode = b._dataGridNode;
700             return reverseMode ? comparator(bDataGirdNode, aDataGirdNode) : comparator(aDataGirdNode, bDataGirdNode);
701         }
702
703         var tbody = this.dataTableBody;
704         var tbodyParent = tbody.parentElement;
705         tbodyParent.removeChild(tbody);
706
707         var childNodes = tbody.childNodes;
708         var fillerRow = childNodes[childNodes.length - 1];
709
710         var sortedRows = Array.prototype.slice.call(childNodes, 0, childNodes.length - 1);
711         sortedRows.sort(comparatorWrapper);
712         var sortedRowsLength = sortedRows.length;
713
714         tbody.removeChildren();
715         var previousSiblingNode = null;
716         for (var i = 0; i < sortedRowsLength; ++i) {
717             var row = sortedRows[i];
718             var node = row._dataGridNode;
719             node.previousSibling = previousSiblingNode;
720             if (previousSiblingNode)
721                 previousSiblingNode.nextSibling = node;
722             tbody.appendChild(row);
723             previousSiblingNode = node;
724         }
725         if (previousSiblingNode)
726             previousSiblingNode.nextSibling = null;
727
728         tbody.appendChild(fillerRow);
729         tbodyParent.appendChild(tbody);
730     },
731
732     _keyDown: function(event)
733     {
734         if (!this.selectedNode || event.shiftKey || event.metaKey || event.ctrlKey || this._editing)
735             return;
736
737         var handled = false;
738         var nextSelectedNode;
739         if (event.keyIdentifier === "Up" && !event.altKey) {
740             nextSelectedNode = this.selectedNode.traversePreviousNode(true);
741             while (nextSelectedNode && !nextSelectedNode.selectable)
742                 nextSelectedNode = nextSelectedNode.traversePreviousNode(!this.expandTreeNodesWhenArrowing);
743             handled = nextSelectedNode ? true : false;
744         } else if (event.keyIdentifier === "Down" && !event.altKey) {
745             nextSelectedNode = this.selectedNode.traverseNextNode(true);
746             while (nextSelectedNode && !nextSelectedNode.selectable)
747                 nextSelectedNode = nextSelectedNode.traverseNextNode(!this.expandTreeNodesWhenArrowing);
748             handled = nextSelectedNode ? true : false;
749         } else if (event.keyIdentifier === "Left") {
750             if (this.selectedNode.expanded) {
751                 if (event.altKey)
752                     this.selectedNode.collapseRecursively();
753                 else
754                     this.selectedNode.collapse();
755                 handled = true;
756             } else if (this.selectedNode.parent && !this.selectedNode.parent.root) {
757                 handled = true;
758                 if (this.selectedNode.parent.selectable) {
759                     nextSelectedNode = this.selectedNode.parent;
760                     handled = nextSelectedNode ? true : false;
761                 } else if (this.selectedNode.parent)
762                     this.selectedNode.parent.collapse();
763             }
764         } else if (event.keyIdentifier === "Right") {
765             if (!this.selectedNode.revealed) {
766                 this.selectedNode.reveal();
767                 handled = true;
768             } else if (this.selectedNode.hasChildren) {
769                 handled = true;
770                 if (this.selectedNode.expanded) {
771                     nextSelectedNode = this.selectedNode.children[0];
772                     handled = nextSelectedNode ? true : false;
773                 } else {
774                     if (event.altKey)
775                         this.selectedNode.expandRecursively();
776                     else
777                         this.selectedNode.expand();
778                 }
779             }
780         } else if (event.keyCode === 8 || event.keyCode === 46) {
781             if (this._deleteCallback) {
782                 handled = true;
783                 this._deleteCallback(this.selectedNode);
784             }
785         } else if (isEnterKey(event)) {
786             if (this._editCallback) {
787                 handled = true;
788                 // The first child of the selected element is the <td class="0-column">,
789                 // and that's what we want to edit.
790                 this._startEditing(this.selectedNode._element.children[0]);
791             }
792         }
793
794         if (nextSelectedNode) {
795             nextSelectedNode.reveal();
796             nextSelectedNode.select();
797         }
798
799         if (handled) {
800             event.preventDefault();
801             event.stopPropagation();
802         }
803     },
804
805     expand: function()
806     {
807         // This is the root, do nothing.
808     },
809
810     collapse: function()
811     {
812         // This is the root, do nothing.
813     },
814
815     reveal: function()
816     {
817         // This is the root, do nothing.
818     },
819
820     revealAndSelect: function()
821     {
822         // This is the root, do nothing.
823     },
824
825     dataGridNodeFromNode: function(target)
826     {
827         var rowElement = target.enclosingNodeOrSelfWithNodeName("tr");
828         return rowElement && rowElement._dataGridNode;
829     },
830
831     dataGridNodeFromPoint: function(x, y)
832     {
833         var node = this._dataTable.ownerDocument.elementFromPoint(x, y);
834         var rowElement = node.enclosingNodeOrSelfWithNodeName("tr");
835         return rowElement && rowElement._dataGridNode;
836     },
837
838     _clickInHeaderCell: function(event)
839     {
840         var cell = event.target.enclosingNodeOrSelfWithNodeName("th");
841         if (!cell || !cell.columnIdentifier || !cell.hasStyleClass("sortable"))
842             return;
843
844         var sortOrder = this.sortOrder;
845
846         if (this._sortColumnCell)
847             this._sortColumnCell.removeMatchingStyleClasses("sort-\\w+");
848
849         if (cell == this._sortColumnCell) {
850             if (sortOrder === "ascending")
851                 sortOrder = "descending";
852             else
853                 sortOrder = "ascending";
854         }
855
856         this._sortColumnCell = cell;
857
858         cell.addStyleClass("sort-" + sortOrder);
859
860         this.dispatchEventToListeners("sorting changed");
861     },
862
863     markColumnAsSortedBy: function(columnIdentifier, sortOrder)
864     {
865         if (this._sortColumnCell)
866             this._sortColumnCell.removeMatchingStyleClasses("sort-\\w+");
867         this._sortColumnCell = this._headerTableHeaders[columnIdentifier];
868         this._sortColumnCell.addStyleClass("sort-" + sortOrder);
869     },
870
871     headerTableHeader: function(columnIdentifier)
872     {
873         return this._headerTableHeaders[columnIdentifier];
874     },
875
876     _mouseDownInDataTable: function(event)
877     {
878         var gridNode = this.dataGridNodeFromNode(event.target);
879         if (!gridNode || !gridNode.selectable)
880             return;
881
882         if (gridNode.isEventWithinDisclosureTriangle(event))
883             return;
884
885         if (event.metaKey) {
886             if (gridNode.selected)
887                 gridNode.deselect();
888             else
889                 gridNode.select();
890         } else
891             gridNode.select();
892     },
893
894     _contextMenuInDataTable: function(event)
895     {
896         var contextMenu = new WebInspector.ContextMenu();
897
898         var gridNode = this.dataGridNodeFromNode(event.target);
899         if (this.dataGrid._refreshCallback && (!gridNode || gridNode !== this.creationNode))
900             contextMenu.appendItem(WebInspector.UIString("Refresh"), this._refreshCallback.bind(this));
901
902         if (gridNode && gridNode.selectable && !gridNode.isEventWithinDisclosureTriangle(event)) {
903             // FIXME: Use the column names for Editing, instead of just "Edit".
904             if (this.dataGrid._editCallback) {
905                 if (gridNode === this.creationNode)
906                     contextMenu.appendItem(WebInspector.UIString("Add New"), this._startEditing.bind(this, event.target));
907                 else
908                     contextMenu.appendItem(WebInspector.UIString("Edit"), this._startEditing.bind(this, event.target));
909             }
910             if (this.dataGrid._deleteCallback && gridNode !== this.creationNode)
911                 contextMenu.appendItem(WebInspector.UIString("Delete"), this._deleteCallback.bind(this, gridNode));
912         }
913
914         contextMenu.show(event);
915     },
916
917     _clickInDataTable: function(event)
918     {
919         var gridNode = this.dataGridNodeFromNode(event.target);
920         if (!gridNode || !gridNode.hasChildren)
921             return;
922
923         if (!gridNode.isEventWithinDisclosureTriangle(event))
924             return;
925
926         if (gridNode.expanded) {
927             if (event.altKey)
928                 gridNode.collapseRecursively();
929             else
930                 gridNode.collapse();
931         } else {
932             if (event.altKey)
933                 gridNode.expandRecursively();
934             else
935                 gridNode.expand();
936         }
937     },
938
939     get resizeMethod()
940     {
941         if (typeof this._resizeMethod === "undefined")
942             return WebInspector.DataGrid.ResizeMethod.Nearest;
943         return this._resizeMethod;
944     },
945
946     set resizeMethod(method)
947     {
948         this._resizeMethod = method;
949     },
950
951     _startResizerDragging: function(event)
952     {
953         this.currentResizer = event.target;
954         if (!this.currentResizer.rightNeighboringColumnID)
955             return;
956         WebInspector.elementDragStart(this.lastResizer, this._resizerDragging.bind(this),
957             this._endResizerDragging.bind(this), event, "col-resize");
958     },
959
960     _resizerDragging: function(event)
961     {
962         var resizer = this.currentResizer;
963         if (!resizer)
964             return;
965
966         // Constrain the dragpoint to be within the containing div of the
967         // datagrid.
968         var dragPoint = event.clientX - this.element.totalOffsetLeft();
969         // Constrain the dragpoint to be within the space made up by the
970         // column directly to the left and the column directly to the right.
971         var leftCellIndex = resizer.leftNeighboringColumnID;
972         var rightCellIndex = resizer.rightNeighboringColumnID;
973         var firstRowCells = this.headerTableBody.rows[0].cells;
974         var leftEdgeOfPreviousColumn = 0;
975         for (var i = 0; i < leftCellIndex; i++)
976             leftEdgeOfPreviousColumn += firstRowCells[i].offsetWidth;
977
978         // Differences for other resize methods
979         if (this.resizeMethod == WebInspector.DataGrid.ResizeMethod.Last) {
980             rightCellIndex = this.resizers.length;
981         } else if (this.resizeMethod == WebInspector.DataGrid.ResizeMethod.First) {
982             leftEdgeOfPreviousColumn += firstRowCells[leftCellIndex].offsetWidth - firstRowCells[0].offsetWidth;
983             leftCellIndex = 0;
984         }
985
986         var rightEdgeOfNextColumn = leftEdgeOfPreviousColumn + firstRowCells[leftCellIndex].offsetWidth + firstRowCells[rightCellIndex].offsetWidth;
987
988         // Give each column some padding so that they don't disappear.
989         var leftMinimum = leftEdgeOfPreviousColumn + this.ColumnResizePadding;
990         var rightMaximum = rightEdgeOfNextColumn - this.ColumnResizePadding;
991
992         dragPoint = Number.constrain(dragPoint, leftMinimum, rightMaximum);
993
994         resizer.style.left = (dragPoint - this.CenterResizerOverBorderAdjustment) + "px";
995
996         var percentLeftColumn = (((dragPoint - leftEdgeOfPreviousColumn) / this._dataTable.offsetWidth) * 100) + "%";
997         this._headerTableColumnGroup.children[leftCellIndex].style.width = percentLeftColumn;
998         this._dataTableColumnGroup.children[leftCellIndex].style.width = percentLeftColumn;
999
1000         var percentRightColumn = (((rightEdgeOfNextColumn - dragPoint) / this._dataTable.offsetWidth) * 100) + "%";
1001         this._headerTableColumnGroup.children[rightCellIndex].style.width =  percentRightColumn;
1002         this._dataTableColumnGroup.children[rightCellIndex].style.width = percentRightColumn;
1003
1004         this._positionResizers();
1005         event.preventDefault();
1006         this.dispatchEventToListeners("width changed");
1007     },
1008
1009     _endResizerDragging: function(event)
1010     {
1011         WebInspector.elementDragEnd(event);
1012         this.currentResizer = null;
1013         this.dispatchEventToListeners("width changed");
1014     },
1015
1016     ColumnResizePadding: 10,
1017
1018     CenterResizerOverBorderAdjustment: 3,
1019 }
1020
1021 WebInspector.DataGrid.ResizeMethod = {
1022     Nearest: "nearest",
1023     First: "first",
1024     Last: "last"
1025 }
1026
1027 WebInspector.DataGrid.prototype.__proto__ = WebInspector.Object.prototype;
1028
1029 WebInspector.DataGridNode = function(data, hasChildren)
1030 {
1031     this._expanded = false;
1032     this._selected = false;
1033     this._shouldRefreshChildren = true;
1034     this._data = data || {};
1035     this.hasChildren = hasChildren || false;
1036     this.children = [];
1037     this.dataGrid = null;
1038     this.parent = null;
1039     this.previousSibling = null;
1040     this.nextSibling = null;
1041     this.disclosureToggleWidth = 10;
1042 }
1043
1044 WebInspector.DataGridNode.prototype = {
1045     selectable: true,
1046
1047     get element()
1048     {
1049         if (this._element)
1050             return this._element;
1051
1052         if (!this.dataGrid)
1053             return null;
1054
1055         this._element = document.createElement("tr");
1056         this._element._dataGridNode = this;
1057
1058         if (this.hasChildren)
1059             this._element.addStyleClass("parent");
1060         if (this.expanded)
1061             this._element.addStyleClass("expanded");
1062         if (this.selected)
1063             this._element.addStyleClass("selected");
1064         if (this.revealed)
1065             this._element.addStyleClass("revealed");
1066
1067         this.createCells();
1068         return this._element;
1069     },
1070
1071     createCells: function()
1072     {
1073         for (var columnIdentifier in this.dataGrid.columns) {
1074             var cell = this.createCell(columnIdentifier);
1075             this._element.appendChild(cell);
1076         }
1077     },
1078
1079     get data()
1080     {
1081         return this._data;
1082     },
1083
1084     set data(x)
1085     {
1086         this._data = x || {};
1087         this.refresh();
1088     },
1089
1090     get revealed()
1091     {
1092         if ("_revealed" in this)
1093             return this._revealed;
1094
1095         var currentAncestor = this.parent;
1096         while (currentAncestor && !currentAncestor.root) {
1097             if (!currentAncestor.expanded) {
1098                 this._revealed = false;
1099                 return false;
1100             }
1101
1102             currentAncestor = currentAncestor.parent;
1103         }
1104
1105         this._revealed = true;
1106         return true;
1107     },
1108
1109     set hasChildren(x)
1110     {
1111         if (this._hasChildren === x)
1112             return;
1113
1114         this._hasChildren = x;
1115
1116         if (!this._element)
1117             return;
1118
1119         if (this._hasChildren)
1120         {
1121             this._element.addStyleClass("parent");
1122             if (this.expanded)
1123                 this._element.addStyleClass("expanded");
1124         }
1125         else
1126         {
1127             this._element.removeStyleClass("parent");
1128             this._element.removeStyleClass("expanded");
1129         }
1130     },
1131
1132     get hasChildren()
1133     {
1134         return this._hasChildren;
1135     },
1136
1137     set revealed(x)
1138     {
1139         if (this._revealed === x)
1140             return;
1141
1142         this._revealed = x;
1143
1144         if (this._element) {
1145             if (this._revealed)
1146                 this._element.addStyleClass("revealed");
1147             else
1148                 this._element.removeStyleClass("revealed");
1149         }
1150
1151         for (var i = 0; i < this.children.length; ++i)
1152             this.children[i].revealed = x && this.expanded;
1153     },
1154
1155     get depth()
1156     {
1157         if ("_depth" in this)
1158             return this._depth;
1159         if (this.parent && !this.parent.root)
1160             this._depth = this.parent.depth + 1;
1161         else
1162             this._depth = 0;
1163         return this._depth;
1164     },
1165
1166     get shouldRefreshChildren()
1167     {
1168         return this._shouldRefreshChildren;
1169     },
1170
1171     set shouldRefreshChildren(x)
1172     {
1173         this._shouldRefreshChildren = x;
1174         if (x && this.expanded)
1175             this.expand();
1176     },
1177
1178     get selected()
1179     {
1180         return this._selected;
1181     },
1182
1183     set selected(x)
1184     {
1185         if (x)
1186             this.select();
1187         else
1188             this.deselect();
1189     },
1190
1191     get expanded()
1192     {
1193         return this._expanded;
1194     },
1195
1196     set expanded(x)
1197     {
1198         if (x)
1199             this.expand();
1200         else
1201             this.collapse();
1202     },
1203
1204     refresh: function()
1205     {
1206         if (!this._element || !this.dataGrid)
1207             return;
1208
1209         this._element.removeChildren();
1210         this.createCells();
1211     },
1212
1213     createCell: function(columnIdentifier)
1214     {
1215         var cell = document.createElement("td");
1216         cell.className = columnIdentifier + "-column";
1217
1218         var alignment = this.dataGrid.aligned[columnIdentifier];
1219         if (alignment)
1220             cell.addStyleClass(alignment);
1221
1222         var div = document.createElement("div");
1223         div.textContent = this.data[columnIdentifier];
1224         cell.appendChild(div);
1225
1226         if (columnIdentifier === this.dataGrid.disclosureColumnIdentifier) {
1227             cell.addStyleClass("disclosure");
1228             if (this.depth)
1229                 cell.style.setProperty("padding-left", (this.depth * this.dataGrid.indentWidth) + "px");
1230         }
1231
1232         return cell;
1233     },
1234
1235     // Share these functions with DataGrid. They are written to work with a DataGridNode this object.
1236     appendChild: WebInspector.DataGrid.prototype.appendChild,
1237     insertChild: WebInspector.DataGrid.prototype.insertChild,
1238     removeChild: WebInspector.DataGrid.prototype.removeChild,
1239     removeChildren: WebInspector.DataGrid.prototype.removeChildren,
1240     removeChildrenRecursive: WebInspector.DataGrid.prototype.removeChildrenRecursive,
1241
1242     _recalculateSiblings: function(myIndex)
1243     {
1244         if (!this.parent)
1245             return;
1246
1247         var previousChild = (myIndex > 0 ? this.parent.children[myIndex - 1] : null);
1248
1249         if (previousChild) {
1250             previousChild.nextSibling = this;
1251             this.previousSibling = previousChild;
1252         } else
1253             this.previousSibling = null;
1254
1255         var nextChild = this.parent.children[myIndex + 1];
1256
1257         if (nextChild) {
1258             nextChild.previousSibling = this;
1259             this.nextSibling = nextChild;
1260         } else
1261             this.nextSibling = null;
1262     },
1263
1264     collapse: function()
1265     {
1266         if (this._element)
1267             this._element.removeStyleClass("expanded");
1268
1269         this._expanded = false;
1270
1271         for (var i = 0; i < this.children.length; ++i)
1272             this.children[i].revealed = false;
1273
1274         this.dispatchEventToListeners("collapsed");
1275     },
1276
1277     collapseRecursively: function()
1278     {
1279         var item = this;
1280         while (item) {
1281             if (item.expanded)
1282                 item.collapse();
1283             item = item.traverseNextNode(false, this, true);
1284         }
1285     },
1286
1287     expand: function()
1288     {
1289         if (!this.hasChildren || this.expanded)
1290             return;
1291
1292         if (this.revealed && !this._shouldRefreshChildren)
1293             for (var i = 0; i < this.children.length; ++i)
1294                 this.children[i].revealed = true;
1295
1296         if (this._shouldRefreshChildren) {
1297             for (var i = 0; i < this.children.length; ++i)
1298                 this.children[i]._detach();
1299
1300             this.dispatchEventToListeners("populate");
1301
1302             if (this._attached) {
1303                 for (var i = 0; i < this.children.length; ++i) {
1304                     var child = this.children[i];
1305                     if (this.revealed)
1306                         child.revealed = true;
1307                     child._attach();
1308                 }
1309             }
1310
1311             delete this._shouldRefreshChildren;
1312         }
1313
1314         if (this._element)
1315             this._element.addStyleClass("expanded");
1316
1317         this._expanded = true;
1318
1319         this.dispatchEventToListeners("expanded");
1320     },
1321
1322     expandRecursively: function()
1323     {
1324         var item = this;
1325         while (item) {
1326             item.expand();
1327             item = item.traverseNextNode(false, this);
1328         }
1329     },
1330
1331     reveal: function()
1332     {
1333         var currentAncestor = this.parent;
1334         while (currentAncestor && !currentAncestor.root) {
1335             if (!currentAncestor.expanded)
1336                 currentAncestor.expand();
1337             currentAncestor = currentAncestor.parent;
1338         }
1339
1340         this.element.scrollIntoViewIfNeeded(false);
1341
1342         this.dispatchEventToListeners("revealed");
1343     },
1344
1345     select: function(supressSelectedEvent)
1346     {
1347         if (!this.dataGrid || !this.selectable || this.selected)
1348             return;
1349
1350         if (this.dataGrid.selectedNode)
1351             this.dataGrid.selectedNode.deselect();
1352
1353         this._selected = true;
1354         this.dataGrid.selectedNode = this;
1355
1356         if (this._element)
1357             this._element.addStyleClass("selected");
1358
1359         if (!supressSelectedEvent)
1360             this.dispatchEventToListeners("selected");
1361     },
1362
1363     revealAndSelect: function()
1364     {
1365         this.reveal();
1366         this.select();
1367     },
1368
1369     deselect: function(supressDeselectedEvent)
1370     {
1371         if (!this.dataGrid || this.dataGrid.selectedNode !== this || !this.selected)
1372             return;
1373
1374         this._selected = false;
1375         this.dataGrid.selectedNode = null;
1376
1377         if (this._element)
1378             this._element.removeStyleClass("selected");
1379
1380         if (!supressDeselectedEvent)
1381             this.dispatchEventToListeners("deselected");
1382     },
1383
1384     traverseNextNode: function(skipHidden, stayWithin, dontPopulate, info)
1385     {
1386         if (!dontPopulate && this.hasChildren)
1387             this.dispatchEventToListeners("populate");
1388
1389         if (info)
1390             info.depthChange = 0;
1391
1392         var node = (!skipHidden || this.revealed) ? this.children[0] : null;
1393         if (node && (!skipHidden || this.expanded)) {
1394             if (info)
1395                 info.depthChange = 1;
1396             return node;
1397         }
1398
1399         if (this === stayWithin)
1400             return null;
1401
1402         node = (!skipHidden || this.revealed) ? this.nextSibling : null;
1403         if (node)
1404             return node;
1405
1406         node = this;
1407         while (node && !node.root && !((!skipHidden || node.revealed) ? node.nextSibling : null) && node.parent !== stayWithin) {
1408             if (info)
1409                 info.depthChange -= 1;
1410             node = node.parent;
1411         }
1412
1413         if (!node)
1414             return null;
1415
1416         return (!skipHidden || node.revealed) ? node.nextSibling : null;
1417     },
1418
1419     traversePreviousNode: function(skipHidden, dontPopulate)
1420     {
1421         var node = (!skipHidden || this.revealed) ? this.previousSibling : null;
1422         if (!dontPopulate && node && node.hasChildren)
1423             node.dispatchEventToListeners("populate");
1424
1425         while (node && ((!skipHidden || (node.revealed && node.expanded)) ? node.children[node.children.length - 1] : null)) {
1426             if (!dontPopulate && node.hasChildren)
1427                 node.dispatchEventToListeners("populate");
1428             node = ((!skipHidden || (node.revealed && node.expanded)) ? node.children[node.children.length - 1] : null);
1429         }
1430
1431         if (node)
1432             return node;
1433
1434         if (!this.parent || this.parent.root)
1435             return null;
1436
1437         return this.parent;
1438     },
1439
1440     isEventWithinDisclosureTriangle: function(event)
1441     {
1442         if (!this.hasChildren)
1443             return false;
1444         var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
1445         if (!cell.hasStyleClass("disclosure"))
1446             return false;
1447         var computedLeftPadding = window.getComputedStyle(cell).getPropertyCSSValue("padding-left").getFloatValue(CSSPrimitiveValue.CSS_PX);
1448         var left = cell.totalOffsetLeft() + computedLeftPadding;
1449         return event.pageX >= left && event.pageX <= left + this.disclosureToggleWidth;
1450     },
1451
1452     _attach: function()
1453     {
1454         if (!this.dataGrid || this._attached)
1455             return;
1456
1457         this._attached = true;
1458
1459         var nextNode = null;
1460         var previousNode = this.traversePreviousNode(true, true);
1461         if (previousNode && previousNode.element.parentNode && previousNode.element.nextSibling)
1462             var nextNode = previousNode.element.nextSibling;
1463         if (!nextNode)
1464             nextNode = this.dataGrid.dataTableBody.lastChild;
1465         this.dataGrid.dataTableBody.insertBefore(this.element, nextNode);
1466
1467         if (this.expanded)
1468             for (var i = 0; i < this.children.length; ++i)
1469                 this.children[i]._attach();
1470     },
1471
1472     _detach: function()
1473     {
1474         if (!this._attached)
1475             return;
1476
1477         this._attached = false;
1478
1479         if (this._element && this._element.parentNode)
1480             this._element.parentNode.removeChild(this._element);
1481
1482         for (var i = 0; i < this.children.length; ++i)
1483             this.children[i]._detach();
1484     },
1485
1486     savePosition: function()
1487     {
1488         if (this._savedPosition)
1489             return;
1490
1491         if (!this.parent)
1492             throw("savePosition: Node must have a parent.");
1493         this._savedPosition = {
1494             parent: this.parent,
1495             index: this.parent.children.indexOf(this)
1496         };
1497     },
1498
1499     restorePosition: function()
1500     {
1501         if (!this._savedPosition)
1502             return;
1503
1504         if (this.parent !== this._savedPosition.parent)
1505             this._savedPosition.parent.insertChild(this, this._savedPosition.index);
1506
1507         delete this._savedPosition;
1508     }
1509 }
1510
1511 WebInspector.DataGridNode.prototype.__proto__ = WebInspector.Object.prototype;
1512
1513 WebInspector.CreationDataGridNode = function(data, hasChildren)
1514 {
1515     WebInspector.DataGridNode.call(this, data, hasChildren);
1516     this.isCreationNode = true;
1517 }
1518
1519 WebInspector.CreationDataGridNode.prototype = {
1520     makeNormal: function()
1521     {
1522         delete this.isCreationNode;
1523         delete this.makeNormal;
1524     }
1525 }
1526
1527 WebInspector.CreationDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype;