Web Inspector: Wrong function name next to scope
authorjoepeck@webkit.org <joepeck@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 29 Jun 2016 23:59:35 +0000 (23:59 +0000)
committerjoepeck@webkit.org <joepeck@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 29 Jun 2016 23:59:35 +0000 (23:59 +0000)
https://bugs.webkit.org/show_bug.cgi?id=158210
<rdar://problem/26543093>

Reviewed by Brian Burg.

Source/JavaScriptCore:

* CMakeLists.txt:
* JavaScriptCore.xcodeproj/project.pbxproj:
Add DebuggerLocation. A helper for describing a unique location.

* bytecode/CodeBlock.cpp:
(JSC::CodeBlock::setConstantRegisters):
When compiled with debug info, add a SymbolTable rare data pointer
back to the CodeBlock. This will be used later to get JSScope debug
info if Web Inspector pauses.

* runtime/SymbolTable.h:
* runtime/SymbolTable.cpp:
(JSC::SymbolTable::cloneScopePart):
(JSC::SymbolTable::prepareForTypeProfiling):
(JSC::SymbolTable::uniqueIDForVariable):
(JSC::SymbolTable::uniqueIDForOffset):
(JSC::SymbolTable::globalTypeSetForOffset):
(JSC::SymbolTable::globalTypeSetForVariable):
Rename rareData and include a CodeBlock pointer.

(JSC::SymbolTable::rareDataCodeBlock):
(JSC::SymbolTable::setRareDataCodeBlock):
Setter and getter for the rare data. It should only be set once.

(JSC::SymbolTable::visitChildren):
Visit the rare data code block if we have one.

* debugger/DebuggerLocation.cpp: Added.
(JSC::DebuggerLocation::DebuggerLocation):
* debugger/DebuggerLocation.h: Added.
(JSC::DebuggerLocation::DebuggerLocation):
Construction from a ScriptExecutable.

* runtime/JSScope.cpp:
(JSC::JSScope::symbolTable):
* runtime/JSScope.h:
* debugger/DebuggerScope.h:
* debugger/DebuggerScope.cpp:
(JSC::DebuggerScope::name):
(JSC::DebuggerScope::location):
Name and location for a scope. This uses:
JSScope -> SymbolTable -> CodeBlock -> Executable

* inspector/protocol/Debugger.json:
* inspector/InjectedScriptSource.js:
(InjectedScript.CallFrameProxy.prototype._wrapScopeChain):
(InjectedScript.CallFrameProxy._createScopeJson):
* inspector/JSJavaScriptCallFrame.cpp:
(Inspector::valueForScopeType):
(Inspector::valueForScopeLocation):
(Inspector::JSJavaScriptCallFrame::scopeDescriptions):
(Inspector::JSJavaScriptCallFrame::scopeType): Deleted.
* inspector/JSJavaScriptCallFrame.h:
* inspector/JSJavaScriptCallFramePrototype.cpp:
(Inspector::JSJavaScriptCallFramePrototype::finishCreation):
(Inspector::jsJavaScriptCallFramePrototypeFunctionScopeDescriptions):
(Inspector::jsJavaScriptCallFramePrototypeFunctionScopeType): Deleted.
Simplify this code to build the objects we will send across the protocol
to descript a Scope.

Source/WebInspectorUI:

* UserInterface/Controllers/DebuggerManager.js:
(WebInspector.DebuggerManager.prototype._scopeChainNodeFromPayload):
Include new payload data in the construction call.
All the new data is optional, so we gracefully handle
legacy backends.

* UserInterface/Models/ScopeChainNode.js:
(WebInspector.ScopeChainNode):
(WebInspector.ScopeChainNode.prototype.get type):
(WebInspector.ScopeChainNode.prototype.get objects):
(WebInspector.ScopeChainNode.prototype.get name):
(WebInspector.ScopeChainNode.prototype.get location):
(WebInspector.ScopeChainNode.prototype.get hash):
Hash is a rough (name:sourceId:line:column) string for quick comparisons.

(WebInspector.ScopeChainNode.prototype.makeLocalScope):
Make this an action you take on a scope, to avoid having to
do it at construction time, or making it a generic setting.

* UserInterface/Views/ScopeChainDetailsSidebarPanel.js:
(WebInspector.ScopeChainDetailsSidebarPanel.prototype._generateCallFramesSection):
This was wrong before. Move the work to CallFrame
and change it to be correct.

* UserInterface/CallFrame.js:
(WebInspector.CallFrame.prototype.mergedScopeChain):

This transforms the scope chain for a call frame from:

     scope1  scope2  scope3  scope4  scope5  scope6  scope7
      foo     foo     foo     bar     bar      -       -
     Block  Closure Closure Closure Closure   GLE     GBL

To:
     scope1  scope2&3   scope4&5  scope6  scope7
      foo      foo*       bar*      -       -
     Block    Local     Closure    GLE     GBL

Doing a few things:

    - Merge the first two Closure scopes sharing a location.
      These are the "var" and "let" Closure scopes in a function,
      and it is better to present these together in the UI.

    - Mark the first Closure scope within a function (*). When
      this is displayed in the UI, we can provide the name of
      the function: "Closure Scope (name)", and we even have
      location information that we can use to display a goto
      arrow if needed.

    - Make the first Closure scope the Local scope if it
      matches the Call Frame's function name. This lets us
      display the section as "Local Variables".

LayoutTests:

* inspector/debugger/paused-scopes-expected.txt: Added.
* inspector/debugger/paused-scopes.html: Added.
* inspector/debugger/resources/paused-scopes.js: Added.
Test dumps the call frames and scope chains for each call frame
when pausing at different locations in a program. Outputting
the hashes we can see even identically named functions have
different hashes because their location is different.

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

26 files changed:
LayoutTests/ChangeLog
LayoutTests/inspector/debugger/paused-scopes-expected.txt [new file with mode: 0644]
LayoutTests/inspector/debugger/paused-scopes.html [new file with mode: 0644]
LayoutTests/inspector/debugger/resources/paused-scopes.js [new file with mode: 0644]
Source/JavaScriptCore/CMakeLists.txt
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj
Source/JavaScriptCore/bytecode/CodeBlock.cpp
Source/JavaScriptCore/debugger/DebuggerLocation.cpp [new file with mode: 0644]
Source/JavaScriptCore/debugger/DebuggerLocation.h [new file with mode: 0644]
Source/JavaScriptCore/debugger/DebuggerScope.cpp
Source/JavaScriptCore/debugger/DebuggerScope.h
Source/JavaScriptCore/inspector/InjectedScriptSource.js
Source/JavaScriptCore/inspector/JSJavaScriptCallFrame.cpp
Source/JavaScriptCore/inspector/JSJavaScriptCallFrame.h
Source/JavaScriptCore/inspector/JSJavaScriptCallFramePrototype.cpp
Source/JavaScriptCore/inspector/protocol/Debugger.json
Source/JavaScriptCore/runtime/JSScope.cpp
Source/JavaScriptCore/runtime/JSScope.h
Source/JavaScriptCore/runtime/SymbolTable.cpp
Source/JavaScriptCore/runtime/SymbolTable.h
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/UserInterface/Controllers/DebuggerManager.js
Source/WebInspectorUI/UserInterface/Models/CallFrame.js
Source/WebInspectorUI/UserInterface/Models/ScopeChainNode.js
Source/WebInspectorUI/UserInterface/Views/ScopeChainDetailsSidebarPanel.js

index 46dc326..78788c2 100644 (file)
@@ -1,3 +1,19 @@
+2016-06-29  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: Wrong function name next to scope
+        https://bugs.webkit.org/show_bug.cgi?id=158210
+        <rdar://problem/26543093>
+
+        Reviewed by Brian Burg.
+
+        * inspector/debugger/paused-scopes-expected.txt: Added.
+        * inspector/debugger/paused-scopes.html: Added.
+        * inspector/debugger/resources/paused-scopes.js: Added.
+        Test dumps the call frames and scope chains for each call frame
+        when pausing at different locations in a program. Outputting
+        the hashes we can see even identically named functions have
+        different hashes because their location is different.
+
 2016-06-29  Ryan Haddad  <ryanhaddad@apple.com>
 
         Marking perf/rel-list-remove.html as flaky on ios-simulator
