Web Inspector: Support console.table
authorjoepeck@webkit.org <joepeck@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 2 Feb 2015 21:07:39 +0000 (21:07 +0000)
committerjoepeck@webkit.org <joepeck@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 2 Feb 2015 21:07:39 +0000 (21:07 +0000)
https://bugs.webkit.org/show_bug.cgi?id=141058

Reviewed by Timothy Hatcher.

Source/JavaScriptCore:

* inspector/InjectedScriptSource.js:
Include the firstLevelKeys filter when generating previews.

* runtime/ConsoleClient.cpp:
(JSC::appendMessagePrefix):
Differentiate console.table logs to system log.

Source/WebCore:

* inspector/CommandLineAPIModuleSource.js:
Include "table(foo)" as an alias of "console.table(foo)" on
the command line.

Source/WebInspectorUI:

* Localizations/en.lproj/localizedStrings.js:
New "Index", "(Index)", "Value", table header strings.

* UserInterface/Views/ConsoleMessage.js:
Add "Table", but add FIXMEs to consider using the protocol generated enums.

* UserInterface/Views/ConsoleMessageImpl.js:
(WebInspector.ConsoleMessageImpl.prototype._format):
Special case console.table messages.

(WebInspector.ConsoleMessageImpl.prototype._appendPropertyPreviews):
(WebInspector.ConsoleMessageImpl.prototype._propertyPreviewElement):
Factor out ProjectPreview printing. Also, replace newlines in strings
with return characters, like we did elsewhere.

(WebInspector.ConsoleMessageImpl.prototype._formatParameterAsTable):
Ultimately try to create a DataGrid from the output. Search first
for rich object data in the list. If no rich object data is found
just check for simple values. If the table is lossy, also do
a log of the object in case the user wants to see more data.

* UserInterface/Views/DataGrid.js:
(WebInspector.DataGrid):
The for..in enumeration is unordered and may not give us the
column ordering we wanted. So include an optional preferred
column names list to get the preferred order.

(WebInspector.DataGrid.createSortableDataGrid):
Numerous bug fixes here. Accidental globals, typos, and sorting failures.

(WebInspector.DataGrid.prototype.autoSizeColumns):
(WebInspector.DataGrid.prototype.textForDataGridNodeColumn):
(WebInspector.DataGrid.prototype._copyTextForDataGridNode):
Create a generic method to get the text for a datagrid node in a column.
This is important for getting the text from console.table previews which
contains Nodes.

* UserInterface/Views/LogContentView.css:
(.console-messages:focus .console-item.selected .data-grid tr.selected):
(.console-item .data-grid tr.selected):
DataGrid selection colors while in the console which may or may
not have selected console items.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@179493 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/inspector/InjectedScriptSource.js
Source/JavaScriptCore/runtime/ConsoleClient.cpp
Source/WebCore/ChangeLog
Source/WebCore/inspector/CommandLineAPIModuleSource.js
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
Source/WebInspectorUI/UserInterface/Views/ConsoleMessage.js
Source/WebInspectorUI/UserInterface/Views/ConsoleMessageImpl.js
Source/WebInspectorUI/UserInterface/Views/DataGrid.js
Source/WebInspectorUI/UserInterface/Views/LogContentView.css

index 0946670..0282942 100644 (file)
@@ -1,3 +1,17 @@
+2015-02-02  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: Support console.table
+        https://bugs.webkit.org/show_bug.cgi?id=141058
+
+        Reviewed by Timothy Hatcher.
+
+        * inspector/InjectedScriptSource.js:
+        Include the firstLevelKeys filter when generating previews.
+
+        * runtime/ConsoleClient.cpp:
+        (JSC::appendMessagePrefix):
+        Differentiate console.table logs to system log.
+
 2015-01-31  Filip Pizlo  <fpizlo@apple.com>
 
         BinarySwitch should be faster on average
