Web Inspector: ES6: Improved Console Format for Set and Map Objects (like Arrays)
authorjoepeck@webkit.org <joepeck@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 29 Jan 2015 19:25:16 +0000 (19:25 +0000)
committerjoepeck@webkit.org <joepeck@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 29 Jan 2015 19:25:16 +0000 (19:25 +0000)
https://bugs.webkit.org/show_bug.cgi?id=122867

Reviewed by Timothy Hatcher.

Source/JavaScriptCore:

Add new Runtime.RemoteObject object subtypes for "map", "set", and "weakmap".

Upgrade Runtime.ObjectPreview to include type/subtype information. Now,
an ObjectPreview can be used for any value, in place of a RemoteObject,
and not capture / hold a reference to the value. The value will be in
the string description.

Adding this information to ObjectPreview can duplicate some information
in the protocol messages if a preview is provided, but simplifies
previews, so that all the information you need for any RemoteObject
preview is available. To slim messages further, make "overflow" and
"properties" only available on previews that may contain properties.
So, not primitives or null.

Finally, for "Map/Set/WeakMap" add an "entries" list to the preview
that will return previews with "key" and "value" properties depending
on the collection type. To get live, non-preview objects from a
collection, use Runtime.getCollectionEntries.

In order to keep the WeakMap's values Weak the frontend may provide
a unique object group name when getting collection entries. It may
then release that object group, e.g. when not showing the WeakMap's
values to the user, and thus remove the strong reference to the keys
so they may be garbage collected.

* runtime/WeakMapData.h:
(JSC::WeakMapData::begin):
(JSC::WeakMapData::end):
Expose iterators so the Inspector may access WeakMap keys/values.

* inspector/JSInjectedScriptHostPrototype.cpp:
(Inspector::JSInjectedScriptHostPrototype::finishCreation):
(Inspector::jsInjectedScriptHostPrototypeFunctionWeakMapEntries):
* inspector/JSInjectedScriptHost.h:
* inspector/JSInjectedScriptHost.cpp:
(Inspector::JSInjectedScriptHost::subtype):
Discern "map", "set", and "weakmap" object subtypes.

(Inspector::JSInjectedScriptHost::weakMapEntries):
Return a list of WeakMap entries. These are strong references
that the Inspector code is responsible for releasing.

* inspector/protocol/Runtime.json:
Update types and expose the new getCollectionEntries command.

* inspector/agents/InspectorRuntimeAgent.h:
* inspector/agents/InspectorRuntimeAgent.cpp:
(Inspector::InspectorRuntimeAgent::getCollectionEntries):
* inspector/InjectedScript.h:
* inspector/InjectedScript.cpp:
(Inspector::InjectedScript::getInternalProperties):
(Inspector::InjectedScript::getCollectionEntries):
Pass through to the InjectedScript and call getCollectionEntries.

* inspector/scripts/codegen/generator.py:
Add another type with runtime casting.

* inspector/InjectedScriptSource.js:
- Implement getCollectionEntries to get a range of values from a
collection. The non-Weak collections have an order to their keys (in
order of added) so range'd gets are okay. WeakMap does not have an
order, so only allow fetching a number of values.
- Update preview generation to address the Runtime.ObjectPreview
type changes.

Source/WebInspectorUI:

This includes Set/Map/WeakMap previews:

    - Set previews: Set {1, 2, 3}
    - Map/WeakMap previews: Map {1 => 2, "key" => "value"}

For WeakMaps:

    - the preview itself shows up to 5 key/value pairs from when the object was logged
    - the previews are strings only, and thus do not retain the actual keys/values
    - when expanding, we get RemoteObjects and strongly retain the keys/values
    - when collapsing / clearing, we release the RemoteObjects so they can get collected

Currently you collapse the <entries> section, and re-expand later the
collection may show you knew keys/values. The UI for this will change.

* Localizations/en.lproj/localizedStrings.js:
* UserInterface/Protocol/RemoteObject.js:
(WebInspector.RemoteObject.prototype.isCollectionType):
(WebInspector.RemoteObject.prototype.isWeakCollection):
(WebInspector.RemoteObject.prototype.getCollectionEntries):
(WebInspector.RemoteObject.prototype.releaseWeakCollectionEntries):
(WebInspector.RemoteObject.prototype.arrayLength):
(WebInspector.RemoteObject.prototype._weakCollectionObjectGroup):
High level functions for dealing with a RemoteObject that may be a
collection / weak collection.

* UserInterface/Views/ConsoleMessageImpl.js:
(WebInspector.ConsoleMessageImpl):
(WebInspector.ConsoleMessageImpl.prototype._formatParameterAsObject):
Include default formatters for collection types.

(WebInspector.ConsoleMessageImpl.prototype._appendPreview):
(WebInspector.ConsoleMessageImpl.prototype._appendEntryPreviews):
(WebInspector.ConsoleMessageImpl.prototype._appendPropertyPreviews):
(WebInspector.ConsoleMessageImpl.prototype._appendValuePreview):
(WebInspector.ConsoleMessageImpl.prototype._appendObjectPreview): Deleted.
Refactor preview generation a bit and include a specific path for
generation the output of a preview with "entries".

* UserInterface/Views/LogContentView.css:
(.console-object-preview-body .console-object-preview-name.console-object-preview-name-Object):
With nested Object previews ("Map {{a:1} => 1}") don't show "Object" for the inner
object preview. Only show it if it has a unique type ("Map {Foo {a:1} => 1}")

(.console-formatted-object, .console-formatted-node, .console-formatted-error, .console-formatted-map, .console-formatted-set, .console-formatted-weakmap):
(:matches(.console-formatted-object, .console-formatted-node, .console-formatted-error, .console-formatted-map, .console-formatted-set, .console-formatted-weakmap) .section):
(:matches(.console-formatted-object, .console-formatted-node, .console-formatted-error, .console-formatted-map, .console-formatted-set, .console-formatted-weakmap) .properties):
Make map/set/weakmap display like Objects.

* UserInterface/Views/ObjectPropertiesSection.js:
(WebInspector.ObjectPropertiesSection.prototype.update):
(WebInspector.ObjectPropertiesSection.prototype.updateProperties):
(WebInspector.ObjectPropertyTreeElement.prototype.onpopulate.callback):
(WebInspector.ObjectPropertyTreeElement.prototype.onpopulate):
(WebInspector.CollectionEntriesMainTreeElement):
(WebInspector.CollectionEntriesMainTreeElement.prototype.onexpand.callback):
(WebInspector.CollectionEntriesMainTreeElement.prototype.onexpand):
(WebInspector.CollectionEntriesMainTreeElement.prototype.oncollapse):
(WebInspector.CollectionEntriesMainTreeElement.prototype.ondetach):
(WebInspector.CollectionEntriesMainTreeElement.prototype._trackWeakEntries):
(WebInspector.CollectionEntriesMainTreeElement.prototype._untrackWeakEntries):
(WebInspector.CollectionEntryTreeElement):
(WebInspector.CollectionEntryTreeElement.prototype.onpopulate):
(WebInspector.CollectionEntryTreeElement.prototype.onattach):
(WebInspector.EmptyCollectionTreeElement):
(WebInspector.ObjectPropertiesSection.prototype.update.callback): Deleted.
Add a quick UI for exploring the entries of a collection. We are actively
changing the styles of objects in the Console, so this should change soon.

LayoutTests:

* TestExpectations:
The test unexpectedly fails in Debug builds, so skip it.

* inspector/model/remote-object-expected.txt:
* inspector/model/remote-object.html:
Update based on Runtime.ObjectPreview changes.

* inspector/model/remote-object-weak-collection-expected.txt: Added.
* inspector/model/remote-object-weak-collection.html: Added.
New test for weak collection handling.

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

24 files changed:
LayoutTests/ChangeLog
LayoutTests/TestExpectations
LayoutTests/inspector/model/remote-object-expected.txt
LayoutTests/inspector/model/remote-object-weak-collection-expected.txt [new file with mode: 0644]
LayoutTests/inspector/model/remote-object-weak-collection.html [new file with mode: 0644]
LayoutTests/inspector/model/remote-object.html
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/inspector/InjectedScript.cpp
Source/JavaScriptCore/inspector/InjectedScript.h
Source/JavaScriptCore/inspector/InjectedScriptSource.js
Source/JavaScriptCore/inspector/JSInjectedScriptHost.cpp
Source/JavaScriptCore/inspector/JSInjectedScriptHost.h
Source/JavaScriptCore/inspector/JSInjectedScriptHostPrototype.cpp
Source/JavaScriptCore/inspector/agents/InspectorRuntimeAgent.cpp
Source/JavaScriptCore/inspector/agents/InspectorRuntimeAgent.h
Source/JavaScriptCore/inspector/protocol/Runtime.json
Source/JavaScriptCore/inspector/scripts/codegen/generator.py
Source/JavaScriptCore/runtime/WeakMapData.h
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
Source/WebInspectorUI/UserInterface/Protocol/RemoteObject.js
Source/WebInspectorUI/UserInterface/Views/ConsoleMessageImpl.js
Source/WebInspectorUI/UserInterface/Views/LogContentView.css
Source/WebInspectorUI/UserInterface/Views/ObjectPropertiesSection.js

index c99fc23..af3bedc 100644 (file)
@@ -1,3 +1,21 @@
+2015-01-29  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: ES6: Improved Console Format for Set and Map Objects (like Arrays)
+        https://bugs.webkit.org/show_bug.cgi?id=122867
+
+        Reviewed by Timothy Hatcher.
+
+        * TestExpectations:
+        Inspector tests are still flakey on bots, so skip the test.
+
+        * inspector/model/remote-object-expected.txt:
+        * inspector/model/remote-object.html:
+        Update based on Runtime.ObjectPreview changes.
+
+        * inspector/model/remote-object-weak-collection-expected.txt: Added.
+        * inspector/model/remote-object-weak-collection.html: Added.
+        New test for weak collection handling.
+
 2015-01-28  Michael Catanzaro  <mcatanzaro@igalia.com>
 
         security/mixedContent/redirect-https-to-http-iframe-in-main-frame test is the same as security/mixedContent/redirect-http-to-https-iframe-in-main-frame.html
index 11f413e..d0f8090 100644 (file)
@@ -95,6 +95,7 @@ webkit.org/b/137131 inspector/debugger [ Skip ]
 webkit.org/b/137130 inspector/replay [ Skip ]
 webkit.org/b/137131 inspector/timeline [ Skip ]
 inspector/model/remote-object-get-properties.html [ Skip ]
+inspector/model/remote-object-weak-collection.html [ Skip ]
 inspector/model/remote-object.html [ Skip ]
 
 # Doesn't work yet, relies on network replay functionality (webkit.org/b/130728, webkit.org/b/129391)
