Title: [218718] trunk
Revision
218718
Author
[email protected]
Date
2017-06-22 14:12:01 -0700 (Thu, 22 Jun 2017)

Log Message

Web Inspector: Pausing with a deep call stack can be very slow, avoid eagerly generating object previews
https://bugs.webkit.org/show_bug.cgi?id=173698

Reviewed by Matt Baker.

Source/_javascript_Core:

When pausing in a deep call stack the majority of the time spent in _javascript_Core
when preparing Inspector pause information is spent generating object previews for
the `thisObject` of each of the call frames. In some cases, this could be more
than 95% of the time generating pause information. In the common case, only one of
these (the top frame) will ever be seen by users. This change avoids eagerly
generating object previews up front and let the frontend request previews if they
are needed.

This introduces the `Runtime.getPreview` protocol command. This can be used to:

    - Get a preview for a RemoteObject that did not have a preview but could.
    - Update a preview for a RemoteObject that had a preview.

This patch only uses it for the first case, but the second is valid and may be
something we want to do in the future.

* inspector/protocol/Runtime.json:
A new command to get an up to date preview for an object.

* inspector/InjectedScript.h:
* inspector/InjectedScript.cpp:
(Inspector::InjectedScript::getPreview):
* inspector/agents/InspectorRuntimeAgent.cpp:
(Inspector::InspectorRuntimeAgent::getPreview):
* inspector/agents/InspectorRuntimeAgent.h:
Plumbing for the new command.

* inspector/InjectedScriptSource.js:
(InjectedScript.prototype.getPreview):
Implementation just uses the existing helper.

(InjectedScript.CallFrameProxy):
Do not generate a preview for the this object as it may not be shown.
Let the frontend request a preview if it wants or needs one.

Source/WebInspectorUI:

Introduce RemoteObject.prototype.updatePreview which can be used to update
the preview of a RemoteObject with a current view for the object. Currently
we only use this to fetch the preview that we did not have for the `thisObject`
in the scope chain sidebar. However this could be used generically to update
a RemoteObject's preview (ObjectPreview) at any time.

* UserInterface/Protocol/RemoteObject.js:
(WebInspector.RemoteObject.prototype.canLoadPreview):
(WebInspector.RemoteObject.prototype.updatePreview):
Allow a RemoteObject to update its preview property. Since this only makes
sense on certain Object values include a helper to know when it is appropriate
to fetch a preview.

* UserInterface/Views/ObjectTreePropertyTreeElement.js:
(WebInspector.ObjectTreePropertyTreeElement.prototype._createTitlePropertyStyle):
(WebInspector.ObjectTreePropertyTreeElement.prototype._loadPreviewLazilyIfNeeded):
If the object being shown in the sidebar does not have a preview but can load a
preview then attempt to load it lazily. This is the case for the `thisObject`
which is injected into an ObjectTree in the scope chain sidebar.

LayoutTests:

* inspector/runtime/getPreview-expected.txt: Added.
* inspector/runtime/getPreview.html: Added.
Test the new protocol command `Runtime.getPreview` as well as the frontend
model method RemoteObject.prototype.updatePreview which uses it with its
own slightly different semantics about when it should be used.

* inspector/debugger/tail-deleted-frames-this-value.html:
This test used `CallFrame.thisObject.preview` so rewrite it to first
load the preview and then check values with it.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (218717 => 218718)


--- trunk/LayoutTests/ChangeLog	2017-06-22 21:06:29 UTC (rev 218717)
+++ trunk/LayoutTests/ChangeLog	2017-06-22 21:12:01 UTC (rev 218718)
@@ -1,3 +1,20 @@
+2017-06-22  Joseph Pecoraro  <[email protected]>
+
+        Web Inspector: Pausing with a deep call stack can be very slow, avoid eagerly generating object previews
+        https://bugs.webkit.org/show_bug.cgi?id=173698
+
+        Reviewed by Matt Baker.
+
+        * inspector/runtime/getPreview-expected.txt: Added.
+        * inspector/runtime/getPreview.html: Added.
+        Test the new protocol command `Runtime.getPreview` as well as the frontend
+        model method RemoteObject.prototype.updatePreview which uses it with its
+        own slightly different semantics about when it should be used.
+
+        * inspector/debugger/tail-deleted-frames-this-value.html:
+        This test used `CallFrame.thisObject.preview` so rewrite it to first
+        load the preview and then check values with it.
+
 2017-06-22  Ryan Haddad  <[email protected]>
 
         Skip fast/forms/file/input-file-write-files-using-open-panel.html on ios-wk2.