index 58631af..5df30be 100644 (file)
@@ -101,14 +101,17 @@ InjectedScript.prototype = {
     {
         if (!canAccessInspectedGlobalObject)
             return this._fallbackWrapper(table);
+
         var columnNames = null;
         if (typeof columns === "string")
             columns = [columns];
+
         if (InjectedScriptHost.subtype(columns) === "array") {
             columnNames = [];
             for (var i = 0; i < columns.length; ++i)
-                columnNames.push(String(columns[i]));
+                columnNames.push(toString(columns[i]));
         }
+
         return this._wrapObject(table, "console", false, true, columnNames);
     },
 
@@ -847,7 +850,7 @@ InjectedScript.RemoteObject.prototype = {
             // Properties.
             preview.properties = [];
             var descriptors = injectedScript._propertyDescriptors(object);
-            this._appendPropertyPreviews(preview, descriptors, propertiesThreshold, secondLevelKeys);
+            this._appendPropertyPreviews(preview, descriptors, propertiesThreshold, firstLevelKeys, secondLevelKeys);
             if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0)
                 return preview;
 
@@ -860,7 +863,7 @@ InjectedScript.RemoteObject.prototype = {
         return preview;
     },
 
-    _appendPropertyPreviews: function(preview, descriptors, propertiesThreshold, secondLevelKeys)
+    _appendPropertyPreviews: function(preview, descriptors, propertiesThreshold, firstLevelKeys, secondLevelKeys)
     {
         for (var descriptor of descriptors) {
             // Seen enough.
@@ -886,6 +889,10 @@ InjectedScript.RemoteObject.prototype = {
             if (!descriptor.enumerable && !descriptor.isOwn && !(this.subtype === "array" && isUInt32(name)))
                 continue;
 
+            // If we have a filter, only show properties in the filter.
+            if (firstLevelKeys && firstLevelKeys.indexOf(name) === -1)
+                continue;
+
             // Getter/setter.
             if (!("value" in descriptor)) {
                 preview.lossless = false;
index b766368..acd9aa6 100644 (file)
@@ -119,6 +119,8 @@ static void appendMessagePrefix(StringBuilder& builder, MessageSource source, Me
 
     if (type == MessageType::Trace)
         levelString = "TRACE";
+    else if (type == MessageType::Table)
+        levelString = "TABLE";
 
     builder.append(sourceString);
     builder.append(' ');
index 80cb17e..3d9b821 100644 (file)
@@ -1,3 +1,14 @@
+2015-02-02  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: Support console.table
+        https://bugs.webkit.org/show_bug.cgi?id=141058
+
+        Reviewed by Timothy Hatcher.
+
+        * inspector/CommandLineAPIModuleSource.js:
+        Include "table(foo)" as an alias of "console.table(foo)" on
+        the command line.
+
 2015-02-02  Roger Fong  <roger_fong@apple.com>
 
         [Win] Build fix following r179482.
index fe3cf3d..abfc7ce 100644 (file)
@@ -132,7 +132,7 @@ function CommandLineAPI(commandLineAPIImpl, callFrame)
  * @const
  */
 CommandLineAPI.members_ = [
-    "$", "$$", "$x", "dir", "dirxml", "keys", "values", "profile", "profileEnd",
+    "$", "$$", "$x", "dir", "dirxml", "keys", "values", "profile", "profileEnd", "table",
     "monitorEvents", "unmonitorEvents", "inspect", "copy", "clear", "getEventListeners"
 ];
 
@@ -243,6 +243,11 @@ CommandLineAPIImpl.prototype = {
         return inspectedWindow.console.profileEnd.apply(inspectedWindow.console, arguments)
     },
 
+    table: function()
+    {
+        return inspectedWindow.console.table.apply(inspectedWindow.console, arguments)
+    },
+
     /**
      * @param {Object} object
      * @param {Array.<string>|string=} types
index 0dde4c6..ac96542 100644 (file)
@@ -1,5 +1,56 @@
 2015-02-02  Joseph Pecoraro  <pecoraro@apple.com>
 
+        Web Inspector: Support console.table
+        https://bugs.webkit.org/show_bug.cgi?id=141058
+
+        Reviewed by Timothy Hatcher.
+
+        * Localizations/en.lproj/localizedStrings.js:
+        New "Index", "(Index)", "Value", table header strings.
+
+        * UserInterface/Views/ConsoleMessage.js:
+        Add "Table", but add FIXMEs to consider using the protocol generated enums.
+
+        * UserInterface/Views/ConsoleMessageImpl.js:
+        (WebInspector.ConsoleMessageImpl.prototype._format):
+        Special case console.table messages.
+
+        (WebInspector.ConsoleMessageImpl.prototype._appendPropertyPreviews):
+        (WebInspector.ConsoleMessageImpl.prototype._propertyPreviewElement):
+        Factor out ProjectPreview printing. Also, replace newlines in strings
+        with return characters, like we did elsewhere.
+
+        (WebInspector.ConsoleMessageImpl.prototype._formatParameterAsTable):
+        Ultimately try to create a DataGrid from the output. Search first
+        for rich object data in the list. If no rich object data is found
+        just check for simple values. If the table is lossy, also do
+        a log of the object in case the user wants to see more data.
+
+
+        * UserInterface/Views/DataGrid.js:
+        (WebInspector.DataGrid):
+        The for..in enumeration is unordered and may not give us the
+        column ordering we wanted. So include an optional preferred
+        column names list to get the preferred order.
+
+        (WebInspector.DataGrid.createSortableDataGrid):
+        Numerous bug fixes here. Accidental globals, typos, and sorting failures.
+
+        (WebInspector.DataGrid.prototype.autoSizeColumns):
+        (WebInspector.DataGrid.prototype.textForDataGridNodeColumn):
+        (WebInspector.DataGrid.prototype._copyTextForDataGridNode):
+        Create a generic method to get the text for a datagrid node in a column.
+        This is important for getting the text from console.table previews which
+        contains Nodes.
+
+        * UserInterface/Views/LogContentView.css:
+        (.console-messages:focus .console-item.selected .data-grid tr.selected):
+        (.console-item .data-grid tr.selected):
+        DataGrid selection colors while in the console which may or may
+        not have selected console items.
+
+2015-02-02  Joseph Pecoraro  <pecoraro@apple.com>
+
         Web Inspector: Extend CSS.getSupportedCSSProperties to provide values for properties for CSS Augmented JSContext
         https://bugs.webkit.org/show_bug.cgi?id=141064
 
index 7582144..4a791f9 100644 (file)
Binary files a/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js and b/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js differ
index a2935ff..00acd82 100644 (file)
@@ -64,7 +64,7 @@ WebInspector.ConsoleMessage.create = function(source, level, message, type, url,
     return new WebInspector.ConsoleMessageImpl(source, level, message, null, type, url, line, column, repeatCount, parameters, stackTrace, request);
 };
 
-// Note: Keep these constants in sync with the ones in Console.h
+// FIXME: Switch to ConsoleAgent.ConsoleMessageSource.
 WebInspector.ConsoleMessage.MessageSource = {
     HTML: "html",
     XML: "xml",
@@ -74,10 +74,12 @@ WebInspector.ConsoleMessage.MessageSource = {
     Other: "other"
 };
 
+// FIXME: Switch to ConsoleAgent.ConsoleMessageType.
 WebInspector.ConsoleMessage.MessageType = {
     Log: "log",
     Dir: "dir",
     DirXML: "dirxml",
+    Table: "table",
     Trace: "trace",
     StartGroup: "startGroup",
     StartGroupCollapsed: "startGroupCollapsed",
@@ -86,6 +88,7 @@ WebInspector.ConsoleMessage.MessageType = {
     Result: "result"
 };
 
+// FIXME: Switch to ConsoleAgent.ConsoleMessageLevel.
 WebInspector.ConsoleMessage.MessageLevel = {
     Tip: "tip",
     Log: "log",
index b8df030..aa33b04 100644 (file)
@@ -234,6 +234,11 @@ WebInspector.ConsoleMessageImpl.prototype = {
                 formattedResult.appendChild(document.createTextNode(" "));
         }
 
+        if (this.type === WebInspector.ConsoleMessage.MessageType.Table) {
+            formattedResult.appendChild(this._formatParameterAsTable(parameters));
+            return formattedResult;
+        }
+
         // Single parameter, or unused substitutions from above.
         for (var i = 0; i < parameters.length; ++i) {
             // Inline strings when formatting.
@@ -373,20 +378,7 @@ WebInspector.ConsoleMessageImpl.prototype = {
                 element.appendChild(document.createTextNode(": "));
             }
 
-            var span = element.createChild("span", "console-formatted-" + property.type);
-            if (property.type === "object") {
-                if (property.subtype === "node")
-                    span.classList.add("console-formatted-preview-node");
-                else if (property.subtype === "regexp")
-                    span.classList.add("console-formatted-regexp");
-            }
-
-            if (property.type === "string")
-                span.textContent = "\"" + property.value + "\"";
-            else if (property.type === "function")
-                span.textContent = "function";
-            else
-                span.textContent = property.value;
+            element.appendChild(this._propertyPreviewElement(property));
         }
 
         if (preview.overflow)
@@ -397,6 +389,32 @@ WebInspector.ConsoleMessageImpl.prototype = {
         return preview.lossless;
     },
 
+    _propertyPreviewElement: function(property)
+    {
+        var span = document.createElement("span");
+        span.classList.add("console-formatted-" + property.type);
+
+        if (property.type === "string") {
+            span.textContent = "\"" + property.value.replace(/\n/g, "\u21B5") + "\"";
+            return span;
+        }
+
+        if (property.type === "function") {
+            span.textContent = "function";
+            return span;
+        }
+
+        if (property.type === "object") {
+            if (property.subtype === "node")
+                span.classList.add("console-formatted-preview-node");
+            else if (property.subtype === "regexp")
+                span.classList.add("console-formatted-regexp");
+        }
+
+        span.textContent = property.value;
+        return span;
+    },
+
     _appendValuePreview: function(element, preview)
     {
         element.appendChild(document.createTextNode(preview.description));
@@ -429,6 +447,86 @@ WebInspector.ConsoleMessageImpl.prototype = {
         arr.getOwnProperties(this._printArray.bind(this, arr, elem));
     },
 
+    _formatParameterAsTable: function(parameters)
+    {
+        var element = document.createElement("span");
+        var table = parameters[0];
+        if (!table || !table.preview)
+            return element;
+
+        var rows = [];
+        var columnNames = [];
+        var flatValues = [];
+        var preview = table.preview;
+
+        // Check first for valuePreviews in the properties meaning this was an array of objects.
+        for (var i = 0; i < preview.properties.length; ++i) {
+            var rowProperty = preview.properties[i];
+            var rowPreview = rowProperty.valuePreview;
+            if (!rowPreview)
+                continue;
+
+            var rowValue = {};
+            const maxColumnsToRender = 10;
+            for (var j = 0; j < rowPreview.properties.length; ++j) {
+                var cellProperty = rowPreview.properties[j];
+                var columnRendered = columnNames.contains(cellProperty.name);
+                if (!columnRendered) {
+                    if (columnNames.length === maxColumnsToRender)
+                        continue;
+                    columnRendered = true;
+                    columnNames.push(cellProperty.name);
+                }
+
+                rowValue[cellProperty.name] = this._propertyPreviewElement(cellProperty);
+            }
+            rows.push([rowProperty.name, rowValue]);
+        }
+
+        // If there were valuePreviews, convert to a flat list.
+        if (rows.length) {
+            columnNames.unshift(WebInspector.UIString("(Index)"));
+            for (var i = 0; i < rows.length; ++i) {
+                var rowName = rows[i][0];
+                var rowValue = rows[i][1];
+                flatValues.push(rowName);
+                for (var j = 1; j < columnNames.length; ++j)
+                    flatValues.push(rowValue[columnNames[j]]);
+            }
+        }
+
+        // If there were no value Previews, then check for an array of values.
+        if (!flatValues.length) {
+            for (var i = 0; i < preview.properties.length; ++i) {
+                var rowProperty = preview.properties[i];
+                if (!("value" in rowProperty))
+                    continue;
+
+                if (!columnNames.length) {
+                    columnNames.push(WebInspector.UIString("Index"));
+                    columnNames.push(WebInspector.UIString("Value"));
+                }
+
+                flatValues.push(rowProperty.name);
+                flatValues.push(this._propertyPreviewElement(rowProperty));
+            }
+        }
+
+        // If lossless or not table data, output the object so full data can be gotten.
+        if (!preview.lossless || !flatValues.length) {
+            element.appendChild(this._formatParameter(table, true, false));
+            if (!flatValues.length)
+                return element;
+        }
+
+        var dataGridContainer = element.createChild("span");
+        var dataGrid = WebInspector.DataGrid.createSortableDataGrid(columnNames, flatValues);
+        dataGrid.element.classList.add("inline");
+        dataGridContainer.appendChild(dataGrid.element);
+
+        return element;
+    },
+
     _formatParameterAsString: function(output, elem)
     {
         var span = document.createElement("span");
index 9e324ed..6235c9f 100644 (file)
@@ -23,7 +23,7 @@
  * 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 = [];
@@ -87,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();
 }
@@ -118,17 +123,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)
@@ -142,34 +145,38 @@ WebInspector.DataGrid.createSortableDataGrid = function(columnNames, values)
     function sortDataGrid()
     {
         var sortColumnIdentifier = dataGrid.sortColumnIdentifier;
-        var sortAscending = dataGrid.sortOrder === WebInspector.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;
 }
 
@@ -360,7 +367,7 @@ WebInspector.DataGrid.prototype = {
         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;
             }
@@ -414,7 +421,8 @@ WebInspector.DataGrid.prototype = {
         this.updateLayout();
     },
 
-    insertColumn: function(columnIdentifier, columnData, insertionIndex) {
+    insertColumn: function(columnIdentifier, columnData, insertionIndex)
+    {
         if (insertionIndex === undefined)
             insertionIndex = this.orderedColumns.length;
         insertionIndex = Number.constrain(insertionIndex, 0, this.orderedColumns.length);
@@ -1179,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;
index 223c5af..f0519e5 100644 (file)
     width: 2px;
 }
 
+.console-messages:focus .console-item.selected .data-grid tr.selected {
+    background-color: hsl(210, 90%, 90%) !important;
+}
+
+.console-item .data-grid tr.selected {
+    background-color: hsl(210, 0%, 90%) !important;
+}
+
 .console-messages:focus .console-item.selected .console-user-command::after,
 .console-messages:focus .console-item.selected .console-message::after {
     background: hsl(210, 100%, 49%);