index b09c587..307bce7 100644 (file)
@@ -231,7 +231,10 @@ EXPRESSION: / /
   "_objectId": "<filtered>",
   "_description": "/ /",
   "_preview": {
+    "type": "object",
+    "description": "/ /",
     "lossless": true,
+    "subtype": "regexp",
     "overflow": false,
     "properties": [
       {
@@ -271,7 +274,10 @@ EXPRESSION: /(?:)/
   "_objectId": "<filtered>",
   "_description": "/(?:)/",
   "_preview": {
+    "type": "object",
+    "description": "/(?:)/",
     "lossless": true,
+    "subtype": "regexp",
     "overflow": false,
     "properties": [
       {
@@ -311,7 +317,10 @@ EXPRESSION: /^r(e)g[e]{1,}x+/
   "_objectId": "<filtered>",
   "_description": "/^r(e)g[e]{1,}x+/",
   "_preview": {
+    "type": "object",
+    "description": "/^r(e)g[e]{1,}x+/",
     "lossless": true,
+    "subtype": "regexp",
     "overflow": false,
     "properties": [
       {
@@ -351,7 +360,10 @@ EXPRESSION: /^r(e)g[e]{1,}x+/ig
   "_objectId": "<filtered>",
   "_description": "/^r(e)g[e]{1,}x+/gi",
   "_preview": {
+    "type": "object",
+    "description": "/^r(e)g[e]{1,}x+/gi",
     "lossless": true,
+    "subtype": "regexp",
     "overflow": false,
     "properties": [
       {
@@ -391,7 +403,10 @@ EXPRESSION: new RegExp('')
   "_objectId": "<filtered>",
   "_description": "/(?:)/",
   "_preview": {
+    "type": "object",
+    "description": "/(?:)/",
     "lossless": true,
+    "subtype": "regexp",
     "overflow": false,
     "properties": [
       {
@@ -431,7 +446,10 @@ EXPRESSION: new RegExp('test', 'i')
   "_objectId": "<filtered>",
   "_description": "/test/i",
   "_preview": {
+    "type": "object",
+    "description": "/test/i",
     "lossless": true,
+    "subtype": "regexp",
     "overflow": false,
     "properties": [
       {
@@ -471,7 +489,10 @@ EXPRESSION: []
   "_objectId": "<filtered>",
   "_description": "Array[0]",
   "_preview": {
+    "type": "object",
+    "description": "Array[0]",
     "lossless": true,
+    "subtype": "array",
     "overflow": false,
     "properties": []
   }
@@ -485,7 +506,10 @@ EXPRESSION: [1, 2]
   "_objectId": "<filtered>",
   "_description": "Array[2]",
   "_preview": {
+    "type": "object",
+    "description": "Array[2]",
     "lossless": true,
+    "subtype": "array",
     "overflow": false,
     "properties": [
       {
@@ -510,7 +534,10 @@ EXPRESSION: [[1],[2],[3]]
   "_objectId": "<filtered>",
   "_description": "Array[3]",
   "_preview": {
+    "type": "object",
+    "description": "Array[3]",
     "lossless": false,
+    "subtype": "array",
     "overflow": false,
     "properties": [
       {
@@ -543,7 +570,10 @@ EXPRESSION: [true, 1, 1.234, 'string', /regex/]
   "_objectId": "<filtered>",
   "_description": "Array[5]",
   "_preview": {
+    "type": "object",
+    "description": "Array[5]",
     "lossless": false,
+    "subtype": "array",
     "overflow": false,
     "properties": [
       {
@@ -577,6 +607,63 @@ EXPRESSION: [true, 1, 1.234, 'string', /regex/]
 }
 
 -----------------------------------------------------
+EXPRESSION: [{a:1}, {b:2}, {c:2}]
+{
+  "_type": "object",
+  "_subtype": "array",
+  "_objectId": "<filtered>",
+  "_description": "Array[3]",
+  "_preview": {
+    "type": "object",
+    "description": "Array[3]",
+    "lossless": false,
+    "subtype": "array",
+    "overflow": false,
+    "properties": [
+      {
+        "name": "0",
+        "type": "object",
+        "value": "Object"
+      },
+      {
+        "name": "1",
+        "type": "object",
+        "value": "Object"
+      },
+      {
+        "name": "2",
+        "type": "object",
+        "value": "Object"
+      }
+    ]
+  }
+}
+
+-----------------------------------------------------
+EXPRESSION: [[{a:1}, {b:2}, {c:2}]]
+{
+  "_type": "object",
+  "_subtype": "array",
+  "_objectId": "<filtered>",
+  "_description": "Array[1]",
+  "_preview": {
+    "type": "object",
+    "description": "Array[1]",
+    "lossless": false,
+    "subtype": "array",
+    "overflow": false,
+    "properties": [
+      {
+        "name": "0",
+        "type": "object",
+        "subtype": "array",
+        "value": "Array[3]"
+      }
+    ]
+  }
+}
+
+-----------------------------------------------------
 EXPRESSION: arr = []; arr.length = 100; arr
 {
   "_type": "object",
@@ -584,7 +671,10 @@ EXPRESSION: arr = []; arr.length = 100; arr
   "_objectId": "<filtered>",
   "_description": "Array[100]",
   "_preview": {
+    "type": "object",
+    "description": "Array[100]",
     "lossless": true,
+    "subtype": "array",
     "overflow": false,
     "properties": []
   }
@@ -598,7 +688,10 @@ EXPRESSION: arr = []; arr.length = 100; arr.fill(1)
   "_objectId": "<filtered>",
   "_description": "Array[100]",
   "_preview": {
+    "type": "object",
+    "description": "Array[100]",
     "lossless": true,
+    "subtype": "array",
     "overflow": false,
     "properties": [
       {
@@ -1113,7 +1206,10 @@ EXPRESSION: arr = []; arr.length = 100; arr[10] = 1; arr
   "_objectId": "<filtered>",
   "_description": "Array[100]",
   "_preview": {
+    "type": "object",
+    "description": "Array[100]",
     "lossless": true,
+    "subtype": "array",
     "overflow": false,
     "properties": [
       {
@@ -1133,7 +1229,10 @@ EXPRESSION: a = null; (function() { a = arguments; })(1, '2', /3/); a
   "_objectId": "<filtered>",
   "_description": "Arguments[3]",
   "_preview": {
+    "type": "object",
+    "description": "Arguments[3]",
     "lossless": false,
+    "subtype": "array",
     "overflow": false,
     "properties": [
       {
@@ -1164,7 +1263,10 @@ EXPRESSION: new Int32Array(new ArrayBuffer(16))
   "_objectId": "<filtered>",
   "_description": "Int32Array[4]",
   "_preview": {
+    "type": "object",
+    "description": "Int32Array[4]",
     "lossless": false,
+    "subtype": "array",
     "overflow": false,
     "properties": [
       {
@@ -1214,7 +1316,10 @@ EXPRESSION: var intArray = new Int32Array(new ArrayBuffer(16)); for (var i = 0;
   "_objectId": "<filtered>",
   "_description": "Int32Array[4]",
   "_preview": {
+    "type": "object",
+    "description": "Int32Array[4]",
     "lossless": false,
+    "subtype": "array",
     "overflow": false,
     "properties": [
       {
@@ -1263,8 +1368,9 @@ EXPRESSION: ({})
   "_objectId": "<filtered>",
   "_description": "Object",
   "_preview": {
+    "type": "object",
+    "description": "Object",
     "lossless": true,
-    "overflow": false,
     "properties": []
   }
 }
@@ -1276,8 +1382,9 @@ EXPRESSION: ({a: 1})
   "_objectId": "<filtered>",
   "_description": "Object",
   "_preview": {
+    "type": "object",
+    "description": "Object",
     "lossless": true,
-    "overflow": false,
     "properties": [
       {
         "name": "a",
@@ -1295,8 +1402,9 @@ EXPRESSION: ({a: 1, b: "string", c :/regex/})
   "_objectId": "<filtered>",
   "_description": "Object",
   "_preview": {
+    "type": "object",
+    "description": "Object",
     "lossless": false,
-    "overflow": false,
     "properties": [
       {
         "name": "a",
@@ -1325,8 +1433,9 @@ EXPRESSION: ({a:function a(){}, b:function b(){}, get getter(){}, set setter(v){
   "_objectId": "<filtered>",
   "_description": "Object",
   "_preview": {
+    "type": "object",
+    "description": "Object",
     "lossless": false,
-    "overflow": false,
     "properties": [
       {
         "name": "a",
@@ -1357,8 +1466,9 @@ EXPRESSION: function Foo() {}; new Foo
   "_objectId": "<filtered>",
   "_description": "Foo",
   "_preview": {
+    "type": "object",
+    "description": "Foo",
     "lossless": true,
-    "overflow": false,
     "properties": []
   }
 }
@@ -1370,8 +1480,9 @@ EXPRESSION: function Bar() { this._x = 5 }; Bar.prototype = {constructor: Bar, g
   "_objectId": "<filtered>",
   "_description": "Bar",
   "_preview": {
+    "type": "object",
+    "description": "Bar",
     "lossless": false,
-    "overflow": false,
     "properties": [
       {
         "name": "_x",
@@ -1398,8 +1509,9 @@ EXPRESSION: window.loadEvent
   "_objectId": "<filtered>",
   "_description": "Event",
   "_preview": {
+    "type": "object",
+    "description": "Event",
     "lossless": false,
-    "overflow": true,
     "properties": [
       {
         "name": "clipboardData",
@@ -1427,7 +1539,8 @@ EXPRESSION: window.loadEvent
         "type": "number",
         "value": "2"
       }
-    ]
+    ],
+    "overflow": true
   }
 }
 
@@ -1438,8 +1551,9 @@ EXPRESSION: new ArrayBuffer(16)
   "_objectId": "<filtered>",
   "_description": "ArrayBuffer",
   "_preview": {
+    "type": "object",
+    "description": "ArrayBuffer",
     "lossless": true,
-    "overflow": false,
     "properties": [
       {
         "name": "byteLength",
@@ -1457,8 +1571,9 @@ EXPRESSION: new DataView(new ArrayBuffer(16))
   "_objectId": "<filtered>",
   "_description": "DataView",
   "_preview": {
+    "type": "object",
+    "description": "DataView",
     "lossless": false,
-    "overflow": false,
     "properties": [
       {
         "name": "byteOffset",
@@ -1487,7 +1602,10 @@ EXPRESSION: document.body
   "_objectId": "<filtered>",
   "_description": "body",
   "_preview": {
+    "type": "object",
+    "description": "body",
     "lossless": false,
+    "subtype": "node",
     "overflow": true,
     "properties": [
       {
@@ -1527,7 +1645,10 @@ EXPRESSION: div = document.createElement('div'); div.className = 'foo bar'; div
   "_objectId": "<filtered>",
   "_description": "div.foo.bar",
   "_preview": {
+    "type": "object",
+    "description": "div.foo.bar",
     "lossless": false,
+    "subtype": "node",
     "overflow": true,
     "properties": [
       {
@@ -1567,7 +1688,10 @@ EXPRESSION: span = document.createElement('span'); span.id = 'foo'; span
   "_objectId": "<filtered>",
   "_description": "span#foo",
   "_preview": {
+    "type": "object",
+    "description": "span#foo",
     "lossless": false,
+    "subtype": "node",
     "overflow": true,
     "properties": [
       {
@@ -1607,7 +1731,10 @@ EXPRESSION: document.createTextNode('text')
   "_objectId": "<filtered>",
   "_description": "#text",
   "_preview": {
+    "type": "object",
+    "description": "#text",
     "lossless": false,
+    "subtype": "node",
     "overflow": true,
     "properties": [
       {
@@ -1647,7 +1774,10 @@ EXPRESSION: document.head.children
   "_objectId": "<filtered>",
   "_description": "HTMLCollection[3]",
   "_preview": {
+    "type": "object",
+    "description": "HTMLCollection[3]",
     "lossless": false,
+    "subtype": "array",
     "overflow": false,
     "properties": [
       {
@@ -1690,7 +1820,10 @@ EXPRESSION: document.getElementsByClassName('my-test')
   "_objectId": "<filtered>",
   "_description": "NodeList[3]",
   "_preview": {
+    "type": "object",
+    "description": "NodeList[3]",
     "lossless": false,
+    "subtype": "array",
     "overflow": false,
     "properties": [
       {
@@ -1733,7 +1866,10 @@ EXPRESSION: document.querySelectorAll('.my-test')
   "_objectId": "<filtered>",
   "_description": "NodeList[3]",
   "_preview": {
+    "type": "object",
+    "description": "NodeList[3]",
     "lossless": false,
+    "subtype": "array",
     "overflow": false,
     "properties": [
       {
@@ -1776,7 +1912,10 @@ EXPRESSION: error = null; try { [].x.x; } catch (e) { error = e; }; error
   "_objectId": "<filtered>",
   "_description": "TypeError: undefined is not an object (evaluating '[].x.x')",
   "_preview": {
+    "type": "object",
+    "description": "TypeError: undefined is not an object (evaluating '[].x.x')",
     "lossless": true,
+    "subtype": "error",
     "overflow": false,
     "properties": [
       {
@@ -1811,7 +1950,10 @@ EXPRESSION: error = null; try { eval('if()'); } catch (e) { error = e; }; error
   "_objectId": "<filtered>",
   "_description": "SyntaxError: Unexpected token ')'",
   "_preview": {
+    "type": "object",
+    "description": "SyntaxError: Unexpected token ')'",
     "lossless": true,
+    "subtype": "error",
     "overflow": false,
     "properties": [
       {
@@ -1846,7 +1988,10 @@ EXPRESSION: error = null; try { document.createTextNode('').splitText(100); } ca
   "_objectId": "<filtered>",
   "_description": "Error: IndexSizeError: DOM Exception 1",
   "_preview": {
+    "type": "object",
+    "description": "Error: IndexSizeError: DOM Exception 1",
     "lossless": false,
+    "subtype": "error",
     "overflow": true,
     "properties": [
       {
@@ -1885,8 +2030,9 @@ EXPRESSION: Object.seal({})
   "_objectId": "<filtered>",
   "_description": "Object",
   "_preview": {
+    "type": "object",
+    "description": "Object",
     "lossless": true,
-    "overflow": false,
     "properties": []
   }
 }
@@ -1898,8 +2044,9 @@ EXPRESSION: Object.freeze({})
   "_objectId": "<filtered>",
   "_description": "Object",
   "_preview": {
+    "type": "object",
+    "description": "Object",
     "lossless": true,
-    "overflow": false,
     "properties": []
   }
 }
@@ -1908,12 +2055,17 @@ EXPRESSION: Object.freeze({})
 EXPRESSION: new Map
 {
   "_type": "object",
+  "_subtype": "map",
   "_objectId": "<filtered>",
   "_description": "Map",
   "_preview": {
+    "type": "object",
+    "description": "Map",
     "lossless": true,
+    "subtype": "map",
     "overflow": false,
-    "properties": []
+    "properties": [],
+    "entries": []
   }
 }
 
@@ -1921,12 +2073,42 @@ EXPRESSION: new Map
 EXPRESSION: map = new Map; map.set(1, 2); map.set('key', 'value'); map
 {
   "_type": "object",
+  "_subtype": "map",
   "_objectId": "<filtered>",
   "_description": "Map",
   "_preview": {
+    "type": "object",
+    "description": "Map",
     "lossless": true,
+    "subtype": "map",
     "overflow": false,
-    "properties": []
+    "properties": [],
+    "entries": [
+      {
+        "key": {
+          "type": "number",
+          "description": "1",
+          "lossless": true
+        },
+        "value": {
+          "type": "number",
+          "description": "2",
+          "lossless": true
+        }
+      },
+      {
+        "key": {
+          "type": "string",
+          "description": "key",
+          "lossless": true
+        },
+        "value": {
+          "type": "string",
+          "description": "value",
+          "lossless": true
+        }
+      }
+    ]
   }
 }
 
@@ -1934,12 +2116,99 @@ EXPRESSION: map = new Map; map.set(1, 2); map.set('key', 'value'); map
 EXPRESSION: map = new Map; map.set({a:1}, {b:2}); map.set(document.body, [1,2]); map
 {
   "_type": "object",
+  "_subtype": "map",
   "_objectId": "<filtered>",
   "_description": "Map",
   "_preview": {
+    "type": "object",
+    "description": "Map",
     "lossless": true,
+    "subtype": "map",
     "overflow": false,
-    "properties": []
+    "properties": [],
+    "entries": [
+      {
+        "key": {
+          "type": "object",
+          "description": "Object",
+          "lossless": true,
+          "properties": [
+            {
+              "name": "a",
+              "type": "number",
+              "value": "1"
+            }
+          ]
+        },
+        "value": {
+          "type": "object",
+          "description": "Object",
+          "lossless": true,
+          "properties": [
+            {
+              "name": "b",
+              "type": "number",
+              "value": "2"
+            }
+          ]
+        }
+      },
+      {
+        "key": {
+          "type": "object",
+          "description": "body",
+          "lossless": false,
+          "subtype": "node",
+          "overflow": true,
+          "properties": [
+            {
+              "name": "aLink",
+              "type": "string",
+              "value": ""
+            },
+            {
+              "name": "background",
+              "type": "string",
+              "value": ""
+            },
+            {
+              "name": "bgColor",
+              "type": "string",
+              "value": ""
+            },
+            {
+              "name": "link",
+              "type": "string",
+              "value": ""
+            },
+            {
+              "name": "text",
+              "type": "string",
+              "value": ""
+            }
+          ]
+        },
+        "value": {
+          "type": "object",
+          "description": "Array[2]",
+          "lossless": true,
+          "subtype": "array",
+          "overflow": false,
+          "properties": [
+            {
+              "name": "0",
+              "type": "number",
+              "value": "1"
+            },
+            {
+              "name": "1",
+              "type": "number",
+              "value": "2"
+            }
+          ]
+        }
+      }
+    ]
   }
 }
 
@@ -1947,25 +2216,130 @@ EXPRESSION: map = new Map; map.set({a:1}, {b:2}); map.set(document.body, [1,2]);
 EXPRESSION: map = new Map; for (var i = 0; i <= 100; i++) map.set(i, i); map
 {
   "_type": "object",
+  "_subtype": "map",
   "_objectId": "<filtered>",
   "_description": "Map",
   "_preview": {
-    "lossless": true,
-    "overflow": false,
-    "properties": []
+    "type": "object",
+    "description": "Map",
+    "lossless": false,
+    "subtype": "map",
+    "overflow": true,
+    "properties": [],
+    "entries": [
+      {
+        "key": {
+          "type": "number",
+          "description": "0",
+          "lossless": true
+        },
+        "value": {
+          "type": "number",
+          "description": "0",
+          "lossless": true
+        }
+      },
+      {
+        "key": {
+          "type": "number",
+          "description": "1",
+          "lossless": true
+        },
+        "value": {
+          "type": "number",
+          "description": "1",
+          "lossless": true
+        }
+      },
+      {
+        "key": {
+          "type": "number",
+          "description": "2",
+          "lossless": true
+        },
+        "value": {
+          "type": "number",
+          "description": "2",
+          "lossless": true
+        }
+      },
+      {
+        "key": {
+          "type": "number",
+          "description": "3",
+          "lossless": true
+        },
+        "value": {
+          "type": "number",
+          "description": "3",
+          "lossless": true
+        }
+      },
+      {
+        "key": {
+          "type": "number",
+          "description": "4",
+          "lossless": true
+        },
+        "value": {
+          "type": "number",
+          "description": "4",
+          "lossless": true
+        }
+      }
+    ]
   }
 }
 
 -----------------------------------------------------
-EXPRESSION: map = new WeakMap; map.set({id:1}, [1,2]); map
+EXPRESSION: map = new WeakMap; strongKey = {id:1}; map.set(strongKey, [1,2]); map
 {
   "_type": "object",
+  "_subtype": "weakmap",
   "_objectId": "<filtered>",
   "_description": "WeakMap",
   "_preview": {
+    "type": "object",
+    "description": "WeakMap",
     "lossless": true,
+    "subtype": "weakmap",
     "overflow": false,
-    "properties": []
+    "properties": [],
+    "entries": [
+      {
+        "key": {
+          "type": "object",
+          "description": "Object",
+          "lossless": true,
+          "properties": [
+            {
+              "name": "id",
+              "type": "number",
+              "value": "1"
+            }
+          ]
+        },
+        "value": {
+          "type": "object",
+          "description": "Array[2]",
+          "lossless": true,
+          "subtype": "array",
+          "overflow": false,
+          "properties": [
+            {
+              "name": "0",
+              "type": "number",
+              "value": "1"
+            },
+            {
+              "name": "1",
+              "type": "number",
+              "value": "2"
+            }
+          ]
+        }
+      }
+    ]
   }
 }
 
@@ -1973,12 +2347,17 @@ EXPRESSION: map = new WeakMap; map.set({id:1}, [1,2]); map
 EXPRESSION: new Set
 {
   "_type": "object",
+  "_subtype": "set",
   "_objectId": "<filtered>",
   "_description": "Set",
   "_preview": {
+    "type": "object",
+    "description": "Set",
     "lossless": true,
+    "subtype": "set",
     "overflow": false,
-    "properties": []
+    "properties": [],
+    "entries": []
   }
 }
 
@@ -1986,12 +2365,39 @@ EXPRESSION: new Set
 EXPRESSION: set = new Set; set.add(1); set.add(2); set.add('key'); set
 {
   "_type": "object",
+  "_subtype": "set",
   "_objectId": "<filtered>",
   "_description": "Set",
   "_preview": {
+    "type": "object",
+    "description": "Set",
     "lossless": true,
+    "subtype": "set",
     "overflow": false,
-    "properties": []
+    "properties": [],
+    "entries": [
+      {
+        "value": {
+          "type": "number",
+          "description": "1",
+          "lossless": true
+        }
+      },
+      {
+        "value": {
+          "type": "number",
+          "description": "2",
+          "lossless": true
+        }
+      },
+      {
+        "value": {
+          "type": "string",
+          "description": "key",
+          "lossless": true
+        }
+      }
+    ]
   }
 }
 
@@ -1999,12 +2405,89 @@ EXPRESSION: set = new Set; set.add(1); set.add(2); set.add('key'); set
 EXPRESSION: set = new Set; set.add({a:1}); set.add(document.body); set.add([1,2]); set
 {
   "_type": "object",
+  "_subtype": "set",
   "_objectId": "<filtered>",
   "_description": "Set",
   "_preview": {
+    "type": "object",
+    "description": "Set",
     "lossless": true,
+    "subtype": "set",
     "overflow": false,
-    "properties": []
+    "properties": [],
+    "entries": [
+      {
+        "value": {
+          "type": "object",
+          "description": "Object",
+          "lossless": true,
+          "properties": [
+            {
+              "name": "a",
+              "type": "number",
+              "value": "1"
+            }
+          ]
+        }
+      },
+      {
+        "value": {
+          "type": "object",
+          "description": "body",
+          "lossless": false,
+          "subtype": "node",
+          "overflow": true,
+          "properties": [
+            {
+              "name": "aLink",
+              "type": "string",
+              "value": ""
+            },
+            {
+              "name": "background",
+              "type": "string",
+              "value": ""
+            },
+            {
+              "name": "bgColor",
+              "type": "string",
+              "value": ""
+            },
+            {
+              "name": "link",
+              "type": "string",
+              "value": ""
+            },
+            {
+              "name": "text",
+              "type": "string",
+              "value": ""
+            }
+          ]
+        }
+      },
+      {
+        "value": {
+          "type": "object",
+          "description": "Array[2]",
+          "lossless": true,
+          "subtype": "array",
+          "overflow": false,
+          "properties": [
+            {
+              "name": "0",
+              "type": "number",
+              "value": "1"
+            },
+            {
+              "name": "1",
+              "type": "number",
+              "value": "2"
+            }
+          ]
+        }
+      }
+    ]
   }
 }
 
@@ -2012,12 +2495,53 @@ EXPRESSION: set = new Set; set.add({a:1}); set.add(document.body); set.add([1,2]
 EXPRESSION: set = new Set; for (var i = 0; i <= 100; i++) set.add(i); set
 {
   "_type": "object",
+  "_subtype": "set",
   "_objectId": "<filtered>",
   "_description": "Set",
   "_preview": {
-    "lossless": true,
-    "overflow": false,
-    "properties": []
+    "type": "object",
+    "description": "Set",
+    "lossless": false,
+    "subtype": "set",
+    "overflow": true,
+    "properties": [],
+    "entries": [
+      {
+        "value": {
+          "type": "number",
+          "description": "0",
+          "lossless": true
+        }
+      },
+      {
+        "value": {
+          "type": "number",
+          "description": "1",
+          "lossless": true
+        }
+      },
+      {
+        "value": {
+          "type": "number",
+          "description": "2",
+          "lossless": true
+        }
+      },
+      {
+        "value": {
+          "type": "number",
+          "description": "3",
+          "lossless": true
+        }
+      },
+      {
+        "value": {
+          "type": "number",
+          "description": "4",
+          "lossless": true
+        }
+      }
+    ]
   }
 }
 
@@ -2028,8 +2552,9 @@ EXPRESSION: new Promise(function(){})
   "_objectId": "<filtered>",
   "_description": "Promise",
   "_preview": {
+    "type": "object",
+    "description": "Promise",
     "lossless": true,
-    "overflow": false,
     "properties": []
   }
 }
@@ -2041,8 +2566,9 @@ EXPRESSION: Promise.reject()
   "_objectId": "<filtered>",
   "_description": "Promise",
   "_preview": {
+    "type": "object",
+    "description": "Promise",
     "lossless": true,
-    "overflow": false,
     "properties": []
   }
 }
@@ -2054,8 +2580,9 @@ EXPRESSION: Promise.resolve()
   "_objectId": "<filtered>",
   "_description": "Promise",
   "_preview": {
+    "type": "object",
+    "description": "Promise",
     "lossless": true,
-    "overflow": false,
     "properties": []
   }
 }
diff --git a/LayoutTests/inspector/model/remote-object-weak-collection-expected.txt b/LayoutTests/inspector/model/remote-object-weak-collection-expected.txt
new file mode 100644 (file)
index 0000000..c3424f0
--- /dev/null
@@ -0,0 +1,129 @@
+
+-----------------------------------------------------
+EXPRESSION:  weakMap
+ENTRIES:
+[]
+
+-----------------------------------------------------
+EXPRESSION: weakMap.set(strongKey1, 1); weakMap.set(strongKey2, 2); weakMap
+ENTRIES:
+[
+  {
+    "key": {
+      "type": "object",
+      "objectId": "<filtered>",
+      "className": "Object",
+      "description": "Object",
+      "preview": {
+        "type": "object",
+        "description": "Object",
+        "lossless": true,
+        "properties": [
+          {
+            "name": "id",
+            "type": "number",
+            "value": "1"
+          }
+        ]
+      }
+    },
+    "value": {
+      "type": "number",
+      "value": 1,
+      "description": "1"
+    }
+  },
+  {
+    "key": {
+      "type": "object",
+      "objectId": "<filtered>",
+      "className": "Object",
+      "description": "Object",
+      "preview": {
+        "type": "object",
+        "description": "Object",
+        "lossless": true,
+        "properties": [
+          {
+            "name": "id",
+            "type": "number",
+            "value": "2"
+          }
+        ]
+      }
+    },
+    "value": {
+      "type": "number",
+      "value": 2,
+      "description": "2"
+    }
+  }
+]
+
+-----------------------------------------------------
+EXPRESSION: delete window.strongKey1; weakMap
+ENTRIES:
+[
+  {
+    "key": {
+      "type": "object",
+      "objectId": "<filtered>",
+      "className": "Object",
+      "description": "Object",
+      "preview": {
+        "type": "object",
+        "description": "Object",
+        "lossless": true,
+        "properties": [
+          {
+            "name": "id",
+            "type": "number",
+            "value": "2"
+          }
+        ]
+      }
+    },
+    "value": {
+      "type": "number",
+      "value": 2,
+      "description": "2"
+    }
+  }
+]
+
+-----------------------------------------------------
+EXPRESSION: weakMap.set({id:3}, 3); weakMap.set({id:4}, 4); weakMap
+ENTRIES:
+[
+  {
+    "key": {
+      "type": "object",
+      "objectId": "<filtered>",
+      "className": "Object",
+      "description": "Object",
+      "preview": {
+        "type": "object",
+        "description": "Object",
+        "lossless": true,
+        "properties": [
+          {
+            "name": "id",
+            "type": "number",
+            "value": "2"
+          }
+        ]
+      }
+    },
+    "value": {
+      "type": "number",
+      "value": 2,
+      "description": "2"
+    }
+  }
+]
+
+-----------------------------------------------------
+EXPRESSION: delete window.strongKey2; weakMap
+ENTRIES:
+[]
+
diff --git a/LayoutTests/inspector/model/remote-object-weak-collection.html b/LayoutTests/inspector/model/remote-object-weak-collection.html
new file mode 100644 (file)
index 0000000..2076454
--- /dev/null
@@ -0,0 +1,76 @@
+<!doctype html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<script type="text/javascript" src="../../http/tests/inspector/inspector-test.js"></script>
+<script>
+strongKey1 = {id:1};
+strongKey2 = {id:2};
+weakMap = new WeakMap;
+
+function test()
+{
+    var weakMapObjectId = null;
+
+    var currentStepIndex = 0;
+    var steps = [
+        // WeakMap {}
+        {expression: ""},
+        
+        // WeakMap {key1 => 1, key2 => 2}
+        {expression: "weakMap.set(strongKey1, 1); weakMap.set(strongKey2, 2);"},
+        
+        // WeakMap {key2 => 2}
+        {expression: "delete window.strongKey1;"},
+
+        // WeakMap {key2 => 2} (add and immediately garbage collect temporary objects)
+        {expression: "weakMap.set({id:3}, 3); weakMap.set({id:4}, 4);"},
+        
+        // WeakMap {}
+        {expression: "delete window.strongKey2;"},
+    ];
+
+    function remoteObjectJSONFilter(key, value)
+    {
+        if (key === "objectId")
+            return "<filtered>";
+
+        return value;
+    }
+
+    function runNextStep() {
+        if (currentStepIndex >= steps.length) {
+            InspectorTest.completeTest();
+            return;
+        }
+
+        var step = steps[currentStepIndex++];
+        step.expression += " weakMap";
+
+        InspectorTest.log("");
+        InspectorTest.log("-----------------------------------------------------");
+        InspectorTest.log("EXPRESSION: " + step.expression);
+
+        // Run the expression, and then run a garbage collection on a different
+        // event loop so no objects are kept alive by the stack.
+        WebInspector.runtimeManager.evaluateInInspectedWindow(step.expression, "test", false, true, false, true, function(remoteObject, wasThrown) {
+            WebInspector.runtimeManager.evaluateInInspectedWindow("GCController.collect()", "test", false, true, false, false, function() {
+                InspectorTest.assert(remoteObject instanceof WebInspector.RemoteObject);
+                remoteObject.getCollectionEntries(0, 100, function(entries) {
+                    InspectorTest.log("ENTRIES:");
+                    entries.sort(function(a, b) { return a.value.value - b.value.value; });
+                    InspectorTest.log(JSON.stringify(entries, remoteObjectJSONFilter, "  "));
+                    remoteObject.releaseWeakCollectionEntries();
+                    runNextStep();
+                });
+            });
+        });
+    }
+
+    runNextStep();
+}
+</script>
+</head>
+<body onload="runTest();">
+</body>
+</html>
index 2b897f1..ea18ff3 100644 (file)
@@ -69,6 +69,8 @@ function test()
         {expression: "[1, 2]"},
         {expression: "[[1],[2],[3]]"},
         {expression: "[true, 1, 1.234, 'string', /regex/]"},
+        {expression: "[{a:1}, {b:2}, {c:2}]"},
+        {expression: "[[{a:1}, {b:2}, {c:2}]]"},
         {expression: "arr = []; arr.length = 100; arr"}, // 100 empty elements
         {expression: "arr = []; arr.length = 100; arr.fill(1)"}, // 100 full elements
         {expression: "arr = []; arr.length = 100; arr[10] = 1; arr"}, // sparse
@@ -117,7 +119,7 @@ function test()
         {expression: "map = new Map; map.set(1, 2); map.set('key', 'value'); map"},
         {expression: "map = new Map; map.set({a:1}, {b:2}); map.set(document.body, [1,2]); map"},
         {expression: "map = new Map; for (var i = 0; i <= 100; i++) map.set(i, i); map"},
-        {expression: "map = new WeakMap; map.set({id:1}, [1,2]); map"},
+        {expression: "map = new WeakMap; strongKey = {id:1}; map.set(strongKey, [1,2]); map"},
 
         // Set
         {expression: "new Set"},
index 6b19d2c..d8c41b6 100644 (file)
@@ -1,3 +1,75 @@
+2015-01-29  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: ES6: Improved Console Format for Set and Map Objects (like Arrays)
+        https://bugs.webkit.org/show_bug.cgi?id=122867
+
+        Reviewed by Timothy Hatcher.
+
+        Add new Runtime.RemoteObject object subtypes for "map", "set", and "weakmap".
+
+        Upgrade Runtime.ObjectPreview to include type/subtype information. Now,
+        an ObjectPreview can be used for any value, in place of a RemoteObject,
+        and not capture / hold a reference to the value. The value will be in
+        the string description.
+
+        Adding this information to ObjectPreview can duplicate some information
+        in the protocol messages if a preview is provided, but simplifies
+        previews, so that all the information you need for any RemoteObject
+        preview is available. To slim messages further, make "overflow" and
+        "properties" only available on previews that may contain properties.
+        So, not primitives or null.
+
+        Finally, for "Map/Set/WeakMap" add an "entries" list to the preview
+        that will return previews with "key" and "value" properties depending
+        on the collection type. To get live, non-preview objects from a
+        collection, use Runtime.getCollectionEntries.
+
+        In order to keep the WeakMap's values Weak the frontend may provide
+        a unique object group name when getting collection entries. It may
+        then release that object group, e.g. when not showing the WeakMap's
+        values to the user, and thus remove the strong reference to the keys
+        so they may be garbage collected.
+
+        * runtime/WeakMapData.h:
+        (JSC::WeakMapData::begin):
+        (JSC::WeakMapData::end):
+        Expose iterators so the Inspector may access WeakMap keys/values.
+
+        * inspector/JSInjectedScriptHostPrototype.cpp:
+        (Inspector::JSInjectedScriptHostPrototype::finishCreation):
+        (Inspector::jsInjectedScriptHostPrototypeFunctionWeakMapEntries):
+        * inspector/JSInjectedScriptHost.h:
+        * inspector/JSInjectedScriptHost.cpp:
+        (Inspector::JSInjectedScriptHost::subtype):
+        Discern "map", "set", and "weakmap" object subtypes.
+
+        (Inspector::JSInjectedScriptHost::weakMapEntries):
+        Return a list of WeakMap entries. These are strong references
+        that the Inspector code is responsible for releasing.
+
+        * inspector/protocol/Runtime.json:
+        Update types and expose the new getCollectionEntries command.
+
+        * inspector/agents/InspectorRuntimeAgent.h:
+        * inspector/agents/InspectorRuntimeAgent.cpp:
+        (Inspector::InspectorRuntimeAgent::getCollectionEntries):
+        * inspector/InjectedScript.h:
+        * inspector/InjectedScript.cpp:
+        (Inspector::InjectedScript::getInternalProperties):
+        (Inspector::InjectedScript::getCollectionEntries):
+        Pass through to the InjectedScript and call getCollectionEntries.
+
+        * inspector/scripts/codegen/generator.py:
+        Add another type with runtime casting.
+
+        * inspector/InjectedScriptSource.js:
+        - Implement getCollectionEntries to get a range of values from a
+        collection. The non-Weak collections have an order to their keys (in
+        order of added) so range'd gets are okay. WeakMap does not have an
+        order, so only allow fetching a number of values.
+        - Update preview generation to address the Runtime.ObjectPreview
+        type changes.
+
 2015-01-28  Geoffrey Garen  <ggaren@apple.com>
 
         Use FastMalloc (bmalloc) instead of BlockAllocator for GC pages
index cf3f9e2..24e80fd 100644 (file)
@@ -137,8 +137,25 @@ void InjectedScript::getInternalProperties(ErrorString& errorString, const Strin
     }
 
     auto array = BindingTraits<Array<Inspector::Protocol::Runtime::InternalPropertyDescriptor>>::runtimeCast(WTF::move(result));
-    if (array->length() > 0)
-        *properties = array;
+    *properties = array->length() > 0 ? array : nullptr;
+}
+
+void InjectedScript::getCollectionEntries(ErrorString& errorString, const String& objectId, const String& objectGroup, int startIndex, int numberToFetch, RefPtr<Protocol::Array<Protocol::Runtime::CollectionEntry>>* entries)
+{
+    Deprecated::ScriptFunctionCall function(injectedScriptObject(), ASCIILiteral("getCollectionEntries"), inspectorEnvironment()->functionCallHandler());
+    function.appendArgument(objectId);
+    function.appendArgument(objectGroup);
+    function.appendArgument(startIndex);
+    function.appendArgument(numberToFetch);
+
+    RefPtr<InspectorValue> result;
+    makeCall(function, &result);
+    if (!result || result->type() != InspectorValue::Type::Array) {
+        errorString = ASCIILiteral("Internal error");
+        return;
+    }
+
+    *entries = BindingTraits<Array<Protocol::Runtime::CollectionEntry>>::runtimeCast(WTF::move(result));
 }
 
 Ref<Array<Inspector::Protocol::Debugger::CallFrame>> InjectedScript::wrapCallFrames(const Deprecated::ScriptValue& callFrames)
index 3050424..3a1aafd 100644 (file)
@@ -58,6 +58,7 @@ public:
     void getFunctionDetails(ErrorString&, const String& functionId, RefPtr<Protocol::Debugger::FunctionDetails>* result);
     void getProperties(ErrorString&, const String& objectId, bool ownProperties, bool ownAndGetterProperties, RefPtr<Protocol::Array<Protocol::Runtime::PropertyDescriptor>>* result);
     void getInternalProperties(ErrorString&, const String& objectId, RefPtr<Protocol::Array<Protocol::Runtime::InternalPropertyDescriptor>>* result);
+    void getCollectionEntries(ErrorString&, const String& objectId, const String& objectGroup, int startIndex, int numberToFetch, RefPtr<Protocol::Array<Protocol::Runtime::CollectionEntry>>* entries);
 
     Ref<Protocol::Array<Protocol::Debugger::CallFrame>> wrapCallFrames(const Deprecated::ScriptValue&);
     RefPtr<Protocol::Runtime::RemoteObject> wrapObject(const Deprecated::ScriptValue&, const String& groupName, bool generatePreview = false) const;
index 859351e..58631af 100644 (file)
@@ -38,7 +38,7 @@ function toString(obj)
 {
     return "" + obj;
 }
-    
+
 function isUInt32(obj)
 {
     if (typeof obj === "number")
@@ -231,6 +231,27 @@ InjectedScript.prototype = {
         return descriptors;
     },
 
+    getCollectionEntries: function(objectId, objectGroupName, startIndex, numberToFetch)
+    {
+        var parsedObjectId = this._parseObjectId(objectId);
+        var object = this._objectForId(parsedObjectId);
+        var objectGroupName = objectGroupName || this._idToObjectGroupName[parsedObjectId.id];
+        if (!this._isDefined(object))
+            return;
+
+        if (typeof object !== "object")
+            return;
+
+        var entries = this._getCollectionEntries(object, InjectedScriptHost.subtype(object), startIndex, numberToFetch);
+        
+        return entries.map(function(entry) {
+            entry.value = injectedScript._wrapObject(entry.value, objectGroupName, false, true);
+            if ("key" in entry)
+                entry.key = injectedScript._wrapObject(entry.key, objectGroupName, false, true);
+            return entry;
+        });
+    },
+
     getFunctionDetails: function(functionId)
     {
         var parsedFunctionId = this._parseObjectId(functionId);
@@ -579,7 +600,7 @@ InjectedScript.prototype = {
             if (ownProperties)
                 break;
         }
-        
+
         // Include __proto__ at the end.
         try {
             if (object.__proto__)
@@ -677,6 +698,61 @@ InjectedScript.prototype = {
         }
 
         return className;
+    },
+
+    _getSetEntries: function(object, skip, numberToFetch)
+    {
+        var entries = [];
+
+        for (var value of object) {
+            if (skip > 0) {
+                skip--;
+                continue;
+            }
+
+            entries.push({value: value});
+
+            if (numberToFetch && entries.length === numberToFetch)
+                break;
+        }
+
+        return entries;
+    },
+
+    _getMapEntries: function(object, skip, numberToFetch)
+    {
+        var entries = [];
+
+        for (var [key, value] of object) {
+            if (skip > 0) {
+                skip--;
+                continue;
+            }
+
+            entries.push({key: key, value: value});
+
+            if (numberToFetch && entries.length === numberToFetch)
+                break;
+        }
+
+        return entries;
+    },
+
+    _getWeakMapEntries: function(object, numberToFetch)
+    {
+        return InjectedScriptHost.weakMapEntries(object, numberToFetch);
+    },
+
+    _getCollectionEntries: function(object, subtype, startIndex, numberToFetch)
+    {
+        if (subtype === "set")
+            return this._getSetEntries(object, startIndex, numberToFetch);
+        if (subtype === "map")
+            return this._getMapEntries(object, startIndex, numberToFetch);
+        if (subtype === "weakmap")
+            return this._getWeakMapEntries(object, numberToFetch);
+
+        throw "unexpected type";
     }
 }
 
@@ -695,7 +771,7 @@ InjectedScript.RemoteObject = function(object, objectGroupName, forceValueType,
         if (this.type !== "undefined")
             this.value = object;
 
-        // Null object is object with 'null' subtype'
+        // Null object is object with 'null' subtype.
         if (object === null)
             this.subtype = "null";
 
@@ -706,6 +782,7 @@ InjectedScript.RemoteObject = function(object, objectGroupName, forceValueType,
     }
 
     this.objectId = injectedScript._bind(object, objectGroupName);
+
     var subtype = injectedScript._subtype(object);
     if (subtype)
         this.subtype = subtype;
@@ -718,12 +795,41 @@ InjectedScript.RemoteObject = function(object, objectGroupName, forceValueType,
 }
 
 InjectedScript.RemoteObject.prototype = {
+    _emptyPreview: function()
+    {
+        var preview = {
+            type: this.type,
+            description: this.description || toString(this.value),
+            lossless: true,
+        };
+
+        if (this.subtype) {
+            preview.subtype = this.subtype;
+            if (this.subtype !== "null") {
+                preview.overflow = false;
+                preview.properties = [];
+            }
+        }
+
+        return preview;
+    },
+
+    _createObjectPreviewForValue: function(value)
+    {
+        var remoteObject = new InjectedScript.RemoteObject(value, undefined, false, true, undefined);
+        if (remoteObject.objectId)
+            injectedScript.releaseObject(remoteObject.objectId);
+
+        return remoteObject.preview || remoteObject._emptyPreview();
+    },
+
     _generatePreview: function(object, firstLevelKeys, secondLevelKeys)
     {
-        var preview = {};
-        preview.lossless = true;
-        preview.overflow = false;
-        preview.properties = [];
+        var preview = this._emptyPreview();
+
+        // Primitives just have a value.
+        if (this.type !== "object")
+            return;
 
         var isTableRowsRequest = secondLevelKeys === null || secondLevelKeys;
         var firstLevelKeysCount = firstLevelKeys ? firstLevelKeys.length : 0;
@@ -734,14 +840,19 @@ InjectedScript.RemoteObject.prototype = {
         };
 
         try {
-            // All properties.
+            // Maps and Sets have entries.
+            if (this.subtype === "map" || this.subtype === "set" || this.subtype === "weakmap")
+                this._appendEntryPreviews(object, preview);
+
+            // Properties.
+            preview.properties = [];
             var descriptors = injectedScript._propertyDescriptors(object);
-            this._appendPropertyDescriptors(preview, descriptors, propertiesThreshold, secondLevelKeys);
+            this._appendPropertyPreviews(preview, descriptors, propertiesThreshold, secondLevelKeys);
             if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0)
                 return preview;
 
             // FIXME: Internal properties.
-            // FIXME: Map/Set/Iterator entries.
+            // FIXME: Iterator entries.
         } catch (e) {
             preview.lossless = false;
         }
@@ -749,7 +860,7 @@ InjectedScript.RemoteObject.prototype = {
         return preview;
     },
 
-    _appendPropertyDescriptors: function(preview, descriptors, propertiesThreshold, secondLevelKeys)
+    _appendPropertyPreviews: function(preview, descriptors, propertiesThreshold, secondLevelKeys)
     {
         for (var descriptor of descriptors) {
             // Seen enough.
@@ -851,6 +962,27 @@ InjectedScript.RemoteObject.prototype = {
         preview.properties.push(property);
     },
 
+    _appendEntryPreviews: function(object, preview)
+    {
+        // Fetch 6, but only return 5, so we can tell if we overflowed.
+        var entries = injectedScript._getCollectionEntries(object, this.subtype, 0, 6);
+        if (!entries)
+            return;
+
+        if (entries.length > 5) {
+            entries.pop();
+            preview.overflow = true;
+            preview.lossless = false;
+        }
+
+        preview.entries = entries.map(function(entry) {
+            entry.value = this._createObjectPreviewForValue(entry.value);
+            if ("key" in entry)
+                entry.key = this._createObjectPreviewForValue(entry.key);
+            return entry;
+        }, this);
+    },
+
     _abbreviateString: function(string, maxLength, middle)
     {
         if (string.length <= maxLength)
index 4459460..297e491 100644 (file)
 #include "Error.h"
 #include "InjectedScriptHost.h"
 #include "JSArray.h"
+#include "JSCInlines.h"
 #include "JSFunction.h"
 #include "JSInjectedScriptHostPrototype.h"
+#include "JSMap.h"
+#include "JSSet.h"
 #include "JSTypedArrays.h"
+#include "JSWeakMap.h"
 #include "ObjectConstructor.h"
-#include "JSCInlines.h"
 #include "RegExpObject.h"
 #include "SourceCode.h"
 #include "TypedArrayInlines.h"
+#include "WeakMapData.h"
 
 using namespace JSC;
 
@@ -129,6 +133,14 @@ JSValue JSInjectedScriptHost::subtype(ExecState* exec)
         return jsNontrivialString(exec, ASCIILiteral("date"));
     if (value.inherits(RegExpObject::info()))
         return jsNontrivialString(exec, ASCIILiteral("regexp"));
+
+    if (value.inherits(JSMap::info()))
+        return jsNontrivialString(exec, ASCIILiteral("map"));
+    if (value.inherits(JSSet::info()))
+        return jsNontrivialString(exec, ASCIILiteral("set"));
+    if (value.inherits(JSWeakMap::info()))
+        return jsNontrivialString(exec, ASCIILiteral("weakmap"));
+
     if (value.inherits(JSInt8Array::info()) || value.inherits(JSInt16Array::info()) || value.inherits(JSInt32Array::info()))
         return jsNontrivialString(exec, ASCIILiteral("array"));
     if (value.inherits(JSUint8Array::info()) || value.inherits(JSUint16Array::info()) || value.inherits(JSUint32Array::info()))
@@ -185,6 +197,37 @@ JSValue JSInjectedScriptHost::getInternalProperties(ExecState*)
     return jsUndefined();
 }
 
+JSValue JSInjectedScriptHost::weakMapEntries(ExecState* exec)
+{
+    if (exec->argumentCount() < 1)
+        return jsUndefined();
+
+    JSValue value = exec->uncheckedArgument(0);
+    JSWeakMap* weakMap = jsDynamicCast<JSWeakMap*>(value);
+    if (!weakMap)
+        return jsUndefined();
+
+    unsigned fetched = 0;
+    unsigned numberToFetch = 100;
+
+    JSValue numberToFetchArg = exec->argument(1);
+    double fetchDouble = numberToFetchArg.toInteger(exec);
+    if (fetchDouble >= 0)
+        numberToFetch = static_cast<unsigned>(fetchDouble);
+
+    JSArray* array = constructEmptyArray(exec, nullptr);
+    for (auto it = weakMap->weakMapData()->begin(); it != weakMap->weakMapData()->end(); ++it) {
+        JSObject* entry = constructEmptyObject(exec);
+        entry->putDirect(exec->vm(), Identifier(exec, "key"), it->key);
+        entry->putDirect(exec->vm(), Identifier(exec, "value"), it->value.get());
+        array->putDirectIndex(exec, fetched++, entry);
+        if (numberToFetch && fetched >= numberToFetch)
+            break;
+    }
+
+    return array;
+}
+
 JSValue toJS(ExecState* exec, JSGlobalObject* globalObject, InjectedScriptHost* impl)
 {
     if (!impl)
index 73068ee..ff11e52 100644 (file)
@@ -65,6 +65,7 @@ public:
     JSC::JSValue subtype(JSC::ExecState*);
     JSC::JSValue functionDetails(JSC::ExecState*);
     JSC::JSValue getInternalProperties(JSC::ExecState*);
+    JSC::JSValue weakMapEntries(JSC::ExecState*);
 
 protected:
     static const unsigned StructureFlags = Base::StructureFlags;
index 31d2c9e..b069073 100644 (file)
@@ -43,6 +43,7 @@ static EncodedJSValue JSC_HOST_CALL jsInjectedScriptHostPrototypeFunctionFunctio
 static EncodedJSValue JSC_HOST_CALL jsInjectedScriptHostPrototypeFunctionGetInternalProperties(ExecState*);
 static EncodedJSValue JSC_HOST_CALL jsInjectedScriptHostPrototypeFunctionInternalConstructorName(ExecState*);
 static EncodedJSValue JSC_HOST_CALL jsInjectedScriptHostPrototypeFunctionIsHTMLAllCollection(ExecState*);
+static EncodedJSValue JSC_HOST_CALL jsInjectedScriptHostPrototypeFunctionWeakMapEntries(ExecState*);
 
 static EncodedJSValue JSC_HOST_CALL jsInjectedScriptHostPrototypeAttributeEvaluate(ExecState*);
 
@@ -59,6 +60,7 @@ void JSInjectedScriptHostPrototype::finishCreation(VM& vm, JSGlobalObject* globa
     JSC_NATIVE_FUNCTION("getInternalProperties", jsInjectedScriptHostPrototypeFunctionGetInternalProperties, DontEnum, 1);
     JSC_NATIVE_FUNCTION("internalConstructorName", jsInjectedScriptHostPrototypeFunctionInternalConstructorName, DontEnum, 1);
     JSC_NATIVE_FUNCTION("isHTMLAllCollection", jsInjectedScriptHostPrototypeFunctionIsHTMLAllCollection, DontEnum, 1);
+    JSC_NATIVE_FUNCTION("weakMapEntries", jsInjectedScriptHostPrototypeFunctionWeakMapEntries, DontEnum, 1);
 
     Identifier evaluateIdentifier(&vm, "evaluate");
     GetterSetter* accessor = GetterSetter::create(vm, globalObject);
@@ -100,6 +102,17 @@ EncodedJSValue JSC_HOST_CALL jsInjectedScriptHostPrototypeFunctionIsHTMLAllColle
     return JSValue::encode(castedThis->isHTMLAllCollection(exec));
 }
 
+EncodedJSValue JSC_HOST_CALL jsInjectedScriptHostPrototypeFunctionWeakMapEntries(ExecState* exec)
+{
+    JSValue thisValue = exec->thisValue();
+    JSInjectedScriptHost* castedThis = jsDynamicCast<JSInjectedScriptHost*>(thisValue);
+    if (!castedThis)
+        return throwVMTypeError(exec);
+
+    ASSERT_GC_OBJECT_INHERITS(castedThis, JSInjectedScriptHost::info());
+    return JSValue::encode(castedThis->weakMapEntries(exec));
+}
+
 EncodedJSValue JSC_HOST_CALL jsInjectedScriptHostPrototypeFunctionSubtype(ExecState* exec)
 {
     JSValue thisValue = exec->thisValue();
index 7a5e29e..9c9725a 100644 (file)
@@ -178,6 +178,20 @@ void InspectorRuntimeAgent::getProperties(ErrorString& errorString, const String
     setPauseOnExceptionsState(m_scriptDebugServer, previousPauseOnExceptionsState);
 }
 
+void InspectorRuntimeAgent::getCollectionEntries(ErrorString& errorString, const String& objectId, const String* objectGroup, const int* startIndex, const int* numberToFetch, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::Runtime::CollectionEntry>>& entries)
+{
+    InjectedScript injectedScript = m_injectedScriptManager->injectedScriptForObjectId(objectId);
+    if (injectedScript.hasNoValue()) {
+        errorString = ASCIILiteral("Inspected frame has gone");
+        return;
+    }
+
+    int start = startIndex && *startIndex >= 0 ? *startIndex : 0;
+    int fetch = numberToFetch && *numberToFetch >= 0 ? *numberToFetch : 0;
+
+    injectedScript.getCollectionEntries(errorString, objectId, objectGroup ? *objectGroup : String(), start, fetch, &entries);
+}
+
 void InspectorRuntimeAgent::releaseObject(ErrorString&, const String& objectId)
 {
     InjectedScript injectedScript = m_injectedScriptManager->injectedScriptForObjectId(objectId);
index 4e793c4..07e4fec 100644 (file)
@@ -64,6 +64,7 @@ public:
     virtual void callFunctionOn(ErrorString&, const String& objectId, const String& expression, const RefPtr<Inspector::InspectorArray>&& optionalArguments, const bool* doNotPauseOnExceptionsAndMuteConsole, const bool* returnByValue, const bool* generatePreview, RefPtr<Inspector::Protocol::Runtime::RemoteObject>& result, Inspector::Protocol::OptOutput<bool>* wasThrown) override final;
     virtual void releaseObject(ErrorString&, const ErrorString& objectId) override final;
     virtual void getProperties(ErrorString&, const String& objectId, const bool* ownProperties, const bool* ownAndGetterProperties, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::Runtime::PropertyDescriptor>>& result, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::Runtime::InternalPropertyDescriptor>>& internalProperties) override final;
+    virtual void getCollectionEntries(ErrorString&, const String& objectId, const String* objectGroup, const int* startIndex, const int* numberToFetch, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::Runtime::CollectionEntry>>& entries) override final;
     virtual void releaseObjectGroup(ErrorString&, const String& objectGroup) override final;
     virtual void run(ErrorString&) override;
     virtual void getRuntimeTypesForVariablesAtOffsets(ErrorString&, const RefPtr<Inspector::InspectorArray>&& locations, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::Runtime::TypeDescription>>&) override;
index f6d1f01..7ffdb78 100644 (file)
@@ -13,7 +13,7 @@
             "description": "Mirror object referencing original JavaScript object.",
             "properties": [
                 { "name": "type", "type": "string", "enum": ["object", "function", "undefined", "string", "number", "boolean"], "description": "Object type." },
-                { "name": "subtype", "type": "string", "optional": true, "enum": ["array", "null", "node", "regexp", "date", "error"], "description": "Object subtype hint. Specified for <code>object</code> type values only." },
+                { "name": "subtype", "type": "string", "optional": true, "enum": ["array", "null", "node", "regexp", "date", "error", "map", "set", "weakmap"], "description": "Object subtype hint. Specified for <code>object</code> type values only." },
                 { "name": "className", "type": "string", "optional": true, "description": "Object class (constructor) name. Specified for <code>object</code> type values only." },
                 { "name": "value", "type": "any", "optional": true, "description": "Remote object value (in case of primitive values or JSON values if it was requested)." },
                 { "name": "description", "type": "string", "optional": true, "description": "String representation of the object." },
             "type": "object",
             "description": "Object containing abbreviated remote object value.",
             "properties": [
+                { "name": "type", "type": "string", "enum": ["object", "function", "undefined", "string", "number", "boolean"], "description": "Object type." },
+                { "name": "subtype", "type": "string", "optional": true, "enum": ["array", "null", "node", "regexp", "date", "error", "map", "set", "weakmap"], "description": "Object subtype hint. Specified for <code>object</code> type values only." },
+                { "name": "description", "type": "string", "optional": true, "description": "String representation of the object." },
                 { "name": "lossless", "type": "boolean", "description": "Determines whether preview is lossless (contains all information of the original object)." },
-                { "name": "overflow", "type": "boolean", "description": "True iff some of the properties of the original did not fit." },
-                { "name": "properties", "type": "array", "items": { "$ref": "PropertyPreview" }, "description": "List of the properties." }
+                { "name": "overflow", "type": "boolean", "optional": true, "description": "True iff some of the properties of the original did not fit." },
+                { "name": "properties", "type": "array", "items": { "$ref": "PropertyPreview" }, "optional": true, "description": "List of the properties." },
+                { "name": "entries", "type": "array", "items": { "$ref": "EntryPreview" }, "optional": true, "description": "List of the entries. Specified for <code>map</code> and <code>set</code> subtype values only." }
             ]
         },
         {
             "properties": [
                 { "name": "name", "type": "string", "description": "Property name." },
                 { "name": "type", "type": "string", "enum": ["object", "function", "undefined", "string", "number", "boolean", "accessor"], "description": "Object type." },
-                { "name": "subtype", "type": "string", "optional": true, "enum": ["array", "null", "node", "regexp", "date", "error"], "description": "Object subtype hint. Specified for <code>object</code> type values only." },
+                { "name": "subtype", "type": "string", "optional": true, "enum": ["array", "null", "node", "regexp", "date", "error", "map", "set", "weakmap"], "description": "Object subtype hint. Specified for <code>object</code> type values only." },
                 { "name": "value", "type": "string", "optional": true, "description": "User-friendly property value string." },
                 { "name": "valuePreview", "$ref": "ObjectPreview", "optional": true, "description": "Nested value preview." }
             ]
         },
         {
+            "id": "EntryPreview",
+            "type": "object",
+            "properties": [
+                { "name": "key", "$ref": "ObjectPreview", "optional": true, "description": "Entry key. Specified for map-like collection entries." },
+                { "name": "value", "$ref": "ObjectPreview", "description": "Entry value." }
+            ]
+        },
+        {
+            "id": "CollectionEntry",
+            "type": "object",
+            "properties": [
+                { "name": "key", "$ref": "Runtime.RemoteObject", "optional": true, "description": "Entry key of a map-like collection, otherwise not provided." },
+                { "name": "value", "$ref": "Runtime.RemoteObject", "description": "Entry value." }
+            ]
+        },
+        {
             "id": "PropertyDescriptor",
             "type": "object",
             "description": "Object property descriptor.",
             "description": "Returns properties of a given object. Object group of the result is inherited from the target object."
         },
         {
+            "name": "getCollectionEntries",
+            "description": "Returns entries of given Map / Set collection.",
+            "parameters": [
+                { "name": "objectId", "$ref": "Runtime.RemoteObjectId", "description": "Id of the collection to get entries for." },
+                { "name": "objectGroup", "optional": true, "type": "string", "description": "Symbolic group name that can be used to release multiple. If not provided, it will be the same objectGroup as the RemoteObject determined from <code>objectId</code>. This is useful for WeakMap to release the collection entries." },
+                { "name": "startIndex", "optional": true, "type": "integer", "description": "If provided skip to this index before collecting values. Otherwise, 0." },
+                { "name": "numberToFetch", "optional": true, "type": "integer", "description": "If provided only return <code>numberToFetch</code> values. Otherwise, return values all the way to the end." }
+            ],
+            "returns": [
+                { "name": "entries", "type": "array", "items": { "$ref": "CollectionEntry" }, "description": "Array of collection entries." }
+            ]
+        },
+        {
             "name": "releaseObject",
             "parameters": [
                 { "name": "objectId", "$ref": "RemoteObjectId", "description": "Identifier of the object to release." }
index 44450fc..a1923fe 100755 (executable)
@@ -50,6 +50,7 @@ _TYPES_NEEDING_RUNTIME_CASTS = set([
     "Runtime.RemoteObject",
     "Runtime.PropertyDescriptor",
     "Runtime.InternalPropertyDescriptor",
+    "Runtime.CollectionEntry",
     "Debugger.FunctionDetails",
     "Debugger.CallFrame",
     "Canvas.TraceLog",
index 122882d..68ea46f 100644 (file)
@@ -63,6 +63,10 @@ public:
 
     static const unsigned StructureFlags = StructureIsImmortal | Base::StructureFlags;
 
+    typedef HashMap<JSObject*, WriteBarrier<Unknown>> MapType;
+    MapType::const_iterator begin() const { return m_map.begin(); }
+    MapType::const_iterator end() const { return m_map.end(); }
+
 private:
     WeakMapData(VM&);
     static void destroy(JSCell*);
@@ -82,7 +86,6 @@ private:
         WeakMapData* m_target;
     };
     DeadKeyCleaner m_deadKeyCleaner;
-    typedef HashMap<JSObject*, WriteBarrier<Unknown>> MapType;
     MapType m_map;
 };
 
index 2fe58a7..1e11594 100644 (file)
@@ -1,3 +1,79 @@
+2015-01-29  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: ES6: Improved Console Format for Set and Map Objects (like Arrays)
+        https://bugs.webkit.org/show_bug.cgi?id=122867
+
+        Reviewed by Timothy Hatcher.
+
+        This includes Set/Map/WeakMap previews:
+
+            - Set previews: Set {1, 2, 3}
+            - Map/WeakMap previews: Map {1 => 2, "key" => "value"}
+
+        For WeakMaps:
+        
+            - the preview itself shows up to 5 key/value pairs from when the object was logged
+            - the previews are strings only, and thus do not retain the actual keys/values
+            - when expanding, we get RemoteObjects and strongly retain the keys/values
+            - when collapsing / clearing, we release the RemoteObjects so they can get collected
+
+        Currently you collapse the <entries> section, and re-expand later the
+        collection may show you knew keys/values. The UI for this will change.
+
+        * Localizations/en.lproj/localizedStrings.js:
+        * UserInterface/Protocol/RemoteObject.js:
+        (WebInspector.RemoteObject.prototype.isCollectionType):
+        (WebInspector.RemoteObject.prototype.isWeakCollection):
+        (WebInspector.RemoteObject.prototype.getCollectionEntries):
+        (WebInspector.RemoteObject.prototype.releaseWeakCollectionEntries):
+        (WebInspector.RemoteObject.prototype.arrayLength):
+        (WebInspector.RemoteObject.prototype._weakCollectionObjectGroup):
+        High level functions for dealing with a RemoteObject that may be a
+        collection / weak collection.
+
+        * UserInterface/Views/ConsoleMessageImpl.js:
+        (WebInspector.ConsoleMessageImpl):
+        (WebInspector.ConsoleMessageImpl.prototype._formatParameterAsObject):
+        Include default formatters for collection types.
+
+        (WebInspector.ConsoleMessageImpl.prototype._appendPreview):
+        (WebInspector.ConsoleMessageImpl.prototype._appendEntryPreviews):
+        (WebInspector.ConsoleMessageImpl.prototype._appendPropertyPreviews):
+        (WebInspector.ConsoleMessageImpl.prototype._appendValuePreview):
+        (WebInspector.ConsoleMessageImpl.prototype._appendObjectPreview): Deleted.
+        Refactor preview generation a bit and include a specific path for
+        generation the output of a preview with "entries".
+
+        * UserInterface/Views/LogContentView.css:
+        (.console-object-preview-body .console-object-preview-name.console-object-preview-name-Object):
+        With nested Object previews ("Map {{a:1} => 1}") don't show "Object" for the inner
+        object preview. Only show it if it has a unique type ("Map {Foo {a:1} => 1}")
+
+        (.console-formatted-object, .console-formatted-node, .console-formatted-error, .console-formatted-map, .console-formatted-set, .console-formatted-weakmap):
+        (:matches(.console-formatted-object, .console-formatted-node, .console-formatted-error, .console-formatted-map, .console-formatted-set, .console-formatted-weakmap) .section):
+        (:matches(.console-formatted-object, .console-formatted-node, .console-formatted-error, .console-formatted-map, .console-formatted-set, .console-formatted-weakmap) .properties):
+        Make map/set/weakmap display like Objects.
+
+        * UserInterface/Views/ObjectPropertiesSection.js:
+        (WebInspector.ObjectPropertiesSection.prototype.update):
+        (WebInspector.ObjectPropertiesSection.prototype.updateProperties):
+        (WebInspector.ObjectPropertyTreeElement.prototype.onpopulate.callback):
+        (WebInspector.ObjectPropertyTreeElement.prototype.onpopulate):
+        (WebInspector.CollectionEntriesMainTreeElement):
+        (WebInspector.CollectionEntriesMainTreeElement.prototype.onexpand.callback):
+        (WebInspector.CollectionEntriesMainTreeElement.prototype.onexpand):
+        (WebInspector.CollectionEntriesMainTreeElement.prototype.oncollapse):
+        (WebInspector.CollectionEntriesMainTreeElement.prototype.ondetach):
+        (WebInspector.CollectionEntriesMainTreeElement.prototype._trackWeakEntries):
+        (WebInspector.CollectionEntriesMainTreeElement.prototype._untrackWeakEntries):
+        (WebInspector.CollectionEntryTreeElement):
+        (WebInspector.CollectionEntryTreeElement.prototype.onpopulate):
+        (WebInspector.CollectionEntryTreeElement.prototype.onattach):
+        (WebInspector.EmptyCollectionTreeElement):
+        (WebInspector.ObjectPropertiesSection.prototype.update.callback): Deleted.
+        Add a quick UI for exploring the entries of a collection. We are actively
+        changing the styles of objects in the Console, so this should change soon.
+
 2015-01-28  Joseph Pecoraro  <pecoraro@apple.com>
 
         Web Inspector: Change Main Frame Status Buttons when debugging Augmented JSContext
index 292fb27..7582144 100644 (file)
Binary files a/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js and b/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js differ
index 066361c..439aa1e 100644 (file)
@@ -223,6 +223,42 @@ WebInspector.RemoteObject.prototype = {
         }
     },
 
+    isCollectionType: function()
+    {
+        return this.subtype === "map" || this.subtype === "set" || this.subtype === "weakmap";
+    },
+
+    isWeakCollection: function()
+    {
+        return this.subtype === "weakmap";
+    },
+
+    getCollectionEntries: function(start, numberToFetch, callback)
+    {
+        start = typeof start === "number" ? start : 0;
+        numberToFetch = typeof numberToFetch === "number" ? numberToFetch : 100;
+
+        console.assert(start >= 0);
+        console.assert(numberToFetch >= 0);
+        console.assert(this.isCollectionType());
+
+        // WeakMaps are not ordered. We should never send a non-zero start.
+        console.assert((this.subtype === "weakmap" && start === 0) || this.subtype !== "weakmap");
+
+        var objectGroup = this.isWeakCollection() ? this._weakCollectionObjectGroup() : "";
+
+        RuntimeAgent.getCollectionEntries(this._objectId, objectGroup, start, numberToFetch, function(error, entries) {
+            callback(entries);
+        });
+    },
+
+    releaseWeakCollectionEntries: function()
+    {
+        console.assert(this.isWeakCollection());
+
+        RuntimeAgent.releaseObjectGroup(this._weakCollectionObjectGroup());
+    },
+
     pushNodeToFrontend: function(callback)
     {
         if (this._objectId)
@@ -265,6 +301,13 @@ WebInspector.RemoteObject.prototype = {
         if (!matches)
             return 0;
         return parseInt(matches[1], 10);
+    },
+
+    // Private
+
+    _weakCollectionObjectGroup: function()
+    {
+        return JSON.stringify(this._objectId) + "-WeakMap";
     }
 };
 
index 7a3b180..b8df030 100644 (file)
@@ -42,6 +42,9 @@ WebInspector.ConsoleMessageImpl = function(source, level, message, linkifier, ty
     this._customFormatters = {
         "object": this._formatParameterAsObject,
         "error": this._formatParameterAsObject,
+        "map": this._formatParameterAsObject,
+        "set": this._formatParameterAsObject,
+        "weakmap": this._formatParameterAsObject,
         "array":  this._formatParameterAsArray,
         "node":   this._formatParameterAsNode,
         "string": this._formatParameterAsString
@@ -276,7 +279,17 @@ WebInspector.ConsoleMessageImpl.prototype = {
         var titleElement = document.createElement("span");
         if (includePreview && obj.preview) {
             titleElement.classList.add("console-object-preview");
-            var lossless = this._appendObjectPreview(titleElement, obj);
+
+            // COMPATIBILITY (iOS 8): iOS 7 and 8 did not have type/subtype/description on
+            // Runtime.ObjectPreview. Copy them over from the RemoteObject.
+            var preview = obj.preview;
+            if (!preview.type) {
+                preview.type = obj.type;
+                preview.subtype = obj.subtype;
+                preview.description = obj.description;
+            }
+
+            var lossless = this._appendPreview(titleElement, preview);
             if (lossless) {
                 titleElement.classList.add("console-object-preview-lossless");
                 elem.appendChild(titleElement);
@@ -289,23 +302,58 @@ WebInspector.ConsoleMessageImpl.prototype = {
         elem.appendChild(section.element);
     },
 
-    _appendObjectPreview: function(titleElement, obj)
+    _appendPreview: function(element, preview)
     {
-        var preview = obj.preview;
-        var isArray = obj.subtype === "array";
-
-        if (obj.description && !isArray) {
+        if (preview.type === "object" && preview.subtype !== "null" && preview.subtype !== "array") {
             var previewObjectNameElement = document.createElement("span");
             previewObjectNameElement.classList.add("console-object-preview-name");
-            if (obj.description === "Object")
+            if (preview.description === "Object")
                 previewObjectNameElement.classList.add("console-object-preview-name-Object");
 
-            previewObjectNameElement.textContent = obj.description + " ";
-            titleElement.appendChild(previewObjectNameElement);
+            previewObjectNameElement.textContent = preview.description + " ";
+            element.appendChild(previewObjectNameElement);
+        }
+
+        var bodyElement = element.createChild("span", "console-object-preview-body");
+        if (preview.entries)
+            return this._appendEntryPreviews(bodyElement, preview);
+        if (preview.properties)
+            return this._appendPropertyPreviews(bodyElement, preview);
+        return this._appendValuePreview(bodyElement, preview);
+    },
+
+    _appendEntryPreviews: function(element, preview)
+    {
+        var lossless = preview.lossless && !preview.properties.length;
+
+        element.appendChild(document.createTextNode("{"));
+
+        for (var i = 0; i < preview.entries.length; ++i) {
+            if (i > 0)
+                element.appendChild(document.createTextNode(", "));
+
+            var entry = preview.entries[i];
+            if (entry.key) {
+                this._appendPreview(element, entry.key);
+                element.appendChild(document.createTextNode(" => "));
+            }
+
+            this._appendPreview(element, entry.value);
         }
 
-        var bodyElement = titleElement.createChild("span", "console-object-preview-body");
-        bodyElement.appendChild(document.createTextNode(isArray ? "[" : "{"));
+        if (preview.overflow)
+            element.createChild("span").textContent = "\u2026";
+        element.appendChild(document.createTextNode("}"));
+
+        return lossless;
+    },
+
+    _appendPropertyPreviews: function(element, preview)
+    {
+        var isArray = preview.subtype === "array";
+
+        element.appendChild(document.createTextNode(isArray ? "[" : "{"));
+
         for (var i = 0; i < preview.properties.length; ++i) {
             var property = preview.properties[i];
 
@@ -318,14 +366,14 @@ WebInspector.ConsoleMessageImpl.prototype = {
                 continue;
 
             if (i > 0)
-                bodyElement.appendChild(document.createTextNode(", "));
+                element.appendChild(document.createTextNode(", "));
     
             if (!isArray || property.name != i) {
-                bodyElement.createChild("span", "name").textContent = property.name;
-                bodyElement.appendChild(document.createTextNode(": "));
+                element.createChild("span", "name").textContent = property.name;
+                element.appendChild(document.createTextNode(": "));
             }
 
-            var span = bodyElement.createChild("span", "console-formatted-" + property.type);
+            var span = element.createChild("span", "console-formatted-" + property.type);
             if (property.type === "object") {
                 if (property.subtype === "node")
                     span.classList.add("console-formatted-preview-node");
@@ -340,12 +388,20 @@ WebInspector.ConsoleMessageImpl.prototype = {
             else
                 span.textContent = property.value;
         }
+
         if (preview.overflow)
-            bodyElement.createChild("span").textContent = "\u2026";
-        bodyElement.appendChild(document.createTextNode(isArray ? "]" : "}"));
+            element.createChild("span").textContent = "\u2026";
+
+        element.appendChild(document.createTextNode(isArray ? "]" : "}"));
+
         return preview.lossless;
     },
 
+    _appendValuePreview: function(element, preview)
+    {
+        element.appendChild(document.createTextNode(preview.description));
+    },
+
     _formatParameterAsNode: function(object, elem)
     {
         function printNode(nodeId)
index 666eba3..223c5af 100644 (file)
     display: none;
 }
 
+.console-object-preview-body .console-object-preview-name.console-object-preview-name-Object { 
+    display: none;
+}
+
 .expanded .console-object-preview > .console-object-preview-name.console-object-preview-name-Object {
     display: inline;
 }
 
-.console-formatted-object, .console-formatted-node, .console-formatted-error {
+.console-formatted-object, .console-formatted-node, .console-formatted-error, .console-formatted-map, .console-formatted-set, .console-formatted-weakmap {
     position: relative;
     display: inline-block;
     vertical-align: top;
     color: black;
 }
 
-.console-formatted-object .section, .console-formatted-node .section, .console-formatted-error .section {
+:matches(.console-formatted-object, .console-formatted-node, .console-formatted-error, .console-formatted-map, .console-formatted-set, .console-formatted-weakmap) .section {
     position: static;
 }
 
-.console-formatted-object .properties, .console-formatted-node .properties, .console-formatted-error .properties {
+:matches(.console-formatted-object, .console-formatted-node, .console-formatted-error, .console-formatted-map, .console-formatted-set, .console-formatted-weakmap) .properties {
     padding-left: 0 !important;
 }
 
index 8113db7..1c55e68 100644 (file)
@@ -43,17 +43,18 @@ WebInspector.ObjectPropertiesSection.prototype = {
 
     update: function()
     {
-        var self = this;
         function callback(properties)
         {
             if (!properties)
                 return;
-            self.updateProperties(properties);
+
+            this.updateProperties(properties);
         }
+
         if (this.getAllProperties)
-            this.object.getAllProperties(callback);
+            this.object.getAllProperties(callback.bind(this));
         else
-            this.object.getOwnAndGetterProperties(callback);
+            this.object.getOwnAndGetterProperties(callback.bind(this));
     },
 
     updateProperties: function(properties, rootTreeElementConstructor, rootPropertyComparer)
@@ -86,6 +87,9 @@ WebInspector.ObjectPropertiesSection.prototype = {
         }
         this.propertiesForTest = properties;
 
+        if (this.object.isCollectionType())
+            this.propertiesTreeOutline.appendChild(new WebInspector.CollectionEntriesMainTreeElement(this.object));
+
         this.dispatchEventToListeners(WebInspector.Section.Event.VisibleContentDidChange);
     }
 };
@@ -153,15 +157,17 @@ WebInspector.ObjectPropertyTreeElement.prototype = {
         if (this.children.length && !this.shouldRefreshChildren)
             return;
 
-        var callback = function(properties) {
+        function callback(properties) {
             this.removeChildren();
             if (!properties)
                 return;
 
             properties.sort(WebInspector.ObjectPropertiesSection.CompareProperties);
-            for (var i = 0; i < properties.length; ++i) {
+            for (var i = 0; i < properties.length; ++i)
                 this.appendChild(new this.treeOutline.section.treeElementConstructor(properties[i]));
-            }
+
+            if (this.property.value.isCollectionType())
+                this.appendChild(new WebInspector.CollectionEntriesMainTreeElement(this.property.value));
         };
 
         if (this.property.name === "__proto__")
@@ -345,3 +351,188 @@ WebInspector.ObjectPropertyTreeElement.prototype = {
 };
 
 WebInspector.ObjectPropertyTreeElement.prototype.__proto__ = TreeElement.prototype;
+
+WebInspector.CollectionEntriesMainTreeElement = function(remoteObject)
+{
+    TreeElement.call(this, "<entries>", null, false);
+
+    console.assert(remoteObject);
+
+    this._remoteObject = remoteObject;
+    this._requestingEntries = false;
+    this._trackingEntries = false;
+
+    this.toggleOnClick = true;
+    this.selectable = false;
+    this.hasChildren = true;
+    this.expand();
+
+    // FIXME: When a parent TreeElement is collapsed, we do not get a chance
+    // to releaseWeakCollectionEntries. We should.
+}
+
+WebInspector.CollectionEntriesMainTreeElement.prototype = {
+    constructor: WebInspector.CollectionEntriesMainTreeElement,
+    __proto__: TreeElement.prototype,
+
+    onexpand: function()
+    {
+        if (this.children.length && !this.shouldRefreshChildren)
+            return;
+
+        if (this._requestingEntries)
+            return;
+
+        this._requestingEntries = true;
+
+        function callback(entries) {
+            this._requestingEntries = false;
+
+            this.removeChildren();
+
+            if (!entries || !entries.length) {
+                this.appendChild(new WebInspector.EmptyCollectionTreeElement);
+                return;
+            }
+
+            this._trackWeakEntries();
+
+            for (var i = 0; i < entries.length; ++i) {
+                var entry = entries[i];
+                if (entry.key)
+                    this.appendChild(new WebInspector.CollectionEntryTreeElement(entry, i));
+                else {
+                    this.appendChild(new WebInspector.ObjectPropertyTreeElement({
+                        name: "" + i,
+                        value: WebInspector.RemoteObject.fromPayload(entry.value),
+                        enumerable: true,
+                        writable: false,
+                    }));
+                }
+            }
+        }
+        
+        this._remoteObject.getCollectionEntries(0, 100, callback.bind(this));
+    },
+
+    oncollapse: function()
+    {
+        this._untrackWeakEntries();
+    },
+
+    ondetach: function()
+    {
+        this._untrackWeakEntries();
+    },
+
+    // Private.
+
+    _trackWeakEntries: function()
+    {
+        if (!this._remoteObject.isWeakCollection())
+            return;
+
+        if (this._trackingEntries)
+            return;
+
+        this._trackingEntries = true;
+
+        WebInspector.logManager.addEventListener(WebInspector.LogManager.Event.Cleared, this._untrackWeakEntries, this);
+        WebInspector.logManager.addEventListener(WebInspector.LogManager.Event.ActiveLogCleared, this._untrackWeakEntries, this);
+        WebInspector.logManager.addEventListener(WebInspector.LogManager.Event.SessionStarted, this._untrackWeakEntries, this);
+    },
+
+    _untrackWeakEntries: function()
+    {
+        if (!this._remoteObject.isWeakCollection())
+            return;
+
+        if (!this._trackingEntries)
+            return;
+
+        this._trackingEntries = false;
+
+        this._remoteObject.releaseWeakCollectionEntries();
+
+        WebInspector.logManager.removeEventListener(WebInspector.LogManager.Event.Cleared, this._untrackWeakEntries, this);
+        WebInspector.logManager.removeEventListener(WebInspector.LogManager.Event.ActiveLogCleared, this._untrackWeakEntries, this);
+        WebInspector.logManager.removeEventListener(WebInspector.LogManager.Event.SessionStarted, this._untrackWeakEntries, this);
+
+        this.removeChildren();
+
+        if (this.expanded)
+            this.collapse();
+    },
+}
+
+WebInspector.CollectionEntryTreeElement = function(entry, index)
+{
+    TreeElement.call(this, "", null, false);
+
+    console.assert(entry);
+
+    this._name = "" + index;
+    this._key = WebInspector.RemoteObject.fromPayload(entry.key);
+    this._value = WebInspector.RemoteObject.fromPayload(entry.value);
+
+    this.toggleOnClick = true;
+    this.selectable = false;
+    this.hasChildren = true;
+}
+
+WebInspector.CollectionEntryTreeElement.prototype = {
+    constructor: WebInspector.CollectionEntryTreeElement,
+    __proto__: TreeElement.prototype,
+
+    onpopulate: function()
+    {
+        if (this.children.length && !this.shouldRefreshChildren)
+            return;
+
+        this.appendChild(new WebInspector.ObjectPropertyTreeElement({
+            name: "key",
+            value: this._key,
+            enumerable: true,
+            writable: false,
+        }));
+
+        this.appendChild(new WebInspector.ObjectPropertyTreeElement({
+            name: "value",
+            value: this._value,
+            enumerable: true,
+            writable: false,
+        }));
+    },
+
+    onattach: function()
+    {
+        var nameElement = document.createElement("span");
+        nameElement.className = "name";
+        nameElement.textContent = "" + this._name;
+
+        var separatorElement = document.createElement("span");
+        separatorElement.className = "separator";
+        separatorElement.textContent = ": ";
+
+        var valueElement = document.createElement("span");
+        valueElement.className = "value";
+        valueElement.textContent = "{" + this._key.description + " => " + this._value.description + "}";
+
+        this.listItemElement.removeChildren();
+        this.listItemElement.appendChild(nameElement);
+        this.listItemElement.appendChild(separatorElement);
+        this.listItemElement.appendChild(valueElement);
+    }
+}
+
+WebInspector.EmptyCollectionTreeElement = function()
+{
+    TreeElement.call(this, WebInspector.UIString("Empty Collection"), null, false);
+
+    this.selectable = false;
+}
+
+WebInspector.EmptyCollectionTreeElement.prototype = {
+    constructor: WebInspector.EmptyCollectionTreeElement,
+    __proto__: TreeElement.prototype
+}