Modified: trunk/LayoutTests/inspector/debugger/tail-deleted-frames-this-value.html (218717 => 218718)


--- trunk/LayoutTests/inspector/debugger/tail-deleted-frames-this-value.html	2017-06-22 21:06:29 UTC (rev 218717)
+++ trunk/LayoutTests/inspector/debugger/tail-deleted-frames-this-value.html	2017-06-22 21:12:01 UTC (rev 218718)
@@ -44,23 +44,36 @@
 
         InspectorTest.assert(callFrames.length >= expectedFrames.length);
 
-        for (let i = 0; i < expectedFrames.length; i++) {
-            let callFrame = callFrames[i];
-            let expectedFrame = expectedFrames[i];
-            InspectorTest.log("Expected frame: " + JSON.stringify(expectedFrame));
-            InspectorTest.assert(callFrame.functionName === expectedFrame.functionName);
-            InspectorTest.assert(callFrame.isTailDeleted === expectedFrame.isTailDeleted);
-
-            let thisObject = callFrame.thisObject;
-            let properties = thisObject.preview.propertyPreviews;
-            InspectorTest.assert(properties.length === 1);
-            let prop = properties[0];
-            InspectorTest.expectThat(expectedFrame.thisValue[0] === prop.name, `'this' value should have expected property: ${expectedFrame.thisValue[0]}`);
-            InspectorTest.assert('' + expectedFrame.thisValue[1] === prop.value, `'this' value object should have expected property value: ${expectedFrame.thisValue[1]}`);
-            InspectorTest.pass(`Call Frame ${i} 'this' value is correct.`);
+        // Resolve a thisObject preview on each of the CallFrames.
+        let promises = [];
+        for (let callFrame of callFrames) {
+            InspectorTest.assert(!callFrame.thisObject.preview, "thisObject should not have a preview");
+            if (callFrame.thisObject.canLoadPreview()) {
+                promises.push(new Promise((resolve, reject) => {
+                    callFrame.thisObject.updatePreview(resolve);
+                }));
+            }
         }
 
-        WebInspector.debuggerManager.resume();
+        Promise.all(promises).then(() => {
+            for (let i = 0; i < expectedFrames.length; i++) {
+                let callFrame = callFrames[i];
+                let expectedFrame = expectedFrames[i];
+                InspectorTest.log("Expected frame: " + JSON.stringify(expectedFrame));
+                InspectorTest.assert(callFrame.functionName === expectedFrame.functionName);
+                InspectorTest.assert(callFrame.isTailDeleted === expectedFrame.isTailDeleted);
+
+                let thisObject = callFrame.thisObject;
+                let properties = thisObject.preview.propertyPreviews;
+                InspectorTest.assert(properties.length === 1);
+                let prop = properties[0];
+                InspectorTest.expectThat(expectedFrame.thisValue[0] === prop.name, `'this' value should have expected property: ${expectedFrame.thisValue[0]}`);
+                InspectorTest.assert('' + expectedFrame.thisValue[1] === prop.value, `'this' value object should have expected property value: ${expectedFrame.thisValue[1]}`);
+                InspectorTest.pass(`Call Frame ${i} 'this' value is correct.`);
+            }
+
+            WebInspector.debuggerManager.resume();
+        });
     });
 
     WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.Resumed, function(event) {

Added: trunk/LayoutTests/inspector/runtime/getPreview-expected.txt (0 => 218718)


--- trunk/LayoutTests/inspector/runtime/getPreview-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/inspector/runtime/getPreview-expected.txt	2017-06-22 21:12:01 UTC (rev 218718)
@@ -0,0 +1,51 @@
+Tests for the Runtime.getPreview command and associated RemoteObject.prototype.updatePreview.
+
+
+== Running test suite: Runtime.getPreview
+-- Running test case: RemoteObject.updatePreview.ObjectWithoutPreview
+PASS: RemoteObject should not have a preview.
+PASS: RemoteObject should be able to load a preview.
+PASS: RemoteObject.updatePreview should produce an ObjectPreview.
+PASS: RemoteObject.preview should be the same object as the callback.
+PASS: Preview should have 2 properties.
+
+-- Running test case: RemoteObject.updatePreview.ObjectWithPreview
+PASS: RemoteObject should have a preview.
+PASS: RemoteObject should be able to load a preview.
+PASS: Original preview should have 3 properties.
+PASS: RemoteObject.updatePreview should produce an ObjectPreview.
+PASS: RemoteObject.preview should be the same object as the callback.
+PASS: Preview should now have 4 properties.
+
+-- Running test case: RemoteObject.updatePreview.NonObject
+PASS: Should not be able to load a preview for a boolean RemoteObject.
+PASS: RemoteObject.updatePreview should return null for a boolean RemoteObject.
+PASS: Should not be able to load a preview for a number RemoteObject.
+PASS: RemoteObject.updatePreview should return null for a number RemoteObject.
+PASS: Should not be able to load a preview for a string RemoteObject.
+PASS: RemoteObject.updatePreview should return null for a string RemoteObject.
+PASS: Should not be able to load a preview for a fake object RemoteObject.
+PASS: RemoteObject.updatePreview should return null for a fake object RemoteObject.
+
+-- Running test case: RemoteObject.updatePreview.Symbol
+PASS: Should not be able to load a preview for a symbol RemoteObject.
+PASS: RemoteObject.updatePreview should return null for a symbol RemoteObject.
+
+-- Running test case: RemoteObject.updatePreview.Function
+PASS: Should not be able to load a preview for a function RemoteObject.
+PASS: RemoteObject.updatePreview should return null for a function RemoteObject.
+
+-- Running test case: RemoteObject.updatePreview.Null
+PASS: Should not be able to load a preview for a null RemoteObject.
+PASS: RemoteObject.updatePreview should return null for a null RemoteObject.
+
+-- Running test case: Runtime.getPreview.Symbol
+{"type":"symbol","description":"Symbol(Symbol.iterator)","lossless":true}
+
+-- Running test case: Runtime.getPreview.Function
+{"type":"function","description":"function f() {}","lossless":true}
+
+-- Running test case: Runtime.getPreview.InvalidObjectId
+PASS: Should produce an error.
+Error: Could not find InjectedScript for objectId
+

Added: trunk/LayoutTests/inspector/runtime/getPreview.html (0 => 218718)


--- trunk/LayoutTests/inspector/runtime/getPreview.html	                        (rev 0)
+++ trunk/LayoutTests/inspector/runtime/getPreview.html	2017-06-22 21:12:01 UTC (rev 218718)
@@ -0,0 +1,170 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src=""
+<script>
+function test()
+{
+    let suite = InspectorTest.createAsyncSuite("Runtime.getPreview");
+
+    // ------ RemoteObject.prototype.updatePreview
+
+    suite.addTestCase({
+        name: "RemoteObject.updatePreview.ObjectWithoutPreview",
+        description: "Should be able to update the preview of a RemoteObject without a preview.",
+        test(resolve, reject) {
+            RuntimeAgent.evaluate.invoke({_expression_: `({a:1, b:2})`, objectGroup: "test", generatePreview: false}, (error, remoteObjectPayload) => {
+                InspectorTest.assert(!error, "Should not be a protocol error.");
+                let remoteObject = WebInspector.RemoteObject.fromPayload(remoteObjectPayload);
+                InspectorTest.expectThat(!remoteObject.preview, "RemoteObject should not have a preview.");
+                InspectorTest.expectThat(remoteObject.canLoadPreview(), "RemoteObject should be able to load a preview.");
+                remoteObject.updatePreview((preview) => {
+                    InspectorTest.expectThat(preview instanceof WebInspector.ObjectPreview, "RemoteObject.updatePreview should produce an ObjectPreview.");
+                    InspectorTest.expectEqual(remoteObject.preview, preview, "RemoteObject.preview should be the same object as the callback.");
+                    InspectorTest.expectEqual(remoteObject.preview.propertyPreviews.length, 2, "Preview should have 2 properties.");
+                    resolve();
+                });
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "RemoteObject.updatePreview.ObjectWithPreview",
+        description: "Should be able to update the preview of a RemoteObject with a preview.",
+        test(resolve, reject) {
+            RuntimeAgent.evaluate.invoke({_expression_: `window.alpha = ({a:1, b:2, c:3})`, objectGroup: "test", generatePreview: true}, (error, remoteObjectPayload) => {
+                InspectorTest.assert(!error, "Should not be a protocol error.");
+                let remoteObject = WebInspector.RemoteObject.fromPayload(remoteObjectPayload);
+                let originalPreview = remoteObject.preview;
+                InspectorTest.expectThat(remoteObject.preview, "RemoteObject should have a preview.");
+                InspectorTest.expectThat(remoteObject.canLoadPreview(), "RemoteObject should be able to load a preview.");
+                InspectorTest.expectEqual(originalPreview.propertyPreviews.length, 3, "Original preview should have 3 properties.");
+                InspectorTest.evaluateInPage(`window.alpha.d = 4`, () => {
+                    remoteObject.updatePreview((preview) => {
+                        InspectorTest.expectThat(preview instanceof WebInspector.ObjectPreview, "RemoteObject.updatePreview should produce an ObjectPreview.");
+                        InspectorTest.expectEqual(remoteObject.preview, preview, "RemoteObject.preview should be the same object as the callback.");
+                        InspectorTest.expectEqual(remoteObject.preview.propertyPreviews.length, 4, "Preview should now have 4 properties.");
+                        resolve();
+                    });
+                });
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "RemoteObject.updatePreview.NonObject",
+        description: "Non-object RemoteObjects should not request a preview.",
+        test(resolve, reject) {
+            function testRemoteObject(remoteObject, preferredTypeName) {
+                InspectorTest.expectThat(!remoteObject.canLoadPreview(), `Should not be able to load a preview for a ${preferredTypeName || remoteObject.type} RemoteObject.`);
+                remoteObject.updatePreview((preview) => {
+                    InspectorTest.expectNull(preview, `RemoteObject.updatePreview should return null for a ${preferredTypeName || remoteObject.type} RemoteObject.`);
+                });
+            }
+
+            testRemoteObject(WebInspector.RemoteObject.fromPrimitiveValue(true));
+            testRemoteObject(WebInspector.RemoteObject.fromPrimitiveValue(100));
+            testRemoteObject(WebInspector.RemoteObject.fromPrimitiveValue("str"));
+            testRemoteObject(WebInspector.RemoteObject.createFakeRemoteObject(), "fake object");
+            InspectorBackend.runAfterPendingDispatches(resolve);
+        }
+    });
+
+    suite.addTestCase({
+        name: "RemoteObject.updatePreview.Symbol",
+        description: "Non-object RemoteObjects should not request a preview.",
+        test(resolve, reject) {
+            InspectorTest.evaluateInPage(`Symbol.iterator`, (error, remoteObjectPayload) => {
+                let remoteObject = WebInspector.RemoteObject.fromPayload(remoteObjectPayload);
+                InspectorTest.expectThat(!remoteObject.canLoadPreview(), `Should not be able to load a preview for a ${remoteObject.type} RemoteObject.`);
+                remoteObject.updatePreview((preview) => {
+                    InspectorTest.expectNull(preview, `RemoteObject.updatePreview should return null for a ${remoteObject.type} RemoteObject.`);
+                    resolve();
+                });
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "RemoteObject.updatePreview.Function",
+        description: "Non-object RemoteObjects should not request a preview.",
+        test(resolve, reject) {
+            InspectorTest.evaluateInPage(`function f() {}; f`, (error, remoteObjectPayload) => {
+                let remoteObject = WebInspector.RemoteObject.fromPayload(remoteObjectPayload);
+                InspectorTest.expectThat(!remoteObject.canLoadPreview(), `Should not be able to load a preview for a ${remoteObject.type} RemoteObject.`);
+                remoteObject.updatePreview((preview) => {
+                    InspectorTest.expectNull(preview, `RemoteObject.updatePreview should return null for a ${remoteObject.type} RemoteObject.`);
+                    resolve();
+                });
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "RemoteObject.updatePreview.Null",
+        description: "Non-object RemoteObjects should not request a preview.",
+        test(resolve, reject) {
+            InspectorTest.evaluateInPage(`null`, (error, remoteObjectPayload) => {
+                let remoteObject = WebInspector.RemoteObject.fromPayload(remoteObjectPayload);
+                InspectorTest.expectThat(!remoteObject.canLoadPreview(), `Should not be able to load a preview for a ${remoteObject.subtype} RemoteObject.`);
+                remoteObject.updatePreview((preview) => {
+                    InspectorTest.expectNull(preview, `RemoteObject.updatePreview should return null for a ${remoteObject.subtype} RemoteObject.`);
+                    resolve();
+                });
+            });
+        }
+    });
+
+    // ------ Runtime.getPreview
+
+    suite.addTestCase({
+        name: "Runtime.getPreview.Symbol",
+        description: "Runtime.getPreview should return an empty preview for non-objects.",
+        test(resolve, reject) {
+            InspectorTest.evaluateInPage(`Symbol.iterator`, (error, remoteObjectPayload) => {
+                let remoteObject = WebInspector.RemoteObject.fromPayload(remoteObjectPayload);
+                RuntimeAgent.getPreview(remoteObject.objectId, (error, result) => {
+                    InspectorTest.log(JSON.stringify(result));
+                    resolve();
+                });
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "Runtime.getPreview.Function",
+        description: "Runtime.getPreview should return an empty preview for non-objects.",
+        test(resolve, reject) {
+            InspectorTest.evaluateInPage(`function f() {}; f`, (error, remoteObjectPayload) => {
+                let remoteObject = WebInspector.RemoteObject.fromPayload(remoteObjectPayload);
+                RuntimeAgent.getPreview(remoteObject.objectId, (error, result) => {
+                    InspectorTest.log(JSON.stringify(result));
+                    resolve();
+                });
+            });
+        }
+    });
+
+    // ------
+
+    suite.addTestCase({
+        name: "Runtime.getPreview.InvalidObjectId",
+        description: "Invalid objectId should produce and error.",
+        test(resolve, reject) {
+            const objectId = "DOES_NOT_EXIST";
+            RuntimeAgent.getPreview(objectId, (error) => {
+                InspectorTest.expectThat(error, "Should produce an error.");
+                InspectorTest.log("Error: " + error);
+                resolve();
+            });
+        }
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body _onload_="runTest()">
+<p>Tests for the Runtime.getPreview command and associated RemoteObject.prototype.updatePreview.</p>
+</body>
+</html>

Modified: trunk/Source/_javascript_Core/ChangeLog (218717 => 218718)


--- trunk/Source/_javascript_Core/ChangeLog	2017-06-22 21:06:29 UTC (rev 218717)
+++ trunk/Source/_javascript_Core/ChangeLog	2017-06-22 21:12:01 UTC (rev 218718)
@@ -1,5 +1,47 @@
 2017-06-22  Joseph Pecoraro  <[email protected]>
 
+        Web Inspector: Pausing with a deep call stack can be very slow, avoid eagerly generating object previews
+        https://bugs.webkit.org/show_bug.cgi?id=173698
+
+        Reviewed by Matt Baker.
+
+        When pausing in a deep call stack the majority of the time spent in _javascript_Core
+        when preparing Inspector pause information is spent generating object previews for
+        the `thisObject` of each of the call frames. In some cases, this could be more
+        than 95% of the time generating pause information. In the common case, only one of
+        these (the top frame) will ever be seen by users. This change avoids eagerly
+        generating object previews up front and let the frontend request previews if they
+        are needed.
+
+        This introduces the `Runtime.getPreview` protocol command. This can be used to:
+
+            - Get a preview for a RemoteObject that did not have a preview but could.
+            - Update a preview for a RemoteObject that had a preview.
+
+        This patch only uses it for the first case, but the second is valid and may be
+        something we want to do in the future.
+
+        * inspector/protocol/Runtime.json:
+        A new command to get an up to date preview for an object.
+
+        * inspector/InjectedScript.h:
+        * inspector/InjectedScript.cpp:
+        (Inspector::InjectedScript::getPreview):
+        * inspector/agents/InspectorRuntimeAgent.cpp:
+        (Inspector::InspectorRuntimeAgent::getPreview):
+        * inspector/agents/InspectorRuntimeAgent.h:
+        Plumbing for the new command.
+
+        * inspector/InjectedScriptSource.js:
+        (InjectedScript.prototype.getPreview):
+        Implementation just uses the existing helper.
+
+        (InjectedScript.CallFrameProxy):
+        Do not generate a preview for the this object as it may not be shown.
+        Let the frontend request a preview if it wants or needs one.
+
+2017-06-22  Joseph Pecoraro  <[email protected]>
+
         Web Inspector: Remove stale "rawScopes" concept that was never available in JSC
         https://bugs.webkit.org/show_bug.cgi?id=173686
 

Modified: trunk/Source/_javascript_Core/inspector/InjectedScript.cpp (218717 => 218718)


--- trunk/Source/_javascript_Core/inspector/InjectedScript.cpp	2017-06-22 21:06:29 UTC (rev 218717)
+++ trunk/Source/_javascript_Core/inspector/InjectedScript.cpp	2017-06-22 21:12:01 UTC (rev 218718)
@@ -125,6 +125,22 @@
     *result = BindingTraits<Inspector::Protocol::Debugger::FunctionDetails>::runtimeCast(WTFMove(resultValue));
 }
 
+void InjectedScript::getPreview(ErrorString& errorString, const String& objectId, RefPtr<Protocol::Runtime::ObjectPreview>* result)
+{
+    Deprecated::ScriptFunctionCall function(injectedScriptObject(), ASCIILiteral("getPreview"), inspectorEnvironment()->functionCallHandler());
+    function.appendArgument(objectId);
+
+    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::Runtime::ObjectPreview>::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());

Modified: trunk/Source/_javascript_Core/inspector/InjectedScript.h (218717 => 218718)


--- trunk/Source/_javascript_Core/inspector/InjectedScript.h	2017-06-22 21:06:29 UTC (rev 218717)
+++ trunk/Source/_javascript_Core/inspector/InjectedScript.h	2017-06-22 21:12:01 UTC (rev 218718)
@@ -56,6 +56,7 @@
     void evaluateOnCallFrame(ErrorString&, JSC::JSValue 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&, JSC::JSValue, RefPtr<Protocol::Debugger::FunctionDetails>* result);
+    void getPreview(ErrorString&, const String& objectId, RefPtr<Protocol::Runtime::ObjectPreview>* 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);

Modified: trunk/Source/_javascript_Core/inspector/InjectedScriptSource.js (218717 => 218718)


--- trunk/Source/_javascript_Core/inspector/InjectedScriptSource.js	2017-06-22 21:06:29 UTC (rev 218717)
+++ trunk/Source/_javascript_Core/inspector/InjectedScriptSource.js	2017-06-22 21:12:01 UTC (rev 218718)
@@ -234,6 +234,14 @@
         return result;
     },
 
+    getPreview: function(objectId)
+    {
+        let parsedObjectId = this._parseObjectId(objectId);
+        let object = this._objectForId(parsedObjectId);
+
+        return InjectedScript.RemoteObject.createObjectPreviewForValue(object, true);
+    },
+
     _getProperties: function(objectId, collectionMode, generatePreview, nativeGettersAsValues)
     {
         var parsedObjectId = this._parseObjectId(objectId);
@@ -464,7 +472,7 @@
                 commandLineAPI = new BasicCommandLineAPI(isEvalOnCallFrame ? object : null);
         }
 
-        var result = evalFunction.call(object, _expression_, commandLineAPI);        
+        var result = evalFunction.call(object, _expression_, commandLineAPI);
         if (saveResult)
             this._saveResult(result);
         return result;
@@ -1298,7 +1306,7 @@
     this.functionName = callFrame.functionName;
     this.location = {scriptId: String(callFrame.sourceID), lineNumber: callFrame.line, columnNumber: callFrame.column};
     this.scopeChain = this._wrapScopeChain(callFrame);
-    this.this = injectedScript._wrapObject(callFrame.thisObject, "backtrace", false, true);
+    this.this = injectedScript._wrapObject(callFrame.thisObject, "backtrace");
     this.isTailDeleted = callFrame.isTailDeleted;
 }
 

Modified: trunk/Source/_javascript_Core/inspector/agents/InspectorRuntimeAgent.cpp (218717 => 218718)


--- trunk/Source/_javascript_Core/inspector/agents/InspectorRuntimeAgent.cpp	2017-06-22 21:06:29 UTC (rev 218717)
+++ trunk/Source/_javascript_Core/inspector/agents/InspectorRuntimeAgent.cpp	2017-06-22 21:12:01 UTC (rev 218718)
@@ -158,6 +158,23 @@
     }
 }
 
+void InspectorRuntimeAgent::getPreview(ErrorString& errorString, const String& objectId, RefPtr<Inspector::Protocol::Runtime::ObjectPreview>& preview)
+{
+    InjectedScript injectedScript = m_injectedScriptManager.injectedScriptForObjectId(objectId);
+    if (injectedScript.hasNoValue()) {
+        errorString = ASCIILiteral("Could not find InjectedScript for objectId");
+        return;
+    }
+
+    ScriptDebugServer::PauseOnExceptionsState previousPauseOnExceptionsState = setPauseOnExceptionsState(m_scriptDebugServer, ScriptDebugServer::DontPauseOnExceptions);
+    muteConsole();
+
+    injectedScript.getPreview(errorString, objectId, &preview);
+
+    unmuteConsole();
+    setPauseOnExceptionsState(m_scriptDebugServer, previousPauseOnExceptionsState);
+}
+
 void InspectorRuntimeAgent::getProperties(ErrorString& errorString, const String& objectId, const bool* const ownProperties, const bool* const generatePreview, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::Runtime::PropertyDescriptor>>& result, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::Runtime::InternalPropertyDescriptor>>& internalProperties)
 {
     InjectedScript injectedScript = m_injectedScriptManager.injectedScriptForObjectId(objectId);

Modified: trunk/Source/_javascript_Core/inspector/agents/InspectorRuntimeAgent.h (218717 => 218718)


--- trunk/Source/_javascript_Core/inspector/agents/InspectorRuntimeAgent.h	2017-06-22 21:06:29 UTC (rev 218717)
+++ trunk/Source/_javascript_Core/inspector/agents/InspectorRuntimeAgent.h	2017-06-22 21:12:01 UTC (rev 218718)
@@ -62,6 +62,7 @@
     void evaluate(ErrorString&, const String& _expression_, const String* const objectGroup, const bool* const includeCommandLineAPI, const bool* const doNotPauseOnExceptionsAndMuteConsole, const int* const executionContextId, const bool* const returnByValue, const bool* const generatePreview, const bool* const saveResult, RefPtr<Inspector::Protocol::Runtime::RemoteObject>& result, Inspector::Protocol::OptOutput<bool>* wasThrown, Inspector::Protocol::OptOutput<int>* savedResultIndex) final;
     void callFunctionOn(ErrorString&, const String& objectId, const String& _expression_, const Inspector::InspectorArray* optionalArguments, const bool* const doNotPauseOnExceptionsAndMuteConsole, const bool* const returnByValue, const bool* const generatePreview, RefPtr<Inspector::Protocol::Runtime::RemoteObject>& result, Inspector::Protocol::OptOutput<bool>* wasThrown) final;
     void releaseObject(ErrorString&, const ErrorString& objectId) final;
+    void getPreview(ErrorString&, const String& objectId, RefPtr<Inspector::Protocol::Runtime::ObjectPreview>&) final;
     void getProperties(ErrorString&, const String& objectId, const bool* const ownProperties, const bool* const generatePreview, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::Runtime::PropertyDescriptor>>& result, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::Runtime::InternalPropertyDescriptor>>& internalProperties) final;
     void getDisplayableProperties(ErrorString&, const String& objectId, const bool* const generatePreview, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::Runtime::PropertyDescriptor>>& result, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::Runtime::InternalPropertyDescriptor>>& internalProperties) final;
     void getCollectionEntries(ErrorString&, const String& objectId, const String* const objectGroup, const int* const startIndex, const int* const numberToFetch, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::Runtime::CollectionEntry>>& entries) final;

Modified: trunk/Source/_javascript_Core/inspector/protocol/Runtime.json (218717 => 218718)


--- trunk/Source/_javascript_Core/inspector/protocol/Runtime.json	2017-06-22 21:06:29 UTC (rev 218717)
+++ trunk/Source/_javascript_Core/inspector/protocol/Runtime.json	2017-06-22 21:12:01 UTC (rev 218718)
@@ -243,6 +243,16 @@
             "description": "Calls function with given declaration on the given object. Object group of the result is inherited from the target object."
         },
         {
+            "name": "getPreview",
+            "description": "Returns a preview for the given object.",
+            "parameters": [
+                { "name": "objectId", "$ref": "RemoteObjectId", "description": "Identifier of the object to return a preview for." }
+            ],
+            "returns": [
+                { "name": "preview", "$ref": "ObjectPreview" }
+            ]
+        },
+        {
             "name": "getProperties",
             "parameters": [
                 { "name": "objectId", "$ref": "RemoteObjectId", "description": "Identifier of the object to return properties for." },

Modified: trunk/Source/WebInspectorUI/ChangeLog (218717 => 218718)


--- trunk/Source/WebInspectorUI/ChangeLog	2017-06-22 21:06:29 UTC (rev 218717)
+++ trunk/Source/WebInspectorUI/ChangeLog	2017-06-22 21:12:01 UTC (rev 218718)
@@ -1,3 +1,30 @@
+2017-06-22  Joseph Pecoraro  <[email protected]>
+
+        Web Inspector: Pausing with a deep call stack can be very slow, avoid eagerly generating object previews
+        https://bugs.webkit.org/show_bug.cgi?id=173698
+
+        Reviewed by Matt Baker.
+
+        Introduce RemoteObject.prototype.updatePreview which can be used to update
+        the preview of a RemoteObject with a current view for the object. Currently
+        we only use this to fetch the preview that we did not have for the `thisObject`
+        in the scope chain sidebar. However this could be used generically to update
+        a RemoteObject's preview (ObjectPreview) at any time.
+
+        * UserInterface/Protocol/RemoteObject.js:
+        (WebInspector.RemoteObject.prototype.canLoadPreview):
+        (WebInspector.RemoteObject.prototype.updatePreview):
+        Allow a RemoteObject to update its preview property. Since this only makes
+        sense on certain Object values include a helper to know when it is appropriate
+        to fetch a preview.
+
+        * UserInterface/Views/ObjectTreePropertyTreeElement.js:
+        (WebInspector.ObjectTreePropertyTreeElement.prototype._createTitlePropertyStyle):
+        (WebInspector.ObjectTreePropertyTreeElement.prototype._loadPreviewLazilyIfNeeded):
+        If the object being shown in the sidebar does not have a preview but can load a
+        preview then attempt to load it lazily. This is the case for the `thisObject`
+        which is injected into an ObjectTree in the scope chain sidebar.
+
 2017-06-22  Fujii Hironori  <[email protected]>
 
         [GTK] Web Inspector: Add icon for Canvas.svg

Modified: trunk/Source/WebInspectorUI/UserInterface/Protocol/RemoteObject.js (218717 => 218718)


--- trunk/Source/WebInspectorUI/UserInterface/Protocol/RemoteObject.js	2017-06-22 21:06:29 UTC (rev 218717)
+++ trunk/Source/WebInspectorUI/UserInterface/Protocol/RemoteObject.js	2017-06-22 21:12:01 UTC (rev 218718)
@@ -231,6 +231,45 @@
         return "_value" in this;
     }
 
+    canLoadPreview()
+    {
+        if (this._failedToLoadPreview)
+            return false;
+
+        if (this._type !== "object")
+            return false;
+
+        if (!this._objectId || this._isSymbol() || this._isFakeObject())
+            return false;
+
+        return true;
+    }
+
+    updatePreview(callback)
+    {
+        if (!this.canLoadPreview()) {
+            callback(null);
+            return;
+        }
+
+        if (!RuntimeAgent.getPreview) {
+            this._failedToLoadPreview = true;
+            callback(null);
+            return;
+        }
+
+        this._target.RuntimeAgent.getPreview(this._objectId, (error, payload) => {
+            if (error) {
+                this._failedToLoadPreview = true;
+                callback(null);
+                return;
+            }
+
+            this._preview = WebInspector.ObjectPreview.fromPayload(payload);
+            callback(this._preview);
+        });
+    }
+
     getOwnPropertyDescriptors(callback)
     {
         this._getPropertyDescriptors(true, callback);

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/ObjectTreePropertyTreeElement.js (218717 => 218718)


--- trunk/Source/WebInspectorUI/UserInterface/Views/ObjectTreePropertyTreeElement.js	2017-06-22 21:06:29 UTC (rev 218717)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/ObjectTreePropertyTreeElement.js	2017-06-22 21:12:01 UTC (rev 218718)
@@ -160,6 +160,8 @@
                 this._previewView = new WebInspector.ObjectPreviewView(resolvedValue.preview);
                 valueOrGetterElement = this._previewView.element;
             } else {
+                this._loadPreviewLazilyIfNeeded();
+
                 valueOrGetterElement = WebInspector.FormattedValue.createElementForRemoteObject(resolvedValue, this.hadError());
 
                 // Special case a function property string.
@@ -220,6 +222,22 @@
         return container;
     }
 
+    _loadPreviewLazilyIfNeeded()
+    {
+        let resolvedValue = this.resolvedValue();
+        if (!resolvedValue.canLoadPreview())
+            return;
+
+        resolvedValue.updatePreview((preview) => {
+            if (preview) {
+                this.mainTitle = this._titleFragment();
+
+                if (this.expanded)
+                    this._previewView.showTitle();
+            }
+        });
+    }
+
     _alwaysDisplayAsProperty()
     {
         // Constructor, though a function, is often better treated as an expandable object.
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to