Web Inspector: unify resizer implementations used by DataGrid and Sidebar
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / DataGrid.js
index b7c7997..e52d87c 100644 (file)
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-WebInspector.DataGrid = function(columnsData, editCallback, deleteCallback)
+WebInspector.DataGrid = function(columnsData, editCallback, deleteCallback, preferredColumnOrder)
 {
     this.columns = new Map;
     this.orderedColumns = [];
 
+    this._sortColumnIdentifier = null;
+    this._sortOrder = WebInspector.DataGrid.SortOrder.Indeterminate;
+
     this.children = [];
     this.selectedNode = null;
     this.expandNodesWhenArrowing = false;
@@ -38,7 +41,7 @@ WebInspector.DataGrid = function(columnsData, editCallback, deleteCallback)
     this.selected = false;
     this.dataGrid = this;
     this.indentWidth = 15;
-    this.resizerElements = [];
+    this.resizers = [];
     this._columnWidthsInitialized = false;
 
     this.element = document.createElement("div");
@@ -84,8 +87,13 @@ WebInspector.DataGrid = function(columnsData, editCallback, deleteCallback)
     this.element.appendChild(this._headerTableElement);
     this.element.appendChild(this._scrollContainerElement);
 
-    for (var columnIdentifier in columnsData)
-        this.insertColumn(columnIdentifier, columnsData[columnIdentifier]);
+    if (preferredColumnOrder) {
+        for (var columnIdentifier of preferredColumnOrder)
+            this.insertColumn(columnIdentifier, columnsData[columnIdentifier]);
+    } else {
+        for (var columnIdentifier in columnsData)
+            this.insertColumn(columnIdentifier, columnsData[columnIdentifier]);
+    }
 
     this._generateSortIndicatorImagesIfNeeded();
 }
@@ -98,10 +106,19 @@ WebInspector.DataGrid.Event = {
     CollapsedNode: "datagrid-collapsed-node"
 };
 
