Web Inspector: Get a RemoteObject or ObjectPreview from HeapSnapshot Object Identifier
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 10 Mar 2016 06:15:25 +0000 (06:15 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 10 Mar 2016 06:15:25 +0000 (06:15 +0000)
https://bugs.webkit.org/show_bug.cgi?id=155264
<rdar://problem/25070716>

Patch by Joseph Pecoraro <pecoraro@apple.com> on 2016-03-09
Reviewed by Timothy Hatcher.

Source/JavaScriptCore:

* inspector/InjectedScript.h:
* inspector/InjectedScript.cpp:
(Inspector::InjectedScript::functionDetails):
(Inspector::InjectedScript::previewValue):
New InjectedScript methods for building Debugger.FunctionDetails
or Runtime.ObjectPreview protocol objects from a JSValue.

* inspector/InjectedScriptSource.js:
(InjectedScript.prototype.previewValue):
(InjectedScript.prototype.functionDetails):
(InjectedScript.prototype.getFunctionDetails):
(InjectedScript.RemoteObject.prototype._isPreviewableObjectInternal):
(InjectedScript.RemoteObject.prototype._createObjectPreviewForValue): Deleted.
(InjectedScript.RemoteObject.prototype._appendEntryPreviews): Deleted.
Share code around creating function details or object preview objects.

* inspector/agents/InspectorHeapAgent.cpp:
(Inspector::InspectorHeapAgent::InspectorHeapAgent):
(Inspector::InspectorHeapAgent::nodeForHeapObjectIdentifier):
(Inspector::InspectorHeapAgent::getPreview):
(Inspector::InspectorHeapAgent::getRemoteObject):
* inspector/agents/InspectorHeapAgent.h:
* inspector/protocol/Heap.json:
New protocol methods that go from heap object identifier to a
remote object or some kind of preview.

* inspector/scripts/codegen/generator.py:
Allow runtime casts for ObjectPreview.

LayoutTests:

* inspector/heap/getPreview-expected.txt: Added.
* inspector/heap/getPreview.html: Added.
* inspector/heap/getRemoteObject-expected.txt: Added.
* inspector/heap/getRemoteObject.html: Added.
Test the new protocol methods in different scenarios.

* inspector/heap/snapshot-expected.txt:
* inspector/heap/snapshot.html:
Fix typo.

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

16 files changed:
LayoutTests/ChangeLog
LayoutTests/inspector/heap/getPreview-expected.txt [new file with mode: 0644]
LayoutTests/inspector/heap/getPreview.html [new file with mode: 0644]
LayoutTests/inspector/heap/getRemoteObject-expected.txt [new file with mode: 0644]
LayoutTests/inspector/heap/getRemoteObject.html [new file with mode: 0644]
LayoutTests/inspector/heap/snapshot-expected.txt
LayoutTests/inspector/heap/snapshot.html
LayoutTests/platform/mac-wk1/TestExpectations
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/inspector/InjectedScript.cpp
Source/JavaScriptCore/inspector/InjectedScript.h
Source/JavaScriptCore/inspector/InjectedScriptSource.js
Source/JavaScriptCore/inspector/agents/InspectorHeapAgent.cpp
Source/JavaScriptCore/inspector/agents/InspectorHeapAgent.h
Source/JavaScriptCore/inspector/protocol/Heap.json
Source/JavaScriptCore/inspector/scripts/codegen/generator.py

index 8ed3e0c..7d1c804 100644 (file)
@@ -1,3 +1,21 @@
+2016-03-09  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: Get a RemoteObject or ObjectPreview from HeapSnapshot Object Identifier
+        https://bugs.webkit.org/show_bug.cgi?id=155264
+        <rdar://problem/25070716>
+
+        Reviewed by Timothy Hatcher.
+
+        * inspector/heap/getPreview-expected.txt: Added.
+        * inspector/heap/getPreview.html: Added.
+        * inspector/heap/getRemoteObject-expected.txt: Added.
+        * inspector/heap/getRemoteObject.html: Added.
+        Test the new protocol methods in different scenarios.
+
+        * inspector/heap/snapshot-expected.txt:
+        * inspector/heap/snapshot.html:
+        Fix typo.
+
 2016-03-09  Andy VanWagoner  <thetalecrafter@gmail.com>
 
         [INTL] Intl Constructors not web compatible with Object.create usage
diff --git a/LayoutTests/inspector/heap/getPreview-expected.txt b/LayoutTests/inspector/heap/getPreview-expected.txt
new file mode 100644 (file)
index 0000000..765a6e8
--- /dev/null
@@ -0,0 +1,62 @@
+Test for the Heap.getRemoteObject command.
+
+
+== Running test suite: Heap.getPreview
+-- Running test case: GetPreviewNoSnapshot
+PASS: Should get an error when no snapshot exists.
+PASS: No heap snapshot
+
+-- Running test case: GetPreviewForString
+PASS: Should not have an error creating a snapshot.
+PASS: Should not have an error getting preview.
+STRING: This is the test string.
+
+-- Running test case: GetPreviewForFunction
+PASS: Should not have an error creating a snapshot.
+PASS: Should not have an error getting preview.
+FUNCTION DETAILS: {
+    "location": {
+        "scriptId": "<filtered>",
+        "lineNumber": 10,
+        "columnNumber": 47
+    },
+    "name": "myFunctionName"
+}
+
+-- Running test case: GetPreviewForObject
+PASS: Should not have an error creating a snapshot.
+PASS: Should not have an error getting preview.
+OBJECT PREVIEW: {
+    "type": "object",
+    "description": "Map",
+    "lossless": true,
+    "subtype": "map",
+    "overflow": false,
+    "properties": [],
+    "size": 1,
+    "entries": [
+        {
+            "key": {
+                "type": "string",
+                "description": "key",
+                "lossless": true
+            },
+            "value": {
+                "type": "string",
+                "description": "value",
+                "lossless": true
+            }
+        }
+    ]
+}
+
+-- Running test case: GetPreviewBadIdentifier
+PASS: Should not have an error creating a snapshot.
+PASS: Should get an error when no object for identifier exists.
+PASS: No object for identifier, it may have been collected
+
+-- Running test case: GetRemoteObjectCollectedObject
+PASS: Should not have an error creating a snapshot.
+PASS: Should get an error when object has been collected.
+PASS: No object for identifier, it may have been collected
+
diff --git a/LayoutTests/inspector/heap/getPreview.html b/LayoutTests/inspector/heap/getPreview.html
new file mode 100644 (file)
index 0000000..d9fa296
--- /dev/null
@@ -0,0 +1,164 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
+<script>
+function triggerCreateStringObject() {
+    window.myString = "This is the test string.";
+}
+
+function triggerCreateFunctionObject() {
+    window.myFunction = function myFunctionName(alpha, beta) {
+        return alpha * beta;
+    };
+}
+
+function triggerCreateMapObject() {
+    window.myMap = new Map;
+    myMap.set("key", "value");
+}
+
+function triggerDeleteMapObject() {
+    window.myMap = null;
+}
+
+function test()
+{
+    let suite = InspectorTest.createAsyncSuite("Heap.getPreview");
+
+    function jsonFilter(key, value) {
+        if (key === "scriptId")
+            return "<filtered>";
+        return value;
+    }
+
+    suite.addTestCase({
+        name: "GetPreviewNoSnapshot",
+        description: "Calling Heap.getPreview when no snapshot exists should result in an error.",
+        test: (resolve, reject) => {
+            HeapAgent.getPreview(1, (error, string, functionDetails, objectPreviewPayload) => {
+                InspectorTest.expectThat(error, "Should get an error when no snapshot exists.");
+                InspectorTest.pass(error);
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "GetPreviewForString",
+        description: "Calling Heap.getPreview for a live string should return the value of that string.",
+        test: (resolve, reject) => {
+            HeapAgent.snapshot((error, timestamp, snapshotStringData) => { // All pre-existing objects.
+                InspectorTest.evaluateInPage("triggerCreateStringObject()");
+                HeapAgent.snapshot((error, timestamp, snapshotStringData) => { // Newly created objects.
+                    InspectorTest.expectThat(!error, "Should not have an error creating a snapshot.");
+                    let payload = JSON.parse(snapshotStringData);
+                    let snapshot = WebInspector.HeapSnapshot.fromPayload(payload);
+
+                    let strings = snapshot.instancesWithClassName("string");
+                    let heapSnapshotNode = strings.reduce((result, x) => result.id < x.id ? x : result, strings[0]);
+                    HeapAgent.getPreview(heapSnapshotNode.id, (error, string, functionDetails, objectPreviewPayload) => {
+                        InspectorTest.expectThat(!error, "Should not have an error getting preview.");
+                        InspectorTest.log("STRING: " + string);
+                        resolve();
+                    });
+                });
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "GetPreviewForFunction",
+        description: "Calling Heap.getPreview for a live Function should return function details for that value.",
+        test: (resolve, reject) => {
+            HeapAgent.snapshot((error, timestamp, snapshotStringData) => { // All pre-existing objects.
+                InspectorTest.evaluateInPage("triggerCreateFunctionObject()");
+                HeapAgent.snapshot((error, timestamp, snapshotStringData) => { // Newly created objects.
+                    InspectorTest.expectThat(!error, "Should not have an error creating a snapshot.");
+                    let payload = JSON.parse(snapshotStringData);
+                    let snapshot = WebInspector.HeapSnapshot.fromPayload(payload);
+
+                    let functions = snapshot.instancesWithClassName("Function");
+                    let heapSnapshotNode = functions.reduce((result, x) => result.id < x.id ? x : result, functions[0]);
+                    HeapAgent.getPreview(heapSnapshotNode.id, (error, string, functionDetails, objectPreviewPayload) => {
+                        InspectorTest.expectThat(!error, "Should not have an error getting preview.");
+                        InspectorTest.log("FUNCTION DETAILS: " + JSON.stringify(functionDetails, jsonFilter, 4));
+                        resolve();
+                    });
+                });
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "GetPreviewForObject",
+        description: "Calling Heap.getPreview for a live Object should return an object preview for that value.",
+        test: (resolve, reject) => {
+            HeapAgent.snapshot((error, timestamp, snapshotStringData) => { // All pre-existing objects.
+                InspectorTest.evaluateInPage("triggerCreateMapObject()");
+                HeapAgent.snapshot((error, timestamp, snapshotStringData) => { // Newly created objects.
+                    InspectorTest.expectThat(!error, "Should not have an error creating a snapshot.");
+                    let payload = JSON.parse(snapshotStringData);
+                    let snapshot = WebInspector.HeapSnapshot.fromPayload(payload);
+
+                    let maps = snapshot.instancesWithClassName("Map");
+                    let heapSnapshotNode = maps.reduce((result, x) => result.id < x.id ? x : result, maps[0]);
+                    HeapAgent.getPreview(heapSnapshotNode.id, (error, string, functionDetails, objectPreviewPayload) => {
+                        InspectorTest.expectThat(!error, "Should not have an error getting preview.");
+                        InspectorTest.log("OBJECT PREVIEW: " + JSON.stringify(objectPreviewPayload, jsonFilter, 4));
+                        resolve();
+                    });
+                });
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "GetPreviewBadIdentifier",
+        description: "Calling Heap.getPreview with a bad identifier should result in an error.",
+        test: (resolve, reject) => {
+            HeapAgent.snapshot((error, timestamp, snapshotStringData) => {
+                InspectorTest.expectThat(!error, "Should not have an error creating a snapshot.");
+                HeapAgent.getPreview(9999999, (error, string, functionDetails, objectPreviewPayload) => {
+                    InspectorTest.expectThat(error, "Should get an error when no object for identifier exists.");
+                    InspectorTest.pass(error);
+                    resolve();
+                });
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "GetRemoteObjectCollectedObject",
+        description: "Calling Heap.getRemoteObject for an object that has been collected should result in an error.",
+        test: (resolve, reject) => {
+            HeapAgent.snapshot((error, timestamp, snapshotStringData) => { // All pre-existing objects.
+                InspectorTest.evaluateInPage("triggerCreateMapObject()");
+                HeapAgent.snapshot((error, timestamp, snapshotStringData) => { // Newly created objects.
+                    InspectorTest.expectThat(!error, "Should not have an error creating a snapshot.");
+                    let payload = JSON.parse(snapshotStringData);
+                    let snapshot = WebInspector.HeapSnapshot.fromPayload(payload);
+
+                    InspectorTest.evaluateInPage("triggerDeleteMapObject()");
+                    HeapAgent.gc();
+
+                    let maps = snapshot.instancesWithClassName("Map");
+                    let heapSnapshotNode = maps.reduce((result, x) => result.id < x.id ? x : result, maps[0]);
+                    HeapAgent.getPreview(heapSnapshotNode.id, (error, string, functionDetails, objectPreviewPayload) => {
+                        InspectorTest.expectThat(error, "Should get an error when object has been collected.");
+                        InspectorTest.pass(error);
+                        resolve();
+                    });
+                });
+            });
+        }
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+<p>Test for the Heap.getRemoteObject command.</p>
+</body>
+</html>
diff --git a/LayoutTests/inspector/heap/getRemoteObject-expected.txt b/LayoutTests/inspector/heap/getRemoteObject-expected.txt
new file mode 100644 (file)
index 0000000..414c4c3
--- /dev/null
@@ -0,0 +1,25 @@
+Test for the Heap.getRemoteObject command.
+
+
+== Running test suite: Heap.getRemoteObject
+-- Running test case: GetRemoteObjectNoSnapshot
+PASS: Should get an error when no snapshot exists.
+PASS: No heap snapshot
+
+-- Running test case: GetRemoteObjectForWindow
+PASS: Should not have an error creating a snapshot.
+PASS: Should should include at least one 'Window' instance.
+PASS: Should not have an error getting remote object.
+Window
+
+-- Running test case: GetRemoteObjectBadIdentifier
+PASS: Should not have an error creating a snapshot.
+PASS: Should get an error when no object for identifier exists.
+PASS: No object for identifier, it may have been collected
+
+-- Running test case: GetRemoteObjectCollectedObject
+PASS: Should not have an error creating a snapshot.
+PASS: Should should include at least one 'Map' instance.
+PASS: Should get an error when object has been collected.
+PASS: No object for identifier, it may have been collected
+
diff --git a/LayoutTests/inspector/heap/getRemoteObject.html b/LayoutTests/inspector/heap/getRemoteObject.html
new file mode 100644 (file)
index 0000000..ddd7ba0
--- /dev/null
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
+<script>
+function triggerCreateMapObject() {
+    window.myMap = new Map;
+}
+
+function triggerDeleteMapObject() {
+    window.myMap = null;
+}
+
+function test()
+{
+    let suite = InspectorTest.createAsyncSuite("Heap.getRemoteObject");
+
+    suite.addTestCase({
+        name: "GetRemoteObjectNoSnapshot",
+        description: "Calling Heap.getRemoteObject when no snapshot exists should result in an error.",
+        test: (resolve, reject) => {
+            HeapAgent.getRemoteObject(1, "test", (error, remoteObjectPayload) => {
+                InspectorTest.expectThat(error, "Should get an error when no snapshot exists.");
+                InspectorTest.pass(error);
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "GetRemoteObjectForWindow",
+        description: "Calling Heap.getRemoteObject for a live value should return a remote object for that value.",
+        test: (resolve, reject) => {
+            HeapAgent.snapshot((error, timestamp, snapshotStringData) => {
+                InspectorTest.expectThat(!error, "Should not have an error creating a snapshot.");
+                let payload = JSON.parse(snapshotStringData);
+                let snapshot = WebInspector.HeapSnapshot.fromPayload(payload);
+                let heapSnapshotNode = snapshot.instancesWithClassName("Window")[0];
+                InspectorTest.expectThat(heapSnapshotNode, "Should should include at least one 'Window' instance.");
+
+                HeapAgent.getRemoteObject(heapSnapshotNode.id, "test", (error, remoteObjectPayload) => {
+                    InspectorTest.expectThat(!error, "Should not have an error getting remote object.");
+                    let remoteObject = WebInspector.RemoteObject.fromPayload(remoteObjectPayload);
+                    InspectorTest.log(remoteObject.description);
+                    resolve();
+                });
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "GetRemoteObjectBadIdentifier",
+        description: "Calling Heap.getRemoteObject with a bad identifier should result in an error.",
+        test: (resolve, reject) => {
+            HeapAgent.snapshot((error, timestamp, snapshotStringData) => {
+                InspectorTest.expectThat(!error, "Should not have an error creating a snapshot.");
+                HeapAgent.getRemoteObject(9999999, "test", (error, remoteObjectPayload) => {
+                    InspectorTest.expectThat(error, "Should get an error when no object for identifier exists.");
+                    InspectorTest.pass(error);
+                    resolve();
+                });
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "GetRemoteObjectCollectedObject",
+        description: "Calling Heap.getRemoteObject for an object that has been collected should result in an error.",
+        test: (resolve, reject) => {
+            HeapAgent.snapshot((error, timestamp, snapshotStringData) => { // All pre-existing objects.
+                InspectorTest.evaluateInPage("triggerCreateMapObject()");
+                HeapAgent.snapshot((error, timestamp, snapshotStringData) => { // Newly created objects.
+                    InspectorTest.expectThat(!error, "Should not have an error creating a snapshot.");
+                    let payload = JSON.parse(snapshotStringData);
+                    let snapshot = WebInspector.HeapSnapshot.fromPayload(payload);
+                    let maps = snapshot.instancesWithClassName("Map");
+                    InspectorTest.expectThat(maps.length, "Should should include at least one 'Map' instance.");
+
+                    InspectorTest.evaluateInPage("triggerDeleteMapObject()");
+                    HeapAgent.gc();
+
+                    let heapSnapshotNode = maps.reduce((result, x) => result.id < x.id ? x : result, maps[0]);
+                    HeapAgent.getRemoteObject(heapSnapshotNode.id, "test", (error, remoteObjectPayload) => {
+                        InspectorTest.expectThat(error, "Should get an error when object has been collected.");
+                        InspectorTest.pass(error);
+                        resolve();
+                    });
+                });
+            });
+        }
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+<p>Test for the Heap.getRemoteObject command.</p>
+</body>
+</html>
index 778514a..61206bc 100644 (file)
@@ -7,5 +7,5 @@ PASS: Should not have an error creating a snapshot.
 PASS: Snapshot size should be greater than 1kb.
 PASS: Snapshot object count should be greater than 100.
 PASS: Snapshot should include a class category for 'Window'.
-PASS: Snapshow should include at least one 'Window' instance.
+PASS: Snapshot should include at least one 'Window' instance.
 
index fd7ad39..3916d2a 100644 (file)
@@ -18,7 +18,7 @@ function test()
                 InspectorTest.expectThat(snapshot.totalSize > 1024, "Snapshot size should be greater than 1kb.");
                 InspectorTest.expectThat(snapshot.totalObjectCount > 100, "Snapshot object count should be greater than 100.");
                 InspectorTest.expectThat(snapshot.categories.get("Window"), "Snapshot should include a class category for 'Window'.");
-                InspectorTest.expectThat(snapshot.instancesWithClassName("Window").length > 0, "Snapshow should include at least one 'Window' instance.");
+                InspectorTest.expectThat(snapshot.instancesWithClassName("Window").length > 0, "Snapshot should include at least one 'Window' instance.");
                 resolve();
             });
         }
index aad24c2..e5fdea1 100644 (file)
@@ -186,8 +186,12 @@ imported/blink/compositing/layer-creation/iframe-clip-removed.html [ Pass Timeou
 # ASAN test only fails on WK1:
 webgl/1.0.3/151055_asan.html [ Failure ]
 
-# Lacking WK1 TestRunner API that evaluates JavaScript through JSC APIs and not WebCore APIs
-inspector/script-profiler/event-type-API.html
+# Lacking WK1 TestRunner API that evaluates JavaScript through JSC APIs and not WebCore APIs.
+inspector/script-profiler/event-type-API.html [ Skip ]
+
+# WK1 Inspector running in the same VM as the inspected page skews heap snapshot results.
+inspector/heap/getPreview.html [ Skip ]
+inspector/heap/getRemoteObject.html [ Skip ]
 
 # This test checks ScrollAnimator events only for main frame scrollbars that use native widgets in WK1.
 fast/scrolling/scroll-animator-overlay-scrollbars-hovered.html [ Skip ]
index 964053d..f7f1fa5 100644 (file)
@@ -1,3 +1,40 @@
+2016-03-09  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: Get a RemoteObject or ObjectPreview from HeapSnapshot Object Identifier
+        https://bugs.webkit.org/show_bug.cgi?id=155264
+        <rdar://problem/25070716>
+
+        Reviewed by Timothy Hatcher.
+
+        * inspector/InjectedScript.h:
+        * inspector/InjectedScript.cpp:
+        (Inspector::InjectedScript::functionDetails):
+        (Inspector::InjectedScript::previewValue):
+        New InjectedScript methods for building Debugger.FunctionDetails
+        or Runtime.ObjectPreview protocol objects from a JSValue.
+
+        * inspector/InjectedScriptSource.js:
+        (InjectedScript.prototype.previewValue):
+        (InjectedScript.prototype.functionDetails):
+        (InjectedScript.prototype.getFunctionDetails):
+        (InjectedScript.RemoteObject.prototype._isPreviewableObjectInternal):
+        (InjectedScript.RemoteObject.prototype._createObjectPreviewForValue): Deleted.
+        (InjectedScript.RemoteObject.prototype._appendEntryPreviews): Deleted.
+        Share code around creating function details or object preview objects.
+
+        * inspector/agents/InspectorHeapAgent.cpp:
+        (Inspector::InspectorHeapAgent::InspectorHeapAgent):
+        (Inspector::InspectorHeapAgent::nodeForHeapObjectIdentifier):
+        (Inspector::InspectorHeapAgent::getPreview):
+        (Inspector::InspectorHeapAgent::getRemoteObject):
+        * inspector/agents/InspectorHeapAgent.h:
+        * inspector/protocol/Heap.json:
+        New protocol methods that go from heap object identifier to a
+        remote object or some kind of preview.
+
+        * inspector/scripts/codegen/generator.py:
+        Allow runtime casts for ObjectPreview.
+
 2016-03-09  Andy VanWagoner  <thetalecrafter@gmail.com>
 
         [INTL] Intl Constructors not web compatible with Object.create usage
index 1e1bc8b..70813c6 100644 (file)
@@ -109,6 +109,23 @@ void InjectedScript::getFunctionDetails(ErrorString& errorString, const String&
     *result = BindingTraits<Inspector::Protocol::Debugger::FunctionDetails>::runtimeCast(WTFMove(resultValue));
 }
 
+void InjectedScript::functionDetails(ErrorString& errorString, const Deprecated::ScriptValue& value, RefPtr<Protocol::Debugger::FunctionDetails>* result)
+{
+    Deprecated::ScriptFunctionCall function(injectedScriptObject(), ASCIILiteral("functionDetails"), inspectorEnvironment()->functionCallHandler());
+    function.appendArgument(value);
+    function.appendArgument(true); // Preview only.
+
+    RefPtr<InspectorValue> resultValue;
+    makeCall(function, &resultValue);
+    if (!resultValue || resultValue->type() != InspectorValue::Type::Object) {
+        if (!resultValue->asString(errorString))
+            errorString = ASCIILiteral("Internal error");
+        return;
+    }
+
+    *result = BindingTraits<Inspector::Protocol::Debugger::FunctionDetails>::runtimeCast(WTFMove(resultValue));
+}
+
 void InjectedScript::getProperties(ErrorString& errorString, const String& objectId, bool ownProperties, bool generatePreview, RefPtr<Array<Inspector::Protocol::Runtime::PropertyDescriptor>>* properties)
 {
     Deprecated::ScriptFunctionCall function(injectedScriptObject(), ASCIILiteral("getProperties"), inspectorEnvironment()->functionCallHandler());
@@ -254,6 +271,24 @@ RefPtr<Inspector::Protocol::Runtime::RemoteObject> InjectedScript::wrapTable(con
     return BindingTraits<Inspector::Protocol::Runtime::RemoteObject>::runtimeCast(resultObject);
 }
 
+RefPtr<Inspector::Protocol::Runtime::ObjectPreview> InjectedScript::previewValue(const Deprecated::ScriptValue& value) const
+{
+    ASSERT(!hasNoValue());
+    Deprecated::ScriptFunctionCall wrapFunction(injectedScriptObject(), ASCIILiteral("previewValue"), inspectorEnvironment()->functionCallHandler());
+    wrapFunction.appendArgument(value);
+
+    bool hadException = false;
+    Deprecated::ScriptValue r = callFunctionWithEvalEnabled(wrapFunction, hadException);
+    if (hadException)
+        return nullptr;
+
+    RefPtr<InspectorObject> resultObject;
+    bool castSucceeded = r.toInspectorValue(scriptState())->asObject(resultObject);
+    ASSERT_UNUSED(castSucceeded, castSucceeded);
+
+    return BindingTraits<Inspector::Protocol::Runtime::ObjectPreview>::runtimeCast(resultObject);
+}
+
 void InjectedScript::setExceptionValue(const Deprecated::ScriptValue& value)
 {
     ASSERT(!hasNoValue());
index 509cd58..dabb231 100644 (file)
@@ -56,6 +56,7 @@ public:
     void callFunctionOn(ErrorString&, const String& objectId, const String& expression, const String& arguments, bool returnByValue, bool generatePreview, RefPtr<Protocol::Runtime::RemoteObject>* result, Protocol::OptOutput<bool>* wasThrown);
     void evaluateOnCallFrame(ErrorString&, const Deprecated::ScriptValue& callFrames, const String& callFrameId, const String& expression, const String& objectGroup, bool includeCommandLineAPI, bool returnByValue, bool generatePreview, bool saveResult, RefPtr<Protocol::Runtime::RemoteObject>* result, Protocol::OptOutput<bool>* wasThrown, Inspector::Protocol::OptOutput<int>* savedResultIndex);
     void getFunctionDetails(ErrorString&, const String& functionId, RefPtr<Protocol::Debugger::FunctionDetails>* result);
+    void functionDetails(ErrorString&, const Deprecated::ScriptValue&, RefPtr<Protocol::Debugger::FunctionDetails>* result);
     void getProperties(ErrorString&, const String& objectId, bool ownProperties, bool generatePreview, RefPtr<Protocol::Array<Protocol::Runtime::PropertyDescriptor>>* result);
     void getDisplayableProperties(ErrorString&, const String& objectId, bool generatePreview, RefPtr<Protocol::Array<Protocol::Runtime::PropertyDescriptor>>* result);
     void getInternalProperties(ErrorString&, const String& objectId, bool generatePreview, RefPtr<Protocol::Array<Protocol::Runtime::InternalPropertyDescriptor>>* result);
@@ -65,6 +66,7 @@ public:
     Ref<Protocol::Array<Protocol::Debugger::CallFrame>> wrapCallFrames(const Deprecated::ScriptValue&) const;
     RefPtr<Protocol::Runtime::RemoteObject> wrapObject(const Deprecated::ScriptValue&, const String& groupName, bool generatePreview = false) const;
     RefPtr<Protocol::Runtime::RemoteObject> wrapTable(const Deprecated::ScriptValue& table, const Deprecated::ScriptValue& columns) const;
+    RefPtr<Protocol::Runtime::ObjectPreview> previewValue(const Deprecated::ScriptValue&) const;
 
     void setExceptionValue(const Deprecated::ScriptValue&);
     void clearExceptionValue();
index bb2b1da..cd23a67 100644 (file)
@@ -98,6 +98,36 @@ InjectedScript.prototype = {
         return InjectedScript.primitiveTypes[typeof object] && !this._isHTMLAllCollection(object);
     },
 
+    previewValue: function(value)
+    {
+        return InjectedScript.RemoteObject.createObjectPreviewForValue(value, true);
+    },
+
+    functionDetails: function(func, previewOnly)
+    {
+        var details = InjectedScriptHost.functionDetails(func);
+        if (!details)
+            return "Cannot resolve function details.";
+
+        // FIXME: provide function scope data in "scopesRaw" property when JSC supports it.
+        // <https://webkit.org/b/87192> [JSC] expose function (closure) inner context to debugger
+        if ("rawScopes" in details) {
+            if (previewOnly)
+                delete details.rawScopes;
+            else {
+                var objectGroupName = this._idToObjectGroupName[parsedFunctionId.id];
+                var rawScopes = details.rawScopes;
+                var scopes = [];
+                delete details.rawScopes;
+                for (var i = 0; i < rawScopes.length; i++)
+                    scopes.push(InjectedScript.CallFrameProxy._createScopeJson(rawScopes[i].type, rawScopes[i].object, objectGroupName));
+                details.scopeChain = scopes;
+            }
+        }
+
+        return details;
+    },
+
     wrapObject: function(object, groupName, canAccessInspectedGlobalObject, generatePreview)
     {
         if (canAccessInspectedGlobalObject)
@@ -332,19 +362,7 @@ InjectedScript.prototype = {
         var func = this._objectForId(parsedFunctionId);
         if (typeof func !== "function")
             return "Cannot resolve function by id.";
-        var details = InjectedScriptHost.functionDetails(func);
-        if (!details)
-            return "Cannot resolve function details.";
-        if ("rawScopes" in details) {
-            var objectGroupName = this._idToObjectGroupName[parsedFunctionId.id];
-            var rawScopes = details.rawScopes;
-            var scopes = [];
-            delete details.rawScopes;
-            for (var i = 0; i < rawScopes.length; i++)
-                scopes.push(InjectedScript.CallFrameProxy._createScopeJson(rawScopes[i].type, rawScopes[i].object, objectGroupName));
-            details.scopeChain = scopes;
-        }
-        return details;
+        return injectedScript.functionDetails(func);
     },
 
     releaseObject: function(objectId)
@@ -1025,7 +1043,17 @@ InjectedScript.RemoteObject = function(object, objectGroupName, forceValueType,
 
     if (generatePreview && this.type === "object")
         this.preview = this._generatePreview(object, undefined, columnNames);
-}
+};
+
+InjectedScript.RemoteObject.createObjectPreviewForValue = function(value, generatePreview)
+{
+    var remoteObject = new InjectedScript.RemoteObject(value, undefined, false, generatePreview, undefined);
+    if (remoteObject.objectId)
+        injectedScript.releaseObject(remoteObject.objectId);
+    if (remoteObject.classPrototype && remoteObject.classPrototype.objectId)
+        injectedScript.releaseObject(remoteObject.classPrototype.objectId);
+    return remoteObject.preview || remoteObject._emptyPreview();
+};
 
 InjectedScript.RemoteObject.prototype = {
     _initialPreview: function()
@@ -1065,17 +1093,6 @@ InjectedScript.RemoteObject.prototype = {
         return preview;
     },
 
-    _createObjectPreviewForValue: function(value, generatePreview)
-    {
-        var remoteObject = new InjectedScript.RemoteObject(value, undefined, false, generatePreview, undefined);
-        if (remoteObject.objectId)
-            injectedScript.releaseObject(remoteObject.objectId);
-        if (remoteObject.classPrototype && remoteObject.classPrototype.objectId)
-            injectedScript.releaseObject(remoteObject.classPrototype.objectId);
-
-        return remoteObject.preview || remoteObject._emptyPreview();
-    },
-
     _generatePreview: function(object, firstLevelKeys, secondLevelKeys)
     {
         var preview = this._initialPreview();
@@ -1202,7 +1219,7 @@ InjectedScript.RemoteObject.prototype = {
             // Second level.
             if ((secondLevelKeys === null || secondLevelKeys) || this._isPreviewableObject(value, object)) {
                 // FIXME: If we want secondLevelKeys filter to continue we would need some refactoring.
-                var subPreview = this._createObjectPreviewForValue(value, value !== object);
+                var subPreview = InjectedScript.RemoteObject.createObjectPreviewForValue(value, value !== object);
                 property.valuePreview = subPreview;
                 if (!subPreview.lossless)
                     preview.lossless = false;
@@ -1266,10 +1283,10 @@ InjectedScript.RemoteObject.prototype = {
         }
 
         preview.entries = entries.map(function(entry) {
-            entry.value = this._createObjectPreviewForValue(entry.value, entry.value !== object);
+            entry.value = InjectedScript.RemoteObject.createObjectPreviewForValue(entry.value, entry.value !== object);
             updateMainPreview(entry.value);
             if ("key" in entry) {
-                entry.key = this._createObjectPreviewForValue(entry.key, entry.key !== object);
+                entry.key = InjectedScript.RemoteObject.createObjectPreviewForValue(entry.key, entry.key !== object);
                 updateMainPreview(entry.key);
             }
             return entry;
index 6b49ea1..ad89bc6 100644 (file)
@@ -27,6 +27,8 @@
 #include "InspectorHeapAgent.h"
 
 #include "HeapProfiler.h"
+#include "InjectedScript.h"
+#include "InjectedScriptManager.h"
 #include "InspectorEnvironment.h"
 #include "JSCInlines.h"
 #include "VM.h"
@@ -39,6 +41,7 @@ namespace Inspector {
 
 InspectorHeapAgent::InspectorHeapAgent(AgentContext& context)
     : InspectorAgentBase(ASCIILiteral("Heap"))
+    , m_injectedScriptManager(context.injectedScriptManager)
     , m_frontendDispatcher(std::make_unique<HeapFrontendDispatcher>(context.frontendRouter))
     , m_backendDispatcher(HeapBackendDispatcher::create(context.backendDispatcher, this))
     , m_environment(context.environment)
@@ -138,6 +141,116 @@ void InspectorHeapAgent::stopTracking(ErrorString& errorString)
     m_frontendDispatcher->trackingComplete(timestamp, snapshotData);
 }
 
+Optional<HeapSnapshotNode> InspectorHeapAgent::nodeForHeapObjectIdentifier(ErrorString& errorString, unsigned heapObjectIdentifier)
+{
+    HeapProfiler* heapProfiler = m_environment.vm().heapProfiler();
+    if (!heapProfiler) {
+        errorString = ASCIILiteral("No heap snapshot");
+        return Nullopt;
+    }
+
+    HeapSnapshot* snapshot = heapProfiler->mostRecentSnapshot();
+    if (!snapshot) {
+        errorString = ASCIILiteral("No heap snapshot");
+        return Nullopt;
+    }
+
+    const Optional<HeapSnapshotNode> optionalNode = snapshot->nodeForObjectIdentifier(heapObjectIdentifier);
+    if (!optionalNode) {
+        errorString = ASCIILiteral("No object for identifier, it may have been collected");
+        return Nullopt;
+    }
+
+    return optionalNode;
+}
+
+void InspectorHeapAgent::getPreview(ErrorString& errorString, int heapObjectId, Inspector::Protocol::OptOutput<String>* resultString, RefPtr<Inspector::Protocol::Debugger::FunctionDetails>& functionDetails, RefPtr<Inspector::Protocol::Runtime::ObjectPreview>& objectPreview)
+{
+    // Prevent the cell from getting collected as we look it up.
+    VM& vm = m_environment.vm();
+    JSLockHolder lock(vm);
+    DeferGC deferGC(vm.heap);
+
+    unsigned heapObjectIdentifier = static_cast<unsigned>(heapObjectId);
+    const Optional<HeapSnapshotNode> optionalNode = nodeForHeapObjectIdentifier(errorString, heapObjectIdentifier);
+    if (!optionalNode)
+        return;
+
+    // String preview.
+    JSCell* cell = optionalNode->cell;
+    if (cell->isString()) {
+        *resultString = cell->getString(nullptr);
+        return;
+    }
+
+    // FIXME: Provide preview information for Internal Objects? CodeBlock, Executable, etc.
+
+    Structure* structure = cell->structure(m_environment.vm());
+    if (!structure) {
+        errorString = ASCIILiteral("Unable to get object details - Structure");
+        return;
+    }
+
+    JSGlobalObject* globalObject = structure->globalObject();
+    if (!globalObject) {
+        errorString = ASCIILiteral("Unable to get object details - GlobalObject");
+        return;
+    }
+
+    InjectedScript injectedScript = m_injectedScriptManager.injectedScriptFor(globalObject->globalExec());
+    if (injectedScript.hasNoValue()) {
+        errorString = ASCIILiteral("Unable to get object details - InjectedScript");
+        return;
+    }
+
+    // Function preview.
+    if (cell->inherits(JSFunction::info())) {
+        Deprecated::ScriptValue functionScriptValue(m_environment.vm(), JSValue(cell));
+        injectedScript.functionDetails(errorString, functionScriptValue, &functionDetails);
+        return;
+    }
+
+    // Object preview.
+    Deprecated::ScriptValue cellScriptValue(m_environment.vm(), JSValue(cell));
+    objectPreview = injectedScript.previewValue(cellScriptValue);
+}
+
+void InspectorHeapAgent::getRemoteObject(ErrorString& errorString, int heapObjectId, const String* optionalObjectGroup, RefPtr<Inspector::Protocol::Runtime::RemoteObject>& result)
+{
+    // Prevent the cell from getting collected as we look it up.
+    VM& vm = m_environment.vm();
+    JSLockHolder lock(vm);
+    DeferGC deferGC(vm.heap);
+
+    unsigned heapObjectIdentifier = static_cast<unsigned>(heapObjectId);
+    const Optional<HeapSnapshotNode> optionalNode = nodeForHeapObjectIdentifier(errorString, heapObjectIdentifier);
+    if (!optionalNode)
+        return;
+
+    JSCell* cell = optionalNode->cell;
+    Structure* structure = cell->structure(m_environment.vm());
+    if (!structure) {
+        errorString = ASCIILiteral("Unable to get object details");
+        return;
+    }
+
+    JSGlobalObject* globalObject = structure->globalObject();
+    if (!globalObject) {
+        errorString = ASCIILiteral("Unable to get object details");
+        return;
+    }
+
+    InjectedScript injectedScript = m_injectedScriptManager.injectedScriptFor(globalObject->globalExec());
+    if (injectedScript.hasNoValue()) {
+        errorString = ASCIILiteral("Unable to get object details - InjectedScript");
+        return;
+    }
+
+    Deprecated::ScriptValue cellScriptValue(m_environment.vm(), JSValue(cell));
+    String objectGroup = optionalObjectGroup ? *optionalObjectGroup : String();
+    result = injectedScript.wrapObject(cellScriptValue, objectGroup, true);
+}
+
 static Inspector::Protocol::Heap::GarbageCollection::Type protocolTypeForHeapOperation(HeapOperation operation)
 {
     switch (operation) {
index 4ba61f1..de6e3ad 100644 (file)
@@ -36,6 +36,7 @@
 
 namespace Inspector {
 
+class InjectedScriptManager;
 typedef String ErrorString;
 
 class JS_EXPORT_PRIVATE InspectorHeapAgent final : public InspectorAgentBase, public HeapBackendDispatcherHandler, public JSC::HeapObserver {
@@ -54,6 +55,8 @@ public:
     void snapshot(ErrorString&, double* timestamp, String* snapshotData) override;
     void startTracking(ErrorString&) override;
     void stopTracking(ErrorString&) override;
+    void getPreview(ErrorString&, int heapObjectId, Inspector::Protocol::OptOutput<String>* resultString, RefPtr<Inspector::Protocol::Debugger::FunctionDetails>& functionDetails, RefPtr<Inspector::Protocol::Runtime::ObjectPreview>& objectPreview) override;
+    void getRemoteObject(ErrorString&, int heapObjectId, const String* optionalObjectGroup, RefPtr<Inspector::Protocol::Runtime::RemoteObject>& result) override;
 
     // HeapObserver
     void willGarbageCollect() override;
@@ -62,6 +65,9 @@ public:
 private:
     void clearHeapSnapshots();
 
+    Optional<JSC::HeapSnapshotNode> nodeForHeapObjectIdentifier(ErrorString&, unsigned heapObjectIdentifier);
+
+    InjectedScriptManager& m_injectedScriptManager;
     std::unique_ptr<HeapFrontendDispatcher> m_frontendDispatcher;
     RefPtr<HeapBackendDispatcher> m_backendDispatcher;
     InspectorEnvironment& m_environment;
index 28c8aa3..19b5206 100644 (file)
         {
             "name": "stopTracking",
             "description": "Stop tracking heap changes. This will produce a `trackingComplete` event."
+        },
+        {
+            "name": "getPreview",
+            "description": "Returns a preview (string, Debugger.FunctionDetails, or Runtime.ObjectPreview) for a Heap.HeapObjectId.",
+            "parameters": [
+                { "name": "heapObjectId", "type": "integer", "description": "Identifier of the heap object within the snapshot." }
+            ],
+            "returns": [
+                { "name": "string", "type": "string", "optional": true, "description": "String value." },
+                { "name": "functionDetails", "$ref": "Debugger.FunctionDetails", "optional": true, "description": "Function details." },
+                { "name": "preview", "$ref": "Runtime.ObjectPreview", "optional": true, "description": "Object preview." }
+            ]
+        },
+        {
+            "name": "getRemoteObject",
+            "description": "Returns the strongly referenced Runtime.RemoteObject for a Heap.HeapObjectId.",
+            "parameters": [
+                { "name": "heapObjectId", "type": "integer", "description": "Identifier of the heap object within the snapshot." },
+                { "name": "objectGroup", "type": "string", "optional": true, "description": "Symbolic group name that can be used to release multiple objects." }
+            ],
+            "returns": [
+                { "name": "result", "$ref": "Runtime.RemoteObject", "description": "Resulting object." }
+            ]
         }
     ],
     "events": [
index 331f9a4..8550272 100755 (executable)
@@ -47,6 +47,7 @@ _ALWAYS_UPPERCASED_ENUM_VALUE_SUBSTRINGS = set(['API', 'CSS', 'DOM', 'HTML', 'JI
 
 # FIXME: This should be converted into a property in JSON.
 _TYPES_NEEDING_RUNTIME_CASTS = set([
+    "Runtime.ObjectPreview",
     "Runtime.RemoteObject",
     "Runtime.PropertyDescriptor",
     "Runtime.InternalPropertyDescriptor",