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