-/**
- * @param {Array.<string>} columnNames
- * @param {Array.<string>} values
- */
+WebInspector.DataGrid.SortOrder = {
+    Indeterminate: "data-grid-sort-order-indeterminate",
+    Ascending: "data-grid-sort-order-ascending",
+    Descending: "data-grid-sort-order-descending"
+};
+
+WebInspector.DataGrid.PreviousColumnOrdinalSymbol = Symbol("previous-column-ordinal");
+WebInspector.DataGrid.NextColumnOrdinalSymbol = Symbol("next-column-ordinal");
+
+WebInspector.DataGrid.SortColumnAscendingStyleClassName = "sort-ascending";
+WebInspector.DataGrid.SortColumnDescendingStyleClassName = "sort-descending";
+WebInspector.DataGrid.SortableColumnStyleClassName = "sortable";
+
 WebInspector.DataGrid.createSortableDataGrid = function(columnNames, values)
 {
     var numColumns = columnNames.length;
@@ -109,17 +126,15 @@ WebInspector.DataGrid.createSortableDataGrid = function(columnNames, values)
         return null;
 
     var columnsData = {};
-
     for (var columnName of columnNames) {
-        var column = {};
-        column.width = columnName.length;
-        column.title = columnName;
-        column.sortable = true;
-
-        columnsData[columnName] = column;
+        columnsData[columnName] = {
+            width: columnName.length,
+            title: columnName,
+            sortable: true,
+        };
     }
 
-    var dataGrid = new WebInspector.DataGrid(columnsData);
+    var dataGrid = new WebInspector.DataGrid(columnsData, undefined, undefined, columnNames);
     for (var i = 0; i < values.length / numColumns; ++i) {
         var data = {};
         for (var j = 0; j < columnNames.length; ++j)
@@ -133,34 +148,38 @@ WebInspector.DataGrid.createSortableDataGrid = function(columnNames, values)
     function sortDataGrid()
     {
         var sortColumnIdentifier = dataGrid.sortColumnIdentifier;
-        var sortAscending = dataGrid.sortOrder === "ascending" ? 1 : -1;
 
+        var columnIsNumeric = true;
         for (var node of dataGrid.children) {
-            if (isNaN(Number(node.data[sortColumnIdentifier] || "")))
+            var value = dataGrid.textForDataGridNodeColumn(node, sortColumnIdentifier);
+            if (isNaN(Number(value)))
                 columnIsNumeric = false;
         }
 
         function comparator(dataGridNode1, dataGridNode2)
         {
-            var item1 = dataGridNode1.data[sortColumnIdentifier] || "";
-            var item2 = dataGridNode2.data[sortColumnIdentifier] || "";
+            var item1 = dataGrid.textForDataGridNodeColumn(dataGridNode1, sortColumnIdentifier);
+            var item2 = dataGrid.textForDataGridNodeColumn(dataGridNode2, sortColumnIdentifier);
 
             var comparison;
             if (columnIsNumeric) {
-                // Sort numbers based on comparing their values rather than a lexicographical comparison.
                 var number1 = parseFloat(item1);
                 var number2 = parseFloat(item2);
                 comparison = number1 < number2 ? -1 : (number1 > number2 ? 1 : 0);
             } else
                 comparison = item1 < item2 ? -1 : (item1 > item2 ? 1 : 0);
 
-            return sortDirection * comparison;
+            return comparison;
         }
 
         dataGrid.sortNodes(comparator);
     }
 
     dataGrid.addEventListener(WebInspector.DataGrid.Event.SortChanged, sortDataGrid, this);
+
+    dataGrid.sortOrder = WebInspector.DataGrid.SortOrder.Ascending;
+    dataGrid.sortColumnIdentifier = columnNames[0];
+
     return dataGrid;
 }
 
@@ -175,6 +194,60 @@ WebInspector.DataGrid.prototype = {
         this._refreshCallback = refreshCallback;
     },
 
+    get sortOrder()
+    {
+        return this._sortOrder;
+    },
+
+    set sortOrder(order)
+    {
+        if (order === this._sortOrder)
+            return;
+
+        this._sortOrder = order;
+
+        if (!this._sortColumnIdentifier)
+            return;
+
+        var sortHeaderCellElement = this._headerTableCellElements.get(this._sortColumnIdentifier);
+
+        sortHeaderCellElement.classList.toggle(WebInspector.DataGrid.SortColumnAscendingStyleClassName, this._sortOrder === WebInspector.DataGrid.SortOrder.Ascending);
+        sortHeaderCellElement.classList.toggle(WebInspector.DataGrid.SortColumnDescendingStyleClassName, this._sortOrder === WebInspector.DataGrid.SortOrder.Descending);
+
+        this.dispatchEventToListeners(WebInspector.DataGrid.Event.SortChanged);
+    },
+
+    get sortColumnIdentifier()
+    {
+        return this._sortColumnIdentifier;
+    },
+
+    set sortColumnIdentifier(columnIdentifier)
+    {
+        console.assert(columnIdentifier && this.columns.has(columnIdentifier));
+        console.assert("sortable" in this.columns.get(columnIdentifier));
+
+        if (this._sortColumnIdentifier === columnIdentifier)
+            return;
+
+        var oldSortColumnIdentifier = this._sortColumnIdentifier;
+        this._sortColumnIdentifier = columnIdentifier;
+
+        if (oldSortColumnIdentifier) {
+            var oldSortHeaderCellElement = this._headerTableCellElements.get(oldSortColumnIdentifier);
+            oldSortHeaderCellElement.classList.remove(WebInspector.DataGrid.SortColumnAscendingStyleClassName);
+            oldSortHeaderCellElement.classList.remove(WebInspector.DataGrid.SortColumnDescendingStyleClassName);
+        }
+
+        if (this._sortColumnIdentifier) {
+            var newSortHeaderCellElement = this._headerTableCellElements.get(this._sortColumnIdentifier);
+            newSortHeaderCellElement.classList.toggle(WebInspector.DataGrid.SortColumnAscendingStyleClassName, this._sortOrder === WebInspector.DataGrid.SortOrder.Ascending);
+            newSortHeaderCellElement.classList.toggle(WebInspector.DataGrid.SortColumnDescendingStyleClassName, this._sortOrder === WebInspector.DataGrid.SortOrder.Descending);
+        }
+
+        this.dispatchEventToListeners(WebInspector.DataGrid.Event.SortChanged);
+    },
+
     _ondblclick: function(event)
     {
         if (this._editing || this._editingNode)
@@ -253,8 +326,8 @@ WebInspector.DataGrid.prototype = {
                 return {shouldSort: true, editingNode: previousDataGridNode || currentEditingNode, columnIndex: this.orderedColumns.length - 1};
             }
 
-            // If we are not moving in any direction, then sort but don't move.
-            return {shouldSort: true, editingNode: currentEditingNode, columnIndex: columnIndex};
+            // If we are not moving in any direction, then sort and stop.
+            return {shouldSort: true};
         }
 
         function moveToNextCell(valueDidChange) {
@@ -263,7 +336,8 @@ WebInspector.DataGrid.prototype = {
                 this._sortAfterEditingCallback();
                 delete this._sortAfterEditingCallback;
             }
-            this._startEditingNodeAtColumnIndex(moveCommand.editingNode, moveCommand.columnIndex);
+            if (moveCommand.editingNode)
+                this._startEditingNodeAtColumnIndex(moveCommand.editingNode, moveCommand.columnIndex);
         }
 
         this._editingCancelled(element);
@@ -283,20 +357,6 @@ WebInspector.DataGrid.prototype = {
         this._editingNode = null;
     },
 
-    get sortColumnIdentifier()
-    {
-        return this._sortColumnCell ? this._sortColumnCell.columnIdentifier : null;
-    },
-
-    get sortOrder()
-    {
-        if (!this._sortColumnCell || this._sortColumnCell.classList.contains("sort-ascending"))
-            return "ascending";
-        if (this._sortColumnCell.classList.contains("sort-descending"))
-            return "descending";
-        return null;
-    },
-
     autoSizeColumns: function(minPercent, maxPercent, maxDescentLevel)
     {
         if (minPercent)
@@ -304,13 +364,13 @@ WebInspector.DataGrid.prototype = {
         var widths = {};
         // For the first width approximation, use the character length of column titles.
         for (var [identifier, column] of this.columns)
-            widths[identifier] = column.get("title", "").length;
+            widths[identifier] = (column["title"] || "").length;
 
         // Now approximate the width of each column as max(title, cells).
         var children = maxDescentLevel ? this._enumerateChildren(this, [], maxDescentLevel + 1) : this.children;
         for (var node of children) {
             for (var identifier of this.columns.keys()) {
-                var text = node.data[identifier] || "";
+                var text = this.textForDataGridNodeColumn(node, identifier);
                 if (text.length > widths[identifier])
                     widths[identifier] = text.length;
             }
@@ -359,75 +419,69 @@ WebInspector.DataGrid.prototype = {
         }
 
         for (var [identifier, column] of this.columns)
-            column.get("element").style.width = widths[identifier] + "%";
+            column["element"].style.width = widths[identifier] + "%";
         this._columnWidthsInitialized = false;
         this.updateLayout();
     },
 
-    insertColumn: function(columnIdentifier, columnData, insertionIndex) {
-        if (typeof insertionIndex === "undefined")
+    insertColumn: function(columnIdentifier, columnData, insertionIndex)
+    {
+        if (insertionIndex === undefined)
             insertionIndex = this.orderedColumns.length;
         insertionIndex = Number.constrain(insertionIndex, 0, this.orderedColumns.length);
 
         var listeners = new WebInspector.EventListenerSet(this, "DataGrid column DOM listeners");
 
         // Copy configuration properties instead of keeping a reference to the passed-in object.
-        var column = new Map;
-        for (var propertyName in columnData)
-            column.set(propertyName, columnData[propertyName]);
+        var column = Object.shallowCopy(columnData);
+        column["listeners"] = listeners;
+        column["ordinal"] = insertionIndex;
+        column["columnIdentifier"] = columnIdentifier;
 
-        column.set("listeners", listeners);
-        column.set("ordinal", insertionIndex);
-        column.set("columnIdentifier", columnIdentifier);
         this.orderedColumns.splice(insertionIndex, 0, columnIdentifier);
 
         for (var [identifier, existingColumn] of this.columns) {
-            var ordinal = existingColumn.get("ordinal");
+            var ordinal = existingColumn["ordinal"];
             if (ordinal >= insertionIndex) // Also adjust the "old" column at insertion index.
-                existingColumn.set("ordinal", ordinal + 1);
+                existingColumn["ordinal"] = ordinal + 1;
         }
         this.columns.set(columnIdentifier, column);
 
-        if (column.has("disclosure"))
+        if (column["disclosure"])
             this.disclosureColumnIdentifier = columnIdentifier;
 
         var headerColumnElement = document.createElement("col");
-        if (column.has("width"))
-            headerColumnElement.style.width = column.get("width");
-        column.set("element", headerColumnElement);
+        if (column["width"])
+            headerColumnElement.style.width = column["width"];
+        column["element"] = headerColumnElement;
         var referenceElement = this._headerTableColumnGroupElement.children[insertionIndex];
         this._headerTableColumnGroupElement.insertBefore(headerColumnElement, referenceElement);
 
         var headerCellElement = document.createElement("th");
         headerCellElement.className = columnIdentifier + "-column";
         headerCellElement.columnIdentifier = columnIdentifier;
-        if (column.has("aligned"))
-            headerCellElement.classList.add(column.get("aligned"));
+        if (column["aligned"])
+            headerCellElement.classList.add(column["aligned"]);
         this._headerTableCellElements.set(columnIdentifier, headerCellElement);
         var referenceElement = this._headerTableRowElement.children[insertionIndex];
         this._headerTableRowElement.insertBefore(headerCellElement, referenceElement);
 
         var div = headerCellElement.createChild("div");
-        if (column.has("titleDOMFragment"))
-            div.appendChild(column.get("titleDOMFragment"));
+        if (column["titleDOMFragment"])
+            div.appendChild(column["titleDOMFragment"]);
         else
-            div.textContent = column.get("title", "");
-
-        if (column.has("sort")) {
-            headerCellElement.classList.add("sort-" + column.get("sort"));
-            this._sortColumnCell = headerCellElement;
-        }
+            div.textContent = column["title"] || "";
 
-        if (column.has("sortable")) {
-            listeners.register(headerCellElement, "click", this._clickInHeaderCell, false);
-            headerCellElement.classList.add("sortable");
+        if (column["sortable"]) {
+            listeners.register(headerCellElement, "click", this._headerCellClicked);
+            headerCellElement.classList.add(WebInspector.DataGrid.SortableColumnStyleClassName);
         }
 
-        if (column.has("group"))
-            headerCellElement.classList.add("column-group-" + column.get("group"));
+        if (column["group"])
+            headerCellElement.classList.add("column-group-" + column["group"]);
 
-        if (column.has("collapsesGroup")) {
-            console.assert(column.get("group") !== column.get("collapsesGroup"));
+        if (column["collapsesGroup"]) {
+            console.assert(column["group"] !== column["collapsesGroup"]);
 
             var dividerElement = headerCellElement.createChild("div");
             dividerElement.className = "divider";
@@ -439,7 +493,7 @@ WebInspector.DataGrid.prototype = {
             listeners.register(collapseDiv, "mouseout", this._mouseoutColumnCollapser);
             listeners.register(collapseDiv, "click", this._clickInColumnCollapser);
 
-            headerCellElement.collapsesGroup = column.get("collapsesGroup");
+            headerCellElement.collapsesGroup = column["collapsesGroup"];
             headerCellElement.classList.add("collapser");
         }
 
@@ -448,19 +502,19 @@ WebInspector.DataGrid.prototype = {
         var dataColumnElement = headerColumnElement.cloneNode();
         var referenceElement = this._dataTableColumnGroupElement.children[insertionIndex];
         this._dataTableColumnGroupElement.insertBefore(dataColumnElement, referenceElement);
-        column.set("bodyElement", dataColumnElement);
+        column["bodyElement"] = dataColumnElement;
 
         var fillerCellElement = document.createElement("td");
         fillerCellElement.className = columnIdentifier + "-column";
         fillerCellElement.__columnIdentifier = columnIdentifier;
-        if (column.has("group"))
-            fillerCellElement.classList.add("column-group-" + column.get("group"));
+        if (column["group"])
+            fillerCellElement.classList.add("column-group-" + column["group"]);
         var referenceElement = this._fillerRowElement.children[insertionIndex];
         this._fillerRowElement.insertBefore(fillerCellElement, referenceElement);
 
         listeners.install();
 
-        if (column.has("hidden"))
+        if (column["hidden"])
             this._hideColumn(columnIdentifier);
     },
 
@@ -471,20 +525,20 @@ WebInspector.DataGrid.prototype = {
         this.columns.delete(columnIdentifier);
         this.orderedColumns.splice(this.orderedColumns.indexOf(columnIdentifier), 1);
 
-        var removedOrdinal = removedColumn.get("ordinal");
+        var removedOrdinal = removedColumn["ordinal"];
         for (var [identifier, column] of this.columns) {
-            var ordinal = column.get("ordinal");
+            var ordinal = column["ordinal"];
             if (ordinal > removedOrdinal)
-                column.set("ordinal", ordinal - 1);
+                column["ordinal"] = ordinal - 1;
         }
 
-        removedColumn.get("listeners").uninstall(true);
+        removedColumn["listeners"].uninstall(true);
 
-        if (removedColumn.has("disclosure"))
+        if (removedColumn["disclosure"])
             delete this.disclosureColumnIdentifier;
 
-        if (removedColumn.has("sort"))
-            delete this._sortColumnCell;
+        if (this.sortColumnIdentifier === columnIdentifier)
+            this.sortColumnIdentifier = null;
 
         this._headerTableCellElements.delete(columnIdentifier);
         this._headerTableRowElement.children[removedOrdinal].remove();
@@ -554,7 +608,7 @@ WebInspector.DataGrid.prototype = {
     {
         var result = {};
         for (var [identifier, column] of this.columns) {
-            var width = this._headerTableColumnGroupElement.children[column.get("ordinal")].style.width;
+            var width = this._headerTableColumnGroupElement.children[column["ordinal"]].style.width;
             result[columnIdentifier] = parseFloat(width);
         }
         return result;
@@ -564,7 +618,7 @@ WebInspector.DataGrid.prototype = {
     {
         for (var [identifier, column] of this.columns) {
             var width = (columnWidthsMap[identifier] || 0) + "%";
-            var ordinal = column.get("ordinal");
+            var ordinal = column["ordinal"];
             this._headerTableColumnGroupElement.children[ordinal].style.width = width;
             this._dataTableColumnGroupElement.children[ordinal].style.width = width;
         }
@@ -574,20 +628,20 @@ WebInspector.DataGrid.prototype = {
 
     _isColumnVisible: function(columnIdentifier)
     {
-        return !this.columns.get(columnIdentifier).has("hidden");
+        return !this.columns.get(columnIdentifier)["hidden"];
     },
 
     _showColumn: function(columnIdentifier)
     {
-        this.columns.get(columnIdentifier).delete("hidden");
+        delete this.columns.get(columnIdentifier)["hidden"];
     },
 
     _hideColumn: function(columnIdentifier)
     {
         var column = this.columns.get(columnIdentifier);
-        column.set("hidden", true);
+        column["hidden"] = true;
 
-        var columnElement = column.get("element");
+        var columnElement = column["element"];
         columnElement.style.width = 0;
 
         this._columnWidthsInitialized = false;
@@ -611,43 +665,40 @@ WebInspector.DataGrid.prototype = {
     _positionResizerElements: function()
     {
         var left = 0;
-        var previousResizerElement = null;
+        var previousResizer = null;
 
         // Make n - 1 resizers for n columns.
         for (var i = 0; i < this.orderedColumns.length - 1; ++i) {
-            var resizerElement = this.resizerElements[i];
-
-            if (!resizerElement) {
-                // This is the first call to updateWidth, so the resizers need
-                // to be created.
-                resizerElement = document.createElement("div");
-                resizerElement.classList.add("data-grid-resizer");
+            // Create a new resizer if one does not exist for this column.
+            if (i === this.resizers.length) {
+                resizer = new WebInspector.Resizer(WebInspector.Resizer.RuleOrientation.Vertical, this);
+                this.resizers[i] = resizer;
                 // This resizer is associated with the column to its right.
-                resizerElement.addEventListener("mousedown", this._startResizerDragging.bind(this), false);
-                this.element.appendChild(resizerElement);
-                this.resizerElements[i] = resizerElement;
+                this.element.appendChild(resizer.element);
             }
 
+            var resizer = this.resizers[i];
+
             // Get the width of the cell in the first (and only) row of the
             // header table in order to determine the width of the column, since
             // it is not possible to query a column for its width.
             left += this._headerTableBodyElement.rows[0].cells[i].offsetWidth;
 
             if (this._isColumnVisible(this.orderedColumns[i])) {
-                resizerElement.style.removeProperty("display");
-                resizerElement.style.left = left + "px";
-                resizerElement.leftNeighboringColumnID = i;
-                if (previousResizerElement)
-                    previousResizerElement.rightNeighboringColumnID = i;
-                previousResizerElement = resizerElement;
+                resizer.element.style.removeProperty("display");
+                resizer.element.style.left = left + "px";
+                resizer[WebInspector.DataGrid.PreviousColumnOrdinalSymbol] = i;
+                if (previousResizer)
+                    previousResizer[WebInspector.DataGrid.NextColumnOrdinalSymbol] = i;
+                previousResizer = resizer;
             } else {
-                resizerElement.style.setProperty("display", "none");
-                resizerElement.leftNeighboringColumnID = 0;
-                resizerElement.rightNeighboringColumnID = 0;
+                resizer.element.style.setProperty("display", "none");
+                resizer[WebInspector.DataGrid.PreviousColumnOrdinalSymbol] = 0;
+                resizer[WebInspector.DataGrid.NextColumnOrdinalSymbol] = 0;
             }
         }
-        if (previousResizerElement)
-            previousResizerElement.rightNeighboringColumnID = this.orderedColumns.length - 1;
+        if (previousResizer)
+            previousResizer[WebInspector.DataGrid.NextColumnOrdinalSymbol] = this.orderedColumns.length - 1;
     },
 
     addPlaceholderNode: function()
@@ -728,7 +779,7 @@ WebInspector.DataGrid.prototype = {
         if (this.children.length <= 0)
             this.hasChildren = false;
 
-        console.assert(!child.isPlaceholderNode, "Shouldn't delete the placeholder node.")
+        console.assert(!child.isPlaceholderNode, "Shouldn't delete the placeholder node.");
     },
 
     removeChildren: function()
@@ -776,9 +827,17 @@ WebInspector.DataGrid.prototype = {
 
     sortNodes: function(comparator)
     {
+        if (this._sortNodesRequestId)
+            return;
+
+        this._sortNodesRequestId = window.requestAnimationFrame(this._sortNodesCallback.bind(this, comparator));
+    },
+
+    _sortNodesCallback: function(comparator)
+    {
         function comparatorWrapper(aRow, bRow)
         {
-            var reverseFactor = this.sortOrder !== "asceding" ? -1 : 1;
+            var reverseFactor = this.sortOrder !== WebInspector.DataGrid.SortOrder.Ascending ? -1 : 1;
             var aNode = aRow._dataGridNode;
             var bNode = bRow._dataGridNode;
             if (aNode._data.summaryRow || aNode.isPlaceholderNode)
@@ -789,6 +848,8 @@ WebInspector.DataGrid.prototype = {
             return reverseFactor * comparator(aNode, bNode);
         }
 
+        delete this._sortNodesRequestId;
+
         if (this._editing) {
             this._sortAfterEditingCallback = this.sortNodes.bind(this, comparator);
             return;
@@ -799,7 +860,7 @@ WebInspector.DataGrid.prototype = {
         var fillerRowElement = tbody.lastChild;
 
         var sortedRowElements = Array.prototype.slice.call(childNodes, 0, childNodes.length - 1);
-        sortedRowElements.sort(comparatorWrapper);
+        sortedRowElements.sort(comparatorWrapper.bind(this));
 
         tbody.removeChildren();
 
@@ -923,29 +984,20 @@ WebInspector.DataGrid.prototype = {
         return rowElement && rowElement._dataGridNode;
     },
 
-    _clickInHeaderCell: function(event)
+    _headerCellClicked: function(event)
     {
         var cell = event.target.enclosingNodeOrSelfWithNodeName("th");
-        if (!cell || !cell.columnIdentifier || !cell.classList.contains("sortable"))
+        if (!cell || !cell.columnIdentifier || !cell.classList.contains(WebInspector.DataGrid.SortableColumnStyleClassName))
             return;
 
-        var sortOrder = this.sortOrder;
-
-        if (this._sortColumnCell)
-            this._sortColumnCell.removeMatchingStyleClasses("sort-\\w+");
-
-        if (cell == this._sortColumnCell) {
-            if (sortOrder === "ascending")
-                sortOrder = "descending";
+        var clickedColumnIdentifier = cell.columnIdentifier;
+        if (this.sortColumnIdentifier === clickedColumnIdentifier) {
+            if (this.sortOrder !== WebInspector.DataGrid.SortOrder.Descending)
+                this.sortOrder = WebInspector.DataGrid.SortOrder.Descending;
             else
-                sortOrder = "ascending";
-        }
-
-        this._sortColumnCell = cell;
-
-        cell.classList.add("sort-" + sortOrder);
-
-        this.dispatchEventToListeners(WebInspector.DataGrid.Event.SortChanged);
+                this.sortOrder = WebInspector.DataGrid.SortOrder.Ascending;
+        } else
+            this.sortColumnIdentifier = clickedColumnIdentifier;
     },
 
     _mouseoverColumnCollapser: function(event)
@@ -982,7 +1034,7 @@ WebInspector.DataGrid.prototype = {
     {
         var collapserColumnIdentifier = null;
         for (var [identifier, column] of this.columns) {
-            if (column.get("collapsesGroup") == columnGroup) {
+            if (column["collapsesGroup"] === columnGroup) {
                 collapserColumnIdentifier = identifier;
                 break;
             }
@@ -1004,7 +1056,7 @@ WebInspector.DataGrid.prototype = {
 
         var showOrHide = columnsWillCollapse ? this._hideColumn : this._showColumn;
         for (var [identifier, column] of this.columns) {
-            if (column.get("group") === cell.collapsesGroup)
+            if (column["group"] === cell.collapsesGroup)
                 showOrHide.call(this, identifier);
         }
 
@@ -1035,19 +1087,6 @@ WebInspector.DataGrid.prototype = {
         // Implemented by subclasses if needed.
     },
 
-    isColumnSortColumn: function(columnIdentifier)
-    {
-        return this._sortColumnCell === this._headerTableCellElements.get(columnIdentifier);
-    },
-
-    markColumnAsSortedBy: function(columnIdentifier, sortOrder)
-    {
-        if (this._sortColumnCell)
-            this._sortColumnCell.removeMatchingStyleClasses("sort-\\w+");
-        this._sortColumnCell = this._headerTableCellElements.get(columnIdentifier);
-        this._sortColumnCell.classList.add("sort-" + sortOrder);
-    },
-
     headerTableHeader: function(columnIdentifier)
     {
         return this._headerTableCellElements.get(columnIdentifier);
@@ -1061,16 +1100,23 @@ WebInspector.DataGrid.prototype = {
         WebInspector.DataGrid._generatedSortIndicatorImages = true;
 
         var specifications = {};
-        specifications["arrow"] = {
-            fillColor: [81, 81, 81],
-            shadowColor: [255, 255, 255, 0.5],
-            shadowOffsetX: 0,
-            shadowOffsetY: 1,
-            shadowBlur: 0
-        };
 
-        generateColoredImagesForCSS("Images/SortIndicatorDownArrow.svg", specifications, 9, 8, "data-grid-sort-indicator-down-");
-        generateColoredImagesForCSS("Images/SortIndicatorUpArrow.svg", specifications, 9, 8, "data-grid-sort-indicator-up-");
+        if (WebInspector.Platform.isLegacyMacOS) {
+            specifications["arrow"] = {
+                fillColor: [81, 81, 81],
+                shadowColor: [255, 255, 255, 0.5],
+                shadowOffsetX: 0,
+                shadowOffsetY: 1,
+                shadowBlur: 0
+            };
+        } else {
+            specifications["arrow"] = {
+                fillColor: [81, 81, 81],
+            };
+        }
+
+        generateColoredImagesForCSS(platformImagePath("SortIndicatorDownArrow.svg"), specifications, 9, 8, "data-grid-sort-indicator-down-");
+        generateColoredImagesForCSS(platformImagePath("SortIndicatorUpArrow.svg"), specifications, 9, 8, "data-grid-sort-indicator-up-");
     },
 
     _mouseDownInDataTable: function(event)
@@ -1099,7 +1145,7 @@ WebInspector.DataGrid.prototype = {
         if (this.dataGrid._refreshCallback && (!gridNode || gridNode !== this.placeholderNode))
             contextMenu.appendItem(WebInspector.UIString("Refresh"), this._refreshCallback.bind(this));
 
-        if (gridNode && gridNode.selectable && !gridNode.isEventWithinDisclosureTriangle(event)) {
+        if (gridNode && gridNode.selectable && gridNode.copyable && !gridNode.isEventWithinDisclosureTriangle(event)) {
             contextMenu.appendItem(WebInspector.UIString("Copy Row"), this._copyRow.bind(this, event.target));
 
             if (this.dataGrid._editCallback) {
@@ -1108,7 +1154,7 @@ WebInspector.DataGrid.prototype = {
                 else {
                     var element = event.target.enclosingNodeOrSelfWithNodeName("td");
                     var columnIdentifier = element.__columnIdentifier;
-                    var columnTitle = this.dataGrid.columns.get(columnIdentifier).get("title");
+                    var columnTitle = this.dataGrid.columns.get(columnIdentifier)["title"];
                     contextMenu.appendItem(WebInspector.UIString("Edit ā€œ%sā€").format(columnTitle), this._startEditing.bind(this, event.target));
                 }
             }
@@ -1141,11 +1187,17 @@ WebInspector.DataGrid.prototype = {
         }
     },
 
+    textForDataGridNodeColumn: function(node, columnIdentifier)
+    {
+        var data = node.data[columnIdentifier];
+        return (data instanceof Node ? data.textContent : data) || "";
+    },
+
     _copyTextForDataGridNode: function(node)
     {
         var fields = [];
         for (var identifier of node.dataGrid.orderedColumns)
-            fields.push(node.data[identifier] || "");
+            fields.push(this.textForDataGridNodeColumn(node, identifier));
 
         var tabSeparatedValues = fields.join("\t");
         return tabSeparatedValues;
@@ -1180,7 +1232,7 @@ WebInspector.DataGrid.prototype = {
 
     get resizeMethod()
     {
-        if (typeof this._resizeMethod === "undefined")
+        if (!this._resizeMethod)
             return WebInspector.DataGrid.ResizeMethod.Nearest;
         return this._resizeMethod;
     },
@@ -1190,44 +1242,36 @@ WebInspector.DataGrid.prototype = {
         this._resizeMethod = method;
     },
 
-    _startResizerDragging: function(event)
+    resizerDragStarted: function(resizer)
     {
-        if (event.button !== 0 || event.ctrlKey)
-            return;
+        if (!resizer[WebInspector.DataGrid.NextColumnOrdinalSymbol])
+            return true; // Abort the drag;
 
-        this._currentResizer = event.target;
-        if (!this._currentResizer.rightNeighboringColumnID)
-            return;
-
-        WebInspector.elementDragStart(this._currentResizer, this._resizerDragging.bind(this),
-            this._endResizerDragging.bind(this), event, "col-resize");
+        this._currentResizer = resizer;
     },
 
-    _resizerDragging: function(event)
+    resizerDragging: function(resizer, positionDelta)
     {
-        if (event.button !== 0)
-            return;
-
-        var resizer = this._currentResizer;
-        if (!resizer)
+        console.assert(resizer === this._currentResizer, resizer, this._currentResizer);
+        if (resizer != this._currentResizer)
             return;
 
         // Constrain the dragpoint to be within the containing div of the
         // datagrid.
-        var dragPoint = event.clientX - this.element.totalOffsetLeft;
+        var dragPoint = (resizer.initialPosition - positionDelta) - this.element.totalOffsetLeft;
         // Constrain the dragpoint to be within the space made up by the
         // column directly to the left and the column directly to the right.
-        var leftCellIndex = resizer.leftNeighboringColumnID;
-        var rightCellIndex = resizer.rightNeighboringColumnID;
+        var leftCellIndex = resizer[WebInspector.DataGrid.PreviousColumnOrdinalSymbol];
+        var rightCellIndex = resizer[WebInspector.DataGrid.NextColumnOrdinalSymbol];
         var firstRowCells = this._headerTableBodyElement.rows[0].cells;
         var leftEdgeOfPreviousColumn = 0;
         for (var i = 0; i < leftCellIndex; i++)
             leftEdgeOfPreviousColumn += firstRowCells[i].offsetWidth;
 
         // Differences for other resize methods
-        if (this.resizeMethod == WebInspector.DataGrid.ResizeMethod.Last) {
-            rightCellIndex = this.resizerElements.length;
-        } else if (this.resizeMethod == WebInspector.DataGrid.ResizeMethod.First) {
+        if (this.resizeMethod === WebInspector.DataGrid.ResizeMethod.Last) {
+            rightCellIndex = this.resizers.length;
+        } else if (this.resizeMethod === WebInspector.DataGrid.ResizeMethod.First) {
             leftEdgeOfPreviousColumn += firstRowCells[leftCellIndex].offsetWidth - firstRowCells[0].offsetWidth;
             leftCellIndex = 0;
         }
@@ -1240,7 +1284,7 @@ WebInspector.DataGrid.prototype = {
 
         dragPoint = Number.constrain(dragPoint, leftMinimum, rightMaximum);
 
-        resizer.style.left = (dragPoint - this.CenterResizerOverBorderAdjustment) + "px";
+        resizer.element.style.left = (dragPoint - this.CenterResizerOverBorderAdjustment) + "px";
 
         var percentLeftColumn = (((dragPoint - leftEdgeOfPreviousColumn) / this._dataTableElement.offsetWidth) * 100) + "%";
         this._headerTableColumnGroupElement.children[leftCellIndex].style.width = percentLeftColumn;
@@ -1255,12 +1299,12 @@ WebInspector.DataGrid.prototype = {
         this.dispatchEventToListeners(WebInspector.DataGrid.Event.DidLayout);
     },
 
-    _endResizerDragging: function(event)
+    resizerDragEnded: function(resizer)
     {
-        if (event.button !== 0)
+        console.assert(resizer === this._currentResizer, resizer, this._currentResizer);
+        if (resizer != this._currentResizer)
             return;
 
-        WebInspector.elementDragEnd(event);
         this._currentResizer = null;
         this.dispatchEventToListeners(WebInspector.DataGrid.Event.DidLayout);
     },
@@ -1278,15 +1322,11 @@ WebInspector.DataGrid.ResizeMethod = {
 
 WebInspector.DataGrid.prototype.__proto__ = WebInspector.Object.prototype;
 
-/**
- * @constructor
- * @extends {WebInspector.Object}
- * @param {boolean=} hasChildren
- */
 WebInspector.DataGridNode = function(data, hasChildren)
 {
     this._expanded = false;
     this._selected = false;
+    this._copyable = true;
     this._shouldRefreshChildren = true;
     this._data = data || {};
     this.hasChildren = hasChildren || false;
@@ -1304,6 +1344,16 @@ WebInspector.DataGridNode.prototype = {
         return !this._element || !this._element.classList.contains("hidden");
     },
 
+    get copyable()
+    {
+        return this._copyable;
+    },
+
+    set copyable(x)
+    {
+        this._copyable = x;
+    },
+
     get element()
     {
         if (this._element)
@@ -1450,7 +1500,7 @@ WebInspector.DataGridNode.prototype = {
     {
         if (typeof(this._leftPadding) === "number")
             return this._leftPadding;
-        
+
         this._leftPadding = this.depth * this.dataGrid.indentWidth;
         return this._leftPadding;
     },
@@ -1522,11 +1572,11 @@ WebInspector.DataGridNode.prototype = {
 
         var column = this.dataGrid.columns.get(columnIdentifier);
 
-        if (column.has("aligned"))
-            cellElement.classList.add(column.get("aligned"));
+        if (column["aligned"])
+            cellElement.classList.add(column["aligned"]);
 
-        if (column.has("group"))
-            cellElement.classList.add("column-group-" + column.get("group"));
+        if (column["group"])
+            cellElement.classList.add("column-group-" + column["group"]);
 
         var div = cellElement.createChild("div");
         var content = this.createCellContent(columnIdentifier, cellElement);
@@ -1671,9 +1721,6 @@ WebInspector.DataGridNode.prototype = {
         this.dispatchEventToListeners("revealed");
     },
 
-    /**
-     * @param {boolean=} supressSelectedEvent
-     */
     select: function(supressSelectedEvent)
     {
         if (!this.dataGrid || !this.selectable || this.selected)
@@ -1698,9 +1745,6 @@ WebInspector.DataGridNode.prototype = {
         this.select();
     },
 
-    /**
-     * @param {boolean=} supressDeselectedEvent
-     */
     deselect: function(supressDeselectedEvent)
     {
         if (!this.dataGrid || this.dataGrid.selectedNode !== this || !this.selected)