diff --git a/LayoutTests/inspector/debugger/paused-scopes-expected.txt b/LayoutTests/inspector/debugger/paused-scopes-expected.txt
new file mode 100644 (file)
index 0000000..8221a3f
--- /dev/null
@@ -0,0 +1,205 @@
+Check scope chains for different call frames at different pauses.
+
+
+== Running test suite: PausedCallFrameScope
+-- Running test case: TriggerFirstPause
+CALLFRAME: firstPause
+
+---- Scope Chain ----
+  SCOPE: Name (firstPause) - Type (Block) - Hash (firstPause:<sourceID>:21:29)
+    - barLexicalVariable2
+  SCOPE: Name (firstPause) - Type (Closure) - Hash (firstPause:<sourceID>:21:29)
+    - barLexicalVariable2
+  SCOPE: Name (firstPause) - Type (Closure) - Hash (firstPause:<sourceID>:21:29)
+    - barVarVariable1
+  SCOPE: Name (firstPause) - Type (FunctionName) - Hash (firstPause:<sourceID>:21:29)
+    - firstPause
+  SCOPE: Name (firstPause) - Type (Closure) - Hash (firstPause:<sourceID>:19:24)
+    - fakeFirstPauseLexicalVariable
+  SCOPE: Name (firstPause) - Type (Closure) - Hash (firstPause:<sourceID>:19:24)
+  SCOPE: Name (entry) - Type (Closure) - Hash (entry:<sourceID>:14:15)
+    - fooLexicalVariable2
+  SCOPE: Name (entry) - Type (Closure) - Hash (entry:<sourceID>:14:15)
+    - firstPause
+    - fooVarVariable1
+  SCOPE: Name () - Type (GlobalLexicalEnvironment) - Hash ()
+    - globalLet2
+  SCOPE: Name () - Type (Global) - Hash ()
+
+---- Merged Scope Chain ----
+  SCOPE: Name (firstPause) - Type (Block) - Hash (firstPause:<sourceID>:21:29)
+    - barLexicalVariable2
+  SCOPE: Name (firstPause) - Type (Local) - Hash (firstPause:<sourceID>:21:29)
+    - barVarVariable1
+    - barLexicalVariable2
+  SCOPE: Name (firstPause) - Type (FunctionName) - Hash (firstPause:<sourceID>:21:29)
+    - firstPause
+  SCOPE: Name (firstPause) - Type (Closure) - Hash (firstPause:<sourceID>:19:24)
+    - fakeFirstPauseLexicalVariable
+  SCOPE: Name (entry) - Type (Closure) - Hash (entry:<sourceID>:14:15)
+    - firstPause
+    - fooVarVariable1
+    - fooLexicalVariable2
+  SCOPE: Name () - Type (GlobalLexicalEnvironment) - Hash ()
+    - globalLet2
+  SCOPE: Name () - Type (Global) - Hash ()
+
+CALLFRAME: firstPause
+
+---- Scope Chain ----
+  SCOPE: Name (firstPause) - Type (Closure) - Hash (firstPause:<sourceID>:19:24)
+    - fakeFirstPauseLexicalVariable
+  SCOPE: Name (firstPause) - Type (Closure) - Hash (firstPause:<sourceID>:19:24)
+  SCOPE: Name (entry) - Type (Closure) - Hash (entry:<sourceID>:14:15)
+    - fooLexicalVariable2
+  SCOPE: Name (entry) - Type (Closure) - Hash (entry:<sourceID>:14:15)
+    - firstPause
+    - fooVarVariable1
+  SCOPE: Name () - Type (GlobalLexicalEnvironment) - Hash ()
+    - globalLet2
+  SCOPE: Name () - Type (Global) - Hash ()
+
+---- Merged Scope Chain ----
+  SCOPE: Name (firstPause) - Type (Local) - Hash (firstPause:<sourceID>:19:24)
+    - fakeFirstPauseLexicalVariable
+  SCOPE: Name (entry) - Type (Closure) - Hash (entry:<sourceID>:14:15)
+    - firstPause
+    - fooVarVariable1
+    - fooLexicalVariable2
+  SCOPE: Name () - Type (GlobalLexicalEnvironment) - Hash ()
+    - globalLet2
+  SCOPE: Name () - Type (Global) - Hash ()
+
+CALLFRAME: entry
+
+---- Scope Chain ----
+  SCOPE: Name (entry) - Type (Closure) - Hash (entry:<sourceID>:14:15)
+    - fooLexicalVariable2
+  SCOPE: Name (entry) - Type (Closure) - Hash (entry:<sourceID>:14:15)
+    - firstPause
+    - fooVarVariable1
+  SCOPE: Name () - Type (GlobalLexicalEnvironment) - Hash ()
+    - globalLet2
+  SCOPE: Name () - Type (Global) - Hash ()
+
+---- Merged Scope Chain ----
+  SCOPE: Name (entry) - Type (Local) - Hash (entry:<sourceID>:14:15)
+    - firstPause
+    - fooVarVariable1
+    - fooLexicalVariable2
+  SCOPE: Name () - Type (GlobalLexicalEnvironment) - Hash ()
+    - globalLet2
+  SCOPE: Name () - Type (Global) - Hash ()
+
+
+-- Running test case: TriggerSecondPause
+CALLFRAME: secondPause
+
+---- Scope Chain ----
+  SCOPE: Name (secondPause) - Type (Block) - Hash (secondPause:<sourceID>:6:21)
+    - blockLexicalVariable
+  SCOPE: Name (secondPause) - Type (Closure) - Hash (secondPause:<sourceID>:6:21)
+    - shoeLexicalVariable1
+  SCOPE: Name (secondPause) - Type (Closure) - Hash (secondPause:<sourceID>:6:21)
+  SCOPE: Name () - Type (GlobalLexicalEnvironment) - Hash ()
+    - globalLet2
+  SCOPE: Name () - Type (Global) - Hash ()
+
+---- Merged Scope Chain ----
+  SCOPE: Name (secondPause) - Type (Block) - Hash (secondPause:<sourceID>:6:21)
+    - blockLexicalVariable
+  SCOPE: Name (secondPause) - Type (Local) - Hash (secondPause:<sourceID>:6:21)
+    - shoeLexicalVariable1
+  SCOPE: Name () - Type (GlobalLexicalEnvironment) - Hash ()
+    - globalLet2
+  SCOPE: Name () - Type (Global) - Hash ()
+
+CALLFRAME: firstPause
+
+---- Scope Chain ----
+  SCOPE: Name (firstPause) - Type (Block) - Hash (firstPause:<sourceID>:21:29)
+    - barLexicalVariable2
+  SCOPE: Name (firstPause) - Type (Closure) - Hash (firstPause:<sourceID>:21:29)
+    - barLexicalVariable2
+  SCOPE: Name (firstPause) - Type (Closure) - Hash (firstPause:<sourceID>:21:29)
+    - barVarVariable1
+  SCOPE: Name (firstPause) - Type (FunctionName) - Hash (firstPause:<sourceID>:21:29)
+    - firstPause
+  SCOPE: Name (firstPause) - Type (Closure) - Hash (firstPause:<sourceID>:19:24)
+    - fakeFirstPauseLexicalVariable
+  SCOPE: Name (firstPause) - Type (Closure) - Hash (firstPause:<sourceID>:19:24)
+  SCOPE: Name (entry) - Type (Closure) - Hash (entry:<sourceID>:14:15)
+    - fooLexicalVariable2
+  SCOPE: Name (entry) - Type (Closure) - Hash (entry:<sourceID>:14:15)
+    - firstPause
+    - fooVarVariable1
+  SCOPE: Name () - Type (GlobalLexicalEnvironment) - Hash ()
+    - globalLet2
+  SCOPE: Name () - Type (Global) - Hash ()
+
+---- Merged Scope Chain ----
+  SCOPE: Name (firstPause) - Type (Block) - Hash (firstPause:<sourceID>:21:29)
+    - barLexicalVariable2
+  SCOPE: Name (firstPause) - Type (Local) - Hash (firstPause:<sourceID>:21:29)
+    - barVarVariable1
+    - barLexicalVariable2
+  SCOPE: Name (firstPause) - Type (FunctionName) - Hash (firstPause:<sourceID>:21:29)
+    - firstPause
+  SCOPE: Name (firstPause) - Type (Closure) - Hash (firstPause:<sourceID>:19:24)
+    - fakeFirstPauseLexicalVariable
+  SCOPE: Name (entry) - Type (Closure) - Hash (entry:<sourceID>:14:15)
+    - firstPause
+    - fooVarVariable1
+    - fooLexicalVariable2
+  SCOPE: Name () - Type (GlobalLexicalEnvironment) - Hash ()
+    - globalLet2
+  SCOPE: Name () - Type (Global) - Hash ()
+
+CALLFRAME: firstPause
+
+---- Scope Chain ----
+  SCOPE: Name (firstPause) - Type (Closure) - Hash (firstPause:<sourceID>:19:24)
+    - fakeFirstPauseLexicalVariable
+  SCOPE: Name (firstPause) - Type (Closure) - Hash (firstPause:<sourceID>:19:24)
+  SCOPE: Name (entry) - Type (Closure) - Hash (entry:<sourceID>:14:15)
+    - fooLexicalVariable2
+  SCOPE: Name (entry) - Type (Closure) - Hash (entry:<sourceID>:14:15)
+    - firstPause
+    - fooVarVariable1
+  SCOPE: Name () - Type (GlobalLexicalEnvironment) - Hash ()
+    - globalLet2
+  SCOPE: Name () - Type (Global) - Hash ()
+
+---- Merged Scope Chain ----
+  SCOPE: Name (firstPause) - Type (Local) - Hash (firstPause:<sourceID>:19:24)
+    - fakeFirstPauseLexicalVariable
+  SCOPE: Name (entry) - Type (Closure) - Hash (entry:<sourceID>:14:15)
+    - firstPause
+    - fooVarVariable1
+    - fooLexicalVariable2
+  SCOPE: Name () - Type (GlobalLexicalEnvironment) - Hash ()
+    - globalLet2
+  SCOPE: Name () - Type (Global) - Hash ()
+
+CALLFRAME: entry
+
+---- Scope Chain ----
+  SCOPE: Name (entry) - Type (Closure) - Hash (entry:<sourceID>:14:15)
+    - fooLexicalVariable2
+  SCOPE: Name (entry) - Type (Closure) - Hash (entry:<sourceID>:14:15)
+    - firstPause
+    - fooVarVariable1
+  SCOPE: Name () - Type (GlobalLexicalEnvironment) - Hash ()
+    - globalLet2
+  SCOPE: Name () - Type (Global) - Hash ()
+
+---- Merged Scope Chain ----
+  SCOPE: Name (entry) - Type (Local) - Hash (entry:<sourceID>:14:15)
+    - firstPause
+    - fooVarVariable1
+    - fooLexicalVariable2
+  SCOPE: Name () - Type (GlobalLexicalEnvironment) - Hash ()
+    - globalLet2
+  SCOPE: Name () - Type (Global) - Hash ()
+
+
diff --git a/LayoutTests/inspector/debugger/paused-scopes.html b/LayoutTests/inspector/debugger/paused-scopes.html
new file mode 100644 (file)
index 0000000..c5cef76
--- /dev/null
@@ -0,0 +1,118 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
+<script src="resources/paused-scopes.js"></script>
+<script>
+
+function test()
+{
+    function scopeTypeName(type) {
+        switch (type) {
+            case WebInspector.ScopeChainNode.Type.Local: return "Local";
+            case WebInspector.ScopeChainNode.Type.Global: return "Global";
+            case WebInspector.ScopeChainNode.Type.GlobalLexicalEnvironment: return "GlobalLexicalEnvironment";
+            case WebInspector.ScopeChainNode.Type.With: return "With";
+            case WebInspector.ScopeChainNode.Type.Closure: return "Closure";
+            case WebInspector.ScopeChainNode.Type.Catch: return "Catch";
+            case WebInspector.ScopeChainNode.Type.FunctionName: return "FunctionName";
+            case WebInspector.ScopeChainNode.Type.Block: return "Block";
+            default: return "Unknown!";
+        };        
+    }
+
+    function sanitizeHash(hash) {
+        return hash.replace(/:(.*?):/, ":<sourceID>:");
+    }
+
+    function collectScopeChainProperties(scopeChain) {
+        let scopeChainData = [];
+
+        let promises = [];
+        for (let scope of scopeChain) {
+            let data = {scope, properties: []};
+            scopeChainData.push(data);
+            if (scope.type === WebInspector.ScopeChainNode.Type.Global)
+                continue;
+
+            for (let scopeObject of scope.objects) {
+                promises.push(new Promise((resolve, reject) => {
+                    scopeObject.getAllPropertyDescriptors((propertyDescriptors) => {
+                        data.properties = data.properties.concat(propertyDescriptors);
+                        resolve();
+                    });
+                }));
+            }
+        }
+
+        return Promise.all(promises)
+            .then(() => scopeChainData);
+    }
+
+    function dumpScopeChainData(scopeChainData) {
+        for (let {scope, properties} of scopeChainData) {
+            InspectorTest.log(`  SCOPE: Name (${scope.name}) - Type (${scopeTypeName(scope.type)}) - Hash (${sanitizeHash(scope.hash)})`);
+            for (let propertyDescriptor of properties)
+                InspectorTest.log(`    - ${propertyDescriptor.name}`);            
+        }
+    }
+
+    function dumpCallFrame(callFrame) {
+        return Promise.all([
+            collectScopeChainProperties(callFrame.scopeChain),
+            collectScopeChainProperties(callFrame.mergedScopeChain()),
+        ]).then((results) => {
+            let [scopeChainData, mergedScopeChainData] = results;
+            InspectorTest.log(`CALLFRAME: ${callFrame.functionName}`);
+            InspectorTest.log("\n---- Scope Chain ----");
+            dumpScopeChainData(scopeChainData);
+            InspectorTest.log("\n---- Merged Scope Chain ----");
+            dumpScopeChainData(mergedScopeChainData);
+            InspectorTest.log("");
+        });
+    }
+
+    function dumpCallFrames() {
+        let callFrames = WebInspector.debuggerManager.callFrames;
+        let chain = Promise.resolve();
+        for (let callFrame of callFrames)
+            chain = chain.then(() => dumpCallFrame(callFrame));
+        return chain;
+    }
+
+
+    let suite = InspectorTest.createAsyncSuite("PausedCallFrameScope");
+
+    suite.addTestCase({
+        name: "TriggerFirstPause",
+        description: "Verify CallFrames and Scopes with the first pause.",
+        test: (resolve, reject) => {
+            InspectorTest.evaluateInPage("setTimeout(entry)");
+            WebInspector.debuggerManager.singleFireEventListener(WebInspector.DebuggerManager.Event.CallFramesDidChange, (event) => {
+                dumpCallFrames().then(resolve, reject);
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "TriggerSecondPause",
+        description: "Verify CallFrames and Scopes with the first pause.",
+        test: (resolve, reject) => {
+            WebInspector.debuggerManager.resume();
+            WebInspector.debuggerManager.singleFireEventListener(WebInspector.DebuggerManager.Event.CallFramesDidChange, (event) => {
+                dumpCallFrames().then(() => {
+                    WebInspector.debuggerManager.resume();
+                    resolve();
+                }, reject);
+            });
+        }
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+<p>Check scope chains for different call frames at different pauses.</p>
+</body>
+</html>
diff --git a/LayoutTests/inspector/debugger/resources/paused-scopes.js b/LayoutTests/inspector/debugger/resources/paused-scopes.js
new file mode 100644 (file)
index 0000000..e634fda
--- /dev/null
@@ -0,0 +1,31 @@
+"use strict";
+
+var globalVar1; // Global (globalVar1)
+let globalLet2; // GlobalLexicalEnvironment (globalLet2)
+
+function secondPause() { // Global (secondPause)
+    let shoeLexicalVariable1 = document; // ClosureScope (shoeLexicalVariable1)
+    if (true) {
+        let blockLexicalVariable = "block"; // NestedBlockScope (blockLexicalVariable)
+        debugger;
+    }
+}
+
+function entry() { // Global (entry)
+    var fooVarVariable1; // foo ClosureScope (fooVarVariable1)
+    let fooLexicalVariable2; // foo ClosureScope (fooLexicalVariable2)
+    firstPause();
+
+    function firstPause() { // foo ClosureScope (firstPause)
+        let fakeFirstPauseLexicalVariable; // firstPause ClosureScope (fakeFirstPauseLexicalVariable)
+        (function firstPause() {
+            var barVarVariable1 = window.navigator; // firstPause ClosureScope (barVarVariable1)
+            let barLexicalVariable2 = window.navigator; // firstPause ClosureScope (barLexicalVariable2)
+            if (true) {
+                let barLexicalVariable2 = window.navigator; // NestedBlockScope (barLexicalVariable2)
+                debugger;
+                secondPause();
+            }
+        })();
+    }
+}
index 20751fd..cafa064 100644 (file)
@@ -239,6 +239,7 @@ set(JavaScriptCore_SOURCES
 
     debugger/Debugger.cpp
     debugger/DebuggerCallFrame.cpp
+    debugger/DebuggerLocation.cpp
     debugger/DebuggerScope.cpp
 
     dfg/DFGAbstractHeap.cpp
index 579b2bf..a6d6588 100644 (file)
@@ -1,3 +1,71 @@
+2016-06-29  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: Wrong function name next to scope
+        https://bugs.webkit.org/show_bug.cgi?id=158210
+        <rdar://problem/26543093>
+
+        Reviewed by Brian Burg.
+
+        * CMakeLists.txt:
+        * JavaScriptCore.xcodeproj/project.pbxproj:
+        Add DebuggerLocation. A helper for describing a unique location.
+
+        * bytecode/CodeBlock.cpp:
+        (JSC::CodeBlock::setConstantRegisters):
+        When compiled with debug info, add a SymbolTable rare data pointer
+        back to the CodeBlock. This will be used later to get JSScope debug
+        info if Web Inspector pauses.
+
+        * runtime/SymbolTable.h:
+        * runtime/SymbolTable.cpp:
+        (JSC::SymbolTable::cloneScopePart):
+        (JSC::SymbolTable::prepareForTypeProfiling):
+        (JSC::SymbolTable::uniqueIDForVariable):
+        (JSC::SymbolTable::uniqueIDForOffset):
+        (JSC::SymbolTable::globalTypeSetForOffset):
+        (JSC::SymbolTable::globalTypeSetForVariable):
+        Rename rareData and include a CodeBlock pointer.
+
+        (JSC::SymbolTable::rareDataCodeBlock):
+        (JSC::SymbolTable::setRareDataCodeBlock):
+        Setter and getter for the rare data. It should only be set once.
+
+        (JSC::SymbolTable::visitChildren):
+        Visit the rare data code block if we have one.
+
+        * debugger/DebuggerLocation.cpp: Added.
+        (JSC::DebuggerLocation::DebuggerLocation):
+        * debugger/DebuggerLocation.h: Added.
+        (JSC::DebuggerLocation::DebuggerLocation):
+        Construction from a ScriptExecutable.
+
+        * runtime/JSScope.cpp:
+        (JSC::JSScope::symbolTable):
+        * runtime/JSScope.h:
+        * debugger/DebuggerScope.h:
+        * debugger/DebuggerScope.cpp:
+        (JSC::DebuggerScope::name):
+        (JSC::DebuggerScope::location):
+        Name and location for a scope. This uses:
+        JSScope -> SymbolTable -> CodeBlock -> Executable
+
+        * inspector/protocol/Debugger.json:
+        * inspector/InjectedScriptSource.js:
+        (InjectedScript.CallFrameProxy.prototype._wrapScopeChain):
+        (InjectedScript.CallFrameProxy._createScopeJson):
+        * inspector/JSJavaScriptCallFrame.cpp:
+        (Inspector::valueForScopeType):
+        (Inspector::valueForScopeLocation):
+        (Inspector::JSJavaScriptCallFrame::scopeDescriptions):
+        (Inspector::JSJavaScriptCallFrame::scopeType): Deleted.
+        * inspector/JSJavaScriptCallFrame.h:
+        * inspector/JSJavaScriptCallFramePrototype.cpp:
+        (Inspector::JSJavaScriptCallFramePrototype::finishCreation):
+        (Inspector::jsJavaScriptCallFramePrototypeFunctionScopeDescriptions):
+        (Inspector::jsJavaScriptCallFramePrototypeFunctionScopeType): Deleted.
+        Simplify this code to build the objects we will send across the protocol
+        to descript a Scope.
+
 2016-06-29  Saam barati  <sbarati@apple.com>
 
         We don't emit TDZ checks for call_eval
index f7f9ebe..0085441 100644 (file)
                A5EF9B171A1D440300702E90 /* generate_cpp_frontend_dispatcher_implementation.py in Headers */ = {isa = PBXBuildFile; fileRef = C4F4B6D41A05C76F005CAB76 /* generate_cpp_frontend_dispatcher_implementation.py */; settings = {ATTRIBUTES = (Private, ); }; };
                A5EF9B181A1D440600702E90 /* generate_cpp_protocol_types_header.py in Headers */ = {isa = PBXBuildFile; fileRef = C4F4B6D51A05C76F005CAB76 /* generate_cpp_protocol_types_header.py */; settings = {ATTRIBUTES = (Private, ); }; };
                A5EF9B191A1D440700702E90 /* generate_cpp_protocol_types_implementation.py in Headers */ = {isa = PBXBuildFile; fileRef = C4F4B6D61A05C76F005CAB76 /* generate_cpp_protocol_types_implementation.py */; settings = {ATTRIBUTES = (Private, ); }; };
+               A5FC84B21D1DDAD6006B5C46 /* DebuggerLocation.h in Headers */ = {isa = PBXBuildFile; fileRef = A5FC84B11D1DDAC8006B5C46 /* DebuggerLocation.h */; };
+               A5FC84B31D1DDAD9006B5C46 /* DebuggerLocation.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A5FC84B01D1DDAC8006B5C46 /* DebuggerLocation.cpp */; };
                A5FD0067189AFE9C00633231 /* ScriptArguments.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A5FD0065189AFE9C00633231 /* ScriptArguments.cpp */; };
                A5FD0068189AFE9C00633231 /* ScriptArguments.h in Headers */ = {isa = PBXBuildFile; fileRef = A5FD0066189AFE9C00633231 /* ScriptArguments.h */; settings = {ATTRIBUTES = (Private, ); }; };
                A5FD006D189B00AA00633231 /* ScriptCallFrame.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A5FD0069189B00A900633231 /* ScriptCallFrame.cpp */; };
                A5EA70F619F6DE5A0098F5EC /* generate_objc_internal_header.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = generate_objc_internal_header.py; sourceTree = "<group>"; };
                A5EA70F819F6DE5A0098F5EC /* objc_generator.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = objc_generator.py; sourceTree = "<group>"; };
                A5EA710D19F6DF810098F5EC /* InspectorAlternateBackendDispatchers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InspectorAlternateBackendDispatchers.h; sourceTree = "<group>"; };
+               A5FC84B01D1DDAC8006B5C46 /* DebuggerLocation.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DebuggerLocation.cpp; sourceTree = "<group>"; };
+               A5FC84B11D1DDAC8006B5C46 /* DebuggerLocation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebuggerLocation.h; sourceTree = "<group>"; };
                A5FD0065189AFE9C00633231 /* ScriptArguments.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ScriptArguments.cpp; sourceTree = "<group>"; };
                A5FD0066189AFE9C00633231 /* ScriptArguments.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ScriptArguments.h; sourceTree = "<group>"; };
                A5FD0069189B00A900633231 /* ScriptCallFrame.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ScriptCallFrame.cpp; sourceTree = "<group>"; };
                                149559ED0DDCDDF700648087 /* DebuggerCallFrame.cpp */,
                                1480DB9B0DDC227F003CFDF2 /* DebuggerCallFrame.h */,
                                6AD2CB4C19B9140100065719 /* DebuggerEvalEnabler.h */,
+                               A5FC84B01D1DDAC8006B5C46 /* DebuggerLocation.cpp */,
+                               A5FC84B11D1DDAC8006B5C46 /* DebuggerLocation.h */,
                                FEA0861F182B7A0400F6D851 /* DebuggerPrimitives.h */,
                                0F2D4DDB19832D34007D4B19 /* DebuggerScope.cpp */,
                                0F2D4DDC19832D34007D4B19 /* DebuggerScope.h */,
                                996B731C1BDA08DD00331B84 /* JSDataViewPrototype.lut.h in Headers */,
                                978801411471AD920041B016 /* JSDateMath.h in Headers */,
                                C2A7F688160432D400F76B98 /* JSDestructibleObject.h in Headers */,
+                               A5FC84B21D1DDAD6006B5C46 /* DebuggerLocation.h in Headers */,
                                FE384EE61ADDB7AD0055DE2C /* JSDollarVM.h in Headers */,
                                FE384EE81ADDB7AD0055DE2C /* JSDollarVMPrototype.h in Headers */,
                                BC18C42D0E16F5CD00B34460 /* JSEnvironmentRecord.h in Headers */,
                                A57D23F11891B5B40031C7FA /* ContentSearchUtilities.cpp in Sources */,
                                52B717B51A0597E1007AF4F3 /* ControlFlowProfiler.cpp in Sources */,
                                0FBADF541BD1F4B800E073C1 /* CopiedBlock.cpp in Sources */,
+                               A5FC84B31D1DDAD9006B5C46 /* DebuggerLocation.cpp in Sources */,
                                C240305514B404E60079EB64 /* CopiedSpace.cpp in Sources */,
                                0F6183301C45BF070072450B /* AirLowerMacros.cpp in Sources */,
                                C2239D1716262BDD005AC5FD /* CopyVisitor.cpp in Sources */,
index 6d5b546..b671321 100644 (file)
@@ -2477,7 +2477,12 @@ void CodeBlock::setConstantRegisters(const Vector<WriteBarrier<Unknown>>& consta
                     ConcurrentJITLocker locker(symbolTable->m_lock);
                     symbolTable->prepareForTypeProfiling(locker);
                 }
-                constant = symbolTable->cloneScopePart(*m_vm);
+
+                SymbolTable* clone = symbolTable->cloneScopePart(*m_vm);
+                if (wasCompiledWithDebuggingOpcodes())
+                    clone->setRareDataCodeBlock(this);
+
+                constant = clone;
             }
         }
 
diff --git a/Source/JavaScriptCore/debugger/DebuggerLocation.cpp b/Source/JavaScriptCore/debugger/DebuggerLocation.cpp
new file mode 100644 (file)
index 0000000..028104e
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+
+#include "config.h"
+#include "DebuggerLocation.h"
+
+#include "Executable.h"
+
+namespace JSC {
+
+DebuggerLocation::DebuggerLocation(ScriptExecutable* executable)
+{
+    if (executable->isHostFunction())
+        return;
+
+    sourceID = executable->sourceID();
+    line = executable->firstLine();
+    column = executable->startColumn();
+    url = executable->sourceURL();
+    if (url.isEmpty())
+        url = executable->source().provider()->sourceURL();
+}
+
+} // namespace JSC
diff --git a/Source/JavaScriptCore/debugger/DebuggerLocation.h b/Source/JavaScriptCore/debugger/DebuggerLocation.h
new file mode 100644 (file)
index 0000000..3e1c416
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+
+#pragma once
+
+#include "DebuggerPrimitives.h"
+#include <wtf/text/WTFString.h>
+
+namespace JSC {
+
+class ScriptExecutable;
+
+struct DebuggerLocation {
+
+    DebuggerLocation() { }
+    DebuggerLocation(const String& url, intptr_t sourceID, unsigned line, unsigned column)
+        : url(url)
+        , sourceID(sourceID)
+        , line(line)
+        , column(column)
+    { }
+
+    DebuggerLocation(ScriptExecutable*);
+
+    String url;
+    intptr_t sourceID { noSourceID };
+    unsigned line { 0 };
+    unsigned column { 0 };
+};
+
+} // namespace JSC
index 01d4322..3106fbb 100644 (file)
@@ -210,6 +210,33 @@ bool DebuggerScope::isNestedLexicalScope() const
     return m_scope->isNestedLexicalScope();
 }
 
+String DebuggerScope::name() const
+{
+    SymbolTable* symbolTable = m_scope->symbolTable();
+    if (!symbolTable)
+        return String();
+
+    CodeBlock* codeBlock = symbolTable->rareDataCodeBlock();
+    if (!codeBlock)
+        return String();
+
+    return String::fromUTF8(codeBlock->inferredName());
+}
+
+DebuggerLocation DebuggerScope::location() const
+{
+    SymbolTable* symbolTable = m_scope->symbolTable();
+    if (!symbolTable)
+        return DebuggerLocation();
+
+    CodeBlock* codeBlock = symbolTable->rareDataCodeBlock();
+    if (!codeBlock)
+        return DebuggerLocation();
+
+    ScriptExecutable* executable = codeBlock->ownerScriptExecutable();
+    return DebuggerLocation(executable);
+}
+
 JSValue DebuggerScope::caughtValue(ExecState* exec) const
 {
     ASSERT(isCatchScope());
index e1e8d02..e28739e 100644 (file)
@@ -26,6 +26,7 @@
 #ifndef DebuggerScope_h
 #define DebuggerScope_h
 
+#include "DebuggerLocation.h"
 #include "JSObject.h"
 
 namespace JSC {
@@ -88,6 +89,9 @@ public:
     bool isGlobalLexicalEnvironment() const;
     bool isNestedLexicalScope() const;
 
+    String name() const;
+    DebuggerLocation location() const;
+
     JSValue caughtValue(ExecState*) const;
 
 private:
index b0eeb73..1f499d1 100644 (file)
@@ -1306,9 +1306,11 @@ InjectedScript.CallFrameProxy.prototype = {
     _wrapScopeChain: function(callFrame)
     {
         var scopeChain = callFrame.scopeChain;
+        var scopeDescriptions = callFrame.scopeDescriptions();
+
         var scopeChainProxy = [];
         for (var i = 0; i < scopeChain.length; i++)
-            scopeChainProxy[i] = InjectedScript.CallFrameProxy._createScopeJson(callFrame.scopeType(i), scopeChain[i], "backtrace");
+            scopeChainProxy[i] = InjectedScript.CallFrameProxy._createScopeJson(scopeChain[i], scopeDescriptions[i], "backtrace");
         return scopeChainProxy;
     }
 }
@@ -1321,14 +1323,21 @@ InjectedScript.CallFrameProxy._scopeTypeNames = {
     4: "functionName", // FUNCTION_NAME_SCOPE
     5: "globalLexicalEnvironment", // GLOBAL_LEXICAL_ENVIRONMENT_SCOPE
     6: "nestedLexical", // NESTED_LEXICAL_SCOPE
-}
+};
 
-InjectedScript.CallFrameProxy._createScopeJson = function(scopeTypeCode, scopeObject, groupId)
+InjectedScript.CallFrameProxy._createScopeJson = function(object, {name, type, location}, groupId)
 {
-    return {
-        object: injectedScript._wrapObject(scopeObject, groupId),
-        type: InjectedScript.CallFrameProxy._scopeTypeNames[scopeTypeCode]
+    var scope = {
+        object: injectedScript._wrapObject(object, groupId),
+        type: InjectedScript.CallFrameProxy._scopeTypeNames[type],
     };
+
+    if (name)
+        scope.name = name;
+    if (location)
+        scope.location = location;
+
+    return scope;
 }
 
 
index 10b2cd0..a1dab51 100644 (file)
 
 #include "DebuggerScope.h"
 #include "Error.h"
+#include "IdentifierInlines.h"
 #include "JSCJSValue.h"
 #include "JSCellInlines.h"
 #include "JSJavaScriptCallFramePrototype.h"
+#include "ObjectConstructor.h"
 #include "StructureInlines.h"
 
 using namespace JSC;
@@ -92,45 +94,58 @@ JSValue JSJavaScriptCallFrame::evaluateWithScopeExtension(ExecState* exec)
     return result;
 }
 
-JSValue JSJavaScriptCallFrame::scopeType(ExecState* exec)
+static JSValue valueForScopeType(DebuggerScope* scope)
 {
-    if (!impl().scopeChain())
-        return jsUndefined();
+    if (scope->isCatchScope())
+        return jsNumber(JSJavaScriptCallFrame::CATCH_SCOPE);
+    if (scope->isFunctionNameScope())
+        return jsNumber(JSJavaScriptCallFrame::FUNCTION_NAME_SCOPE);
+    if (scope->isWithScope())
+        return jsNumber(JSJavaScriptCallFrame::WITH_SCOPE);
+    if (scope->isNestedLexicalScope())
+        return jsNumber(JSJavaScriptCallFrame::NESTED_LEXICAL_SCOPE);
+    if (scope->isGlobalLexicalEnvironment())
+        return jsNumber(JSJavaScriptCallFrame::GLOBAL_LEXICAL_ENVIRONMENT_SCOPE);
+    if (scope->isGlobalScope())
+        return jsNumber(JSJavaScriptCallFrame::GLOBAL_SCOPE);
 
-    if (!exec->argument(0).isInt32())
-        return jsUndefined();
-    int index = exec->argument(0).asInt32();
+    ASSERT(scope->isClosureScope());
+    return jsNumber(JSJavaScriptCallFrame::CLOSURE_SCOPE);
+}
 
+static JSValue valueForScopeLocation(ExecState* exec, const DebuggerLocation& location)
+{
+    if (location.sourceID == noSourceID)
+        return jsNull();
+
+    // Debugger.Location protocol object.
+    JSObject* result = constructEmptyObject(exec);
+    result->putDirect(exec->vm(), Identifier::fromString(exec, "scriptId"), jsString(exec, String::number(location.sourceID)));
+    result->putDirect(exec->vm(), Identifier::fromString(exec, "lineNumber"), jsNumber(location.line));
+    result->putDirect(exec->vm(), Identifier::fromString(exec, "columnNumber"), jsNumber(location.column));
+    return result;
+}
+
+JSValue JSJavaScriptCallFrame::scopeDescriptions(ExecState* exec)
+{
     DebuggerScope* scopeChain = impl().scopeChain();
-    DebuggerScope::iterator end = scopeChain->end();
+    if (!scopeChain)
+        return jsUndefined();
 
+    int index = 0;
+    JSArray* array = constructEmptyArray(exec, nullptr);
+
+    DebuggerScope::iterator end = scopeChain->end();
     for (DebuggerScope::iterator iter = scopeChain->begin(); iter != end; ++iter) {
         DebuggerScope* scope = iter.get();
-
-        if (!index) {
-            if (scope->isCatchScope())
-                return jsNumber(JSJavaScriptCallFrame::CATCH_SCOPE);
-            if (scope->isFunctionNameScope())
-                return jsNumber(JSJavaScriptCallFrame::FUNCTION_NAME_SCOPE);
-            if (scope->isWithScope())
-                return jsNumber(JSJavaScriptCallFrame::WITH_SCOPE);
-            if (scope->isNestedLexicalScope())
-                return jsNumber(JSJavaScriptCallFrame::NESTED_LEXICAL_SCOPE);
-            if (scope->isGlobalLexicalEnvironment())
-                return jsNumber(JSJavaScriptCallFrame::GLOBAL_LEXICAL_ENVIRONMENT_SCOPE);
-            if (scope->isGlobalScope()) {
-                ASSERT(++iter == end);
-                return jsNumber(JSJavaScriptCallFrame::GLOBAL_SCOPE);
-            }
-            ASSERT(scope->isClosureScope());
-            return jsNumber(JSJavaScriptCallFrame::CLOSURE_SCOPE);
-        }
-
-        --index;
+        JSObject* description = constructEmptyObject(exec);
+        description->putDirect(exec->vm(), Identifier::fromString(exec, "type"), valueForScopeType(scope));
+        description->putDirect(exec->vm(), Identifier::fromString(exec, "name"), jsString(exec, scope->name()));
+        description->putDirect(exec->vm(), Identifier::fromString(exec, "location"), valueForScopeLocation(exec, scope->location()));
+        array->putDirectIndex(exec, index++, description);
     }
 
-    ASSERT_NOT_REACHED();
-    return jsUndefined();
+    return array;
 }
 
 JSValue JSJavaScriptCallFrame::caller(ExecState* exec) const
index c4b0b36..0f7335c 100644 (file)
@@ -58,7 +58,7 @@ public:
 
     // Functions.
     JSC::JSValue evaluateWithScopeExtension(JSC::ExecState*);
-    JSC::JSValue scopeType(JSC::ExecState*);
+    JSC::JSValue scopeDescriptions(JSC::ExecState*);
 
     // Attributes.
     JSC::JSValue caller(JSC::ExecState*) const;
index 3af39e5..c6fc2c6 100644 (file)
@@ -39,7 +39,7 @@ namespace Inspector {
 
 // Functions.
 static EncodedJSValue JSC_HOST_CALL jsJavaScriptCallFramePrototypeFunctionEvaluateWithScopeExtension(ExecState*);
-static EncodedJSValue JSC_HOST_CALL jsJavaScriptCallFramePrototypeFunctionScopeType(ExecState*);
+static EncodedJSValue JSC_HOST_CALL jsJavaScriptCallFramePrototypeFunctionScopeDescriptions(ExecState*);
 
 // Attributes.
 static EncodedJSValue JSC_HOST_CALL jsJavaScriptCallFrameAttributeCaller(ExecState*);
@@ -61,7 +61,7 @@ void JSJavaScriptCallFramePrototype::finishCreation(VM& vm, JSGlobalObject* glob
     vm.prototypeMap.addPrototype(this);
 
     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("evaluateWithScopeExtension", jsJavaScriptCallFramePrototypeFunctionEvaluateWithScopeExtension, DontEnum, 1);
-    JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("scopeType", jsJavaScriptCallFramePrototypeFunctionScopeType, DontEnum, 1);
+    JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("scopeDescriptions", jsJavaScriptCallFramePrototypeFunctionScopeDescriptions, DontEnum, 0);
 
     JSC_NATIVE_GETTER("caller", jsJavaScriptCallFrameAttributeCaller, DontEnum | Accessor);
     JSC_NATIVE_GETTER("sourceID", jsJavaScriptCallFrameAttributeSourceID, DontEnum | Accessor);
@@ -84,14 +84,14 @@ EncodedJSValue JSC_HOST_CALL jsJavaScriptCallFramePrototypeFunctionEvaluateWithS
     return JSValue::encode(castedThis->evaluateWithScopeExtension(exec));
 }
 
-EncodedJSValue JSC_HOST_CALL jsJavaScriptCallFramePrototypeFunctionScopeType(ExecState* exec)
+EncodedJSValue JSC_HOST_CALL jsJavaScriptCallFramePrototypeFunctionScopeDescriptions(ExecState* exec)
 {
     JSValue thisValue = exec->thisValue();
     JSJavaScriptCallFrame* castedThis = jsDynamicCast<JSJavaScriptCallFrame*>(thisValue);
     if (!castedThis)
         return throwVMTypeError(exec);
 
-    return JSValue::encode(castedThis->scopeType(exec));
+    return JSValue::encode(castedThis->scopeDescriptions(exec));
 }
 
 EncodedJSValue JSC_HOST_CALL jsJavaScriptCallFrameAttributeCaller(ExecState* exec)
index 949418f..c25c9df 100644 (file)
             "id": "Scope",
             "type": "object",
             "properties": [
+                { "name": "object", "$ref": "Runtime.RemoteObject", "description": "Object representing the scope. For <code>global</code> and <code>with</code> scopes it represents the actual object; for the rest of the scopes, it is artificial transient object enumerating scope variables as its properties." },
                 { "name": "type", "type": "string", "enum": ["global", "with", "closure", "catch", "functionName", "globalLexicalEnvironment", "nestedLexical"], "description": "Scope type." },
-                { "name": "object", "$ref": "Runtime.RemoteObject", "description": "Object representing the scope. For <code>global</code> and <code>with</code> scopes it represents the actual object; for the rest of the scopes, it is artificial transient object enumerating scope variables as its properties." }
+                { "name": "name", "type": "string", "optional": true, "description": "Name associated with the scope." },
+                { "name": "location", "$ref": "Location", "optional": true, "description": "Location if available of the scope definition." }
             ],
             "description": "Scope description."
         },
index 1b7ae0f..718bfcb 100644 (file)
@@ -340,4 +340,12 @@ JSScope* JSScope::constantScopeForCodeBlock(ResolveType type, CodeBlock* codeBlo
     return nullptr;
 }
 
+SymbolTable* JSScope::symbolTable()
+{
+    if (JSSymbolTableObject* symbolTableObject = jsDynamicCast<JSSymbolTableObject*>(this))
+        return symbolTableObject->symbolTable();
+
+    return nullptr;
+}
+
 } // namespace JSC
index e34d966..52ad1ac 100644 (file)
@@ -32,8 +32,9 @@
 namespace JSC {
 
 class ScopeChainIterator;
-class WatchpointSet;
+class SymbolTable;
 class VariableEnvironment;
+class WatchpointSet;
 
 class JSScope : public JSNonFinalObject {
 public:
@@ -71,6 +72,8 @@ public:
     VM* vm();
     JSObject* globalThis();
 
+    SymbolTable* symbolTable();
+
 protected:
     JSScope(VM&, Structure*, JSScope* next);
 
index f40e14e..a127301 100644 (file)
@@ -104,6 +104,9 @@ void SymbolTable::visitChildren(JSCell* thisCell, SlotVisitor& visitor)
     visitor.append(&thisSymbolTable->m_arguments);
     visitor.append(&thisSymbolTable->m_singletonScope);
     
+    if (thisSymbolTable->m_rareData)
+        visitor.append(&thisSymbolTable->m_rareData->m_codeBlock);
+    
     // Save some memory. This is O(n) to rebuild and we do so on the fly.
     ConcurrentJITLocker locker(thisSymbolTable->m_lock);
     thisSymbolTable->m_localToEntry = nullptr;
@@ -159,28 +162,28 @@ SymbolTable* SymbolTable::cloneScopePart(VM& vm)
     if (ScopedArgumentsTable* arguments = this->arguments())
         result->m_arguments.set(vm, result, arguments);
     
-    if (m_typeProfilingRareData) {
-        result->m_typeProfilingRareData = std::make_unique<TypeProfilingRareData>();
+    if (m_rareData) {
+        result->m_rareData = std::make_unique<SymbolTableRareData>();
 
         {
-            auto iter = m_typeProfilingRareData->m_uniqueIDMap.begin();
-            auto end = m_typeProfilingRareData->m_uniqueIDMap.end();
+            auto iter = m_rareData->m_uniqueIDMap.begin();
+            auto end = m_rareData->m_uniqueIDMap.end();
             for (; iter != end; ++iter)
-                result->m_typeProfilingRareData->m_uniqueIDMap.set(iter->key, iter->value);
+                result->m_rareData->m_uniqueIDMap.set(iter->key, iter->value);
         }
 
         {
-            auto iter = m_typeProfilingRareData->m_offsetToVariableMap.begin();
-            auto end = m_typeProfilingRareData->m_offsetToVariableMap.end();
+            auto iter = m_rareData->m_offsetToVariableMap.begin();
+            auto end = m_rareData->m_offsetToVariableMap.end();
             for (; iter != end; ++iter)
-                result->m_typeProfilingRareData->m_offsetToVariableMap.set(iter->key, iter->value);
+                result->m_rareData->m_offsetToVariableMap.set(iter->key, iter->value);
         }
 
         {
-            auto iter = m_typeProfilingRareData->m_uniqueTypeSetMap.begin();
-            auto end = m_typeProfilingRareData->m_uniqueTypeSetMap.end();
+            auto iter = m_rareData->m_uniqueTypeSetMap.begin();
+            auto end = m_rareData->m_uniqueTypeSetMap.end();
             for (; iter != end; ++iter)
-                result->m_typeProfilingRareData->m_uniqueTypeSetMap.set(iter->key, iter->value);
+                result->m_rareData->m_uniqueTypeSetMap.set(iter->key, iter->value);
         }
     }
     
@@ -189,31 +192,48 @@ SymbolTable* SymbolTable::cloneScopePart(VM& vm)
 
 void SymbolTable::prepareForTypeProfiling(const ConcurrentJITLocker&)
 {
-    if (m_typeProfilingRareData)
+    if (m_rareData)
         return;
 
-    m_typeProfilingRareData = std::make_unique<TypeProfilingRareData>();
+    m_rareData = std::make_unique<SymbolTableRareData>();
 
     for (auto iter = m_map.begin(), end = m_map.end(); iter != end; ++iter) {
-        m_typeProfilingRareData->m_uniqueIDMap.set(iter->key, TypeProfilerNeedsUniqueIDGeneration);
-        m_typeProfilingRareData->m_offsetToVariableMap.set(iter->value.varOffset(), iter->key);
+        m_rareData->m_uniqueIDMap.set(iter->key, TypeProfilerNeedsUniqueIDGeneration);
+        m_rareData->m_offsetToVariableMap.set(iter->value.varOffset(), iter->key);
     }
 }
 
+CodeBlock* SymbolTable::rareDataCodeBlock()
+{
+    if (!m_rareData)
+        return nullptr;
+
+    return m_rareData->m_codeBlock.get();
+}
+
+void SymbolTable::setRareDataCodeBlock(CodeBlock* codeBlock)
+{
+    if (!m_rareData)
+        m_rareData = std::make_unique<SymbolTableRareData>();
+
+    ASSERT(!m_rareData->m_codeBlock);
+    m_rareData->m_codeBlock.set(*codeBlock->vm(), this, codeBlock);
+}
+
 GlobalVariableID SymbolTable::uniqueIDForVariable(const ConcurrentJITLocker&, UniquedStringImpl* key, VM& vm)
 {
-    RELEASE_ASSERT(m_typeProfilingRareData);
+    RELEASE_ASSERT(m_rareData);
 
-    auto iter = m_typeProfilingRareData->m_uniqueIDMap.find(key);
-    auto end = m_typeProfilingRareData->m_uniqueIDMap.end();
+    auto iter = m_rareData->m_uniqueIDMap.find(key);
+    auto end = m_rareData->m_uniqueIDMap.end();
     if (iter == end)
         return TypeProfilerNoGlobalIDExists;
 
     GlobalVariableID id = iter->value;
     if (id == TypeProfilerNeedsUniqueIDGeneration) {
         id = vm.typeProfiler()->getNextUniqueVariableID();
-        m_typeProfilingRareData->m_uniqueIDMap.set(key, id);
-        m_typeProfilingRareData->m_uniqueTypeSetMap.set(key, TypeSet::create()); // Make a new global typeset for this corresponding ID.
+        m_rareData->m_uniqueIDMap.set(key, id);
+        m_rareData->m_uniqueTypeSetMap.set(key, TypeSet::create()); // Make a new global typeset for this corresponding ID.
     }
 
     return id;
@@ -221,10 +241,10 @@ GlobalVariableID SymbolTable::uniqueIDForVariable(const ConcurrentJITLocker&, Un
 
 GlobalVariableID SymbolTable::uniqueIDForOffset(const ConcurrentJITLocker& locker, VarOffset offset, VM& vm)
 {
-    RELEASE_ASSERT(m_typeProfilingRareData);
+    RELEASE_ASSERT(m_rareData);
 
-    auto iter = m_typeProfilingRareData->m_offsetToVariableMap.find(offset);
-    auto end = m_typeProfilingRareData->m_offsetToVariableMap.end();
+    auto iter = m_rareData->m_offsetToVariableMap.find(offset);
+    auto end = m_rareData->m_offsetToVariableMap.end();
     if (iter == end)
         return TypeProfilerNoGlobalIDExists;
 
@@ -233,12 +253,12 @@ GlobalVariableID SymbolTable::uniqueIDForOffset(const ConcurrentJITLocker& locke
 
 RefPtr<TypeSet> SymbolTable::globalTypeSetForOffset(const ConcurrentJITLocker& locker, VarOffset offset, VM& vm)
 {
-    RELEASE_ASSERT(m_typeProfilingRareData);
+    RELEASE_ASSERT(m_rareData);
 
     uniqueIDForOffset(locker, offset, vm); // Lazily create the TypeSet if necessary.
 
-    auto iter = m_typeProfilingRareData->m_offsetToVariableMap.find(offset);
-    auto end = m_typeProfilingRareData->m_offsetToVariableMap.end();
+    auto iter = m_rareData->m_offsetToVariableMap.find(offset);
+    auto end = m_rareData->m_offsetToVariableMap.end();
     if (iter == end)
         return nullptr;
 
@@ -247,12 +267,12 @@ RefPtr<TypeSet> SymbolTable::globalTypeSetForOffset(const ConcurrentJITLocker& l
 
 RefPtr<TypeSet> SymbolTable::globalTypeSetForVariable(const ConcurrentJITLocker& locker, UniquedStringImpl* key, VM& vm)
 {
-    RELEASE_ASSERT(m_typeProfilingRareData);
+    RELEASE_ASSERT(m_rareData);
 
     uniqueIDForVariable(locker, key, vm); // Lazily create the TypeSet if necessary.
 
-    auto iter = m_typeProfilingRareData->m_uniqueTypeSetMap.find(key);
-    auto end = m_typeProfilingRareData->m_uniqueTypeSetMap.end();
+    auto iter = m_rareData->m_uniqueTypeSetMap.find(key);
+    auto end = m_rareData->m_uniqueTypeSetMap.end();
     if (iter == end)
         return nullptr;
 
index e452f8d..d11daba 100644 (file)
@@ -679,6 +679,9 @@ public:
     SymbolTable* cloneScopePart(VM&);
 
     void prepareForTypeProfiling(const ConcurrentJITLocker&);
+
+    CodeBlock* rareDataCodeBlock();
+    void setRareDataCodeBlock(CodeBlock*);
     
     InferredValue* singletonScope() { return m_singletonScope.get(); }
 
@@ -695,12 +698,13 @@ private:
     Map m_map;
     ScopeOffset m_maxScopeOffset;
     
-    struct TypeProfilingRareData {
+    struct SymbolTableRareData {
         UniqueIDMap m_uniqueIDMap;
         OffsetToVariableMap m_offsetToVariableMap;
         UniqueTypeSetMap m_uniqueTypeSetMap;
+        WriteBarrier<CodeBlock> m_codeBlock;
     };
-    std::unique_ptr<TypeProfilingRareData> m_typeProfilingRareData;
+    std::unique_ptr<SymbolTableRareData> m_rareData;
 
     bool m_usesNonStrictEval : 1;
     bool m_nestedLexicalScope : 1; // Non-function LexicalScope.
index 583847c..4641a65 100644 (file)
@@ -1,3 +1,65 @@
+2016-06-29  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: Wrong function name next to scope
+        https://bugs.webkit.org/show_bug.cgi?id=158210
+        <rdar://problem/26543093>
+
+        Reviewed by Brian Burg.
+
+        * UserInterface/Controllers/DebuggerManager.js:
+        (WebInspector.DebuggerManager.prototype._scopeChainNodeFromPayload):
+        Include new payload data in the construction call.
+        All the new data is optional, so we gracefully handle
+        legacy backends.
+
+        * UserInterface/Models/ScopeChainNode.js:
+        (WebInspector.ScopeChainNode):
+        (WebInspector.ScopeChainNode.prototype.get type):
+        (WebInspector.ScopeChainNode.prototype.get objects):
+        (WebInspector.ScopeChainNode.prototype.get name):
+        (WebInspector.ScopeChainNode.prototype.get location):
+        (WebInspector.ScopeChainNode.prototype.get hash):
+        Hash is a rough (name:sourceId:line:column) string for quick comparisons.
+
+        (WebInspector.ScopeChainNode.prototype.makeLocalScope):
+        Make this an action you take on a scope, to avoid having to
+        do it at construction time, or making it a generic setting.
+
+        * UserInterface/Views/ScopeChainDetailsSidebarPanel.js:
+        (WebInspector.ScopeChainDetailsSidebarPanel.prototype._generateCallFramesSection):
+        This was wrong before. Move the work to CallFrame
+        and change it to be correct.
+
+        * UserInterface/CallFrame.js:
+        (WebInspector.CallFrame.prototype.mergedScopeChain):
+
+        This transforms the scope chain for a call frame from:
+        
+             scope1  scope2  scope3  scope4  scope5  scope6  scope7
+              foo     foo     foo     bar     bar      -       -
+             Block  Closure Closure Closure Closure   GLE     GBL
+
+        To:
+             scope1  scope2&3   scope4&5  scope6  scope7
+              foo      foo*       bar*      -       -
+             Block    Local     Closure    GLE     GBL
+
+        Doing a few things:
+
+            - Merge the first two Closure scopes sharing a location.
+              These are the "var" and "let" Closure scopes in a function,
+              and it is better to present these together in the UI.
+
+            - Mark the first Closure scope within a function (*). When
+              this is displayed in the UI, we can provide the name of
+              the function: "Closure Scope (name)", and we even have
+              location information that we can use to display a goto
+              arrow if needed.
+
+            - Make the first Closure scope the Local scope if it
+              matches the Call Frame's function name. This lets us
+              display the section as "Local Variables".
+
 2016-06-29  Brian Burg  <bburg@apple.com>
 
         Web Inspector: Uncaught Exception page never shows if exception is thrown while processing a protocol event
index e38fec5..c614b74 100644 (file)
@@ -662,7 +662,7 @@ WebInspector.DebuggerManager = class DebuggerManager extends WebInspector.Object
         }
 
         var object = WebInspector.RemoteObject.fromPayload(payload.object);
-        return new WebInspector.ScopeChainNode(type, [object]);
+        return new WebInspector.ScopeChainNode(type, [object], payload.name, payload.location);
     }
 
     _pauseReasonFromPayload(payload)
index fef7895..a8fd4b0 100644 (file)
@@ -45,45 +45,14 @@ WebInspector.CallFrame = class CallFrame extends WebInspector.Object
 
     // Public
 
-    get id()
-    {
-        return this._id;
-    }
-
-    get sourceCodeLocation()
-    {
-        return this._sourceCodeLocation;
-    }
-
-    get functionName()
-    {
-        return this._functionName;
-    }
-
-    get nativeCode()
-    {
-        return this._nativeCode;
-    }
-
-    get programCode()
-    {
-        return this._programCode;
-    }
-
-    get thisObject()
-    {
-        return this._thisObject;
-    }
-
-    get scopeChain()
-    {
-        return this._scopeChain;
-    }
-
-    get isTailDeleted()
-    {
-        return this._isTailDeleted;
-    }
+    get id() { return this._id; }
+    get sourceCodeLocation() { return this._sourceCodeLocation; }
+    get functionName() { return this._functionName; }
+    get nativeCode() { return this._nativeCode; }
+    get programCode() { return this._programCode; }
+    get thisObject() { return this._thisObject; }
+    get scopeChain() { return this._scopeChain; }
+    get isTailDeleted() { return this._isTailDeleted; }
 
     saveIdentityToCookie()
     {
@@ -112,6 +81,99 @@ WebInspector.CallFrame = class CallFrame extends WebInspector.Object
             this._scopeChain[i].objects[0].deprecatedGetAllProperties(propertiesCollected);
     }
 
+    mergedScopeChain()
+    {
+        let mergedScopes = [];
+
+        // Scopes list goes from top/local (1) to bottom/global (5)
+        //   [scope1, scope2, scope3, scope4, scope5]
+        let scopes = this._scopeChain.slice();
+
+        // Merge similiar scopes. Some function call frames may have multiple
+        // top level closure scopes (one for `var`s one for `let`s) that can be
+        // combined to a single scope of variables. Go in reverse order so we
+        // merge the first two closure scopes with the same name. Also mark
+        // the first time we see a new name, so we know the base for the name.
+        //   [scope1&2, scope3, scope4, scope5]
+        //      foo      bar     GLE    global
+        let lastMarkedHash = null;
+        function markAsBaseIfNeeded(scope) {
+            if (!scope.hash)
+                return false;
+            if (scope.type !== WebInspector.ScopeChainNode.Type.Closure)
+                return false;
+            if (scope.hash === lastMarkedHash)
+                return false;
+            lastMarkedHash = scope.hash;
+            scope.__baseClosureScope = true;
+            return true;
+        }
+
+        function shouldMergeClosureScopes(youngScope, oldScope, lastMerge) {
+            if (!youngScope || !oldScope)
+                return false;
+
+            // Don't merge unknown locations.
+            if (!youngScope.hash || !oldScope.hash)
+                return false;
+
+            // Only merge closure scopes.
+            if (youngScope.type !== WebInspector.ScopeChainNode.Type.Closure)
+                return false;
+            if (oldScope.type !== WebInspector.ScopeChainNode.Type.Closure)
+                return false;
+
+            // Don't merge if they are not the same.
+            if (youngScope.hash !== oldScope.hash)
+                return false;
+
+            // Don't merge if there was already a merge.
+            if (lastMerge && youngScope.hash === lastMerge.hash)
+                return false;
+
+            return true;
+        }
+
+        let lastScope = null;
+        let lastMerge = null;
+        for (let i = scopes.length - 1; i >= 0; --i) {
+            let scope = scopes[i];
+            markAsBaseIfNeeded(scope);
+            if (shouldMergeClosureScopes(scope, lastScope, lastMerge)) {
+                console.assert(lastScope.__baseClosureScope);
+                let type = WebInspector.ScopeChainNode.Type.Closure;
+                let objects = lastScope.objects.concat(scope.objects);
+                let merged = new WebInspector.ScopeChainNode(type, objects, scope.name, scope.location);
+                merged.__baseClosureScope = true;
+                console.assert(objects.length === 2);
+
+                mergedScopes.pop(); // Remove the last.
+                mergedScopes.push(merged); // Add the merged scope.
+
+                lastMerge = merged;
+                lastScope = null;
+            } else {
+                mergedScopes.push(scope);
+
+                lastMerge = null;
+                lastScope = scope;
+            }
+        }
+
+        mergedScopes = mergedScopes.reverse();
+
+        // Mark the first Closure as Local if the name matches this call frame.
+        for (let scope of mergedScopes) {
+            if (scope.type === WebInspector.ScopeChainNode.Type.Closure) {
+                if (scope.name === this._functionName)
+                    scope.convertToLocalScope();
+                break;
+            }
+        }
+
+        return mergedScopes;
+    }
+
     // Static
 
     static functionNameFromPayload(payload)
index e7c6edd..1398c1e 100644 (file)
@@ -25,7 +25,7 @@
 
 WebInspector.ScopeChainNode = class ScopeChainNode extends WebInspector.Object
 {
-    constructor(type, objects)
+    constructor(type, objects, name, location)
     {
         super();
 
@@ -37,18 +37,31 @@ WebInspector.ScopeChainNode = class ScopeChainNode extends WebInspector.Object
 
         this._type = type || null;
         this._objects = objects || [];
+        this._name = name || "";
+        this._location = location || null;
     }
 
     // Public
 
-    get type()
+    get type() { return this._type; }
+    get objects() { return this._objects; }
+    get name() { return this._name; }
+    get location() { return this._location; }
+
+    get hash()
     {
-        return this._type;
+        if (this._hash)
+            return this._hash;
+
+        this._hash = this._name;
+        if (this._location)
+            this._hash += `:${this._location.scriptId}:${this._location.lineNumber}:${this._location.columnNumber}`;
+        return this._hash;
     }
 
-    get objects()
+    convertToLocalScope()
     {
-        return this._objects;
+        this._type = WebInspector.ScopeChainNode.Type.Local;
     }
 };
 
index 99104b5..51ca5c7 100644 (file)
@@ -164,159 +164,92 @@ WebInspector.ScopeChainDetailsSidebarPanel = class ScopeChainDetailsSidebarPanel
         for (let type in WebInspector.ScopeChainNode.Type)
             sectionCountByType.set(WebInspector.ScopeChainNode.Type[type], 0);
 
-        // Scopes list goes from top/local (1) to bottom/global (5)
-        // Call frames list goes from top/local (1) to bottom/global (2)
-        //   [scope1, scope2, scope3, scope4, scope5]
-        //   [CallFrame1, CallFrame2]
-        let scopeChain = callFrame.scopeChain;
-        let callFrames = WebInspector.debuggerManager.callFrames;
-
-        // Group scopes with the call frame containing them.
-        // Creating a map that looks like:
-        //   CallFrame2 => [scope5, scope4]
-        //   CallFrame1 => [scope3, scope2, scope1]
-        let reversedScopeChain = scopeChain.slice().reverse();
-        let callFrameScopes = new Map;
-        let lastLength = 0;
-        for (let i = callFrames.length - 1; i >= 0; --i) {
-            let nextCallFrame = callFrames[i];
-            console.assert(nextCallFrame.scopeChain.length > lastLength);
-            callFrameScopes.set(nextCallFrame, reversedScopeChain.slice(lastLength, nextCallFrame.scopeChain.length));
-            lastLength = nextCallFrame.scopeChain.length;
-            if (nextCallFrame === callFrame) {
-                console.assert(lastLength === scopeChain.length);
+        let scopeChain = callFrame.mergedScopeChain();
+        for (let scope of scopeChain) {
+            let title = null;
+            let extraPropertyDescriptor = null;
+            let collapsedByDefault = false;
+
+            let count = sectionCountByType.get(scope.type);
+            sectionCountByType.set(scope.type, ++count);
+
+            switch (scope.type) {
+            case WebInspector.ScopeChainNode.Type.Local:
+                foundLocalScope = true;
+                collapsedByDefault = false;
+                title = WebInspector.UIString("Local Variables");
+                if (callFrame.thisObject)
+                    extraPropertyDescriptor = new WebInspector.PropertyDescriptor({name: "this", value: callFrame.thisObject});
                 break;
-            }
-        }
 
-        // Now that we have this map we can merge some of the scopes within an individual
-        // call frame. In particular, function call frames may have multiple top level
-        // closure scopes (one for `var`s one for `let`s) that can be combined to a
-        // single scope of variables.
-        // This modifies the Map, resulting in:
-        //   CallFrame2 => [scope4, scope5]
-        //   CallFrame1 => [scope1, scope2&3]
-        for (let [currentCallFrame, scopes] of callFrameScopes) {
-            let firstClosureScope = null;
-            for (let scope of scopes) {
-                // Reached a non-closure scope. Bail.
-                let isClosureScope = scope.type === WebInspector.ScopeChainNode.Type.Closure;
-                if (!isClosureScope && firstClosureScope)
-                    break;
-
-                // Found first closure scope. Mark it so we can provide the function name later in the UI.
-                if (isClosureScope && !firstClosureScope) {
-                    firstClosureScope = scope;
-                    firstClosureScope[WebInspector.ScopeChainDetailsSidebarPanel.CallFrameBaseClosureScopeSymbol] = true;
-                    continue;
-                }
+            case WebInspector.ScopeChainNode.Type.Closure:
+                if (scope.__baseClosureScope && scope.name)
+                    title = WebInspector.UIString("Closure Variables (%s)").format(scope.name);
+                else
+                    title = WebInspector.UIString("Closure Variables");
+                collapsedByDefault = false;
+                break;
 
-                // Found 2 sequential top level closure scopes. Merge and mark it so we can provide the function name later in the UI.
-                if (isClosureScope && firstClosureScope) {
-                    let type = currentCallFrame === callFrame ? WebInspector.ScopeChainNode.Type.Local : WebInspector.ScopeChainNode.Type.Closure;
-                    let objects = firstClosureScope.objects.concat(scope.objects);
-                    let merged = new WebInspector.ScopeChainNode(type, objects);
-                    merged[WebInspector.ScopeChainDetailsSidebarPanel.CallFrameBaseClosureScopeSymbol] = true;
-                    console.assert(objects.length === 2);
-
-                    let index = scopes.indexOf(firstClosureScope);
-                    scopes.splice(index, 1); // Remove one of them.
-                    scopes[index] = merged; // Replace the remaining with the merged.
-                    break;
-                }
-            }
-            scopes.reverse();
-        }
+            case WebInspector.ScopeChainNode.Type.Block:
+                title = WebInspector.UIString("Block Variables");
+                collapsedByDefault = false;
+                break;
 
-        // Now we can walk the list of call frames and their scopes.
-        // We walk in top -> down order:
-        //   CallFrame1 => [scope1, scope2&3]
-        //   CallFrame2 => [scope5, scope4]
-        for (let [call, scopes] of [...callFrameScopes.entries()].reverse()) {
-            for (let scope of scopes) {
-                let title = null;
-                let extraPropertyDescriptor = null;
-                let collapsedByDefault = false;
-
-                let count = sectionCountByType.get(scope.type);
-                sectionCountByType.set(scope.type, ++count);
-
-                switch (scope.type) {
-                    case WebInspector.ScopeChainNode.Type.Local:
-                        foundLocalScope = true;
-                        collapsedByDefault = false;
-                        title = WebInspector.UIString("Local Variables");
-                        if (call.thisObject)
-                            extraPropertyDescriptor = new WebInspector.PropertyDescriptor({name: "this", value: call.thisObject});
-                        break;
-
-                    case WebInspector.ScopeChainNode.Type.Closure:
-                        if (scope[WebInspector.ScopeChainDetailsSidebarPanel.CallFrameBaseClosureScopeSymbol] && call.functionName)
-                            title = WebInspector.UIString("Closure Variables (%s)").format(call.functionName);
-                        else
-                            title = WebInspector.UIString("Closure Variables");
-                        collapsedByDefault = false;
-                        break;
-
-                    case WebInspector.ScopeChainNode.Type.Block:
-                        title = WebInspector.UIString("Block Variables");
-                        collapsedByDefault = false;
-                        break;
-
-                    case WebInspector.ScopeChainNode.Type.Catch:
-                        title = WebInspector.UIString("Catch Variables");
-                        collapsedByDefault = false;
-                        break;
-
-                    case WebInspector.ScopeChainNode.Type.FunctionName:
-                        title = WebInspector.UIString("Function Name Variable");
-                        collapsedByDefault = true;
-                        break;
-
-                    case WebInspector.ScopeChainNode.Type.With:
-                        title = WebInspector.UIString("With Object Properties");
-                        collapsedByDefault = foundLocalScope;
-                        break;
-
-                    case WebInspector.ScopeChainNode.Type.Global:
-                        title = WebInspector.UIString("Global Variables");
-                        collapsedByDefault = true;
-                        break;
-
-                    case WebInspector.ScopeChainNode.Type.GlobalLexicalEnvironment:
-                        title = WebInspector.UIString("Global Lexical Environment");
-                        collapsedByDefault = true;
-                        break;
-                }
+            case WebInspector.ScopeChainNode.Type.Catch:
+                title = WebInspector.UIString("Catch Variables");
+                collapsedByDefault = false;
+                break;
 
-                let detailsSectionIdentifier = scope.type + "-" + sectionCountByType.get(scope.type);
+            case WebInspector.ScopeChainNode.Type.FunctionName:
+                title = WebInspector.UIString("Function Name Variable");
+                collapsedByDefault = true;
+                break;
 
-                // FIXME: This just puts two ObjectTreeViews next to eachother, but that means
-                // that properties are not nicely sorted between the two separate lists.
+            case WebInspector.ScopeChainNode.Type.With:
+                title = WebInspector.UIString("With Object Properties");
+                collapsedByDefault = foundLocalScope;
+                break;
 
-                let rows = [];
-                for (let object of scope.objects) {
-                    let scopePropertyPath = WebInspector.PropertyPath.emptyPropertyPathForScope(object);
-                    let objectTree = new WebInspector.ObjectTreeView(object, WebInspector.ObjectTreeView.Mode.Properties, scopePropertyPath);
+            case WebInspector.ScopeChainNode.Type.Global:
+                title = WebInspector.UIString("Global Variables");
+                collapsedByDefault = true;
+                break;
 
-                    objectTree.showOnlyProperties();
+            case WebInspector.ScopeChainNode.Type.GlobalLexicalEnvironment:
+                title = WebInspector.UIString("Global Lexical Environment");
+                collapsedByDefault = true;
+                break;
+            }
 
-                    if (extraPropertyDescriptor) {
-                        objectTree.appendExtraPropertyDescriptor(extraPropertyDescriptor);
-                        extraPropertyDescriptor = null;
-                    }
+            let detailsSectionIdentifier = scope.type + "-" + sectionCountByType.get(scope.type);
 
-                    let treeOutline = objectTree.treeOutline;
-                    treeOutline.addEventListener(WebInspector.TreeOutline.Event.ElementAdded, this._treeElementAdded.bind(this, detailsSectionIdentifier), this);
-                    treeOutline.addEventListener(WebInspector.TreeOutline.Event.ElementDisclosureDidChanged, this._treeElementDisclosureDidChange.bind(this, detailsSectionIdentifier), this);
+            // FIXME: This just puts two ObjectTreeViews next to each other, but that means
+            // that properties are not nicely sorted between the two separate lists.
 
-                    rows.push(new WebInspector.DetailsSectionPropertiesRow(objectTree));
+            let rows = [];
+            for (let object of scope.objects) {
+                let scopePropertyPath = WebInspector.PropertyPath.emptyPropertyPathForScope(object);
+                let objectTree = new WebInspector.ObjectTreeView(object, WebInspector.ObjectTreeView.Mode.Properties, scopePropertyPath);
+
+                objectTree.showOnlyProperties();
+
+                if (extraPropertyDescriptor) {
+                    objectTree.appendExtraPropertyDescriptor(extraPropertyDescriptor);
+                    extraPropertyDescriptor = null;
                 }
 
-                let detailsSection = new WebInspector.DetailsSection(detailsSectionIdentifier, title, null, null, collapsedByDefault);
-                detailsSection.groups[0].rows = rows;
-                detailsSections.push(detailsSection);
+                let treeOutline = objectTree.treeOutline;
+                treeOutline.addEventListener(WebInspector.TreeOutline.Event.ElementAdded, this._treeElementAdded.bind(this, detailsSectionIdentifier), this);
+                treeOutline.addEventListener(WebInspector.TreeOutline.Event.ElementDisclosureDidChanged, this._treeElementDisclosureDidChange.bind(this, detailsSectionIdentifier), this);
+
+                // FIXME: <https://webkit.org/b/140567> Web Inspector: Do not request Scope Chain lists if section is collapsed (mainly Global Variables)
+                // This autoexpands the ObjectTreeView and fetches all properties. Should wait to see if we are collapsed or not.
+                rows.push(new WebInspector.DetailsSectionPropertiesRow(objectTree));
             }
+
+            let detailsSection = new WebInspector.DetailsSection(detailsSectionIdentifier, title, null, null, collapsedByDefault);
+            detailsSection.groups[0].rows = rows;
+            detailsSections.push(detailsSection);
         }
 
         return Promise.resolve(detailsSections);
@@ -545,4 +478,3 @@ WebInspector.ScopeChainDetailsSidebarPanel = class ScopeChainDetailsSidebarPanel
 
 WebInspector.ScopeChainDetailsSidebarPanel._autoExpandProperties = new Set;
 WebInspector.ScopeChainDetailsSidebarPanel.WatchExpressionsObjectGroupName = "watch-expressions";
-WebInspector.ScopeChainDetailsSidebarPanel.CallFrameBaseClosureScopeSymbol = Symbol("scope-chain-call-frame-base-closure-scope");