summaryrefslogtreecommitdiff
path: root/Source/WebInspectorUI/UserInterface/Controllers
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
committerLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
commit1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch)
tree46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebInspectorUI/UserInterface/Controllers
parent32761a6cee1d0dee366b885b7b9c777e67885688 (diff)
downloadWebKitGtk-tarball-master.tar.gz
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Controllers')
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/AnalyzerManager.js122
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/Annotator.js110
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/ApplicationCacheManager.js204
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/BasicBlockAnnotator.js131
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/BranchManager.js110
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/BreakpointLogMessageLexer.js197
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/BreakpointPopoverController.js382
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/CSSStyleManager.js551
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorBezierEditingController.js68
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorColorEditingController.js64
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorCompletionController.css29
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorCompletionController.js875
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorDragToAdjustNumberController.css28
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorDragToAdjustNumberController.js121
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorEditingController.js200
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorGradientEditingController.js83
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorSpringEditingController.js63
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorTextKillController.js138
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorTokenTrackingController.css31
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorTokenTrackingController.js618
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/DOMTreeManager.js795
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/DashboardManager.js42
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/DebuggerManager.js1217
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/DragToAdjustController.js238
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/Formatter.js167
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/FormatterSourceMap.js97
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/FrameResourceManager.js640
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/HeapManager.js53
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/IssueManager.js101
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/JavaScriptLogViewController.js350
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/JavaScriptRuntimeCompletionProvider.js307
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/LayerTreeManager.js154
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/LogManager.js129
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/MemoryManager.js49
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/ProbeManager.js177
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/ReplayManager.js678
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/ResourceQueryController.js203
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/RuntimeManager.js262
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/SourceMapManager.js186
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/StorageManager.js336
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/TargetManager.js75
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/TimelineManager.js1091
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/TypeTokenAnnotator.js199
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/VisualStyleCompletionsController.js132
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/WorkerManager.js69
45 files changed, 11872 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/AnalyzerManager.js b/Source/WebInspectorUI/UserInterface/Controllers/AnalyzerManager.js
new file mode 100644
index 000000000..99c62738c
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/AnalyzerManager.js
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2014 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.AnalyzerManager = class AnalyzerManager extends WebInspector.Object
+{
+ constructor()
+ {
+ super();
+
+ this._eslintConfig = {
+ env: {
+ "browser": true,
+ "node": false
+ },
+ globals: {
+ "document": true
+ },
+ rules: {
+ "consistent-return": 2,
+ "curly": 0,
+ "eqeqeq": 0,
+ "new-parens": 0,
+ "no-comma-dangle": 0,
+ "no-console": 0,
+ "no-constant-condition": 0,
+ "no-extra-bind": 2,
+ "no-extra-semi": 2,
+ "no-proto": 0,
+ "no-return-assign": 2,
+ "no-trailing-spaces": 2,
+ "no-underscore-dangle": 0,
+ "no-unused-expressions": 2,
+ "no-wrap-func": 2,
+ "semi": 2,
+ "space-infix-ops": 2,
+ "space-return-throw-case": 2,
+ "strict": 0,
+ "valid-typeof": 2
+ }
+ };
+
+ this._sourceCodeMessagesMap = new WeakMap;
+
+ WebInspector.SourceCode.addEventListener(WebInspector.SourceCode.Event.ContentDidChange, this._handleSourceCodeContentDidChange, this);
+ }
+
+ // Public
+
+ getAnalyzerMessagesForSourceCode(sourceCode)
+ {
+ return new Promise(function(resolve, reject) {
+ var analyzer = WebInspector.AnalyzerManager._typeAnalyzerMap.get(sourceCode.type);
+ if (!analyzer) {
+ reject(new Error("This resource type cannot be analyzed."));
+ return;
+ }
+
+ if (this._sourceCodeMessagesMap.has(sourceCode)) {
+ resolve(this._sourceCodeMessagesMap.get(sourceCode));
+ return;
+ }
+
+ function retrieveAnalyzerMessages(properties)
+ {
+ var analyzerMessages = [];
+ var rawAnalyzerMessages = analyzer.verify(sourceCode.content, this._eslintConfig);
+
+ // Raw line and column numbers are one-based. SourceCodeLocation expects them to be zero-based so we subtract 1 from each.
+ for (var rawAnalyzerMessage of rawAnalyzerMessages)
+ analyzerMessages.push(new WebInspector.AnalyzerMessage(new WebInspector.SourceCodeLocation(sourceCode, rawAnalyzerMessage.line - 1, rawAnalyzerMessage.column - 1), rawAnalyzerMessage.message, rawAnalyzerMessage.ruleId));
+
+ this._sourceCodeMessagesMap.set(sourceCode, analyzerMessages);
+
+ resolve(analyzerMessages);
+ }
+
+ sourceCode.requestContent().then(retrieveAnalyzerMessages.bind(this)).catch(handlePromiseException);
+ }.bind(this));
+ }
+
+ sourceCodeCanBeAnalyzed(sourceCode)
+ {
+ return sourceCode.type === WebInspector.Resource.Type.Script;
+ }
+
+ // Private
+
+ _handleSourceCodeContentDidChange(event)
+ {
+ var sourceCode = event.target;
+
+ // Since sourceCode has changed, remove it and its messages from the map so getAnalyzerMessagesForSourceCode will have to reanalyze the next time it is called.
+ this._sourceCodeMessagesMap.delete(sourceCode);
+ }
+};
+
+WebInspector.AnalyzerManager._typeAnalyzerMap = new Map;
+
+// <https://webkit.org/b/136515> Web Inspector: JavaScript source text editor should have a linter
+// WebInspector.AnalyzerManager._typeAnalyzerMap.set(WebInspector.Resource.Type.Script, eslint);
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/Annotator.js b/Source/WebInspectorUI/UserInterface/Controllers/Annotator.js
new file mode 100644
index 000000000..3e87bb5a4
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/Annotator.js
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2014 Apple Inc. All rights reserved.
+ * Copyright (C) 2014 Saam Barati <saambarati1@gmail.com>
+ *
+ * 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.Annotator = class Annotator extends WebInspector.Object
+{
+ constructor(sourceCodeTextEditor)
+ {
+ super();
+
+ console.assert(sourceCodeTextEditor instanceof WebInspector.SourceCodeTextEditor, sourceCodeTextEditor);
+
+ this._sourceCodeTextEditor = sourceCodeTextEditor;
+ this._timeoutIdentifier = null;
+ this._isActive = false;
+ }
+
+ // Public
+
+ get sourceCodeTextEditor()
+ {
+ return this._sourceCodeTextEditor;
+ }
+
+ isActive()
+ {
+ return this._isActive;
+ }
+
+ pause()
+ {
+ this._clearTimeoutIfNeeded();
+ this._isActive = false;
+ }
+
+ resume()
+ {
+ this._clearTimeoutIfNeeded();
+ this._isActive = true;
+ this.insertAnnotations();
+ }
+
+ refresh()
+ {
+ console.assert(this._isActive);
+ if (!this._isActive)
+ return;
+
+ this._clearTimeoutIfNeeded();
+ this.insertAnnotations();
+ }
+
+ reset()
+ {
+ this._clearTimeoutIfNeeded();
+ this._isActive = true;
+ this.clearAnnotations();
+ this.insertAnnotations();
+ }
+
+ clear()
+ {
+ this.pause();
+ this.clearAnnotations();
+ }
+
+ // Protected
+
+ insertAnnotations()
+ {
+ // Implemented by subclasses.
+ }
+
+ clearAnnotations()
+ {
+ // Implemented by subclasses.
+ }
+
+ // Private
+
+ _clearTimeoutIfNeeded()
+ {
+ if (this._timeoutIdentifier) {
+ clearTimeout(this._timeoutIdentifier);
+ this._timeoutIdentifier = null;
+ }
+ }
+};
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/ApplicationCacheManager.js b/Source/WebInspectorUI/UserInterface/Controllers/ApplicationCacheManager.js
new file mode 100644
index 000000000..61a55a9f7
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/ApplicationCacheManager.js
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2013 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.ApplicationCacheManager = class ApplicationCacheManager extends WebInspector.Object
+{
+ constructor()
+ {
+ super();
+
+ if (window.ApplicationCacheAgent)
+ ApplicationCacheAgent.enable();
+
+ WebInspector.Frame.addEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
+ WebInspector.Frame.addEventListener(WebInspector.Frame.Event.ChildFrameWasRemoved, this._childFrameWasRemoved, this);
+
+ this._online = true;
+
+ this.initialize();
+ }
+
+ // Public
+
+ initialize()
+ {
+ this._applicationCacheObjects = {};
+
+ if (window.ApplicationCacheAgent)
+ ApplicationCacheAgent.getFramesWithManifests(this._framesWithManifestsLoaded.bind(this));
+ }
+
+ get applicationCacheObjects()
+ {
+ var applicationCacheObjects = [];
+ for (var id in this._applicationCacheObjects)
+ applicationCacheObjects.push(this._applicationCacheObjects[id]);
+ return applicationCacheObjects;
+ }
+
+ networkStateUpdated(isNowOnline)
+ {
+ this._online = isNowOnline;
+
+ this.dispatchEventToListeners(WebInspector.ApplicationCacheManager.Event.NetworkStateUpdated, {online: this._online});
+ }
+
+ get online()
+ {
+ return this._online;
+ }
+
+ applicationCacheStatusUpdated(frameId, manifestURL, status)
+ {
+ var frame = WebInspector.frameResourceManager.frameForIdentifier(frameId);
+ if (!frame)
+ return;
+
+ this._frameManifestUpdated(frame, manifestURL, status);
+ }
+
+ requestApplicationCache(frame, callback)
+ {
+ function callbackWrapper(error, applicationCache)
+ {
+ if (error) {
+ callback(null);
+ return;
+ }
+
+ callback(applicationCache);
+ }
+
+ ApplicationCacheAgent.getApplicationCacheForFrame(frame.id, callbackWrapper);
+ }
+
+ // Private
+
+ _mainResourceDidChange(event)
+ {
+ console.assert(event.target instanceof WebInspector.Frame);
+
+ if (event.target.isMainFrame()) {
+ // If we are dealing with the main frame, we want to clear our list of objects, because we are navigating to a new page.
+ this.initialize();
+
+ this.dispatchEventToListeners(WebInspector.ApplicationCacheManager.Event.Cleared);
+
+ return;
+ }
+
+ if (window.ApplicationCacheAgent)
+ ApplicationCacheAgent.getManifestForFrame(event.target.id, this._manifestForFrameLoaded.bind(this, event.target.id));
+ }
+
+ _childFrameWasRemoved(event)
+ {
+ this._frameManifestRemoved(event.data.childFrame);
+ }
+
+ _manifestForFrameLoaded(frameId, error, manifestURL)
+ {
+ if (error)
+ return;
+
+ var frame = WebInspector.frameResourceManager.frameForIdentifier(frameId);
+ if (!frame)
+ return;
+
+ if (!manifestURL)
+ this._frameManifestRemoved(frame);
+ }
+
+ _framesWithManifestsLoaded(error, framesWithManifests)
+ {
+ if (error)
+ return;
+
+ for (var i = 0; i < framesWithManifests.length; ++i) {
+ var frame = WebInspector.frameResourceManager.frameForIdentifier(framesWithManifests[i].frameId);
+ if (!frame)
+ continue;
+
+ this._frameManifestUpdated(frame, framesWithManifests[i].manifestURL, framesWithManifests[i].status);
+ }
+ }
+
+ _frameManifestUpdated(frame, manifestURL, status)
+ {
+ if (status === WebInspector.ApplicationCacheManager.Status.Uncached) {
+ this._frameManifestRemoved(frame);
+ return;
+ }
+
+ if (!manifestURL)
+ return;
+
+ var manifestFrame = this._applicationCacheObjects[frame.id];
+ if (manifestFrame && manifestURL !== manifestFrame.manifest.manifestURL)
+ this._frameManifestRemoved(frame);
+
+ var oldStatus = manifestFrame ? manifestFrame.status : -1;
+ var statusChanged = manifestFrame && status !== oldStatus;
+ if (manifestFrame)
+ manifestFrame.status = status;
+
+ if (!this._applicationCacheObjects[frame.id]) {
+ var cacheManifest = new WebInspector.ApplicationCacheManifest(manifestURL);
+ this._applicationCacheObjects[frame.id] = new WebInspector.ApplicationCacheFrame(frame, cacheManifest, status);
+
+ this.dispatchEventToListeners(WebInspector.ApplicationCacheManager.Event.FrameManifestAdded, {frameManifest: this._applicationCacheObjects[frame.id]});
+ }
+
+ if (statusChanged)
+ this.dispatchEventToListeners(WebInspector.ApplicationCacheManager.Event.FrameManifestStatusChanged, {frameManifest: this._applicationCacheObjects[frame.id]});
+ }
+
+ _frameManifestRemoved(frame)
+ {
+ if (!this._applicationCacheObjects[frame.id])
+ return;
+
+ delete this._applicationCacheObjects[frame.id];
+
+ this.dispatchEventToListeners(WebInspector.ApplicationCacheManager.Event.FrameManifestRemoved, {frame});
+ }
+};
+
+WebInspector.ApplicationCacheManager.Event = {
+ Cleared: "application-cache-manager-cleared",
+ FrameManifestAdded: "application-cache-manager-frame-manifest-added",
+ FrameManifestRemoved: "application-cache-manager-frame-manifest-removed",
+ FrameManifestStatusChanged: "application-cache-manager-frame-manifest-status-changed",
+ NetworkStateUpdated: "application-cache-manager-network-state-updated"
+};
+
+WebInspector.ApplicationCacheManager.Status = {
+ Uncached: 0,
+ Idle: 1,
+ Checking: 2,
+ Downloading: 3,
+ UpdateReady: 4,
+ Obsolete: 5
+};
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/BasicBlockAnnotator.js b/Source/WebInspectorUI/UserInterface/Controllers/BasicBlockAnnotator.js
new file mode 100644
index 000000000..b4b86f9d3
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/BasicBlockAnnotator.js
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ * Copyright (C) 2015 Saam Barati <saambarati1@gmail.com>
+ *
+ * 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.BasicBlockAnnotator = class BasicBlockAnnotator extends WebInspector.Annotator
+{
+ constructor(sourceCodeTextEditor, script)
+ {
+ super(sourceCodeTextEditor);
+
+ this._script = script;
+ this._basicBlockMarkers = new Map; // Only contains unexecuted basic blocks.
+ }
+
+ // Protected
+
+ clearAnnotations()
+ {
+ for (var key of this._basicBlockMarkers.keys())
+ this._clearRangeForBasicBlockMarker(key);
+ }
+
+ insertAnnotations()
+ {
+ if (!this.isActive())
+ return;
+ this._annotateBasicBlockExecutionRanges();
+ }
+
+ // Private
+
+ _annotateBasicBlockExecutionRanges()
+ {
+ var sourceID = this._script.id;
+ var startTime = Date.now();
+
+ this._script.target.RuntimeAgent.getBasicBlocks(sourceID, function(error, basicBlocks) {
+ if (error) {
+ console.error("Error in getting basic block locations: " + error);
+ return;
+ }
+
+ if (!this.isActive())
+ return;
+
+ var {startOffset, endOffset} = this.sourceCodeTextEditor.visibleRangeOffsets();
+ basicBlocks = basicBlocks.filter(function(block) {
+ // Viewport: [--]
+ // Block: [--]
+ if (block.startOffset > endOffset)
+ return false;
+
+ // Viewport: [--]
+ // Block: [--]
+ if (block.endOffset < startOffset)
+ return false;
+
+ return true;
+ });
+
+ for (var block of basicBlocks) {
+ var key = block.startOffset + ":" + block.endOffset;
+ var hasKey = this._basicBlockMarkers.has(key);
+ var hasExecuted = block.hasExecuted;
+ if (hasKey && hasExecuted)
+ this._clearRangeForBasicBlockMarker(key);
+ else if (!hasKey && !hasExecuted) {
+ var marker = this._highlightTextForBasicBlock(block);
+ this._basicBlockMarkers.set(key, marker);
+ }
+ }
+
+ var totalTime = Date.now() - startTime;
+ var timeoutTime = Number.constrain(30 * totalTime, 500, 5000);
+ this._timeoutIdentifier = setTimeout(this.insertAnnotations.bind(this), timeoutTime);
+ }.bind(this));
+ }
+
+ _highlightTextForBasicBlock(basicBlock)
+ {
+ console.assert(basicBlock.startOffset <= basicBlock.endOffset && basicBlock.startOffset >= 0 && basicBlock.endOffset >= 0, "" + basicBlock.startOffset + ":" + basicBlock.endOffset);
+ console.assert(!basicBlock.hasExecuted);
+
+ var startPosition = this.sourceCodeTextEditor.originalOffsetToCurrentPosition(basicBlock.startOffset);
+ var endPosition = this.sourceCodeTextEditor.originalOffsetToCurrentPosition(basicBlock.endOffset);
+ if (this._isTextRangeOnlyClosingBrace(startPosition, endPosition))
+ return null;
+
+ var marker = this.sourceCodeTextEditor.addStyleToTextRange(startPosition, endPosition, WebInspector.BasicBlockAnnotator.HasNotExecutedClassName);
+ return marker;
+ }
+
+ _isTextRangeOnlyClosingBrace(startPosition, endPosition)
+ {
+ var isOnlyClosingBrace = /^\s*\}$/;
+ return isOnlyClosingBrace.test(this.sourceCodeTextEditor.getTextInRange(startPosition, endPosition));
+ }
+
+ _clearRangeForBasicBlockMarker(key)
+ {
+ console.assert(this._basicBlockMarkers.has(key));
+ var marker = this._basicBlockMarkers.get(key);
+ if (marker)
+ marker.clear();
+ this._basicBlockMarkers.delete(key);
+ }
+};
+
+WebInspector.BasicBlockAnnotator.HasNotExecutedClassName = "basic-block-has-not-executed";
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/BranchManager.js b/Source/WebInspectorUI/UserInterface/Controllers/BranchManager.js
new file mode 100644
index 000000000..6dc8de1ca
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/BranchManager.js
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2013 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.BranchManager = class BranchManager extends WebInspector.Object
+{
+ constructor()
+ {
+ super();
+
+ WebInspector.Frame.addEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
+
+ this.initialize();
+ }
+
+ // Public
+
+ initialize()
+ {
+ this._originalBranch = new WebInspector.Branch(WebInspector.UIString("Original"), null, true);
+ this._currentBranch = this._originalBranch.fork(WebInspector.UIString("Working Copy"));
+ this._branches = [this._originalBranch, this._currentBranch];
+ }
+
+ get branches()
+ {
+ return this._branches;
+ }
+
+ get currentBranch()
+ {
+ return this._currentBranch;
+ }
+
+ set currentBranch(branch)
+ {
+ console.assert(branch instanceof WebInspector.Branch);
+ if (!(branch instanceof WebInspector.Branch))
+ return;
+
+ this._currentBranch.revert();
+
+ this._currentBranch = branch;
+
+ this._currentBranch.apply();
+ }
+
+ createBranch(displayName, fromBranch)
+ {
+ if (!fromBranch)
+ fromBranch = this._originalBranch;
+
+ console.assert(fromBranch instanceof WebInspector.Branch);
+ if (!(fromBranch instanceof WebInspector.Branch))
+ return null;
+
+ var newBranch = fromBranch.fork(displayName);
+ this._branches.push(newBranch);
+ return newBranch;
+ }
+
+ deleteBranch(branch)
+ {
+ console.assert(branch instanceof WebInspector.Branch);
+ if (!(branch instanceof WebInspector.Branch))
+ return;
+
+ console.assert(branch !== this._originalBranch);
+ if (branch === this._originalBranch)
+ return;
+
+ this._branches.remove(branch);
+
+ if (branch === this._currentBranch)
+ this._currentBranch = this._originalBranch;
+ }
+
+ // Private
+
+ _mainResourceDidChange(event)
+ {
+ console.assert(event.target instanceof WebInspector.Frame);
+
+ if (!event.target.isMainFrame())
+ return;
+
+ this.initialize();
+ }
+};
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/BreakpointLogMessageLexer.js b/Source/WebInspectorUI/UserInterface/Controllers/BreakpointLogMessageLexer.js
new file mode 100644
index 000000000..071704a6b
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/BreakpointLogMessageLexer.js
@@ -0,0 +1,197 @@
+/*
+ * 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.BreakpointLogMessageLexer = class BreakpointLogMessageLexer extends WebInspector.Object
+{
+ constructor()
+ {
+ super();
+
+ this._stateFunctions = {
+ [WebInspector.BreakpointLogMessageLexer.State.Expression]: this._expression,
+ [WebInspector.BreakpointLogMessageLexer.State.PlainText]: this._plainText,
+ [WebInspector.BreakpointLogMessageLexer.State.PossiblePlaceholder]: this._possiblePlaceholder,
+ [WebInspector.BreakpointLogMessageLexer.State.RegExpOrStringLiteral]: this._regExpOrStringLiteral,
+ };
+
+ this.reset();
+ }
+
+ // Public
+
+ tokenize(input)
+ {
+ this.reset();
+ this._input = input;
+
+ while (this._index < this._input.length) {
+ let stateFunction = this._stateFunctions[this._states.lastValue];
+ console.assert(stateFunction);
+ if (!stateFunction) {
+ this.reset();
+ return null;
+ }
+
+ stateFunction.call(this);
+ }
+
+ // Needed for trailing plain text.
+ this._finishPlainText();
+
+ return this._tokens;
+ }
+
+ reset()
+ {
+ this._input = "";
+ this._buffer = "";
+
+ this._index = 0;
+ this._states = [WebInspector.BreakpointLogMessageLexer.State.PlainText];
+ this._literalStartCharacter = "";
+ this._curlyBraceDepth = 0;
+ this._tokens = [];
+ }
+
+ // Private
+
+ _finishPlainText()
+ {
+ this._appendToken(WebInspector.BreakpointLogMessageLexer.TokenType.PlainText);
+ }
+
+ _finishExpression()
+ {
+ this._appendToken(WebInspector.BreakpointLogMessageLexer.TokenType.Expression);
+ }
+
+ _appendToken(type)
+ {
+ if (!this._buffer)
+ return;
+
+ this._tokens.push({type, data: this._buffer});
+ this._buffer = "";
+ }
+
+ _consume()
+ {
+ console.assert(this._index < this._input.length);
+
+ let character = this._peek();
+ this._index++;
+ return character;
+ }
+
+ _peek()
+ {
+ return this._input[this._index] || null;
+ }
+
+ // States
+
+ _expression()
+ {
+ let character = this._consume();
+
+ if (character === "}") {
+ if (this._curlyBraceDepth === 0) {
+ this._finishExpression();
+
+ console.assert(this._states.lastValue === WebInspector.BreakpointLogMessageLexer.State.Expression);
+ this._states.pop();
+ return;
+ }
+
+ this._curlyBraceDepth--;
+ }
+
+ this._buffer += character;
+
+ if (character === "/" || character === "\"" || character === "'") {
+ this._literalStartCharacter = character;
+ this._states.push(WebInspector.BreakpointLogMessageLexer.State.RegExpOrStringLiteral);
+ } else if (character === "{")
+ this._curlyBraceDepth++;
+ }
+
+ _plainText()
+ {
+ let character = this._peek();
+
+ if (character === "$")
+ this._states.push(WebInspector.BreakpointLogMessageLexer.State.PossiblePlaceholder);
+ else {
+ this._buffer += character;
+ this._consume();
+ }
+ }
+
+ _possiblePlaceholder()
+ {
+ let character = this._consume();
+ console.assert(character === "$");
+ let nextCharacter = this._peek();
+
+ console.assert(this._states.lastValue === WebInspector.BreakpointLogMessageLexer.State.PossiblePlaceholder);
+ this._states.pop();
+
+ if (nextCharacter === "{") {
+ this._finishPlainText();
+ this._consume();
+ this._states.push(WebInspector.BreakpointLogMessageLexer.State.Expression);
+ } else
+ this._buffer += character;
+ }
+
+ _regExpOrStringLiteral()
+ {
+ let character = this._consume();
+ this._buffer += character;
+
+ if (character === "\\") {
+ if (this._peek() !== null)
+ this._buffer += this._consume();
+ return;
+ }
+
+ if (character === this._literalStartCharacter) {
+ console.assert(this._states.lastValue === WebInspector.BreakpointLogMessageLexer.State.RegExpOrStringLiteral);
+ this._states.pop();
+ }
+ }
+};
+
+WebInspector.BreakpointLogMessageLexer.State = {
+ Expression: Symbol("expression"),
+ PlainText: Symbol("plain-text"),
+ PossiblePlaceholder: Symbol("possible-placeholder"),
+ RegExpOrStringLiteral: Symbol("regexp-or-string-literal"),
+};
+
+WebInspector.BreakpointLogMessageLexer.TokenType = {
+ PlainText: "token-type-plain-text",
+ Expression: "token-type-expression",
+};
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/BreakpointPopoverController.js b/Source/WebInspectorUI/UserInterface/Controllers/BreakpointPopoverController.js
new file mode 100644
index 000000000..1b30fc98c
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/BreakpointPopoverController.js
@@ -0,0 +1,382 @@
+/*
+ * Copyright (C) 2015 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.BreakpointPopoverController = class BreakpointPopoverController extends WebInspector.Object
+{
+ constructor()
+ {
+ super();
+
+ this._breakpoint = null;
+ this._popover = null;
+ this._popoverContentElement = null;
+ }
+
+ // Public
+
+ appendContextMenuItems(contextMenu, breakpoint, breakpointDisplayElement)
+ {
+ console.assert(document.body.contains(breakpointDisplayElement), "Breakpoint popover display element must be in the DOM.");
+
+ const editBreakpoint = () => {
+ console.assert(!this._popover, "Breakpoint popover already exists.");
+ if (this._popover)
+ return;
+
+ this._createPopoverContent(breakpoint);
+ this._popover = new WebInspector.Popover(this);
+ this._popover.content = this._popoverContentElement;
+
+ let bounds = WebInspector.Rect.rectFromClientRect(breakpointDisplayElement.getBoundingClientRect());
+ bounds.origin.x -= 1; // Move the anchor left one pixel so it looks more centered.
+ this._popover.present(bounds.pad(2), [WebInspector.RectEdge.MAX_Y]);
+ };
+
+ const removeBreakpoint = () => {
+ WebInspector.debuggerManager.removeBreakpoint(breakpoint);
+ };
+
+ const toggleBreakpoint = () => {
+ breakpoint.disabled = !breakpoint.disabled;
+ };
+
+ const toggleAutoContinue = () => {
+ breakpoint.autoContinue = !breakpoint.autoContinue;
+ };
+
+ const revealOriginalSourceCodeLocation = () => {
+ WebInspector.showOriginalOrFormattedSourceCodeLocation(breakpoint.sourceCodeLocation);
+ };
+
+ if (WebInspector.debuggerManager.isBreakpointEditable(breakpoint))
+ contextMenu.appendItem(WebInspector.UIString("Edit Breakpoint…"), editBreakpoint);
+
+ if (breakpoint.autoContinue && !breakpoint.disabled) {
+ contextMenu.appendItem(WebInspector.UIString("Disable Breakpoint"), toggleBreakpoint);
+ contextMenu.appendItem(WebInspector.UIString("Cancel Automatic Continue"), toggleAutoContinue);
+ } else if (!breakpoint.disabled)
+ contextMenu.appendItem(WebInspector.UIString("Disable Breakpoint"), toggleBreakpoint);
+ else
+ contextMenu.appendItem(WebInspector.UIString("Enable Breakpoint"), toggleBreakpoint);
+
+ if (!breakpoint.autoContinue && !breakpoint.disabled && breakpoint.actions.length)
+ contextMenu.appendItem(WebInspector.UIString("Set to Automatically Continue"), toggleAutoContinue);
+
+ if (WebInspector.debuggerManager.isBreakpointRemovable(breakpoint)) {
+ contextMenu.appendSeparator();
+ contextMenu.appendItem(WebInspector.UIString("Delete Breakpoint"), removeBreakpoint);
+ }
+
+ if (breakpoint._sourceCodeLocation.hasMappedLocation()) {
+ contextMenu.appendSeparator();
+ contextMenu.appendItem(WebInspector.UIString("Reveal in Original Resource"), revealOriginalSourceCodeLocation);
+ }
+ }
+
+ // CodeMirrorCompletionController delegate
+
+ completionControllerShouldAllowEscapeCompletion()
+ {
+ return false;
+ }
+
+ // Private
+
+ _createPopoverContent(breakpoint)
+ {
+ console.assert(!this._popoverContentElement, "Popover content element already exists.");
+ if (this._popoverContentElement)
+ return;
+
+ this._breakpoint = breakpoint;
+ this._popoverContentElement = document.createElement("div");
+ this._popoverContentElement.className = "edit-breakpoint-popover-content";
+
+ let checkboxElement = document.createElement("input");
+ checkboxElement.type = "checkbox";
+ checkboxElement.checked = !this._breakpoint.disabled;
+ checkboxElement.addEventListener("change", this._popoverToggleEnabledCheckboxChanged.bind(this));
+
+ let checkboxLabel = document.createElement("label");
+ checkboxLabel.className = "toggle";
+ checkboxLabel.appendChild(checkboxElement);
+ checkboxLabel.append(this._breakpoint.sourceCodeLocation.displayLocationString());
+
+ let table = document.createElement("table");
+
+ let conditionRow = table.appendChild(document.createElement("tr"));
+ let conditionHeader = conditionRow.appendChild(document.createElement("th"));
+ let conditionData = conditionRow.appendChild(document.createElement("td"));
+ let conditionLabel = conditionHeader.appendChild(document.createElement("label"));
+ conditionLabel.textContent = WebInspector.UIString("Condition");
+ let conditionEditorElement = conditionData.appendChild(document.createElement("div"));
+ conditionEditorElement.classList.add("edit-breakpoint-popover-condition", WebInspector.SyntaxHighlightedStyleClassName);
+
+ this._conditionCodeMirror = WebInspector.CodeMirrorEditor.create(conditionEditorElement, {
+ extraKeys: {Tab: false},
+ lineWrapping: false,
+ mode: "text/javascript",
+ matchBrackets: true,
+ placeholder: WebInspector.UIString("Conditional expression"),
+ scrollbarStyle: null,
+ value: this._breakpoint.condition || "",
+ });
+
+ let conditionCodeMirrorInputField = this._conditionCodeMirror.getInputField();
+ conditionCodeMirrorInputField.id = "codemirror-condition-input-field";
+ conditionLabel.setAttribute("for", conditionCodeMirrorInputField.id);
+
+ this._conditionCodeMirrorEscapeOrEnterKeyHandler = this._conditionCodeMirrorEscapeOrEnterKey.bind(this);
+ this._conditionCodeMirror.addKeyMap({
+ "Esc": this._conditionCodeMirrorEscapeOrEnterKeyHandler,
+ "Enter": this._conditionCodeMirrorEscapeOrEnterKeyHandler,
+ });
+
+ this._conditionCodeMirror.on("change", this._conditionCodeMirrorChanged.bind(this));
+ this._conditionCodeMirror.on("beforeChange", this._conditionCodeMirrorBeforeChange.bind(this));
+
+ let completionController = new WebInspector.CodeMirrorCompletionController(this._conditionCodeMirror, this);
+ completionController.addExtendedCompletionProvider("javascript", WebInspector.javaScriptRuntimeCompletionProvider);
+
+ // CodeMirror needs a refresh after the popover displays, to layout, otherwise it doesn't appear.
+ setTimeout(() => {
+ this._conditionCodeMirror.refresh();
+ this._conditionCodeMirror.focus();
+ }, 0);
+
+ // COMPATIBILITY (iOS 7): Debugger.setBreakpoint did not support options.
+ if (DebuggerAgent.setBreakpoint.supports("options")) {
+ // COMPATIBILITY (iOS 9): Legacy backends don't support breakpoint ignore count. Since support
+ // can't be tested directly, check for CSS.getSupportedSystemFontFamilyNames.
+ // FIXME: Use explicit version checking once https://webkit.org/b/148680 is fixed.
+ if (CSSAgent.getSupportedSystemFontFamilyNames) {
+ let ignoreCountRow = table.appendChild(document.createElement("tr"));
+ let ignoreCountHeader = ignoreCountRow.appendChild(document.createElement("th"));
+ let ignoreCountLabel = ignoreCountHeader.appendChild(document.createElement("label"));
+ let ignoreCountData = ignoreCountRow.appendChild(document.createElement("td"));
+ this._ignoreCountInput = ignoreCountData.appendChild(document.createElement("input"));
+ this._ignoreCountInput.id = "edit-breakpoint-popover-ignore";
+ this._ignoreCountInput.type = "number";
+ this._ignoreCountInput.min = 0;
+ this._ignoreCountInput.value = 0;
+ this._ignoreCountInput.addEventListener("change", this._popoverIgnoreInputChanged.bind(this));
+
+ ignoreCountLabel.setAttribute("for", this._ignoreCountInput.id);
+ ignoreCountLabel.textContent = WebInspector.UIString("Ignore");
+
+ this._ignoreCountText = ignoreCountData.appendChild(document.createElement("span"));
+ this._updateIgnoreCountText();
+ }
+
+ let actionRow = table.appendChild(document.createElement("tr"));
+ let actionHeader = actionRow.appendChild(document.createElement("th"));
+ let actionData = this._actionsContainer = actionRow.appendChild(document.createElement("td"));
+ let actionLabel = actionHeader.appendChild(document.createElement("label"));
+ actionLabel.textContent = WebInspector.UIString("Action");
+
+ if (!this._breakpoint.actions.length)
+ this._popoverActionsCreateAddActionButton();
+ else {
+ this._popoverContentElement.classList.add(WebInspector.BreakpointPopoverController.WidePopoverClassName);
+ for (let i = 0; i < this._breakpoint.actions.length; ++i) {
+ let breakpointActionView = new WebInspector.BreakpointActionView(this._breakpoint.actions[i], this, true);
+ this._popoverActionsInsertBreakpointActionView(breakpointActionView, i);
+ }
+ }
+
+ let optionsRow = this._popoverOptionsRowElement = table.appendChild(document.createElement("tr"));
+ if (!this._breakpoint.actions.length)
+ optionsRow.classList.add(WebInspector.BreakpointPopoverController.HiddenStyleClassName);
+ let optionsHeader = optionsRow.appendChild(document.createElement("th"));
+ let optionsData = optionsRow.appendChild(document.createElement("td"));
+ let optionsLabel = optionsHeader.appendChild(document.createElement("label"));
+ let optionsCheckbox = this._popoverOptionsCheckboxElement = optionsData.appendChild(document.createElement("input"));
+ let optionsCheckboxLabel = optionsData.appendChild(document.createElement("label"));
+ optionsCheckbox.id = "edit-breakpoint-popoover-auto-continue";
+ optionsCheckbox.type = "checkbox";
+ optionsCheckbox.checked = this._breakpoint.autoContinue;
+ optionsCheckbox.addEventListener("change", this._popoverToggleAutoContinueCheckboxChanged.bind(this));
+ optionsLabel.textContent = WebInspector.UIString("Options");
+ optionsCheckboxLabel.setAttribute("for", optionsCheckbox.id);
+ optionsCheckboxLabel.textContent = WebInspector.UIString("Automatically continue after evaluating");
+ }
+
+ this._popoverContentElement.appendChild(checkboxLabel);
+ this._popoverContentElement.appendChild(table);
+ }
+
+ _popoverToggleEnabledCheckboxChanged(event)
+ {
+ this._breakpoint.disabled = !event.target.checked;
+ }
+
+ _conditionCodeMirrorChanged(codeMirror, change)
+ {
+ this._breakpoint.condition = (codeMirror.getValue() || "").trim();
+ }
+
+ _conditionCodeMirrorBeforeChange(codeMirror, change)
+ {
+ if (change.update) {
+ let newText = change.text.join("").replace(/\n/g, "");
+ change.update(change.from, change.to, [newText]);
+ }
+
+ return true;
+ }
+
+ _conditionCodeMirrorEscapeOrEnterKey()
+ {
+ if (!this._popover)
+ return;
+
+ this._popover.dismiss();
+ }
+
+ _popoverIgnoreInputChanged(event)
+ {
+ let ignoreCount = 0;
+ if (event.target.value) {
+ ignoreCount = parseInt(event.target.value, 10);
+ if (isNaN(ignoreCount) || ignoreCount < 0)
+ ignoreCount = 0;
+ }
+
+ this._ignoreCountInput.value = ignoreCount;
+ this._breakpoint.ignoreCount = ignoreCount;
+
+ this._updateIgnoreCountText();
+ }
+
+ _popoverToggleAutoContinueCheckboxChanged(event)
+ {
+ this._breakpoint.autoContinue = event.target.checked;
+ }
+
+ _popoverActionsCreateAddActionButton()
+ {
+ this._popoverContentElement.classList.remove(WebInspector.BreakpointPopoverController.WidePopoverClassName);
+ this._actionsContainer.removeChildren();
+
+ let addActionButton = this._actionsContainer.appendChild(document.createElement("button"));
+ addActionButton.textContent = WebInspector.UIString("Add Action");
+ addActionButton.addEventListener("click", this._popoverActionsAddActionButtonClicked.bind(this));
+ }
+
+ _popoverActionsAddActionButtonClicked(event)
+ {
+ this._popoverContentElement.classList.add(WebInspector.BreakpointPopoverController.WidePopoverClassName);
+ this._actionsContainer.removeChildren();
+
+ let newAction = this._breakpoint.createAction(WebInspector.Breakpoint.DefaultBreakpointActionType);
+ let newBreakpointActionView = new WebInspector.BreakpointActionView(newAction, this);
+ this._popoverActionsInsertBreakpointActionView(newBreakpointActionView, -1);
+ this._popoverOptionsRowElement.classList.remove(WebInspector.BreakpointPopoverController.HiddenStyleClassName);
+ this._popover.update();
+ }
+
+ _popoverActionsInsertBreakpointActionView(breakpointActionView, index)
+ {
+ if (index === -1)
+ this._actionsContainer.appendChild(breakpointActionView.element);
+ else {
+ let nextElement = this._actionsContainer.children[index + 1] || null;
+ this._actionsContainer.insertBefore(breakpointActionView.element, nextElement);
+ }
+ }
+
+ _updateIgnoreCountText()
+ {
+ if (this._breakpoint.ignoreCount === 1)
+ this._ignoreCountText.textContent = WebInspector.UIString("time before stopping");
+ else
+ this._ignoreCountText.textContent = WebInspector.UIString("times before stopping");
+ }
+
+ breakpointActionViewAppendActionView(breakpointActionView, newAction)
+ {
+ let newBreakpointActionView = new WebInspector.BreakpointActionView(newAction, this);
+
+ let index = 0;
+ let children = this._actionsContainer.children;
+ for (let i = 0; children.length; ++i) {
+ if (children[i] === breakpointActionView.element) {
+ index = i;
+ break;
+ }
+ }
+
+ this._popoverActionsInsertBreakpointActionView(newBreakpointActionView, index);
+ this._popoverOptionsRowElement.classList.remove(WebInspector.BreakpointPopoverController.HiddenStyleClassName);
+
+ this._popover.update();
+ }
+
+ breakpointActionViewRemoveActionView(breakpointActionView)
+ {
+ breakpointActionView.element.remove();
+
+ if (!this._actionsContainer.children.length) {
+ this._popoverActionsCreateAddActionButton();
+ this._popoverOptionsRowElement.classList.add(WebInspector.BreakpointPopoverController.HiddenStyleClassName);
+ this._popoverOptionsCheckboxElement.checked = false;
+ }
+
+ this._popover.update();
+ }
+
+ breakpointActionViewResized(breakpointActionView)
+ {
+ this._popover.update();
+ }
+
+ willDismissPopover(popover)
+ {
+ console.assert(this._popover === popover);
+ this._popoverContentElement = null;
+ this._popoverOptionsRowElement = null;
+ this._popoverOptionsCheckboxElement = null;
+ this._actionsContainer = null;
+ this._popover = null;
+ }
+
+ didDismissPopover(popover)
+ {
+ // Remove Evaluate and Probe actions that have no data.
+ let emptyActions = this._breakpoint.actions.filter(function(action) {
+ if (action.type !== WebInspector.BreakpointAction.Type.Evaluate && action.type !== WebInspector.BreakpointAction.Type.Probe)
+ return false;
+ return !(action.data && action.data.trim());
+ });
+
+ for (let action of emptyActions)
+ this._breakpoint.removeAction(action);
+
+ this._breakpoint = null;
+ }
+};
+
+WebInspector.BreakpointPopoverController.WidePopoverClassName = "wide";
+WebInspector.BreakpointPopoverController.HiddenStyleClassName = "hidden";
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/CSSStyleManager.js b/Source/WebInspectorUI/UserInterface/Controllers/CSSStyleManager.js
new file mode 100644
index 000000000..8e101e31f
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/CSSStyleManager.js
@@ -0,0 +1,551 @@
+/*
+ * Copyright (C) 2013 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.CSSStyleManager = class CSSStyleManager extends WebInspector.Object
+{
+ constructor()
+ {
+ super();
+
+ if (window.CSSAgent)
+ CSSAgent.enable();
+
+ WebInspector.Frame.addEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
+ WebInspector.Frame.addEventListener(WebInspector.Frame.Event.ResourceWasAdded, this._resourceAdded, this);
+ WebInspector.Resource.addEventListener(WebInspector.SourceCode.Event.ContentDidChange, this._resourceContentDidChange, this);
+ WebInspector.Resource.addEventListener(WebInspector.Resource.Event.TypeDidChange, this._resourceTypeDidChange, this);
+
+ WebInspector.DOMNode.addEventListener(WebInspector.DOMNode.Event.AttributeModified, this._nodeAttributesDidChange, this);
+ WebInspector.DOMNode.addEventListener(WebInspector.DOMNode.Event.AttributeRemoved, this._nodeAttributesDidChange, this);
+ WebInspector.DOMNode.addEventListener(WebInspector.DOMNode.Event.EnabledPseudoClassesChanged, this._nodePseudoClassesDidChange, this);
+
+ this._colorFormatSetting = new WebInspector.Setting("default-color-format", WebInspector.Color.Format.Original);
+
+ this._styleSheetIdentifierMap = new Map;
+ this._styleSheetFrameURLMap = new Map;
+ this._nodeStylesMap = {};
+
+ // COMPATIBILITY (iOS 9): Legacy backends did not send stylesheet
+ // added/removed events and must be fetched manually.
+ this._fetchedInitialStyleSheets = window.CSSAgent && window.CSSAgent.hasEvent("styleSheetAdded");
+ }
+
+ // Static
+
+ static protocolStyleSheetOriginToEnum(origin)
+ {
+ switch (origin) {
+ case CSSAgent.StyleSheetOrigin.Regular:
+ return WebInspector.CSSStyleSheet.Type.Author;
+ case CSSAgent.StyleSheetOrigin.User:
+ return WebInspector.CSSStyleSheet.Type.User;
+ case CSSAgent.StyleSheetOrigin.UserAgent:
+ return WebInspector.CSSStyleSheet.Type.UserAgent;
+ case CSSAgent.StyleSheetOrigin.Inspector:
+ return WebInspector.CSSStyleSheet.Type.Inspector;
+ default:
+ console.assert(false, "Unknown CSS.StyleSheetOrigin", origin);
+ return CSSAgent.StyleSheetOrigin.Regular;
+ }
+ }
+
+ static protocolMediaSourceToEnum(source)
+ {
+ switch (source) {
+ case CSSAgent.CSSMediaSource.MediaRule:
+ return WebInspector.CSSMedia.Type.MediaRule;
+ case CSSAgent.CSSMediaSource.ImportRule:
+ return WebInspector.CSSMedia.Type.ImportRule;
+ case CSSAgent.CSSMediaSource.LinkedSheet:
+ return WebInspector.CSSMedia.Type.LinkedStyleSheet;
+ case CSSAgent.CSSMediaSource.InlineSheet:
+ return WebInspector.CSSMedia.Type.InlineStyleSheet;
+ default:
+ console.assert(false, "Unknown CSS.CSSMediaSource", source);
+ return WebInspector.CSSMedia.Type.MediaRule;
+ }
+ }
+
+ // Public
+
+ get preferredColorFormat()
+ {
+ return this._colorFormatSetting.value;
+ }
+
+ get styleSheets()
+ {
+ return [...this._styleSheetIdentifierMap.values()];
+ }
+
+ canForcePseudoClasses()
+ {
+ return window.CSSAgent && !!CSSAgent.forcePseudoState;
+ }
+
+ propertyNameHasOtherVendorPrefix(name)
+ {
+ if (!name || name.length < 4 || name.charAt(0) !== "-")
+ return false;
+
+ var match = name.match(/^(?:-moz-|-ms-|-o-|-epub-)/);
+ if (!match)
+ return false;
+
+ return true;
+ }
+
+ propertyValueHasOtherVendorKeyword(value)
+ {
+ var match = value.match(/(?:-moz-|-ms-|-o-|-epub-)[-\w]+/);
+ if (!match)
+ return false;
+
+ return true;
+ }
+
+ canonicalNameForPropertyName(name)
+ {
+ if (!name || name.length < 8 || name.charAt(0) !== "-")
+ return name;
+
+ var match = name.match(/^(?:-webkit-|-khtml-|-apple-)(.+)/);
+ if (!match)
+ return name;
+
+ return match[1];
+ }
+
+ fetchStyleSheetsIfNeeded()
+ {
+ if (this._fetchedInitialStyleSheets)
+ return;
+
+ this._fetchInfoForAllStyleSheets(function() {});
+ }
+
+ styleSheetForIdentifier(id)
+ {
+ let styleSheet = this._styleSheetIdentifierMap.get(id);
+ if (styleSheet)
+ return styleSheet;
+
+ styleSheet = new WebInspector.CSSStyleSheet(id);
+ this._styleSheetIdentifierMap.set(id, styleSheet);
+ return styleSheet;
+ }
+
+ stylesForNode(node)
+ {
+ if (node.id in this._nodeStylesMap)
+ return this._nodeStylesMap[node.id];
+
+ var styles = new WebInspector.DOMNodeStyles(node);
+ this._nodeStylesMap[node.id] = styles;
+ return styles;
+ }
+
+ preferredInspectorStyleSheetForFrame(frame, callback)
+ {
+ var inspectorStyleSheets = this._inspectorStyleSheetsForFrame(frame);
+ for (let styleSheet of inspectorStyleSheets) {
+ if (styleSheet[WebInspector.CSSStyleManager.PreferredInspectorStyleSheetSymbol]) {
+ callback(styleSheet);
+ return;
+ }
+ }
+
+ if (CSSAgent.createStyleSheet) {
+ CSSAgent.createStyleSheet(frame.id, function(error, styleSheetId) {
+ let styleSheet = WebInspector.cssStyleManager.styleSheetForIdentifier(styleSheetId);
+ styleSheet[WebInspector.CSSStyleManager.PreferredInspectorStyleSheetSymbol] = true;
+ callback(styleSheet);
+ });
+ return;
+ }
+
+ // COMPATIBILITY (iOS 9): CSS.createStyleSheet did not exist.
+ // Legacy backends can only create the Inspector StyleSheet through CSS.addRule.
+ // Exploit that to create the Inspector StyleSheet for the document.body node in
+ // this frame, then get the StyleSheet for the new rule.
+
+ let expression = appendWebInspectorSourceURL("document");
+ let contextId = frame.pageExecutionContext.id;
+ RuntimeAgent.evaluate.invoke({expression, objectGroup: "", includeCommandLineAPI: false, doNotPauseOnExceptionsAndMuteConsole: true, contextId, returnByValue: false, generatePreview: false}, documentAvailable);
+
+ function documentAvailable(error, documentRemoteObjectPayload)
+ {
+ if (error) {
+ callback(null);
+ return;
+ }
+
+ let remoteObject = WebInspector.RemoteObject.fromPayload(documentRemoteObjectPayload);
+ remoteObject.pushNodeToFrontend(documentNodeAvailable.bind(null, remoteObject));
+ }
+
+ function documentNodeAvailable(remoteObject, documentNodeId)
+ {
+ remoteObject.release();
+
+ if (!documentNodeId) {
+ callback(null);
+ return;
+ }
+
+ DOMAgent.querySelector(documentNodeId, "body", bodyNodeAvailable);
+ }
+
+ function bodyNodeAvailable(error, bodyNodeId)
+ {
+ if (error) {
+ console.error(error);
+ callback(null);
+ return;
+ }
+
+ let selector = ""; // Intentionally empty.
+ CSSAgent.addRule(bodyNodeId, selector, cssRuleAvailable);
+ }
+
+ function cssRuleAvailable(error, payload)
+ {
+ if (error || !payload.ruleId) {
+ callback(null);
+ return;
+ }
+
+ let styleSheetId = payload.ruleId.styleSheetId;
+ let styleSheet = WebInspector.cssStyleManager.styleSheetForIdentifier(styleSheetId);
+ if (!styleSheet) {
+ callback(null);
+ return;
+ }
+
+ styleSheet[WebInspector.CSSStyleManager.PreferredInspectorStyleSheetSymbol] = true;
+
+ console.assert(styleSheet.isInspectorStyleSheet());
+ console.assert(styleSheet.parentFrame === frame);
+
+ callback(styleSheet);
+ }
+ }
+
+ mediaTypeChanged()
+ {
+ // Act the same as if media queries changed.
+ this.mediaQueryResultChanged();
+ }
+
+ // Protected
+
+ mediaQueryResultChanged()
+ {
+ // Called from WebInspector.CSSObserver.
+
+ for (var key in this._nodeStylesMap)
+ this._nodeStylesMap[key].mediaQueryResultDidChange();
+ }
+
+ styleSheetChanged(styleSheetIdentifier)
+ {
+ // Called from WebInspector.CSSObserver.
+ var styleSheet = this.styleSheetForIdentifier(styleSheetIdentifier);
+ console.assert(styleSheet);
+
+ // Do not observe inline styles
+ if (styleSheet.isInlineStyleAttributeStyleSheet())
+ return;
+
+ styleSheet.noteContentDidChange();
+ this._updateResourceContent(styleSheet);
+ }
+
+ styleSheetAdded(styleSheetInfo)
+ {
+ console.assert(!this._styleSheetIdentifierMap.has(styleSheetInfo.styleSheetId), "Attempted to add a CSSStyleSheet but identifier was already in use");
+ let styleSheet = this.styleSheetForIdentifier(styleSheetInfo.styleSheetId);
+ let parentFrame = WebInspector.frameResourceManager.frameForIdentifier(styleSheetInfo.frameId);
+ let origin = WebInspector.CSSStyleManager.protocolStyleSheetOriginToEnum(styleSheetInfo.origin);
+ styleSheet.updateInfo(styleSheetInfo.sourceURL, parentFrame, origin, styleSheetInfo.isInline, styleSheetInfo.startLine, styleSheetInfo.startColumn);
+
+ this.dispatchEventToListeners(WebInspector.CSSStyleManager.Event.StyleSheetAdded, {styleSheet});
+ }
+
+ styleSheetRemoved(styleSheetIdentifier)
+ {
+ let styleSheet = this._styleSheetIdentifierMap.get(styleSheetIdentifier);
+ console.assert(styleSheet, "Attempted to remove a CSSStyleSheet that was not tracked");
+ if (!styleSheet)
+ return;
+
+ this._styleSheetIdentifierMap.delete(styleSheetIdentifier);
+
+ this.dispatchEventToListeners(WebInspector.CSSStyleManager.Event.StyleSheetRemoved, {styleSheet});
+ }
+
+ // Private
+
+ _inspectorStyleSheetsForFrame(frame)
+ {
+ let styleSheets = [];
+
+ for (let styleSheet of this.styleSheets) {
+ if (styleSheet.isInspectorStyleSheet() && styleSheet.parentFrame === frame)
+ styleSheets.push(styleSheet);
+ }
+
+ return styleSheets;
+ }
+
+ _nodePseudoClassesDidChange(event)
+ {
+ var node = event.target;
+
+ for (var key in this._nodeStylesMap) {
+ var nodeStyles = this._nodeStylesMap[key];
+ if (nodeStyles.node !== node && !nodeStyles.node.isDescendant(node))
+ continue;
+ nodeStyles.pseudoClassesDidChange(node);
+ }
+ }
+
+ _nodeAttributesDidChange(event)
+ {
+ var node = event.target;
+
+ for (var key in this._nodeStylesMap) {
+ var nodeStyles = this._nodeStylesMap[key];
+ if (nodeStyles.node !== node && !nodeStyles.node.isDescendant(node))
+ continue;
+ nodeStyles.attributeDidChange(node, event.data.name);
+ }
+ }
+
+ _mainResourceDidChange(event)
+ {
+ console.assert(event.target instanceof WebInspector.Frame);
+
+ if (!event.target.isMainFrame())
+ return;
+
+ // Clear our maps when the main frame navigates.
+
+ this._fetchedInitialStyleSheets = window.CSSAgent && window.CSSAgent.hasEvent("styleSheetAdded");
+ this._styleSheetIdentifierMap.clear();
+ this._styleSheetFrameURLMap.clear();
+ this._nodeStylesMap = {};
+ }
+
+ _resourceAdded(event)
+ {
+ console.assert(event.target instanceof WebInspector.Frame);
+
+ var resource = event.data.resource;
+ console.assert(resource);
+
+ if (resource.type !== WebInspector.Resource.Type.Stylesheet)
+ return;
+
+ this._clearStyleSheetsForResource(resource);
+ }
+
+ _resourceTypeDidChange(event)
+ {
+ console.assert(event.target instanceof WebInspector.Resource);
+
+ var resource = event.target;
+ if (resource.type !== WebInspector.Resource.Type.Stylesheet)
+ return;
+
+ this._clearStyleSheetsForResource(resource);
+ }
+
+ _clearStyleSheetsForResource(resource)
+ {
+ // Clear known stylesheets for this URL and frame. This will cause the stylesheets to
+ // be updated next time _fetchInfoForAllStyleSheets is called.
+ this._styleSheetIdentifierMap.delete(this._frameURLMapKey(resource.parentFrame, resource.url));
+ }
+
+ _frameURLMapKey(frame, url)
+ {
+ return frame.id + ":" + url;
+ }
+
+ _lookupStyleSheetForResource(resource, callback)
+ {
+ this._lookupStyleSheet(resource.parentFrame, resource.url, callback);
+ }
+
+ _lookupStyleSheet(frame, url, callback)
+ {
+ console.assert(frame instanceof WebInspector.Frame);
+
+ let key = this._frameURLMapKey(frame, url);
+
+ function styleSheetsFetched()
+ {
+ callback(this._styleSheetFrameURLMap.get(key) || null);
+ }
+
+ let styleSheet = this._styleSheetFrameURLMap.get(key) || null;
+ if (styleSheet)
+ callback(styleSheet);
+ else
+ this._fetchInfoForAllStyleSheets(styleSheetsFetched.bind(this));
+ }
+
+ _fetchInfoForAllStyleSheets(callback)
+ {
+ console.assert(typeof callback === "function");
+
+ function processStyleSheets(error, styleSheets)
+ {
+ this._styleSheetFrameURLMap.clear();
+
+ if (error) {
+ callback();
+ return;
+ }
+
+ for (let styleSheetInfo of styleSheets) {
+ let parentFrame = WebInspector.frameResourceManager.frameForIdentifier(styleSheetInfo.frameId);
+ let origin = WebInspector.CSSStyleManager.protocolStyleSheetOriginToEnum(styleSheetInfo.origin);
+
+ // COMPATIBILITY (iOS 9): The info did not have 'isInline', 'startLine', and 'startColumn', so make false and 0 in these cases.
+ let isInline = styleSheetInfo.isInline || false;
+ let startLine = styleSheetInfo.startLine || 0;
+ let startColumn = styleSheetInfo.startColumn || 0;
+
+ let styleSheet = this.styleSheetForIdentifier(styleSheetInfo.styleSheetId);
+ styleSheet.updateInfo(styleSheetInfo.sourceURL, parentFrame, origin, isInline, startLine, startColumn);
+
+ let key = this._frameURLMapKey(parentFrame, styleSheetInfo.sourceURL);
+ this._styleSheetFrameURLMap.set(key, styleSheet);
+ }
+
+ callback();
+ }
+
+ CSSAgent.getAllStyleSheets(processStyleSheets.bind(this));
+ }
+
+ _resourceContentDidChange(event)
+ {
+ var resource = event.target;
+ if (resource === this._ignoreResourceContentDidChangeEventForResource)
+ return;
+
+ // Ignore if it isn't a CSS stylesheet.
+ if (resource.type !== WebInspector.Resource.Type.Stylesheet || resource.syntheticMIMEType !== "text/css")
+ return;
+
+ function applyStyleSheetChanges()
+ {
+ function styleSheetFound(styleSheet)
+ {
+ resource.__pendingChangeTimeout = undefined;
+
+ console.assert(styleSheet);
+ if (!styleSheet)
+ return;
+
+ // To prevent updating a TextEditor's content while the user is typing in it we want to
+ // ignore the next _updateResourceContent call.
+ resource.__ignoreNextUpdateResourceContent = true;
+
+ WebInspector.branchManager.currentBranch.revisionForRepresentedObject(styleSheet).content = resource.content;
+ }
+
+ this._lookupStyleSheetForResource(resource, styleSheetFound.bind(this));
+ }
+
+ if (resource.__pendingChangeTimeout)
+ clearTimeout(resource.__pendingChangeTimeout);
+ resource.__pendingChangeTimeout = setTimeout(applyStyleSheetChanges.bind(this), 500);
+ }
+
+ _updateResourceContent(styleSheet)
+ {
+ console.assert(styleSheet);
+
+ function fetchedStyleSheetContent(parameters)
+ {
+ var styleSheet = parameters.sourceCode;
+ var content = parameters.content;
+
+ styleSheet.__pendingChangeTimeout = undefined;
+
+ console.assert(styleSheet.url);
+ if (!styleSheet.url)
+ return;
+
+ var resource = styleSheet.parentFrame.resourceForURL(styleSheet.url);
+ if (!resource)
+ return;
+
+ // Only try to update stylesheet resources. Other resources, like documents, can contain
+ // multiple stylesheets and we don't have the source ranges to update those.
+ if (resource.type !== WebInspector.Resource.Type.Stylesheet)
+ return;
+
+ if (resource.__ignoreNextUpdateResourceContent) {
+ resource.__ignoreNextUpdateResourceContent = false;
+ return;
+ }
+
+ this._ignoreResourceContentDidChangeEventForResource = resource;
+ WebInspector.branchManager.currentBranch.revisionForRepresentedObject(resource).content = content;
+ this._ignoreResourceContentDidChangeEventForResource = null;
+ }
+
+ function styleSheetReady()
+ {
+ styleSheet.requestContent().then(fetchedStyleSheetContent.bind(this));
+ }
+
+ function applyStyleSheetChanges()
+ {
+ if (styleSheet.url)
+ styleSheetReady.call(this);
+ else
+ this._fetchInfoForAllStyleSheets(styleSheetReady.bind(this));
+ }
+
+ if (styleSheet.__pendingChangeTimeout)
+ clearTimeout(styleSheet.__pendingChangeTimeout);
+ styleSheet.__pendingChangeTimeout = setTimeout(applyStyleSheetChanges.bind(this), 500);
+ }
+};
+
+WebInspector.CSSStyleManager.Event = {
+ StyleSheetAdded: "css-style-manager-style-sheet-added",
+ StyleSheetRemoved: "css-style-manager-style-sheet-removed",
+};
+
+WebInspector.CSSStyleManager.PseudoElementNames = ["before", "after"];
+WebInspector.CSSStyleManager.ForceablePseudoClasses = ["active", "focus", "hover", "visited"];
+WebInspector.CSSStyleManager.PreferredInspectorStyleSheetSymbol = Symbol("css-style-manager-preferred-inspector-stylesheet");
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorBezierEditingController.js b/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorBezierEditingController.js
new file mode 100644
index 000000000..e79da97f9
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorBezierEditingController.js
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2015 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.CodeMirrorBezierEditingController = class CodeMirrorBezierEditingController extends WebInspector.CodeMirrorEditingController
+{
+ constructor(codeMirror, marker)
+ {
+ super(codeMirror, marker);
+ }
+
+ // Public
+
+ get initialValue()
+ {
+ return WebInspector.CubicBezier.fromString(this.text);
+ }
+
+ get cssClassName()
+ {
+ return "cubic-bezier";
+ }
+
+ popoverWillPresent(popover)
+ {
+ this._bezierEditor = new WebInspector.BezierEditor;
+ this._bezierEditor.addEventListener(WebInspector.BezierEditor.Event.BezierChanged, this._bezierEditorBezierChanged, this);
+ popover.content = this._bezierEditor.element;
+ }
+
+ popoverDidPresent(popover)
+ {
+ this._bezierEditor.bezier = this.value;
+ }
+
+ popoverDidDismiss(popover)
+ {
+ this._bezierEditor.removeListeners();
+ }
+
+ // Private
+
+ _bezierEditorBezierChanged(event)
+ {
+ this.value = event.data.bezier;
+ }
+};
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorColorEditingController.js b/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorColorEditingController.js
new file mode 100644
index 000000000..b45846fd5
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorColorEditingController.js
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2013 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.CodeMirrorColorEditingController = class CodeMirrorColorEditingController extends WebInspector.CodeMirrorEditingController
+{
+ constructor(codeMirror, marker)
+ {
+ super(codeMirror, marker);
+ }
+
+ // Public
+
+ get initialValue()
+ {
+ return WebInspector.Color.fromString(this.text);
+ }
+
+ get cssClassName()
+ {
+ return "color";
+ }
+
+ popoverWillPresent(popover)
+ {
+ this._colorPicker = new WebInspector.ColorPicker;
+ this._colorPicker.addEventListener(WebInspector.ColorPicker.Event.ColorChanged, this._colorPickerColorChanged, this);
+ this._colorPicker.addEventListener(WebInspector.ColorPicker.Event.FormatChanged, (event) => popover.update());
+ popover.content = this._colorPicker.element;
+ }
+
+ popoverDidPresent(popover)
+ {
+ this._colorPicker.color = this._value;
+ }
+
+ // Private
+
+ _colorPickerColorChanged(event)
+ {
+ this.value = event.target.color;
+ }
+};
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorCompletionController.css b/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorCompletionController.css
new file mode 100644
index 000000000..eff18cfb1
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorCompletionController.css
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2013 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+.CodeMirror .CodeMirror-lines .completion-hint {
+ text-decoration: none !important;
+ opacity: 0.4;
+}
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorCompletionController.js b/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorCompletionController.js
new file mode 100644
index 000000000..33d7ccbd6
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorCompletionController.js
@@ -0,0 +1,875 @@
+/*
+ * Copyright (C) 2013 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.CodeMirrorCompletionController = class CodeMirrorCompletionController extends WebInspector.Object
+{
+ constructor(codeMirror, delegate, stopCharactersRegex)
+ {
+ super();
+
+ console.assert(codeMirror);
+
+ this._codeMirror = codeMirror;
+ this._stopCharactersRegex = stopCharactersRegex || null;
+ this._delegate = delegate || null;
+
+ this._startOffset = NaN;
+ this._endOffset = NaN;
+ this._lineNumber = NaN;
+ this._prefix = "";
+ this._noEndingSemicolon = false;
+ this._completions = [];
+ this._extendedCompletionProviders = {};
+
+ this._suggestionsView = new WebInspector.CompletionSuggestionsView(this);
+
+ this._keyMap = {
+ "Up": this._handleUpKey.bind(this),
+ "Down": this._handleDownKey.bind(this),
+ "Right": this._handleRightOrEnterKey.bind(this),
+ "Esc": this._handleEscapeKey.bind(this),
+ "Enter": this._handleRightOrEnterKey.bind(this),
+ "Tab": this._handleTabKey.bind(this),
+ "Cmd-A": this._handleHideKey.bind(this),
+ "Cmd-Z": this._handleHideKey.bind(this),
+ "Shift-Cmd-Z": this._handleHideKey.bind(this),
+ "Cmd-Y": this._handleHideKey.bind(this)
+ };
+
+ this._handleChangeListener = this._handleChange.bind(this);
+ this._handleCursorActivityListener = this._handleCursorActivity.bind(this);
+ this._handleHideActionListener = this._handleHideAction.bind(this);
+
+ this._codeMirror.addKeyMap(this._keyMap);
+
+ this._codeMirror.on("change", this._handleChangeListener);
+ this._codeMirror.on("cursorActivity", this._handleCursorActivityListener);
+ this._codeMirror.on("blur", this._handleHideActionListener);
+ this._codeMirror.on("scroll", this._handleHideActionListener);
+
+ this._updatePromise = null;
+ }
+
+ // Public
+
+ get delegate()
+ {
+ return this._delegate;
+ }
+
+ addExtendedCompletionProvider(modeName, provider)
+ {
+ this._extendedCompletionProviders[modeName] = provider;
+ }
+
+ updateCompletions(completions, implicitSuffix)
+ {
+ if (isNaN(this._startOffset) || isNaN(this._endOffset) || isNaN(this._lineNumber))
+ return;
+
+ if (!completions || !completions.length) {
+ this.hideCompletions();
+ return;
+ }
+
+ this._completions = completions;
+
+ if (typeof implicitSuffix === "string")
+ this._implicitSuffix = implicitSuffix;
+
+ var from = {line: this._lineNumber, ch: this._startOffset};
+ var to = {line: this._lineNumber, ch: this._endOffset};
+
+ var firstCharCoords = this._codeMirror.cursorCoords(from);
+ var lastCharCoords = this._codeMirror.cursorCoords(to);
+ var bounds = new WebInspector.Rect(firstCharCoords.left, firstCharCoords.top, lastCharCoords.right - firstCharCoords.left, firstCharCoords.bottom - firstCharCoords.top);
+
+ // Try to restore the previous selected index, otherwise just select the first.
+ var index = this._currentCompletion ? completions.indexOf(this._currentCompletion) : 0;
+ if (index === -1)
+ index = 0;
+
+ if (this._forced || completions.length > 1 || completions[index] !== this._prefix) {
+ // Update and show the suggestion list.
+ this._suggestionsView.update(completions, index);
+ this._suggestionsView.show(bounds);
+ } else if (this._implicitSuffix) {
+ // The prefix and the completion exactly match, but there is an implicit suffix.
+ // Just hide the suggestion list and keep the completion hint for the implicit suffix.
+ this._suggestionsView.hide();
+ } else {
+ // The prefix and the completion exactly match, hide the completions. Return early so
+ // the completion hint isn't updated.
+ this.hideCompletions();
+ return;
+ }
+
+ this._applyCompletionHint(completions[index]);
+
+ this._resolveUpdatePromise(WebInspector.CodeMirrorCompletionController.UpdatePromise.CompletionsFound);
+ }
+
+ isCompletionChange(change)
+ {
+ return this._ignoreChange || change.origin === WebInspector.CodeMirrorCompletionController.CompletionOrigin || change.origin === WebInspector.CodeMirrorCompletionController.DeleteCompletionOrigin;
+ }
+
+ isShowingCompletions()
+ {
+ return this._suggestionsView.visible || (this._completionHintMarker && this._completionHintMarker.find());
+ }
+
+ isHandlingClickEvent()
+ {
+ return this._suggestionsView.isHandlingClickEvent();
+ }
+
+ hideCompletions()
+ {
+ this._suggestionsView.hide();
+
+ this._removeCompletionHint();
+
+ this._startOffset = NaN;
+ this._endOffset = NaN;
+ this._lineNumber = NaN;
+ this._prefix = "";
+ this._completions = [];
+ this._implicitSuffix = "";
+ this._forced = false;
+
+ delete this._currentCompletion;
+ delete this._ignoreNextCursorActivity;
+
+ this._resolveUpdatePromise(WebInspector.CodeMirrorCompletionController.UpdatePromise.NoCompletionsFound);
+ }
+
+ close()
+ {
+ this._codeMirror.removeKeyMap(this._keyMap);
+
+ this._codeMirror.off("change", this._handleChangeListener);
+ this._codeMirror.off("cursorActivity", this._handleCursorActivityListener);
+ this._codeMirror.off("blur", this._handleHideActionListener);
+ this._codeMirror.off("scroll", this._handleHideActionListener);
+ }
+
+ completeAtCurrentPositionIfNeeded(force)
+ {
+ this._resolveUpdatePromise(WebInspector.CodeMirrorCompletionController.UpdatePromise.Canceled);
+
+ var update = this._updatePromise = new WebInspector.WrappedPromise;
+
+ this._completeAtCurrentPosition(force);
+
+ return update.promise;
+ }
+
+ // Protected
+
+ completionSuggestionsSelectedCompletion(suggestionsView, completionText)
+ {
+ this._applyCompletionHint(completionText);
+ }
+
+ completionSuggestionsClickedCompletion(suggestionsView, completionText)
+ {
+ // The clicked suggestion causes the editor to loose focus. Restore it so the user can keep typing.
+ this._codeMirror.focus();
+
+ this._applyCompletionHint(completionText);
+ this._commitCompletionHint();
+ }
+
+ set noEndingSemicolon(noEndingSemicolon)
+ {
+ this._noEndingSemicolon = noEndingSemicolon;
+ }
+
+ // Private
+
+ _resolveUpdatePromise(message)
+ {
+ if (!this._updatePromise)
+ return;
+
+ this._updatePromise.resolve(message);
+ this._updatePromise = null;
+ }
+
+ get _currentReplacementText()
+ {
+ return this._currentCompletion + this._implicitSuffix;
+ }
+
+ _hasPendingCompletion()
+ {
+ return !isNaN(this._startOffset) && !isNaN(this._endOffset) && !isNaN(this._lineNumber);
+ }
+
+ _notifyCompletionsHiddenSoon()
+ {
+ function notify()
+ {
+ if (this._completionHintMarker)
+ return;
+
+ if (this._delegate && typeof this._delegate.completionControllerCompletionsHidden === "function")
+ this._delegate.completionControllerCompletionsHidden(this);
+ }
+
+ if (this._notifyCompletionsHiddenIfNeededTimeout)
+ clearTimeout(this._notifyCompletionsHiddenIfNeededTimeout);
+ this._notifyCompletionsHiddenIfNeededTimeout = setTimeout(notify.bind(this), WebInspector.CodeMirrorCompletionController.CompletionsHiddenDelay);
+ }
+
+ _createCompletionHintMarker(position, text)
+ {
+ var container = document.createElement("span");
+ container.classList.add(WebInspector.CodeMirrorCompletionController.CompletionHintStyleClassName);
+ container.textContent = text;
+
+ this._completionHintMarker = this._codeMirror.setUniqueBookmark(position, {widget: container, insertLeft: true});
+ }
+
+ _applyCompletionHint(completionText)
+ {
+ console.assert(completionText);
+ if (!completionText)
+ return;
+
+ function update()
+ {
+ this._currentCompletion = completionText;
+
+ this._removeCompletionHint(true, true);
+
+ var replacementText = this._currentReplacementText;
+
+ var from = {line: this._lineNumber, ch: this._startOffset};
+ var cursor = {line: this._lineNumber, ch: this._endOffset};
+ var currentText = this._codeMirror.getRange(from, cursor);
+
+ this._createCompletionHintMarker(cursor, replacementText.replace(currentText, ""));
+ }
+
+ this._ignoreChange = true;
+ this._ignoreNextCursorActivity = true;
+
+ this._codeMirror.operation(update.bind(this));
+
+ delete this._ignoreChange;
+ }
+
+ _commitCompletionHint()
+ {
+ function update()
+ {
+ this._removeCompletionHint(true, true);
+
+ var replacementText = this._currentReplacementText;
+
+ var from = {line: this._lineNumber, ch: this._startOffset};
+ var cursor = {line: this._lineNumber, ch: this._endOffset};
+ var to = {line: this._lineNumber, ch: this._startOffset + replacementText.length};
+
+ var lastChar = this._currentCompletion.charAt(this._currentCompletion.length - 1);
+ var isClosing = ")]}".indexOf(lastChar);
+ if (isClosing !== -1)
+ to.ch -= 1 + this._implicitSuffix.length;
+
+ this._codeMirror.replaceRange(replacementText, from, cursor, WebInspector.CodeMirrorCompletionController.CompletionOrigin);
+
+ // Don't call _removeLastChangeFromHistory here to allow the committed completion to be undone.
+
+ this._codeMirror.setCursor(to);
+
+ this.hideCompletions();
+ }
+
+ this._ignoreChange = true;
+ this._ignoreNextCursorActivity = true;
+
+ this._codeMirror.operation(update.bind(this));
+
+ delete this._ignoreChange;
+ }
+
+ _removeLastChangeFromHistory()
+ {
+ var history = this._codeMirror.getHistory();
+
+ // We don't expect a undone history. But if there is one clear it. If could lead to undefined behavior.
+ console.assert(!history.undone.length);
+ history.undone = [];
+
+ // Pop the last item from the done history.
+ console.assert(history.done.length);
+ history.done.pop();
+
+ this._codeMirror.setHistory(history);
+ }
+
+ _removeCompletionHint(nonatomic, dontRestorePrefix)
+ {
+ if (!this._completionHintMarker)
+ return;
+
+ this._notifyCompletionsHiddenSoon();
+
+ function clearMarker(marker)
+ {
+ if (!marker)
+ return;
+
+ var range = marker.find();
+ if (range)
+ marker.clear();
+
+ return null;
+ }
+
+ function update()
+ {
+ this._completionHintMarker = clearMarker(this._completionHintMarker);
+
+ if (dontRestorePrefix)
+ return;
+
+ console.assert(!isNaN(this._startOffset));
+ console.assert(!isNaN(this._endOffset));
+ console.assert(!isNaN(this._lineNumber));
+
+ var from = {line: this._lineNumber, ch: this._startOffset};
+ var to = {line: this._lineNumber, ch: this._endOffset};
+
+ this._codeMirror.replaceRange(this._prefix, from, to, WebInspector.CodeMirrorCompletionController.DeleteCompletionOrigin);
+ this._removeLastChangeFromHistory();
+ }
+
+ if (nonatomic) {
+ update.call(this);
+ return;
+ }
+
+ this._ignoreChange = true;
+
+ this._codeMirror.operation(update.bind(this));
+
+ delete this._ignoreChange;
+ }
+
+ _scanStringForExpression(modeName, string, startOffset, direction, allowMiddleAndEmpty, includeStopCharacter, ignoreInitialUnmatchedOpenBracket, stopCharactersRegex)
+ {
+ console.assert(direction === -1 || direction === 1);
+
+ var stopCharactersRegex = stopCharactersRegex || this._stopCharactersRegex || WebInspector.CodeMirrorCompletionController.DefaultStopCharactersRegexModeMap[modeName] || WebInspector.CodeMirrorCompletionController.GenericStopCharactersRegex;
+
+ function isStopCharacter(character)
+ {
+ return stopCharactersRegex.test(character);
+ }
+
+ function isOpenBracketCharacter(character)
+ {
+ return WebInspector.CodeMirrorCompletionController.OpenBracketCharactersRegex.test(character);
+ }
+
+ function isCloseBracketCharacter(character)
+ {
+ return WebInspector.CodeMirrorCompletionController.CloseBracketCharactersRegex.test(character);
+ }
+
+ function matchingBracketCharacter(character)
+ {
+ return WebInspector.CodeMirrorCompletionController.MatchingBrackets[character];
+ }
+
+ var endOffset = Math.min(startOffset, string.length);
+
+ var endOfLineOrWord = endOffset === string.length || isStopCharacter(string.charAt(endOffset));
+
+ if (!endOfLineOrWord && !allowMiddleAndEmpty)
+ return null;
+
+ var bracketStack = [];
+ var bracketOffsetStack = [];
+
+ var startOffset = endOffset;
+ var firstOffset = endOffset + direction;
+ for (var i = firstOffset; direction > 0 ? i < string.length : i >= 0; i += direction) {
+ var character = string.charAt(i);
+
+ // Ignore stop characters when we are inside brackets.
+ if (isStopCharacter(character) && !bracketStack.length)
+ break;
+
+ if (isCloseBracketCharacter(character)) {
+ bracketStack.push(character);
+ bracketOffsetStack.push(i);
+ } else if (isOpenBracketCharacter(character)) {
+ if ((!ignoreInitialUnmatchedOpenBracket || i !== firstOffset) && (!bracketStack.length || matchingBracketCharacter(character) !== bracketStack.lastValue))
+ break;
+
+ bracketOffsetStack.pop();
+ bracketStack.pop();
+ }
+
+ startOffset = i + (direction > 0 ? 1 : 0);
+ }
+
+ if (bracketOffsetStack.length)
+ startOffset = bracketOffsetStack.pop() + 1;
+
+ if (includeStopCharacter && startOffset > 0 && startOffset < string.length)
+ startOffset += direction;
+
+ if (direction > 0) {
+ var tempEndOffset = endOffset;
+ endOffset = startOffset;
+ startOffset = tempEndOffset;
+ }
+
+ return {string: string.substring(startOffset, endOffset), startOffset, endOffset};
+ }
+
+ _completeAtCurrentPosition(force)
+ {
+ if (this._codeMirror.somethingSelected()) {
+ this.hideCompletions();
+ return;
+ }
+
+ this._removeCompletionHint(true, true);
+
+ var cursor = this._codeMirror.getCursor();
+ var token = this._codeMirror.getTokenAt(cursor);
+
+ // Don't try to complete inside comments.
+ if (token.type && /\bcomment\b/.test(token.type)) {
+ this.hideCompletions();
+ return;
+ }
+
+ var mode = this._codeMirror.getMode();
+ var innerMode = CodeMirror.innerMode(mode, token.state).mode;
+ var modeName = innerMode.alternateName || innerMode.name;
+
+ var lineNumber = cursor.line;
+ var lineString = this._codeMirror.getLine(lineNumber);
+
+ var backwardScanResult = this._scanStringForExpression(modeName, lineString, cursor.ch, -1, force);
+ if (!backwardScanResult) {
+ this.hideCompletions();
+ return;
+ }
+
+ var forwardScanResult = this._scanStringForExpression(modeName, lineString, cursor.ch, 1, true, true);
+ var suffix = forwardScanResult.string;
+
+ this._ignoreNextCursorActivity = true;
+
+ this._startOffset = backwardScanResult.startOffset;
+ this._endOffset = backwardScanResult.endOffset;
+ this._lineNumber = lineNumber;
+ this._prefix = backwardScanResult.string;
+ this._completions = [];
+ this._implicitSuffix = "";
+ this._forced = force;
+
+ var baseExpressionStopCharactersRegex = WebInspector.CodeMirrorCompletionController.BaseExpressionStopCharactersRegexModeMap[modeName];
+ if (baseExpressionStopCharactersRegex)
+ var baseScanResult = this._scanStringForExpression(modeName, lineString, this._startOffset, -1, true, false, true, baseExpressionStopCharactersRegex);
+
+ if (!force && !backwardScanResult.string && (!baseScanResult || !baseScanResult.string)) {
+ this.hideCompletions();
+ return;
+ }
+
+ var defaultCompletions = [];
+
+ switch (modeName) {
+ case "css":
+ defaultCompletions = this._generateCSSCompletions(token, baseScanResult ? baseScanResult.string : null, suffix);
+ break;
+ case "javascript":
+ defaultCompletions = this._generateJavaScriptCompletions(token, baseScanResult ? baseScanResult.string : null, suffix);
+ break;
+ }
+
+ var extendedCompletionsProvider = this._extendedCompletionProviders[modeName];
+ if (extendedCompletionsProvider) {
+ extendedCompletionsProvider.completionControllerCompletionsNeeded(this, defaultCompletions, baseScanResult ? baseScanResult.string : null, this._prefix, suffix, force);
+ return;
+ }
+
+ if (this._delegate && typeof this._delegate.completionControllerCompletionsNeeded === "function")
+ this._delegate.completionControllerCompletionsNeeded(this, this._prefix, defaultCompletions, baseScanResult ? baseScanResult.string : null, suffix, force);
+ else
+ this.updateCompletions(defaultCompletions);
+ }
+
+ _generateCSSCompletions(mainToken, base, suffix)
+ {
+ // We only support completion inside CSS block context.
+ if (mainToken.state.state === "media" || mainToken.state.state === "top" || mainToken.state.state === "parens")
+ return [];
+
+ // Don't complete in the middle of a property name.
+ if (/^[a-z]/i.test(suffix))
+ return [];
+
+ var token = mainToken;
+ var lineNumber = this._lineNumber;
+
+ // Scan backwards looking for the current property.
+ while (token.state.state === "prop") {
+ // Found the beginning of the line. Go to the previous line.
+ if (!token.start) {
+ --lineNumber;
+
+ // No more lines, stop.
+ if (lineNumber < 0)
+ break;
+ }
+
+ // Get the previous token.
+ token = this._codeMirror.getTokenAt({line: lineNumber, ch: token.start ? token.start : Number.MAX_VALUE});
+ }
+
+ // If we have a property token and it's not the main token, then we are working on
+ // the value for that property and should complete allowed values.
+ if (mainToken !== token && token.type && /\bproperty\b/.test(token.type)) {
+ var propertyName = token.string;
+
+ // If there is a suffix and it isn't a semicolon, then we should use a space since
+ // the user is editing in the middle. Likewise if the suffix starts with an open
+ // paren we are changing a function name so don't add a suffix.
+ this._implicitSuffix = " ";
+ if (suffix === ";")
+ this._implicitSuffix = this._noEndingSemicolon ? "" : ";";
+ else if (suffix.startsWith("("))
+ this._implicitSuffix = "";
+
+ // Don't use an implicit suffix if it would be the same as the existing suffix.
+ if (this._implicitSuffix === suffix)
+ this._implicitSuffix = "";
+
+ let completions = WebInspector.CSSKeywordCompletions.forProperty(propertyName).startsWith(this._prefix);
+
+ if (suffix.startsWith("("))
+ completions = completions.map((x) => x.replace(/\(\)$/, ""));
+
+ return completions;
+ }
+
+ this._implicitSuffix = suffix !== ":" ? ": " : "";
+
+ // Complete property names.
+ return WebInspector.CSSCompletions.cssNameCompletions.startsWith(this._prefix);
+ }
+
+ _generateJavaScriptCompletions(mainToken, base, suffix)
+ {
+ // If there is a base expression then we should not attempt to match any keywords or variables.
+ // Allow only open bracket characters at the end of the base, otherwise leave completions with
+ // a base up to the delegate to figure out.
+ if (base && !/[({[]$/.test(base))
+ return [];
+
+ var matchingWords = [];
+
+ var prefix = this._prefix;
+
+ var localState = mainToken.state.localState ? mainToken.state.localState : mainToken.state;
+
+ var declaringVariable = localState.lexical.type === "vardef";
+ var insideSwitch = localState.lexical.prev ? localState.lexical.prev.info === "switch" : false;
+ var insideBlock = localState.lexical.prev ? localState.lexical.prev.type === "}" : false;
+ var insideParenthesis = localState.lexical.type === ")";
+ var insideBrackets = localState.lexical.type === "]";
+
+ // FIXME: Include module keywords if we know this is a module environment.
+ // var moduleKeywords = ["default", "export", "import"];
+
+ var allKeywords = [
+ "break", "case", "catch", "class", "const", "continue", "debugger", "default",
+ "delete", "do", "else", "extends", "false", "finally", "for", "function",
+ "if", "in", "Infinity", "instanceof", "let", "NaN", "new", "null", "of",
+ "return", "static", "super", "switch", "this", "throw", "true", "try",
+ "typeof", "undefined", "var", "void", "while", "with", "yield"
+ ];
+ var valueKeywords = ["false", "Infinity", "NaN", "null", "this", "true", "undefined"];
+
+ var allowedKeywordsInsideBlocks = allKeywords.keySet();
+ var allowedKeywordsWhenDeclaringVariable = valueKeywords.keySet();
+ var allowedKeywordsInsideParenthesis = valueKeywords.concat(["class", "function"]).keySet();
+ var allowedKeywordsInsideBrackets = allowedKeywordsInsideParenthesis;
+ var allowedKeywordsOnlyInsideSwitch = ["case", "default"].keySet();
+
+ function matchKeywords(keywords)
+ {
+ matchingWords = matchingWords.concat(keywords.filter(function(word) {
+ if (!insideSwitch && word in allowedKeywordsOnlyInsideSwitch)
+ return false;
+ if (insideBlock && !(word in allowedKeywordsInsideBlocks))
+ return false;
+ if (insideBrackets && !(word in allowedKeywordsInsideBrackets))
+ return false;
+ if (insideParenthesis && !(word in allowedKeywordsInsideParenthesis))
+ return false;
+ if (declaringVariable && !(word in allowedKeywordsWhenDeclaringVariable))
+ return false;
+ return word.startsWith(prefix);
+ }));
+ }
+
+ function matchVariables()
+ {
+ function filterVariables(variables)
+ {
+ for (var variable = variables; variable; variable = variable.next) {
+ // Don't match the variable if this token is in a variable declaration.
+ // Otherwise the currently typed text will always match and that isn't useful.
+ if (declaringVariable && variable.name === prefix)
+ continue;
+
+ if (variable.name.startsWith(prefix) && !matchingWords.includes(variable.name))
+ matchingWords.push(variable.name);
+ }
+ }
+
+ var context = localState.context;
+ while (context) {
+ if (context.vars)
+ filterVariables(context.vars);
+ context = context.prev;
+ }
+
+ if (localState.localVars)
+ filterVariables(localState.localVars);
+ if (localState.globalVars)
+ filterVariables(localState.globalVars);
+ }
+
+ switch (suffix.substring(0, 1)) {
+ case "":
+ case " ":
+ matchVariables();
+ matchKeywords(allKeywords);
+ break;
+
+ case ".":
+ case "[":
+ matchVariables();
+ matchKeywords(["false", "Infinity", "NaN", "this", "true"]);
+ break;
+
+ case "(":
+ matchVariables();
+ matchKeywords(["catch", "else", "for", "function", "if", "return", "switch", "throw", "while", "with", "yield"]);
+ break;
+
+ case "{":
+ matchKeywords(["do", "else", "finally", "return", "try", "yield"]);
+ break;
+
+ case ":":
+ if (insideSwitch)
+ matchKeywords(["case", "default"]);
+ break;
+
+ case ";":
+ matchVariables();
+ matchKeywords(valueKeywords);
+ matchKeywords(["break", "continue", "debugger", "return", "void"]);
+ break;
+ }
+
+ return matchingWords;
+ }
+
+ _handleUpKey(codeMirror)
+ {
+ if (!this._hasPendingCompletion())
+ return CodeMirror.Pass;
+
+ if (!this.isShowingCompletions())
+ return;
+
+ this._suggestionsView.selectPrevious();
+ }
+
+ _handleDownKey(codeMirror)
+ {
+ if (!this._hasPendingCompletion())
+ return CodeMirror.Pass;
+
+ if (!this.isShowingCompletions())
+ return;
+
+ this._suggestionsView.selectNext();
+ }
+
+ _handleRightOrEnterKey(codeMirror)
+ {
+ if (!this._hasPendingCompletion())
+ return CodeMirror.Pass;
+
+ if (!this.isShowingCompletions())
+ return;
+
+ this._commitCompletionHint();
+ }
+
+ _handleEscapeKey(codeMirror)
+ {
+ var delegateImplementsShouldAllowEscapeCompletion = this._delegate && typeof this._delegate.completionControllerShouldAllowEscapeCompletion === "function";
+ if (this._hasPendingCompletion())
+ this.hideCompletions();
+ else if (this._codeMirror.getOption("readOnly"))
+ return CodeMirror.Pass;
+ else if (!delegateImplementsShouldAllowEscapeCompletion || this._delegate.completionControllerShouldAllowEscapeCompletion(this))
+ this._completeAtCurrentPosition(true);
+ else
+ return CodeMirror.Pass;
+ }
+
+ _handleTabKey(codeMirror)
+ {
+ if (!this._hasPendingCompletion())
+ return CodeMirror.Pass;
+
+ if (!this.isShowingCompletions())
+ return;
+
+ console.assert(this._completions.length);
+ if (!this._completions.length)
+ return;
+
+ console.assert(this._currentCompletion);
+ if (!this._currentCompletion)
+ return;
+
+ // Commit the current completion if there is only one suggestion.
+ if (this._completions.length === 1) {
+ this._commitCompletionHint();
+ return;
+ }
+
+ var prefixLength = this._prefix.length;
+
+ var commonPrefix = this._completions[0];
+ for (var i = 1; i < this._completions.length; ++i) {
+ var completion = this._completions[i];
+ var lastIndex = Math.min(commonPrefix.length, completion.length);
+ for (var j = prefixLength; j < lastIndex; ++j) {
+ if (commonPrefix[j] !== completion[j]) {
+ commonPrefix = commonPrefix.substr(0, j);
+ break;
+ }
+ }
+ }
+
+ // Commit the current completion if there is no common prefix that is longer.
+ if (commonPrefix === this._prefix) {
+ this._commitCompletionHint();
+ return;
+ }
+
+ // Set the prefix to the common prefix so _applyCompletionHint will insert the
+ // common prefix as commited text. Adjust _endOffset to match the new prefix.
+ this._prefix = commonPrefix;
+ this._endOffset = this._startOffset + commonPrefix.length;
+
+ this._applyCompletionHint(this._currentCompletion);
+ }
+
+ _handleChange(codeMirror, change)
+ {
+ if (this.isCompletionChange(change))
+ return;
+
+ this._ignoreNextCursorActivity = true;
+
+ if (!change.origin || change.origin.charAt(0) !== "+") {
+ this.hideCompletions();
+ return;
+ }
+
+ // Only complete on delete if we are showing completions already.
+ if (change.origin === "+delete" && !this._hasPendingCompletion())
+ return;
+
+ this._completeAtCurrentPosition(false);
+ }
+
+ _handleCursorActivity(codeMirror)
+ {
+ if (this._ignoreChange)
+ return;
+
+ if (this._ignoreNextCursorActivity) {
+ delete this._ignoreNextCursorActivity;
+ return;
+ }
+
+ this.hideCompletions();
+ }
+
+ _handleHideKey(codeMirror)
+ {
+ this.hideCompletions();
+
+ return CodeMirror.Pass;
+ }
+
+ _handleHideAction(codeMirror)
+ {
+ // Clicking a suggestion causes the editor to blur. We don't want to hide completions in this case.
+ if (this.isHandlingClickEvent())
+ return;
+
+ this.hideCompletions();
+ }
+};
+
+WebInspector.CodeMirrorCompletionController.UpdatePromise = {
+ Canceled: "code-mirror-completion-controller-canceled",
+ CompletionsFound: "code-mirror-completion-controller-completions-found",
+ NoCompletionsFound: "code-mirror-completion-controller-no-completions-found"
+};
+
+WebInspector.CodeMirrorCompletionController.GenericStopCharactersRegex = /[\s=:;,]/;
+WebInspector.CodeMirrorCompletionController.DefaultStopCharactersRegexModeMap = {"css": /[\s:;,{}()]/, "javascript": /[\s=:;,!+\-*/%&|^~?<>.{}()[\]]/};
+WebInspector.CodeMirrorCompletionController.BaseExpressionStopCharactersRegexModeMap = {"javascript": /[\s=:;,!+\-*/%&|^~?<>]/};
+WebInspector.CodeMirrorCompletionController.OpenBracketCharactersRegex = /[({[]/;
+WebInspector.CodeMirrorCompletionController.CloseBracketCharactersRegex = /[)}\]]/;
+WebInspector.CodeMirrorCompletionController.MatchingBrackets = {"{": "}", "(": ")", "[": "]", "}": "{", ")": "(", "]": "["};
+WebInspector.CodeMirrorCompletionController.CompletionHintStyleClassName = "completion-hint";
+WebInspector.CodeMirrorCompletionController.CompletionsHiddenDelay = 250;
+WebInspector.CodeMirrorCompletionController.CompletionTypingDelay = 250;
+WebInspector.CodeMirrorCompletionController.CompletionOrigin = "+completion";
+WebInspector.CodeMirrorCompletionController.DeleteCompletionOrigin = "+delete-completion";
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorDragToAdjustNumberController.css b/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorDragToAdjustNumberController.css
new file mode 100644
index 000000000..d67d6b361
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorDragToAdjustNumberController.css
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2013 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+.CodeMirror.drag-to-adjust .CodeMirror-lines {
+ cursor: col-resize;
+}
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorDragToAdjustNumberController.js b/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorDragToAdjustNumberController.js
new file mode 100644
index 000000000..a8105998a
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorDragToAdjustNumberController.js
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2013 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.CodeMirrorDragToAdjustNumberController = class CodeMirrorDragToAdjustNumberController extends WebInspector.Object
+{
+ constructor(codeMirror)
+ {
+ super();
+
+ this._codeMirror = codeMirror;
+
+ this._dragToAdjustController = new WebInspector.DragToAdjustController(this);
+ }
+
+ // Public
+
+ get enabled()
+ {
+ return this._dragToAdjustController.enabled;
+ }
+
+ set enabled(enabled)
+ {
+ if (this.enabled === enabled)
+ return;
+
+ this._dragToAdjustController.element = this._codeMirror.getWrapperElement();
+ this._dragToAdjustController.enabled = enabled;
+ }
+
+ // Protected
+
+ dragToAdjustControllerActiveStateChanged(dragToAdjustController)
+ {
+ if (!dragToAdjustController.active)
+ this._hoveredTokenInfo = null;
+ }
+
+ dragToAdjustControllerCanBeActivated(dragToAdjustController)
+ {
+ return !this._codeMirror.getOption("readOnly");
+ }
+
+ dragToAdjustControllerCanBeAdjusted(dragToAdjustController)
+ {
+
+ return this._hoveredTokenInfo && this._hoveredTokenInfo.containsNumber;
+ }
+
+ dragToAdjustControllerWasAdjustedByAmount(dragToAdjustController, amount)
+ {
+ this._codeMirror.alterNumberInRange(amount, this._hoveredTokenInfo.startPosition, this._hoveredTokenInfo.endPosition, false);
+ }
+
+ dragToAdjustControllerDidReset(dragToAdjustController)
+ {
+ this._hoveredTokenInfo = null;
+ }
+
+ dragToAdjustControllerCanAdjustObjectAtPoint(dragToAdjustController, point)
+ {
+ var position = this._codeMirror.coordsChar({left: point.x, top: point.y});
+ var token = this._codeMirror.getTokenAt(position);
+
+ if (!token || !token.type || !token.string) {
+ if (this._hoveredTokenInfo)
+ dragToAdjustController.reset();
+ return false;
+ }
+
+ // Stop right here if we're hovering the same token as we were last time.
+ if (this._hoveredTokenInfo && this._hoveredTokenInfo.line === position.line &&
+ this._hoveredTokenInfo.token.start === token.start && this._hoveredTokenInfo.token.end === token.end)
+ return this._hoveredTokenInfo.token.type.indexOf("number") !== -1;
+
+ var containsNumber = token.type.indexOf("number") !== -1;
+ this._hoveredTokenInfo = {
+ token,
+ line: position.line,
+ containsNumber,
+ startPosition: {
+ ch: token.start,
+ line: position.line
+ },
+ endPosition: {
+ ch: token.end,
+ line: position.line
+ }
+ };
+
+ return containsNumber;
+ }
+};
+
+CodeMirror.defineOption("dragToAdjustNumbers", true, function(codeMirror, value, oldValue) {
+ if (!codeMirror.dragToAdjustNumberController)
+ codeMirror.dragToAdjustNumberController = new WebInspector.CodeMirrorDragToAdjustNumberController(codeMirror);
+ codeMirror.dragToAdjustNumberController.enabled = value;
+});
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorEditingController.js b/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorEditingController.js
new file mode 100644
index 000000000..3496ac437
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorEditingController.js
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2014 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.CodeMirrorEditingController = class CodeMirrorEditingController extends WebInspector.Object
+{
+ constructor(codeMirror, marker)
+ {
+ super();
+
+ this._codeMirror = codeMirror;
+ this._marker = marker;
+ this._delegate = null;
+
+ this._range = marker.range;
+
+ // The value must support .toString() and .copy() methods.
+ this._value = this.initialValue;
+
+ this._keyboardShortcutEsc = new WebInspector.KeyboardShortcut(null, WebInspector.KeyboardShortcut.Key.Escape);
+ }
+
+ // Public
+
+ get marker()
+ {
+ return this._marker;
+ }
+
+ get range()
+ {
+ return this._range;
+ }
+
+ get value()
+ {
+ return this._value;
+ }
+
+ set value(value)
+ {
+ this.text = value.toString();
+ this._value = value;
+ }
+
+ get delegate()
+ {
+ return this._delegate;
+ }
+
+ set delegate(delegate)
+ {
+ this._delegate = delegate;
+ }
+
+ get text()
+ {
+ var from = {line: this._range.startLine, ch: this._range.startColumn};
+ var to = {line: this._range.endLine, ch: this._range.endColumn};
+ return this._codeMirror.getRange(from, to);
+ }
+
+ set text(text)
+ {
+ var from = {line: this._range.startLine, ch: this._range.startColumn};
+ var to = {line: this._range.endLine, ch: this._range.endColumn};
+ this._codeMirror.replaceRange(text, from, to);
+
+ var lines = text.split("\n");
+ var endLine = this._range.startLine + lines.length - 1;
+ var endColumn = lines.length > 1 ? lines.lastValue.length : this._range.startColumn + text.length;
+ this._range = new WebInspector.TextRange(this._range.startLine, this._range.startColumn, endLine, endColumn);
+ }
+
+ get initialValue()
+ {
+ // Implemented by subclasses.
+ return this.text;
+ }
+
+ get cssClassName()
+ {
+ // Implemented by subclasses.
+ return "";
+ }
+
+ get popover()
+ {
+ return this._popover;
+ }
+
+ get popoverPreferredEdges()
+ {
+ // Best to display the popover to the left or above the edited range since its end position may change, but not its start
+ // position. This way we minimize the chances of overlaying the edited range as it changes.
+ return [WebInspector.RectEdge.MIN_X, WebInspector.RectEdge.MIN_Y, WebInspector.RectEdge.MAX_Y, WebInspector.RectEdge.MAX_X];
+ }
+
+ popoverTargetFrameWithRects(rects)
+ {
+ return WebInspector.Rect.unionOfRects(rects);
+ }
+
+ presentHoverMenu()
+ {
+ if (!this.cssClassName)
+ return;
+
+ this._hoverMenu = new WebInspector.HoverMenu(this);
+ this._hoverMenu.element.classList.add(this.cssClassName);
+ this._rects = this._marker.rects;
+ this._hoverMenu.present(this._rects);
+ }
+
+ dismissHoverMenu(discrete)
+ {
+ if (!this._hoverMenu)
+ return;
+
+ this._hoverMenu.dismiss(discrete);
+ }
+
+ popoverWillPresent(popover)
+ {
+ // Implemented by subclasses.
+ }
+
+ popoverDidPresent(popover)
+ {
+ // Implemented by subclasses.
+ }
+
+ popoverDidDismiss(popover)
+ {
+ // Implemented by subclasses.
+ }
+
+ // Protected
+
+ handleKeydownEvent(event)
+ {
+ if (!this._keyboardShortcutEsc.matchesEvent(event) || !this._popover.visible)
+ return false;
+
+ this.value = this._originalValue;
+ this._popover.dismiss();
+
+ return true;
+ }
+
+ hoverMenuButtonWasPressed(hoverMenu)
+ {
+ this._popover = new WebInspector.Popover(this);
+ this.popoverWillPresent(this._popover);
+ this._popover.present(this.popoverTargetFrameWithRects(this._rects).pad(2), this.popoverPreferredEdges);
+ this.popoverDidPresent(this._popover);
+
+ WebInspector.addWindowKeydownListener(this);
+
+ hoverMenu.dismiss();
+
+ if (this._delegate && typeof this._delegate.editingControllerDidStartEditing === "function")
+ this._delegate.editingControllerDidStartEditing(this);
+
+ this._originalValue = this._value.copy();
+ }
+
+ didDismissPopover(popover)
+ {
+ delete this._popover;
+ delete this._originalValue;
+
+ WebInspector.removeWindowKeydownListener(this);
+ this.popoverDidDismiss();
+
+ if (this._delegate && typeof this._delegate.editingControllerDidFinishEditing === "function")
+ this._delegate.editingControllerDidFinishEditing(this);
+ }
+};
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorGradientEditingController.js b/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorGradientEditingController.js
new file mode 100644
index 000000000..7ff38e0e7
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorGradientEditingController.js
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2014 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.CodeMirrorGradientEditingController = class CodeMirrorGradientEditingController extends WebInspector.CodeMirrorEditingController
+{
+ constructor(codeMirror, marker)
+ {
+ super(codeMirror, marker);
+ }
+
+ // Public
+
+ get initialValue()
+ {
+ return WebInspector.Gradient.fromString(this.text);
+ }
+
+ get cssClassName()
+ {
+ return "gradient";
+ }
+
+ get popoverPreferredEdges()
+ {
+ // Since the gradient editor can resize to be quite tall, let's avoid displaying the popover
+ // above the edited value so that it may not change which edge it attaches to upon editing a stop.
+ return [WebInspector.RectEdge.MIN_X, WebInspector.RectEdge.MAX_Y, WebInspector.RectEdge.MAX_X];
+ }
+
+ popoverTargetFrameWithRects(rects)
+ {
+ // If a gradient is defined across several lines, we probably want to use the first line only
+ // as a target frame for the editor since we may reformat the gradient value to fit on a single line.
+ return rects[0];
+ }
+
+ popoverWillPresent(popover)
+ {
+ function handleColorPickerToggled(event)
+ {
+ popover.update();
+ }
+
+ this._gradientEditor = new WebInspector.GradientEditor;
+ this._gradientEditor.addEventListener(WebInspector.GradientEditor.Event.GradientChanged, this._gradientEditorGradientChanged, this);
+ this._gradientEditor.addEventListener(WebInspector.GradientEditor.Event.ColorPickerToggled, handleColorPickerToggled, this);
+ popover.content = this._gradientEditor.element;
+ }
+
+ popoverDidPresent(popover)
+ {
+ this._gradientEditor.gradient = this.value;
+ }
+
+ // Private
+
+ _gradientEditorGradientChanged(event)
+ {
+ this.value = event.data.gradient;
+ }
+};
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorSpringEditingController.js b/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorSpringEditingController.js
new file mode 100644
index 000000000..7f1f91f14
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorSpringEditingController.js
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2016 Devin Rousso <dcrousso+webkit@gmail.com>. 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.CodeMirrorSpringEditingController = class CodeMirrorSpringEditingController extends WebInspector.CodeMirrorEditingController
+{
+ // Public
+
+ get initialValue()
+ {
+ return WebInspector.Spring.fromString(this.text);
+ }
+
+ get cssClassName()
+ {
+ return "spring";
+ }
+
+ popoverWillPresent(popover)
+ {
+ this._springEditor = new WebInspector.SpringEditor;
+ this._springEditor.addEventListener(WebInspector.SpringEditor.Event.SpringChanged, this._springEditorSpringChanged, this);
+ popover.content = this._springEditor.element;
+ }
+
+ popoverDidPresent(popover)
+ {
+ this._springEditor.spring = this.value;
+ }
+
+ popoverDidDismiss(popover)
+ {
+ this._springEditor.removeListeners();
+ }
+
+ // Private
+
+ _springEditorSpringChanged(event)
+ {
+ this.value = event.data.spring;
+ }
+};
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorTextKillController.js b/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorTextKillController.js
new file mode 100644
index 000000000..8cd0b7300
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorTextKillController.js
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2015 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.CodeMirrorTextKillController = class CodeMirrorTextKillController extends WebInspector.Object
+{
+ constructor(codeMirror)
+ {
+ super();
+
+ console.assert(codeMirror);
+
+ this._codeMirror = codeMirror;
+ this._expectingChangeEventForKill = false;
+ this._nextKillStartsNewSequence = true;
+ this._shouldPrependToKillRing = false;
+
+ this._handleTextChangeListener = this._handleTextChange.bind(this);
+ this._handleEditorBlurListener = this._handleEditorBlur.bind(this);
+ this._handleSelectionOrCaretChangeListener = this._handleSelectionOrCaretChange.bind(this);
+
+ // FIXME: these keybindings match CodeMirror's default keymap for OS X.
+ // They should probably be altered for Windows / Linux someday.
+ this._codeMirror.addKeyMap({
+ // Overrides for the 'emacsy' keymap.
+ "Ctrl-K": this._handleTextKillCommand.bind(this, "killLine", false),
+ "Alt-D": this._handleTextKillCommand.bind(this, "delWordAfter", false),
+ // Overrides for the 'macDefault' keymap.
+ "Alt-Delete": this._handleTextKillCommand.bind(this, "delGroupAfter", false),
+ "Cmd-Backspace": this._handleTextKillCommand.bind(this, "delWrappedLineLeft", true),
+ "Cmd-Delete": this._handleTextKillCommand.bind(this, "delWrappedLineRight", false),
+ "Alt-Backspace": this._handleTextKillCommand.bind(this, "delGroupBefore", true),
+ "Ctrl-Alt-Backspace": this._handleTextKillCommand.bind(this, "delGroupAfter", false),
+ });
+ }
+
+ _handleTextKillCommand(command, prependsToKillRing, codeMirror)
+ {
+ // Read-only mode is dynamic in some editors, so check every time
+ // and ignore the shortcut if in read-only mode.
+ if (this._codeMirror.getOption("readOnly"))
+ return;
+
+ this._shouldPrependToKillRing = prependsToKillRing;
+
+ // Don't add the listener if it's still registered because
+ // a previous empty kill didn't generate change events.
+ if (!this._expectingChangeEventForKill)
+ this._codeMirror.on("changes", this._handleTextChangeListener);
+
+ this._expectingChangeEventForKill = true;
+ this._codeMirror.execCommand(command);
+ }
+
+ _handleTextChange(codeMirror, changes)
+ {
+ this._codeMirror.off("changes", this._handleTextChangeListener);
+
+ // Sometimes a second change event fires after removing the listener
+ // if you perform an "empty kill" and type after moving the caret.
+ if (!this._expectingChangeEventForKill)
+ return;
+
+ this._expectingChangeEventForKill = false;
+
+ // It doesn't make sense to get more than one change per kill.
+ console.assert(changes.length === 1);
+ let change = changes[0];
+
+ // If an "empty kill" is followed by up/down or typing,
+ // the empty kill won't fire a change event, then we'll get an
+ // unrelated change event that shouldn't be treated as a kill.
+ if (change.origin !== "+delete")
+ return;
+
+ // When killed text includes a newline, CodeMirror returns
+ // strange change objects. Special-case for when this could happen.
+ let killedText;
+ if (change.to.line === change.from.line + 1 && change.removed.length === 2) {
+ // An entire line was deleted, including newline (deleteLine).
+ if (change.removed[0].length && !change.removed[1].length)
+ killedText = change.removed[0] + "\n";
+ // A newline was killed by itself (Ctrl-K).
+ else
+ killedText = "\n";
+ } else {
+ console.assert(change.removed.length === 1);
+ killedText = change.removed[0];
+ }
+
+ InspectorFrontendHost.killText(killedText, this._shouldPrependToKillRing, this._nextKillStartsNewSequence);
+
+ // If the editor loses focus or the caret / selection changes
+ // (not as a result of the kill), then the next kill should
+ // start a new kill ring sequence.
+ this._nextKillStartsNewSequence = false;
+ this._codeMirror.on("blur", this._handleEditorBlurListener);
+ this._codeMirror.on("cursorActivity", this._handleSelectionOrCaretChangeListener);
+ }
+
+ _handleEditorBlur(codeMirror)
+ {
+ this._nextKillStartsNewSequence = true;
+ this._codeMirror.off("blur", this._handleEditorBlurListener);
+ this._codeMirror.off("cursorActivity", this._handleSelectionOrCaretChangeListener);
+ }
+
+ _handleSelectionOrCaretChange(codeMirror)
+ {
+ if (this._expectingChangeEventForKill)
+ return;
+
+ this._nextKillStartsNewSequence = true;
+ this._codeMirror.off("blur", this._handleEditorBlurListener);
+ this._codeMirror.off("cursorActivity", this._handleSelectionOrCaretChangeListener);
+ }
+};
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorTokenTrackingController.css b/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorTokenTrackingController.css
new file mode 100644
index 000000000..516755d1c
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorTokenTrackingController.css
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2013 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+.CodeMirror .jump-to-symbol-highlight {
+ color: blue !important;
+ text-decoration: underline !important;
+ cursor: pointer !important;
+ -webkit-text-stroke-width: 0 !important;
+}
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorTokenTrackingController.js b/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorTokenTrackingController.js
new file mode 100644
index 000000000..4d4fdac99
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorTokenTrackingController.js
@@ -0,0 +1,618 @@
+/*
+ * Copyright (C) 2013 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.CodeMirrorTokenTrackingController = class CodeMirrorTokenTrackingController extends WebInspector.Object
+{
+ constructor(codeMirror, delegate)
+ {
+ super();
+
+ console.assert(codeMirror);
+
+ this._codeMirror = codeMirror;
+ this._delegate = delegate || null;
+ this._mode = WebInspector.CodeMirrorTokenTrackingController.Mode.None;
+
+ this._mouseOverDelayDuration = 0;
+ this._mouseOutReleaseDelayDuration = 0;
+ this._classNameForHighlightedRange = null;
+
+ this._enabled = false;
+ this._tracking = false;
+ this._previousTokenInfo = null;
+ this._hoveredMarker = null;
+
+ const hidePopover = this._hidePopover.bind(this);
+
+ this._codeMirror.addKeyMap({
+ "Cmd-Enter": this._handleCommandEnterKey.bind(this),
+ "Esc": hidePopover
+ });
+
+ this._codeMirror.on("cursorActivity", hidePopover);
+ }
+
+ // Public
+
+ get delegate()
+ {
+ return this._delegate;
+ }
+
+ set delegate(x)
+ {
+ this._delegate = x;
+ }
+
+ get enabled()
+ {
+ return this._enabled;
+ }
+
+ set enabled(enabled)
+ {
+ if (this._enabled === enabled)
+ return;
+
+ this._enabled = enabled;
+
+ var wrapper = this._codeMirror.getWrapperElement();
+ if (enabled) {
+ wrapper.addEventListener("mouseenter", this);
+ wrapper.addEventListener("mouseleave", this);
+ this._updateHoveredTokenInfo({left: WebInspector.mouseCoords.x, top: WebInspector.mouseCoords.y});
+ this._startTracking();
+ } else {
+ wrapper.removeEventListener("mouseenter", this);
+ wrapper.removeEventListener("mouseleave", this);
+ this._stopTracking();
+ }
+ }
+
+ get mode()
+ {
+ return this._mode;
+ }
+
+ set mode(mode)
+ {
+ var oldMode = this._mode;
+
+ this._mode = mode || WebInspector.CodeMirrorTokenTrackingController.Mode.None;
+
+ if (oldMode !== this._mode && this._tracking && this._previousTokenInfo)
+ this._processNewHoveredToken(this._previousTokenInfo);
+ }
+
+ get mouseOverDelayDuration()
+ {
+ return this._mouseOverDelayDuration;
+ }
+
+ set mouseOverDelayDuration(x)
+ {
+ console.assert(x >= 0);
+ this._mouseOverDelayDuration = Math.max(x, 0);
+ }
+
+ get mouseOutReleaseDelayDuration()
+ {
+ return this._mouseOutReleaseDelayDuration;
+ }
+
+ set mouseOutReleaseDelayDuration(x)
+ {
+ console.assert(x >= 0);
+ this._mouseOutReleaseDelayDuration = Math.max(x, 0);
+ }
+
+ get classNameForHighlightedRange()
+ {
+ return this._classNameForHighlightedRange;
+ }
+
+ set classNameForHighlightedRange(x)
+ {
+ this._classNameForHighlightedRange = x || null;
+ }
+
+ get candidate()
+ {
+ return this._candidate;
+ }
+
+ get hoveredMarker()
+ {
+ return this._hoveredMarker;
+ }
+
+ set hoveredMarker(hoveredMarker)
+ {
+ this._hoveredMarker = hoveredMarker;
+ }
+
+ highlightLastHoveredRange()
+ {
+ if (this._candidate)
+ this.highlightRange(this._candidate.hoveredTokenRange);
+ }
+
+ highlightRange(range)
+ {
+ // Nothing to do if we're trying to highlight the same range.
+ if (this._codeMirrorMarkedText && this._codeMirrorMarkedText.className === this._classNameForHighlightedRange) {
+ var highlightedRange = this._codeMirrorMarkedText.find();
+ if (!highlightedRange)
+ return;
+ if (WebInspector.compareCodeMirrorPositions(highlightedRange.from, range.start) === 0 &&
+ WebInspector.compareCodeMirrorPositions(highlightedRange.to, range.end) === 0)
+ return;
+ }
+
+ this.removeHighlightedRange();
+
+ var className = this._classNameForHighlightedRange || "";
+ this._codeMirrorMarkedText = this._codeMirror.markText(range.start, range.end, {className});
+
+ window.addEventListener("mousemove", this, true);
+ }
+
+ removeHighlightedRange()
+ {
+ if (!this._codeMirrorMarkedText)
+ return;
+
+ this._codeMirrorMarkedText.clear();
+ this._codeMirrorMarkedText = null;
+
+ window.removeEventListener("mousemove", this, true);
+ }
+
+ // Private
+
+ _startTracking()
+ {
+ if (this._tracking)
+ return;
+
+ this._tracking = true;
+
+ var wrapper = this._codeMirror.getWrapperElement();
+ wrapper.addEventListener("mousemove", this, true);
+ wrapper.addEventListener("mouseout", this, false);
+ wrapper.addEventListener("mousedown", this, false);
+ wrapper.addEventListener("mouseup", this, false);
+ window.addEventListener("blur", this, true);
+ }
+
+ _stopTracking()
+ {
+ if (!this._tracking)
+ return;
+
+ this._tracking = false;
+ this._candidate = null;
+
+ var wrapper = this._codeMirror.getWrapperElement();
+ wrapper.removeEventListener("mousemove", this, true);
+ wrapper.removeEventListener("mouseout", this, false);
+ wrapper.removeEventListener("mousedown", this, false);
+ wrapper.removeEventListener("mouseup", this, false);
+ window.removeEventListener("blur", this, true);
+ window.removeEventListener("mousemove", this, true);
+
+ this._resetTrackingStates();
+ }
+
+ handleEvent(event)
+ {
+ switch (event.type) {
+ case "mouseenter":
+ this._mouseEntered(event);
+ break;
+ case "mouseleave":
+ this._mouseLeft(event);
+ break;
+ case "mousemove":
+ if (event.currentTarget === window)
+ this._mouseMovedWithMarkedText(event);
+ else
+ this._mouseMovedOverEditor(event);
+ break;
+ case "mouseout":
+ // Only deal with a mouseout event that has the editor wrapper as the target.
+ if (!event.currentTarget.contains(event.relatedTarget))
+ this._mouseMovedOutOfEditor(event);
+ break;
+ case "mousedown":
+ this._mouseButtonWasPressedOverEditor(event);
+ break;
+ case "mouseup":
+ this._mouseButtonWasReleasedOverEditor(event);
+ break;
+ case "blur":
+ this._windowLostFocus(event);
+ break;
+ }
+ }
+
+ _handleCommandEnterKey(codeMirror)
+ {
+ const tokenInfo = this._getTokenInfoForPosition(codeMirror.getCursor("head"));
+ tokenInfo.triggeredBy = WebInspector.CodeMirrorTokenTrackingController.TriggeredBy.Keyboard;
+ this._processNewHoveredToken(tokenInfo);
+ }
+
+ _hidePopover()
+ {
+ if (!this._candidate)
+ return CodeMirror.Pass;
+
+ if (this._delegate && typeof this._delegate.tokenTrackingControllerHighlightedRangeReleased === "function") {
+ const forceHidePopover = true;
+ this._delegate.tokenTrackingControllerHighlightedRangeReleased(this, forceHidePopover);
+ }
+ }
+
+ _mouseEntered(event)
+ {
+ if (!this._tracking)
+ this._startTracking();
+ }
+
+ _mouseLeft(event)
+ {
+ this._stopTracking();
+ }
+
+ _mouseMovedWithMarkedText(event)
+ {
+ if (this._candidate && this._candidate.triggeredBy === WebInspector.CodeMirrorTokenTrackingController.TriggeredBy.Keyboard)
+ return;
+
+ var shouldRelease = !event.target.classList.contains(this._classNameForHighlightedRange);
+ if (shouldRelease && this._delegate && typeof this._delegate.tokenTrackingControllerCanReleaseHighlightedRange === "function")
+ shouldRelease = this._delegate.tokenTrackingControllerCanReleaseHighlightedRange(this, event.target);
+
+ if (shouldRelease) {
+ if (!this._markedTextMouseoutTimer)
+ this._markedTextMouseoutTimer = setTimeout(this._markedTextIsNoLongerHovered.bind(this), this._mouseOutReleaseDelayDuration);
+ return;
+ }
+
+ if (this._markedTextMouseoutTimer)
+ clearTimeout(this._markedTextMouseoutTimer);
+
+ this._markedTextMouseoutTimer = 0;
+ }
+
+ _markedTextIsNoLongerHovered()
+ {
+ if (this._delegate && typeof this._delegate.tokenTrackingControllerHighlightedRangeReleased === "function")
+ this._delegate.tokenTrackingControllerHighlightedRangeReleased(this);
+
+ this._markedTextMouseoutTimer = 0;
+ }
+
+ _mouseMovedOverEditor(event)
+ {
+ this._updateHoveredTokenInfo({left: event.pageX, top: event.pageY});
+ }
+
+ _updateHoveredTokenInfo(mouseCoords)
+ {
+ // Get the position in the text and the token at that position.
+ var position = this._codeMirror.coordsChar(mouseCoords);
+ var token = this._codeMirror.getTokenAt(position);
+
+ if (!token || !token.type || !token.string) {
+ if (this._hoveredMarker && this._delegate && typeof this._delegate.tokenTrackingControllerMouseOutOfHoveredMarker === "function") {
+ if (!this._codeMirror.findMarksAt(position).includes(this._hoveredMarker.codeMirrorTextMarker))
+ this._delegate.tokenTrackingControllerMouseOutOfHoveredMarker(this, this._hoveredMarker);
+ }
+
+ this._resetTrackingStates();
+ return;
+ }
+
+ // Stop right here if we're hovering the same token as we were last time.
+ if (this._previousTokenInfo &&
+ this._previousTokenInfo.position.line === position.line &&
+ this._previousTokenInfo.token.start === token.start &&
+ this._previousTokenInfo.token.end === token.end)
+ return;
+
+ // We have a new hovered token.
+ var tokenInfo = this._previousTokenInfo = this._getTokenInfoForPosition(position);
+
+ if (/\bmeta\b/.test(token.type)) {
+ let nextTokenPosition = Object.shallowCopy(position);
+ nextTokenPosition.ch = tokenInfo.token.end + 1;
+
+ let nextToken = this._codeMirror.getTokenAt(nextTokenPosition);
+ if (nextToken && nextToken.type && !/\bmeta\b/.test(nextToken.type)) {
+ console.assert(tokenInfo.token.end === nextToken.start);
+
+ tokenInfo.token.type = nextToken.type;
+ tokenInfo.token.string = tokenInfo.token.string + nextToken.string;
+ tokenInfo.token.end = nextToken.end;
+ }
+ } else {
+ let previousTokenPosition = Object.shallowCopy(position);
+ previousTokenPosition.ch = tokenInfo.token.start - 1;
+
+ let previousToken = this._codeMirror.getTokenAt(previousTokenPosition);
+ if (previousToken && previousToken.type && /\bmeta\b/.test(previousToken.type)) {
+ console.assert(tokenInfo.token.start === previousToken.end);
+
+ tokenInfo.token.string = previousToken.string + tokenInfo.token.string;
+ tokenInfo.token.start = previousToken.start;
+ }
+ }
+
+ if (this._tokenHoverTimer)
+ clearTimeout(this._tokenHoverTimer);
+
+ this._tokenHoverTimer = 0;
+
+ if (this._codeMirrorMarkedText || !this._mouseOverDelayDuration)
+ this._processNewHoveredToken(tokenInfo);
+ else
+ this._tokenHoverTimer = setTimeout(this._processNewHoveredToken.bind(this, tokenInfo), this._mouseOverDelayDuration);
+ }
+
+ _getTokenInfoForPosition(position)
+ {
+ var token = this._codeMirror.getTokenAt(position);
+ var innerMode = CodeMirror.innerMode(this._codeMirror.getMode(), token.state);
+ var codeMirrorModeName = innerMode.mode.alternateName || innerMode.mode.name;
+ return {
+ token,
+ position,
+ innerMode,
+ modeName: codeMirrorModeName
+ };
+ }
+
+ _mouseMovedOutOfEditor(event)
+ {
+ if (this._tokenHoverTimer)
+ clearTimeout(this._tokenHoverTimer);
+
+ this._tokenHoverTimer = 0;
+ this._previousTokenInfo = null;
+ this._selectionMayBeInProgress = false;
+ }
+
+ _mouseButtonWasPressedOverEditor(event)
+ {
+ this._selectionMayBeInProgress = true;
+ }
+
+ _mouseButtonWasReleasedOverEditor(event)
+ {
+ this._selectionMayBeInProgress = false;
+ this._mouseMovedOverEditor(event);
+
+ if (this._codeMirrorMarkedText && this._previousTokenInfo) {
+ var position = this._codeMirror.coordsChar({left: event.pageX, top: event.pageY});
+ var marks = this._codeMirror.findMarksAt(position);
+ for (var i = 0; i < marks.length; ++i) {
+ if (marks[i] === this._codeMirrorMarkedText) {
+ if (this._delegate && typeof this._delegate.tokenTrackingControllerHighlightedRangeWasClicked === "function")
+ this._delegate.tokenTrackingControllerHighlightedRangeWasClicked(this);
+
+ break;
+ }
+ }
+ }
+ }
+
+ _windowLostFocus(event)
+ {
+ this._resetTrackingStates();
+ }
+
+ _processNewHoveredToken(tokenInfo)
+ {
+ console.assert(tokenInfo);
+
+ if (this._selectionMayBeInProgress)
+ return;
+
+ this._candidate = null;
+
+ switch (this._mode) {
+ case WebInspector.CodeMirrorTokenTrackingController.Mode.NonSymbolTokens:
+ this._candidate = this._processNonSymbolToken(tokenInfo);
+ break;
+ case WebInspector.CodeMirrorTokenTrackingController.Mode.JavaScriptExpression:
+ case WebInspector.CodeMirrorTokenTrackingController.Mode.JavaScriptTypeInformation:
+ this._candidate = this._processJavaScriptExpression(tokenInfo);
+ break;
+ case WebInspector.CodeMirrorTokenTrackingController.Mode.MarkedTokens:
+ this._candidate = this._processMarkedToken(tokenInfo);
+ break;
+ }
+
+ if (!this._candidate)
+ return;
+
+ this._candidate.triggeredBy = tokenInfo.triggeredBy;
+
+ if (this._markedTextMouseoutTimer)
+ clearTimeout(this._markedTextMouseoutTimer);
+
+ this._markedTextMouseoutTimer = 0;
+
+ if (this._delegate && typeof this._delegate.tokenTrackingControllerNewHighlightCandidate === "function")
+ this._delegate.tokenTrackingControllerNewHighlightCandidate(this, this._candidate);
+ }
+
+ _processNonSymbolToken(tokenInfo)
+ {
+ // Ignore any symbol tokens.
+ var type = tokenInfo.token.type;
+ if (!type)
+ return null;
+
+ var startPosition = {line: tokenInfo.position.line, ch: tokenInfo.token.start};
+ var endPosition = {line: tokenInfo.position.line, ch: tokenInfo.token.end};
+
+ return {
+ hoveredToken: tokenInfo.token,
+ hoveredTokenRange: {start: startPosition, end: endPosition},
+ };
+ }
+
+ _processJavaScriptExpression(tokenInfo)
+ {
+ // Only valid within JavaScript.
+ if (tokenInfo.modeName !== "javascript")
+ return null;
+
+ var startPosition = {line: tokenInfo.position.line, ch: tokenInfo.token.start};
+ var endPosition = {line: tokenInfo.position.line, ch: tokenInfo.token.end};
+
+ function tokenIsInRange(token, range)
+ {
+ return token.line >= range.start.line && token.ch >= range.start.ch &&
+ token.line <= range.end.line && token.ch <= range.end.ch;
+ }
+
+ // If the hovered token is within a selection, use the selection as our expression.
+ if (this._codeMirror.somethingSelected()) {
+ var selectionRange = {
+ start: this._codeMirror.getCursor("start"),
+ end: this._codeMirror.getCursor("end")
+ };
+
+ if (tokenIsInRange(startPosition, selectionRange) || tokenIsInRange(endPosition, selectionRange)) {
+ return {
+ hoveredToken: tokenInfo.token,
+ hoveredTokenRange: selectionRange,
+ expression: this._codeMirror.getSelection(),
+ expressionRange: selectionRange,
+ };
+ }
+ }
+
+ // We only handle vars, definitions, properties, and the keyword 'this'.
+ var type = tokenInfo.token.type;
+ var isProperty = type.indexOf("property") !== -1;
+ var isKeyword = type.indexOf("keyword") !== -1;
+ if (!isProperty && !isKeyword && type.indexOf("variable") === -1 && type.indexOf("def") === -1)
+ return null;
+
+ // Not object literal property names, but yes if an object literal shorthand property, which is a variable.
+ let state = tokenInfo.innerMode.state;
+ if (isProperty && state.lexical && state.lexical.type === "}") {
+ // Peek ahead to see if the next token is "}" or ",". If it is, we are a shorthand and therefore a variable.
+ let shorthand = false;
+ let mode = tokenInfo.innerMode.mode;
+ let position = {line: tokenInfo.position.line, ch: tokenInfo.token.end};
+ WebInspector.walkTokens(this._codeMirror, mode, position, function(tokenType, string) {
+ if (tokenType)
+ return false;
+ if (string === "(")
+ return false;
+ if (string === "," || string === "}") {
+ shorthand = true;
+ return false;
+ }
+ return true;
+ });
+
+ if (!shorthand)
+ return null;
+ }
+
+ // Only the "this" keyword.
+ if (isKeyword && tokenInfo.token.string !== "this")
+ return null;
+
+ // Work out the full hovered expression.
+ var expression = tokenInfo.token.string;
+ var expressionStartPosition = {line: tokenInfo.position.line, ch: tokenInfo.token.start};
+ while (true) {
+ var token = this._codeMirror.getTokenAt(expressionStartPosition);
+ if (!token)
+ break;
+
+ var isDot = !token.type && token.string === ".";
+ var isExpression = token.type && token.type.includes("m-javascript");
+ if (!isDot && !isExpression)
+ break;
+
+ // Disallow operators. We want the hovered expression to be just a single operand.
+ // Also, some operators can modify values, such as pre-increment and assignment operators.
+ if (isExpression && token.type.includes("operator"))
+ break;
+
+ expression = token.string + expression;
+ expressionStartPosition.ch = token.start;
+ }
+
+ // Return the candidate for this token and expression.
+ return {
+ hoveredToken: tokenInfo.token,
+ hoveredTokenRange: {start: startPosition, end: endPosition},
+ expression,
+ expressionRange: {start: expressionStartPosition, end: endPosition},
+ };
+ }
+
+ _processMarkedToken(tokenInfo)
+ {
+ return this._processNonSymbolToken(tokenInfo);
+ }
+
+ _resetTrackingStates()
+ {
+ if (this._tokenHoverTimer)
+ clearTimeout(this._tokenHoverTimer);
+
+ this._tokenHoverTimer = 0;
+
+ this._selectionMayBeInProgress = false;
+ this._previousTokenInfo = null;
+ this.removeHighlightedRange();
+ }
+};
+
+WebInspector.CodeMirrorTokenTrackingController.JumpToSymbolHighlightStyleClassName = "jump-to-symbol-highlight";
+
+WebInspector.CodeMirrorTokenTrackingController.Mode = {
+ None: "none",
+ NonSymbolTokens: "non-symbol-tokens",
+ JavaScriptExpression: "javascript-expression",
+ JavaScriptTypeInformation: "javascript-type-information",
+ MarkedTokens: "marked-tokens"
+};
+
+WebInspector.CodeMirrorTokenTrackingController.TriggeredBy = {
+ Keyboard: "keyboard",
+ Hover: "hover"
+};
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/DOMTreeManager.js b/Source/WebInspectorUI/UserInterface/Controllers/DOMTreeManager.js
new file mode 100644
index 000000000..c7c4003de
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/DOMTreeManager.js
@@ -0,0 +1,795 @@
+/*
+ * Copyright (C) 2009, 2010 Google Inc. All rights reserved.
+ * Copyright (C) 2009 Joseph Pecoraro
+ * Copyright (C) 2013 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:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * 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.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 THE COPYRIGHT
+ * OWNER 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.
+ */
+
+WebInspector.DOMTreeManager = class DOMTreeManager extends WebInspector.Object
+{
+ constructor()
+ {
+ super();
+
+ this._idToDOMNode = {};
+ this._document = null;
+ this._attributeLoadNodeIds = {};
+ this._flows = new Map;
+ this._contentNodesToFlowsMap = new Map;
+ this._restoreSelectedNodeIsAllowed = true;
+ this._loadNodeAttributesTimeout = 0;
+
+ WebInspector.Frame.addEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
+ }
+
+ // Static
+
+ static _flowPayloadHashKey(flowPayload)
+ {
+ // Use the flow node id, to avoid collisions when we change main document id.
+ return flowPayload.documentNodeId + ":" + flowPayload.name;
+ }
+
+ // Public
+
+ requestDocument(callback)
+ {
+ if (this._document) {
+ callback(this._document);
+ return;
+ }
+
+ if (this._pendingDocumentRequestCallbacks) {
+ this._pendingDocumentRequestCallbacks.push(callback);
+ return;
+ }
+
+ this._pendingDocumentRequestCallbacks = [callback];
+
+ function onDocumentAvailable(error, root)
+ {
+ if (!error)
+ this._setDocument(root);
+
+ for (let callback of this._pendingDocumentRequestCallbacks)
+ callback(this._document);
+
+ this._pendingDocumentRequestCallbacks = null;
+ }
+
+ DOMAgent.getDocument(onDocumentAvailable.bind(this));
+ }
+
+ pushNodeToFrontend(objectId, callback)
+ {
+ this._dispatchWhenDocumentAvailable(DOMAgent.requestNode.bind(DOMAgent, objectId), callback);
+ }
+
+ pushNodeByPathToFrontend(path, callback)
+ {
+ this._dispatchWhenDocumentAvailable(DOMAgent.pushNodeByPathToFrontend.bind(DOMAgent, path), callback);
+ }
+
+ // Private
+
+ _wrapClientCallback(callback)
+ {
+ if (!callback)
+ return null;
+
+ return function(error, result) {
+ if (error)
+ console.error("Error during DOMAgent operation: " + error);
+ callback(error ? null : result);
+ };
+ }
+
+ _dispatchWhenDocumentAvailable(func, callback)
+ {
+ var callbackWrapper = this._wrapClientCallback(callback);
+
+ function onDocumentAvailable()
+ {
+ if (this._document)
+ func(callbackWrapper);
+ else {
+ if (callbackWrapper)
+ callbackWrapper("No document");
+ }
+ }
+ this.requestDocument(onDocumentAvailable.bind(this));
+ }
+
+ _attributeModified(nodeId, name, value)
+ {
+ var node = this._idToDOMNode[nodeId];
+ if (!node)
+ return;
+
+ node._setAttribute(name, value);
+ this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.AttributeModified, {node, name});
+ node.dispatchEventToListeners(WebInspector.DOMNode.Event.AttributeModified, {name});
+ }
+
+ _attributeRemoved(nodeId, name)
+ {
+ var node = this._idToDOMNode[nodeId];
+ if (!node)
+ return;
+
+ node._removeAttribute(name);
+ this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.AttributeRemoved, {node, name});
+ node.dispatchEventToListeners(WebInspector.DOMNode.Event.AttributeRemoved, {name});
+ }
+
+ _inlineStyleInvalidated(nodeIds)
+ {
+ for (var nodeId of nodeIds)
+ this._attributeLoadNodeIds[nodeId] = true;
+ if (this._loadNodeAttributesTimeout)
+ return;
+ this._loadNodeAttributesTimeout = setTimeout(this._loadNodeAttributes.bind(this), 0);
+ }
+
+ _loadNodeAttributes()
+ {
+ function callback(nodeId, error, attributes)
+ {
+ if (error) {
+ console.error("Error during DOMAgent operation: " + error);
+ return;
+ }
+ var node = this._idToDOMNode[nodeId];
+ if (node) {
+ node._setAttributesPayload(attributes);
+ this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.AttributeModified, {node, name: "style"});
+ node.dispatchEventToListeners(WebInspector.DOMNode.Event.AttributeModified, {name: "style"});
+ }
+ }
+
+ this._loadNodeAttributesTimeout = 0;
+
+ for (var nodeId in this._attributeLoadNodeIds) {
+ var nodeIdAsNumber = parseInt(nodeId);
+ DOMAgent.getAttributes(nodeIdAsNumber, callback.bind(this, nodeIdAsNumber));
+ }
+ this._attributeLoadNodeIds = {};
+ }
+
+ _characterDataModified(nodeId, newValue)
+ {
+ var node = this._idToDOMNode[nodeId];
+ node._nodeValue = newValue;
+ this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.CharacterDataModified, {node});
+ }
+
+ nodeForId(nodeId)
+ {
+ return this._idToDOMNode[nodeId];
+ }
+
+ _documentUpdated()
+ {
+ this._setDocument(null);
+ }
+
+ _setDocument(payload)
+ {
+ this._idToDOMNode = {};
+ if (payload && "nodeId" in payload)
+ this._document = new WebInspector.DOMNode(this, null, false, payload);
+ else
+ this._document = null;
+ this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.DocumentUpdated, this._document);
+ }
+
+ _setDetachedRoot(payload)
+ {
+ new WebInspector.DOMNode(this, null, false, payload);
+ }
+
+ _setChildNodes(parentId, payloads)
+ {
+ if (!parentId && payloads.length) {
+ this._setDetachedRoot(payloads[0]);
+ return;
+ }
+
+ var parent = this._idToDOMNode[parentId];
+ parent._setChildrenPayload(payloads);
+ }
+
+ _childNodeCountUpdated(nodeId, newValue)
+ {
+ var node = this._idToDOMNode[nodeId];
+ node.childNodeCount = newValue;
+ this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.ChildNodeCountUpdated, node);
+ }
+
+ _childNodeInserted(parentId, prevId, payload)
+ {
+ var parent = this._idToDOMNode[parentId];
+ var prev = this._idToDOMNode[prevId];
+ var node = parent._insertChild(prev, payload);
+ this._idToDOMNode[node.id] = node;
+ this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.NodeInserted, {node, parent});
+ }
+
+ _childNodeRemoved(parentId, nodeId)
+ {
+ var parent = this._idToDOMNode[parentId];
+ var node = this._idToDOMNode[nodeId];
+ parent._removeChild(node);
+ this._unbind(node);
+ this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.NodeRemoved, {node, parent});
+ }
+
+ _customElementStateChanged(elementId, newState)
+ {
+ const node = this._idToDOMNode[elementId];
+ node._customElementState = newState;
+ this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.CustomElementStateChanged, {node});
+ }
+
+ _pseudoElementAdded(parentId, pseudoElement)
+ {
+ var parent = this._idToDOMNode[parentId];
+ if (!parent)
+ return;
+
+ var node = new WebInspector.DOMNode(this, parent.ownerDocument, false, pseudoElement);
+ node.parentNode = parent;
+ this._idToDOMNode[node.id] = node;
+ console.assert(!parent.pseudoElements().get(node.pseudoType()));
+ parent.pseudoElements().set(node.pseudoType(), node);
+ this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.NodeInserted, {node, parent});
+ }
+
+ _pseudoElementRemoved(parentId, pseudoElementId)
+ {
+ var pseudoElement = this._idToDOMNode[pseudoElementId];
+ if (!pseudoElement)
+ return;
+
+ var parent = pseudoElement.parentNode;
+ console.assert(parent);
+ console.assert(parent.id === parentId);
+ if (!parent)
+ return;
+
+ parent._removeChild(pseudoElement);
+ this._unbind(pseudoElement);
+ this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.NodeRemoved, {node: pseudoElement, parent});
+ }
+
+ _unbind(node)
+ {
+ this._removeContentNodeFromFlowIfNeeded(node);
+
+ delete this._idToDOMNode[node.id];
+
+ for (let i = 0; node.children && i < node.children.length; ++i)
+ this._unbind(node.children[i]);
+
+ let templateContent = node.templateContent();
+ if (templateContent)
+ this._unbind(templateContent);
+
+ for (let pseudoElement of node.pseudoElements().values())
+ this._unbind(pseudoElement);
+
+ // FIXME: Handle shadow roots.
+ }
+
+ get restoreSelectedNodeIsAllowed()
+ {
+ return this._restoreSelectedNodeIsAllowed;
+ }
+
+ inspectElement(nodeId)
+ {
+ var node = this._idToDOMNode[nodeId];
+ if (!node || !node.ownerDocument)
+ return;
+
+ this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.DOMNodeWasInspected, {node});
+
+ this._inspectModeEnabled = false;
+ this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.InspectModeStateChanged);
+ }
+
+ inspectNodeObject(remoteObject)
+ {
+ this._restoreSelectedNodeIsAllowed = false;
+
+ function nodeAvailable(nodeId)
+ {
+ remoteObject.release();
+
+ console.assert(nodeId);
+ if (!nodeId)
+ return;
+
+ this.inspectElement(nodeId);
+
+ // Re-resolve the node in the console's object group when adding to the console.
+ let domNode = this.nodeForId(nodeId);
+ WebInspector.RemoteObject.resolveNode(domNode, WebInspector.RuntimeManager.ConsoleObjectGroup, function(remoteObject) {
+ if (!remoteObject)
+ return;
+ let specialLogStyles = true;
+ let shouldRevealConsole = false;
+ WebInspector.consoleLogViewController.appendImmediateExecutionWithResult(WebInspector.UIString("Selected Element"), remoteObject, specialLogStyles, shouldRevealConsole);
+ });
+ }
+
+ remoteObject.pushNodeToFrontend(nodeAvailable.bind(this));
+ }
+
+ performSearch(query, searchCallback)
+ {
+ this.cancelSearch();
+
+ function callback(error, searchId, resultsCount)
+ {
+ this._searchId = searchId;
+ searchCallback(resultsCount);
+ }
+ DOMAgent.performSearch(query, callback.bind(this));
+ }
+
+ searchResult(index, callback)
+ {
+ function mycallback(error, nodeIds)
+ {
+ if (error) {
+ console.error(error);
+ callback(null);
+ return;
+ }
+ if (nodeIds.length !== 1)
+ return;
+
+ callback(this._idToDOMNode[nodeIds[0]]);
+ }
+
+ if (this._searchId)
+ DOMAgent.getSearchResults(this._searchId, index, index + 1, mycallback.bind(this));
+ else
+ callback(null);
+ }
+
+ cancelSearch()
+ {
+ if (this._searchId) {
+ DOMAgent.discardSearchResults(this._searchId);
+ this._searchId = undefined;
+ }
+ }
+
+ querySelector(nodeId, selectors, callback)
+ {
+ DOMAgent.querySelector(nodeId, selectors, this._wrapClientCallback(callback));
+ }
+
+ querySelectorAll(nodeId, selectors, callback)
+ {
+ DOMAgent.querySelectorAll(nodeId, selectors, this._wrapClientCallback(callback));
+ }
+
+ highlightDOMNode(nodeId, mode)
+ {
+ if (this._hideDOMNodeHighlightTimeout) {
+ clearTimeout(this._hideDOMNodeHighlightTimeout);
+ this._hideDOMNodeHighlightTimeout = undefined;
+ }
+
+ this._highlightedDOMNodeId = nodeId;
+ if (nodeId)
+ DOMAgent.highlightNode.invoke({nodeId, highlightConfig: this._buildHighlightConfig(mode)});
+ else
+ DOMAgent.hideHighlight();
+ }
+
+ highlightSelector(selectorText, frameId, mode)
+ {
+ // COMPATIBILITY (iOS 8): DOM.highlightSelector did not exist.
+ if (!DOMAgent.highlightSelector)
+ return;
+
+ DOMAgent.highlightSelector(this._buildHighlightConfig(mode), selectorText, frameId);
+ }
+
+ highlightRect(rect, usePageCoordinates)
+ {
+ DOMAgent.highlightRect.invoke({
+ x: rect.x,
+ y: rect.y,
+ width: rect.width,
+ height: rect.height,
+ color: {r: 111, g: 168, b: 220, a: 0.66},
+ outlineColor: {r: 255, g: 229, b: 153, a: 0.66},
+ usePageCoordinates
+ });
+ }
+
+ hideDOMNodeHighlight()
+ {
+ this.highlightDOMNode(0);
+ }
+
+ highlightDOMNodeForTwoSeconds(nodeId)
+ {
+ this.highlightDOMNode(nodeId);
+ this._hideDOMNodeHighlightTimeout = setTimeout(this.hideDOMNodeHighlight.bind(this), 2000);
+ }
+
+ get inspectModeEnabled()
+ {
+ return this._inspectModeEnabled;
+ }
+
+ set inspectModeEnabled(enabled)
+ {
+ if (enabled === this._inspectModeEnabled)
+ return;
+
+ DOMAgent.setInspectModeEnabled(enabled, this._buildHighlightConfig(), (error) => {
+ this._inspectModeEnabled = error ? false : enabled;
+ this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.InspectModeStateChanged);
+ });
+ }
+
+ _buildHighlightConfig(mode = "all")
+ {
+ let highlightConfig = {showInfo: mode === "all"};
+
+ if (mode === "all" || mode === "content")
+ highlightConfig.contentColor = {r: 111, g: 168, b: 220, a: 0.66};
+
+ if (mode === "all" || mode === "padding")
+ highlightConfig.paddingColor = {r: 147, g: 196, b: 125, a: 0.66};
+
+ if (mode === "all" || mode === "border")
+ highlightConfig.borderColor = {r: 255, g: 229, b: 153, a: 0.66};
+
+ if (mode === "all" || mode === "margin")
+ highlightConfig.marginColor = {r: 246, g: 178, b: 107, a: 0.66};
+
+ return highlightConfig;
+ }
+
+ _createContentFlowFromPayload(flowPayload)
+ {
+ // FIXME: Collect the regions from the payload.
+ var flow = new WebInspector.ContentFlow(flowPayload.documentNodeId, flowPayload.name, flowPayload.overset, flowPayload.content.map(this.nodeForId.bind(this)));
+
+ for (var contentNode of flow.contentNodes) {
+ console.assert(!this._contentNodesToFlowsMap.has(contentNode.id));
+ this._contentNodesToFlowsMap.set(contentNode.id, flow);
+ }
+
+ return flow;
+ }
+
+ _updateContentFlowFromPayload(contentFlow, flowPayload)
+ {
+ console.assert(contentFlow.contentNodes.length === flowPayload.content.length);
+ console.assert(contentFlow.contentNodes.every((node, i) => node.id === flowPayload.content[i]));
+
+ // FIXME: Collect the regions from the payload.
+ contentFlow.overset = flowPayload.overset;
+ }
+
+ getNamedFlowCollection(documentNodeIdentifier)
+ {
+ function onNamedFlowCollectionAvailable(error, flows)
+ {
+ if (error)
+ return;
+ this._contentNodesToFlowsMap.clear();
+ var contentFlows = [];
+ for (var i = 0; i < flows.length; ++i) {
+ var flowPayload = flows[i];
+ var flowKey = WebInspector.DOMTreeManager._flowPayloadHashKey(flowPayload);
+ var contentFlow = this._flows.get(flowKey);
+ if (contentFlow)
+ this._updateContentFlowFromPayload(contentFlow, flowPayload);
+ else {
+ contentFlow = this._createContentFlowFromPayload(flowPayload);
+ this._flows.set(flowKey, contentFlow);
+ }
+ contentFlows.push(contentFlow);
+ }
+ this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.ContentFlowListWasUpdated, {documentNodeIdentifier, flows: contentFlows});
+ }
+
+ if (window.CSSAgent)
+ CSSAgent.getNamedFlowCollection(documentNodeIdentifier, onNamedFlowCollectionAvailable.bind(this));
+ }
+
+ namedFlowCreated(flowPayload)
+ {
+ var flowKey = WebInspector.DOMTreeManager._flowPayloadHashKey(flowPayload);
+ console.assert(!this._flows.has(flowKey));
+ var contentFlow = this._createContentFlowFromPayload(flowPayload);
+ this._flows.set(flowKey, contentFlow);
+ this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.ContentFlowWasAdded, {flow: contentFlow});
+ }
+
+ namedFlowRemoved(documentNodeIdentifier, flowName)
+ {
+ var flowKey = WebInspector.DOMTreeManager._flowPayloadHashKey({documentNodeId: documentNodeIdentifier, name: flowName});
+ var contentFlow = this._flows.get(flowKey);
+ console.assert(contentFlow);
+ this._flows.delete(flowKey);
+
+ // Remove any back links to this flow from the content nodes.
+ for (var contentNode of contentFlow.contentNodes)
+ this._contentNodesToFlowsMap.delete(contentNode.id);
+
+ this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.ContentFlowWasRemoved, {flow: contentFlow});
+ }
+
+ _sendNamedFlowUpdateEvents(flowPayload)
+ {
+ var flowKey = WebInspector.DOMTreeManager._flowPayloadHashKey(flowPayload);
+ console.assert(this._flows.has(flowKey));
+ this._updateContentFlowFromPayload(this._flows.get(flowKey), flowPayload);
+ }
+
+ regionOversetChanged(flowPayload)
+ {
+ this._sendNamedFlowUpdateEvents(flowPayload);
+ }
+
+ registeredNamedFlowContentElement(documentNodeIdentifier, flowName, contentNodeId, nextContentElementNodeId)
+ {
+ var flowKey = WebInspector.DOMTreeManager._flowPayloadHashKey({documentNodeId: documentNodeIdentifier, name: flowName});
+ console.assert(this._flows.has(flowKey));
+ console.assert(!this._contentNodesToFlowsMap.has(contentNodeId));
+
+ var flow = this._flows.get(flowKey);
+ var contentNode = this.nodeForId(contentNodeId);
+
+ this._contentNodesToFlowsMap.set(contentNode.id, flow);
+
+ if (nextContentElementNodeId)
+ flow.insertContentNodeBefore(contentNode, this.nodeForId(nextContentElementNodeId));
+ else
+ flow.appendContentNode(contentNode);
+ }
+
+ _removeContentNodeFromFlowIfNeeded(node)
+ {
+ if (!this._contentNodesToFlowsMap.has(node.id))
+ return;
+ var flow = this._contentNodesToFlowsMap.get(node.id);
+ this._contentNodesToFlowsMap.delete(node.id);
+ flow.removeContentNode(node);
+ }
+
+ unregisteredNamedFlowContentElement(documentNodeIdentifier, flowName, contentNodeId)
+ {
+ console.assert(this._contentNodesToFlowsMap.has(contentNodeId));
+
+ var flow = this._contentNodesToFlowsMap.get(contentNodeId);
+ console.assert(flow.id === WebInspector.DOMTreeManager._flowPayloadHashKey({documentNodeId: documentNodeIdentifier, name: flowName}));
+
+ this._contentNodesToFlowsMap.delete(contentNodeId);
+ flow.removeContentNode(this.nodeForId(contentNodeId));
+ }
+
+ _coerceRemoteArrayOfDOMNodes(remoteObject, callback)
+ {
+ console.assert(remoteObject.type === "object");
+ console.assert(remoteObject.subtype === "array");
+
+ let length = remoteObject.size;
+ if (!length) {
+ callback(null, []);
+ return;
+ }
+
+ let nodes;
+ let received = 0;
+ let lastError = null;
+ let domTreeManager = this;
+
+ function nodeRequested(index, error, nodeId)
+ {
+ if (error)
+ lastError = error;
+ else
+ nodes[index] = domTreeManager._idToDOMNode[nodeId];
+ if (++received === length)
+ callback(lastError, nodes);
+ }
+
+ WebInspector.runtimeManager.getPropertiesForRemoteObject(remoteObject.objectId, function(error, properties) {
+ if (error) {
+ callback(error);
+ return;
+ }
+
+ nodes = new Array(length);
+ for (let i = 0; i < length; ++i) {
+ let nodeProperty = properties.get(String(i));
+ console.assert(nodeProperty.value.type === "object");
+ console.assert(nodeProperty.value.subtype === "node");
+ DOMAgent.requestNode(nodeProperty.value.objectId, nodeRequested.bind(null, i));
+ }
+ });
+ }
+
+ getNodeContentFlowInfo(domNode, resultReadyCallback)
+ {
+ DOMAgent.resolveNode(domNode.id, domNodeResolved.bind(this));
+
+ function domNodeResolved(error, remoteObject)
+ {
+ if (error) {
+ resultReadyCallback(error);
+ return;
+ }
+
+ var evalParameters = {
+ objectId: remoteObject.objectId,
+ functionDeclaration: appendWebInspectorSourceURL(inspectedPage_node_getFlowInfo.toString()),
+ doNotPauseOnExceptionsAndMuteConsole: true,
+ returnByValue: false,
+ generatePreview: false
+ };
+ RuntimeAgent.callFunctionOn.invoke(evalParameters, regionNodesAvailable.bind(this));
+ }
+
+ function regionNodesAvailable(error, remoteObject, wasThrown)
+ {
+ if (error) {
+ resultReadyCallback(error);
+ return;
+ }
+
+ if (wasThrown) {
+ // We should never get here, but having the error is useful for debugging.
+ console.error("Error while executing backend function:", JSON.stringify(remoteObject));
+ resultReadyCallback(null);
+ return;
+ }
+
+ // The backend function can never return null.
+ console.assert(remoteObject.type === "object");
+ console.assert(remoteObject.objectId);
+ WebInspector.runtimeManager.getPropertiesForRemoteObject(remoteObject.objectId, remoteObjectPropertiesAvailable.bind(this));
+ }
+
+ function remoteObjectPropertiesAvailable(error, properties) {
+ if (error) {
+ resultReadyCallback(error);
+ return;
+ }
+
+ var result = {
+ regionFlow: null,
+ contentFlow: null,
+ regions: null
+ };
+
+ var regionFlowNameProperty = properties.get("regionFlowName");
+ if (regionFlowNameProperty && regionFlowNameProperty.value && regionFlowNameProperty.value.value) {
+ console.assert(regionFlowNameProperty.value.type === "string");
+ var regionFlowKey = WebInspector.DOMTreeManager._flowPayloadHashKey({documentNodeId: domNode.ownerDocument.id, name: regionFlowNameProperty.value.value});
+ result.regionFlow = this._flows.get(regionFlowKey);
+ }
+
+ var contentFlowNameProperty = properties.get("contentFlowName");
+ if (contentFlowNameProperty && contentFlowNameProperty.value && contentFlowNameProperty.value.value) {
+ console.assert(contentFlowNameProperty.value.type === "string");
+ var contentFlowKey = WebInspector.DOMTreeManager._flowPayloadHashKey({documentNodeId: domNode.ownerDocument.id, name: contentFlowNameProperty.value.value});
+ result.contentFlow = this._flows.get(contentFlowKey);
+ }
+
+ var regionsProperty = properties.get("regions");
+ if (!regionsProperty || !regionsProperty.value.objectId) {
+ // The list of regions is null.
+ resultReadyCallback(null, result);
+ return;
+ }
+
+ this._coerceRemoteArrayOfDOMNodes(regionsProperty.value, function(error, nodes) {
+ result.regions = nodes;
+ resultReadyCallback(error, result);
+ });
+ }
+
+ function inspectedPage_node_getFlowInfo()
+ {
+ function getComputedProperty(node, propertyName)
+ {
+ if (!node.ownerDocument || !node.ownerDocument.defaultView)
+ return null;
+ var computedStyle = node.ownerDocument.defaultView.getComputedStyle(node);
+ return computedStyle ? computedStyle[propertyName] : null;
+ }
+
+ function getContentFlowName(node)
+ {
+ for (; node; node = node.parentNode) {
+ var flowName = getComputedProperty(node, "webkitFlowInto");
+ if (flowName && flowName !== "none")
+ return flowName;
+ }
+ return null;
+ }
+
+ var node = this;
+
+ // Even detached nodes have an ownerDocument.
+ console.assert(node.ownerDocument);
+
+ var result = {
+ regionFlowName: getComputedProperty(node, "webkitFlowFrom"),
+ contentFlowName: getContentFlowName(node),
+ regions: null
+ };
+
+ if (result.contentFlowName) {
+ var flowThread = node.ownerDocument.webkitGetNamedFlows().namedItem(result.contentFlowName);
+ if (flowThread)
+ result.regions = Array.from(flowThread.getRegionsByContent(node));
+ }
+
+ return result;
+ }
+ }
+
+ // Private
+
+ _mainResourceDidChange(event)
+ {
+ if (event.target.isMainFrame())
+ this._restoreSelectedNodeIsAllowed = true;
+ }
+};
+
+WebInspector.DOMTreeManager.Event = {
+ AttributeModified: "dom-tree-manager-attribute-modified",
+ AttributeRemoved: "dom-tree-manager-attribute-removed",
+ CharacterDataModified: "dom-tree-manager-character-data-modified",
+ NodeInserted: "dom-tree-manager-node-inserted",
+ NodeRemoved: "dom-tree-manager-node-removed",
+ CustomElementStateChanged: "dom-tree-manager-custom-element-state-changed",
+ DocumentUpdated: "dom-tree-manager-document-updated",
+ ChildNodeCountUpdated: "dom-tree-manager-child-node-count-updated",
+ DOMNodeWasInspected: "dom-tree-manager-dom-node-was-inspected",
+ InspectModeStateChanged: "dom-tree-manager-inspect-mode-state-changed",
+ ContentFlowListWasUpdated: "dom-tree-manager-content-flow-list-was-updated",
+ ContentFlowWasAdded: "dom-tree-manager-content-flow-was-added",
+ ContentFlowWasRemoved: "dom-tree-manager-content-flow-was-removed",
+ RegionOversetChanged: "dom-tree-manager-region-overset-changed"
+};
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/DashboardManager.js b/Source/WebInspectorUI/UserInterface/Controllers/DashboardManager.js
new file mode 100644
index 000000000..48c4a8fd5
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/DashboardManager.js
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2013, 2014 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.DashboardManager = class DashboardManager extends WebInspector.Object
+{
+ constructor()
+ {
+ super();
+
+ this._dashboards = {};
+ this._dashboards.default = new WebInspector.DefaultDashboard;
+ this._dashboards.debugger = new WebInspector.DebuggerDashboard;
+ this._dashboards.replay = new WebInspector.ReplayDashboard;
+ }
+
+ get dashboards()
+ {
+ return this._dashboards;
+ }
+};
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/DebuggerManager.js b/Source/WebInspectorUI/UserInterface/Controllers/DebuggerManager.js
new file mode 100644
index 000000000..827263add
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/DebuggerManager.js
@@ -0,0 +1,1217 @@
+/*
+ * Copyright (C) 2013-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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.DebuggerManager = class DebuggerManager extends WebInspector.Object
+{
+ constructor()
+ {
+ super();
+
+ DebuggerAgent.enable();
+
+ WebInspector.notifications.addEventListener(WebInspector.Notification.DebugUIEnabledDidChange, this._debugUIEnabledDidChange, this);
+
+ WebInspector.Breakpoint.addEventListener(WebInspector.Breakpoint.Event.DisplayLocationDidChange, this._breakpointDisplayLocationDidChange, this);
+ WebInspector.Breakpoint.addEventListener(WebInspector.Breakpoint.Event.DisabledStateDidChange, this._breakpointDisabledStateDidChange, this);
+ WebInspector.Breakpoint.addEventListener(WebInspector.Breakpoint.Event.ConditionDidChange, this._breakpointEditablePropertyDidChange, this);
+ WebInspector.Breakpoint.addEventListener(WebInspector.Breakpoint.Event.IgnoreCountDidChange, this._breakpointEditablePropertyDidChange, this);
+ WebInspector.Breakpoint.addEventListener(WebInspector.Breakpoint.Event.AutoContinueDidChange, this._breakpointEditablePropertyDidChange, this);
+ WebInspector.Breakpoint.addEventListener(WebInspector.Breakpoint.Event.ActionsDidChange, this._breakpointEditablePropertyDidChange, this);
+
+ WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.CapturingWillStart, this._timelineCapturingWillStart, this);
+ WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.CapturingStopped, this._timelineCapturingStopped, this);
+
+ WebInspector.targetManager.addEventListener(WebInspector.TargetManager.Event.TargetRemoved, this._targetRemoved, this);
+
+ WebInspector.Frame.addEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
+
+ this._breakpointsSetting = new WebInspector.Setting("breakpoints", []);
+ this._breakpointsEnabledSetting = new WebInspector.Setting("breakpoints-enabled", true);
+ this._allExceptionsBreakpointEnabledSetting = new WebInspector.Setting("break-on-all-exceptions", false);
+ this._allUncaughtExceptionsBreakpointEnabledSetting = new WebInspector.Setting("break-on-all-uncaught-exceptions", false);
+ this._assertionsBreakpointEnabledSetting = new WebInspector.Setting("break-on-assertions", false);
+ this._asyncStackTraceDepthSetting = new WebInspector.Setting("async-stack-trace-depth", 200);
+
+ let specialBreakpointLocation = new WebInspector.SourceCodeLocation(null, Infinity, Infinity);
+
+ this._allExceptionsBreakpoint = new WebInspector.Breakpoint(specialBreakpointLocation, !this._allExceptionsBreakpointEnabledSetting.value);
+ this._allExceptionsBreakpoint.resolved = true;
+
+ this._allUncaughtExceptionsBreakpoint = new WebInspector.Breakpoint(specialBreakpointLocation, !this._allUncaughtExceptionsBreakpointEnabledSetting.value);
+
+ this._assertionsBreakpoint = new WebInspector.Breakpoint(specialBreakpointLocation, !this._assertionsBreakpointEnabledSetting.value);
+ this._assertionsBreakpoint.resolved = true;
+
+ this._breakpoints = [];
+ this._breakpointContentIdentifierMap = new Map;
+ this._breakpointScriptIdentifierMap = new Map;
+ this._breakpointIdMap = new Map;
+
+ this._breakOnExceptionsState = "none";
+ this._updateBreakOnExceptionsState();
+
+ this._nextBreakpointActionIdentifier = 1;
+
+ this._activeCallFrame = null;
+
+ this._internalWebKitScripts = [];
+ this._targetDebuggerDataMap = new Map;
+ this._targetDebuggerDataMap.set(WebInspector.mainTarget, new WebInspector.DebuggerData(WebInspector.mainTarget));
+
+ // Restore the correct breakpoints enabled setting if Web Inspector had
+ // previously been left in a state where breakpoints were temporarily disabled.
+ this._temporarilyDisabledBreakpointsRestoreSetting = new WebInspector.Setting("temporarily-disabled-breakpoints-restore", null);
+ if (this._temporarilyDisabledBreakpointsRestoreSetting.value !== null) {
+ this._breakpointsEnabledSetting.value = this._temporarilyDisabledBreakpointsRestoreSetting.value;
+ this._temporarilyDisabledBreakpointsRestoreSetting.value = null;
+ }
+
+ DebuggerAgent.setBreakpointsActive(this._breakpointsEnabledSetting.value);
+ DebuggerAgent.setPauseOnExceptions(this._breakOnExceptionsState);
+
+ // COMPATIBILITY (iOS 10): DebuggerAgent.setPauseOnAssertions did not exist yet.
+ if (DebuggerAgent.setPauseOnAssertions)
+ DebuggerAgent.setPauseOnAssertions(this._assertionsBreakpointEnabledSetting.value);
+
+ // COMPATIBILITY (iOS 10): Debugger.setAsyncStackTraceDepth did not exist yet.
+ if (DebuggerAgent.setAsyncStackTraceDepth)
+ DebuggerAgent.setAsyncStackTraceDepth(this._asyncStackTraceDepthSetting.value);
+
+ this._ignoreBreakpointDisplayLocationDidChangeEvent = false;
+
+ function restoreBreakpointsSoon() {
+ this._restoringBreakpoints = true;
+ for (let cookie of this._breakpointsSetting.value)
+ this.addBreakpoint(new WebInspector.Breakpoint(cookie));
+ this._restoringBreakpoints = false;
+ }
+
+ // Ensure that all managers learn about restored breakpoints,
+ // regardless of their initialization order.
+ setTimeout(restoreBreakpointsSoon.bind(this), 0);
+ }
+
+ // Public
+
+ get paused()
+ {
+ for (let [target, targetData] of this._targetDebuggerDataMap) {
+ if (targetData.paused)
+ return true;
+ }
+
+ return false;
+ }
+
+ get activeCallFrame()
+ {
+ return this._activeCallFrame;
+ }
+
+ set activeCallFrame(callFrame)
+ {
+ if (callFrame === this._activeCallFrame)
+ return;
+
+ this._activeCallFrame = callFrame || null;
+
+ this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.ActiveCallFrameDidChange);
+ }
+
+ dataForTarget(target)
+ {
+ let targetData = this._targetDebuggerDataMap.get(target);
+ if (targetData)
+ return targetData;
+
+ targetData = new WebInspector.DebuggerData(target);
+ this._targetDebuggerDataMap.set(target, targetData);
+ return targetData;
+ }
+
+ get allExceptionsBreakpoint()
+ {
+ return this._allExceptionsBreakpoint;
+ }
+
+ get allUncaughtExceptionsBreakpoint()
+ {
+ return this._allUncaughtExceptionsBreakpoint;
+ }
+
+ get assertionsBreakpoint()
+ {
+ return this._assertionsBreakpoint;
+ }
+
+ get breakpoints()
+ {
+ return this._breakpoints;
+ }
+
+ breakpointForIdentifier(id)
+ {
+ return this._breakpointIdMap.get(id) || null;
+ }
+
+ breakpointsForSourceCode(sourceCode)
+ {
+ console.assert(sourceCode instanceof WebInspector.Resource || sourceCode instanceof WebInspector.Script);
+
+ if (sourceCode instanceof WebInspector.SourceMapResource) {
+ let originalSourceCodeBreakpoints = this.breakpointsForSourceCode(sourceCode.sourceMap.originalSourceCode);
+ return originalSourceCodeBreakpoints.filter(function(breakpoint) {
+ return breakpoint.sourceCodeLocation.displaySourceCode === sourceCode;
+ });
+ }
+
+ let contentIdentifierBreakpoints = this._breakpointContentIdentifierMap.get(sourceCode.contentIdentifier);
+ if (contentIdentifierBreakpoints) {
+ this._associateBreakpointsWithSourceCode(contentIdentifierBreakpoints, sourceCode);
+ return contentIdentifierBreakpoints;
+ }
+
+ if (sourceCode instanceof WebInspector.Script) {
+ let scriptIdentifierBreakpoints = this._breakpointScriptIdentifierMap.get(sourceCode.id);
+ if (scriptIdentifierBreakpoints) {
+ this._associateBreakpointsWithSourceCode(scriptIdentifierBreakpoints, sourceCode);
+ return scriptIdentifierBreakpoints;
+ }
+ }
+
+ return [];
+ }
+
+ isBreakpointRemovable(breakpoint)
+ {
+ return breakpoint !== this._allExceptionsBreakpoint
+ && breakpoint !== this._allUncaughtExceptionsBreakpoint
+ && breakpoint !== this._assertionsBreakpoint;
+ }
+
+ isBreakpointEditable(breakpoint)
+ {
+ return this.isBreakpointRemovable(breakpoint);
+ }
+
+ get breakpointsEnabled()
+ {
+ return this._breakpointsEnabledSetting.value;
+ }
+
+ set breakpointsEnabled(enabled)
+ {
+ if (this._breakpointsEnabledSetting.value === enabled)
+ return;
+
+ console.assert(!(enabled && this.breakpointsDisabledTemporarily), "Should not enable breakpoints when we are temporarily disabling breakpoints.");
+ if (enabled && this.breakpointsDisabledTemporarily)
+ return;
+
+ this._breakpointsEnabledSetting.value = enabled;
+
+ this._updateBreakOnExceptionsState();
+
+ for (let target of WebInspector.targets) {
+ target.DebuggerAgent.setBreakpointsActive(enabled);
+ target.DebuggerAgent.setPauseOnExceptions(this._breakOnExceptionsState);
+ }
+
+ this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.BreakpointsEnabledDidChange);
+ }
+
+ get breakpointsDisabledTemporarily()
+ {
+ return this._temporarilyDisabledBreakpointsRestoreSetting.value !== null;
+ }
+
+ scriptForIdentifier(id, target)
+ {
+ console.assert(target instanceof WebInspector.Target);
+ return this.dataForTarget(target).scriptForIdentifier(id);
+ }
+
+ scriptsForURL(url, target)
+ {
+ // FIXME: This may not be safe. A Resource's URL may differ from a Script's URL.
+ console.assert(target instanceof WebInspector.Target);
+ return this.dataForTarget(target).scriptsForURL(url);
+ }
+
+ get searchableScripts()
+ {
+ return this.knownNonResourceScripts.filter((script) => !!script.contentIdentifier);
+ }
+
+ get knownNonResourceScripts()
+ {
+ let knownScripts = [];
+
+ for (let [target, targetData] of this._targetDebuggerDataMap) {
+ for (let script of targetData.scripts) {
+ if (script.resource)
+ continue;
+ if (!WebInspector.isDebugUIEnabled() && isWebKitInternalScript(script.sourceURL))
+ continue;
+ knownScripts.push(script);
+ }
+ }
+
+ return knownScripts;
+ }
+
+ get asyncStackTraceDepth()
+ {
+ return this._asyncStackTraceDepthSetting.value;
+ }
+
+ set asyncStackTraceDepth(x)
+ {
+ if (this._asyncStackTraceDepthSetting.value === x)
+ return;
+
+ this._asyncStackTraceDepthSetting.value = x;
+
+ for (let target of WebInspector.targets)
+ target.DebuggerAgent.setAsyncStackTraceDepth(this._asyncStackTraceDepthSetting.value);
+ }
+
+ pause()
+ {
+ if (this.paused)
+ return Promise.resolve();
+
+ this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.WaitingToPause);
+
+ let listener = new WebInspector.EventListener(this, true);
+
+ let managerResult = new Promise(function(resolve, reject) {
+ listener.connect(WebInspector.debuggerManager, WebInspector.DebuggerManager.Event.Paused, resolve);
+ });
+
+ let promises = [];
+ for (let [target, targetData] of this._targetDebuggerDataMap)
+ promises.push(targetData.pauseIfNeeded());
+
+ return Promise.all([managerResult, ...promises]);
+ }
+
+ resume()
+ {
+ if (!this.paused)
+ return Promise.resolve();
+
+ let listener = new WebInspector.EventListener(this, true);
+
+ let managerResult = new Promise(function(resolve, reject) {
+ listener.connect(WebInspector.debuggerManager, WebInspector.DebuggerManager.Event.Resumed, resolve);
+ });
+
+ let promises = [];
+ for (let [target, targetData] of this._targetDebuggerDataMap)
+ promises.push(targetData.resumeIfNeeded());
+
+ return Promise.all([managerResult, ...promises]);
+ }
+
+ stepOver()
+ {
+ if (!this.paused)
+ return Promise.reject(new Error("Cannot step over because debugger is not paused."));
+
+ let listener = new WebInspector.EventListener(this, true);
+
+ let managerResult = new Promise(function(resolve, reject) {
+ listener.connect(WebInspector.debuggerManager, WebInspector.DebuggerManager.Event.ActiveCallFrameDidChange, resolve);
+ });
+
+ let protocolResult = this._activeCallFrame.target.DebuggerAgent.stepOver()
+ .catch(function(error) {
+ listener.disconnect();
+ console.error("DebuggerManager.stepOver failed: ", error);
+ throw error;
+ });
+
+ return Promise.all([managerResult, protocolResult]);
+ }
+
+ stepInto()
+ {
+ if (!this.paused)
+ return Promise.reject(new Error("Cannot step into because debugger is not paused."));
+
+ let listener = new WebInspector.EventListener(this, true);
+
+ let managerResult = new Promise(function(resolve, reject) {
+ listener.connect(WebInspector.debuggerManager, WebInspector.DebuggerManager.Event.ActiveCallFrameDidChange, resolve);
+ });
+
+ let protocolResult = this._activeCallFrame.target.DebuggerAgent.stepInto()
+ .catch(function(error) {
+ listener.disconnect();
+ console.error("DebuggerManager.stepInto failed: ", error);
+ throw error;
+ });
+
+ return Promise.all([managerResult, protocolResult]);
+ }
+
+ stepOut()
+ {
+ if (!this.paused)
+ return Promise.reject(new Error("Cannot step out because debugger is not paused."));
+
+ let listener = new WebInspector.EventListener(this, true);
+
+ let managerResult = new Promise(function(resolve, reject) {
+ listener.connect(WebInspector.debuggerManager, WebInspector.DebuggerManager.Event.ActiveCallFrameDidChange, resolve);
+ });
+
+ let protocolResult = this._activeCallFrame.target.DebuggerAgent.stepOut()
+ .catch(function(error) {
+ listener.disconnect();
+ console.error("DebuggerManager.stepOut failed: ", error);
+ throw error;
+ });
+
+ return Promise.all([managerResult, protocolResult]);
+ }
+
+ continueUntilNextRunLoop(target)
+ {
+ return this.dataForTarget(target).continueUntilNextRunLoop();
+ }
+
+ continueToLocation(script, lineNumber, columnNumber)
+ {
+ return script.target.DebuggerAgent.continueToLocation({scriptId: script.id, lineNumber, columnNumber});
+ }
+
+ addBreakpoint(breakpoint, shouldSpeculativelyResolve)
+ {
+ console.assert(breakpoint instanceof WebInspector.Breakpoint);
+ if (!breakpoint)
+ return;
+
+ if (breakpoint.contentIdentifier) {
+ let contentIdentifierBreakpoints = this._breakpointContentIdentifierMap.get(breakpoint.contentIdentifier);
+ if (!contentIdentifierBreakpoints) {
+ contentIdentifierBreakpoints = [];
+ this._breakpointContentIdentifierMap.set(breakpoint.contentIdentifier, contentIdentifierBreakpoints);
+ }
+ contentIdentifierBreakpoints.push(breakpoint);
+ }
+
+ if (breakpoint.scriptIdentifier) {
+ let scriptIdentifierBreakpoints = this._breakpointScriptIdentifierMap.get(breakpoint.scriptIdentifier);
+ if (!scriptIdentifierBreakpoints) {
+ scriptIdentifierBreakpoints = [];
+ this._breakpointScriptIdentifierMap.set(breakpoint.scriptIdentifier, scriptIdentifierBreakpoints);
+ }
+ scriptIdentifierBreakpoints.push(breakpoint);
+ }
+
+ this._breakpoints.push(breakpoint);
+
+ if (!breakpoint.disabled) {
+ const specificTarget = undefined;
+ this._setBreakpoint(breakpoint, specificTarget, () => {
+ if (shouldSpeculativelyResolve)
+ breakpoint.resolved = true;
+ });
+ }
+
+ this._saveBreakpoints();
+
+ this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.BreakpointAdded, {breakpoint});
+ }
+
+ removeBreakpoint(breakpoint)
+ {
+ console.assert(breakpoint instanceof WebInspector.Breakpoint);
+ if (!breakpoint)
+ return;
+
+ console.assert(this.isBreakpointRemovable(breakpoint));
+ if (!this.isBreakpointRemovable(breakpoint))
+ return;
+
+ this._breakpoints.remove(breakpoint);
+
+ if (breakpoint.identifier)
+ this._removeBreakpoint(breakpoint);
+
+ if (breakpoint.contentIdentifier) {
+ let contentIdentifierBreakpoints = this._breakpointContentIdentifierMap.get(breakpoint.contentIdentifier);
+ if (contentIdentifierBreakpoints) {
+ contentIdentifierBreakpoints.remove(breakpoint);
+ if (!contentIdentifierBreakpoints.length)
+ this._breakpointContentIdentifierMap.delete(breakpoint.contentIdentifier);
+ }
+ }
+
+ if (breakpoint.scriptIdentifier) {
+ let scriptIdentifierBreakpoints = this._breakpointScriptIdentifierMap.get(breakpoint.scriptIdentifier);
+ if (scriptIdentifierBreakpoints) {
+ scriptIdentifierBreakpoints.remove(breakpoint);
+ if (!scriptIdentifierBreakpoints.length)
+ this._breakpointScriptIdentifierMap.delete(breakpoint.scriptIdentifier);
+ }
+ }
+
+ // Disable the breakpoint first, so removing actions doesn't re-add the breakpoint.
+ breakpoint.disabled = true;
+ breakpoint.clearActions();
+
+ this._saveBreakpoints();
+
+ this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.BreakpointRemoved, {breakpoint});
+ }
+
+ nextBreakpointActionIdentifier()
+ {
+ return this._nextBreakpointActionIdentifier++;
+ }
+
+ initializeTarget(target)
+ {
+ let DebuggerAgent = target.DebuggerAgent;
+ let targetData = this.dataForTarget(target);
+
+ // Initialize global state.
+ DebuggerAgent.enable();
+ DebuggerAgent.setBreakpointsActive(this._breakpointsEnabledSetting.value);
+ DebuggerAgent.setPauseOnAssertions(this._assertionsBreakpointEnabledSetting.value);
+ DebuggerAgent.setPauseOnExceptions(this._breakOnExceptionsState);
+ DebuggerAgent.setAsyncStackTraceDepth(this._asyncStackTraceDepthSetting.value);
+
+ if (this.paused)
+ targetData.pauseIfNeeded();
+
+ // Initialize breakpoints.
+ this._restoringBreakpoints = true;
+ for (let breakpoint of this._breakpoints) {
+ if (breakpoint.disabled)
+ continue;
+ if (!breakpoint.contentIdentifier)
+ continue;
+ this._setBreakpoint(breakpoint, target);
+ }
+ this._restoringBreakpoints = false;
+ }
+
+ // Protected (Called from WebInspector.DebuggerObserver)
+
+ breakpointResolved(target, breakpointIdentifier, location)
+ {
+ // Called from WebInspector.DebuggerObserver.
+
+ let breakpoint = this._breakpointIdMap.get(breakpointIdentifier);
+ console.assert(breakpoint);
+ if (!breakpoint)
+ return;
+
+ console.assert(breakpoint.identifier === breakpointIdentifier);
+
+ if (!breakpoint.sourceCodeLocation.sourceCode) {
+ let sourceCodeLocation = this._sourceCodeLocationFromPayload(target, location);
+ breakpoint.sourceCodeLocation.sourceCode = sourceCodeLocation.sourceCode;
+ }
+
+ breakpoint.resolved = true;
+ }
+
+ reset()
+ {
+ // Called from WebInspector.DebuggerObserver.
+
+ let wasPaused = this.paused;
+
+ WebInspector.Script.resetUniqueDisplayNameNumbers();
+
+ this._internalWebKitScripts = [];
+ this._targetDebuggerDataMap.clear();
+
+ this._ignoreBreakpointDisplayLocationDidChangeEvent = true;
+
+ // Mark all the breakpoints as unresolved. They will be reported as resolved when
+ // breakpointResolved is called as the page loads.
+ for (let breakpoint of this._breakpoints) {
+ breakpoint.resolved = false;
+ if (breakpoint.sourceCodeLocation.sourceCode)
+ breakpoint.sourceCodeLocation.sourceCode = null;
+ }
+
+ this._ignoreBreakpointDisplayLocationDidChangeEvent = false;
+
+ this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.ScriptsCleared);
+
+ if (wasPaused)
+ this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.Resumed);
+ }
+
+ debuggerDidPause(target, callFramesPayload, reason, data, asyncStackTracePayload)
+ {
+ // Called from WebInspector.DebuggerObserver.
+
+ if (this._delayedResumeTimeout) {
+ clearTimeout(this._delayedResumeTimeout);
+ this._delayedResumeTimeout = undefined;
+ }
+
+ let wasPaused = this.paused;
+ let targetData = this._targetDebuggerDataMap.get(target);
+
+ let callFrames = [];
+ let pauseReason = this._pauseReasonFromPayload(reason);
+ let pauseData = data || null;
+
+ for (var i = 0; i < callFramesPayload.length; ++i) {
+ var callFramePayload = callFramesPayload[i];
+ var sourceCodeLocation = this._sourceCodeLocationFromPayload(target, callFramePayload.location);
+ // FIXME: There may be useful call frames without a source code location (native callframes), should we include them?
+ if (!sourceCodeLocation)
+ continue;
+ if (!sourceCodeLocation.sourceCode)
+ continue;
+
+ // Exclude the case where the call frame is in the inspector code.
+ if (!WebInspector.isDebugUIEnabled() && isWebKitInternalScript(sourceCodeLocation.sourceCode.sourceURL))
+ continue;
+
+ let scopeChain = this._scopeChainFromPayload(target, callFramePayload.scopeChain);
+ let callFrame = WebInspector.CallFrame.fromDebuggerPayload(target, callFramePayload, scopeChain, sourceCodeLocation);
+ callFrames.push(callFrame);
+ }
+
+ let activeCallFrame = callFrames[0];
+
+ if (!activeCallFrame) {
+ // FIXME: This may not be safe for multiple threads/targets.
+ // This indicates we were pausing in internal scripts only (Injected Scripts).
+ // Just resume and skip past this pause. We should be fixing the backend to
+ // not send such pauses.
+ if (wasPaused)
+ target.DebuggerAgent.continueUntilNextRunLoop();
+ else
+ target.DebuggerAgent.resume();
+ this._didResumeInternal(target);
+ return;
+ }
+
+ let asyncStackTrace = WebInspector.StackTrace.fromPayload(target, asyncStackTracePayload);
+ targetData.updateForPause(callFrames, pauseReason, pauseData, asyncStackTrace);
+
+ // Pause other targets because at least one target has paused.
+ // FIXME: Should this be done on the backend?
+ for (let [otherTarget, otherTargetData] of this._targetDebuggerDataMap)
+ otherTargetData.pauseIfNeeded();
+
+ let activeCallFrameDidChange = this._activeCallFrame && this._activeCallFrame.target === target;
+ if (activeCallFrameDidChange)
+ this._activeCallFrame = activeCallFrame;
+ else if (!wasPaused) {
+ this._activeCallFrame = activeCallFrame;
+ activeCallFrameDidChange = true;
+ }
+
+ if (!wasPaused)
+ this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.Paused);
+
+ this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.CallFramesDidChange, {target});
+
+ if (activeCallFrameDidChange)
+ this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.ActiveCallFrameDidChange);
+ }
+
+ debuggerDidResume(target)
+ {
+ // Called from WebInspector.DebuggerObserver.
+
+ // COMPATIBILITY (iOS 10): Debugger.resumed event was ambiguous. When stepping
+ // we would receive a Debugger.resumed and we would not know if it really meant
+ // the backend resumed or would pause again due to a step. Legacy backends wait
+ // 50ms, and treat it as a real resume if we haven't paused in that time frame.
+ // This delay ensures the user interface does not flash between brief steps
+ // or successive breakpoints.
+ if (!DebuggerAgent.setPauseOnAssertions) {
+ this._delayedResumeTimeout = setTimeout(this._didResumeInternal.bind(this, target), 50);
+ return;
+ }
+
+ this._didResumeInternal(target);
+ }
+
+ playBreakpointActionSound(breakpointActionIdentifier)
+ {
+ // Called from WebInspector.DebuggerObserver.
+
+ InspectorFrontendHost.beep();
+ }
+
+ scriptDidParse(target, scriptIdentifier, url, startLine, startColumn, endLine, endColumn, isModule, isContentScript, sourceURL, sourceMapURL)
+ {
+ // Called from WebInspector.DebuggerObserver.
+
+ // Don't add the script again if it is already known.
+ let targetData = this.dataForTarget(target);
+ let existingScript = targetData.scriptForIdentifier(scriptIdentifier);
+ if (existingScript) {
+ console.assert(existingScript.url === (url || null));
+ console.assert(existingScript.range.startLine === startLine);
+ console.assert(existingScript.range.startColumn === startColumn);
+ console.assert(existingScript.range.endLine === endLine);
+ console.assert(existingScript.range.endColumn === endColumn);
+ return;
+ }
+
+ if (!WebInspector.isDebugUIEnabled() && isWebKitInternalScript(sourceURL))
+ return;
+
+ let range = new WebInspector.TextRange(startLine, startColumn, endLine, endColumn);
+ let sourceType = isModule ? WebInspector.Script.SourceType.Module : WebInspector.Script.SourceType.Program;
+ let script = new WebInspector.Script(target, scriptIdentifier, range, url, sourceType, isContentScript, sourceURL, sourceMapURL);
+
+ targetData.addScript(script);
+
+ if (target !== WebInspector.mainTarget && !target.mainResource) {
+ // FIXME: <https://webkit.org/b/164427> Web Inspector: WorkerTarget's mainResource should be a Resource not a Script
+ // We make the main resource of a WorkerTarget the Script instead of the Resource
+ // because the frontend may not be informed of the Resource. We should guarantee
+ // the frontend is informed of the Resource.
+ if (script.url === target.name) {
+ target.mainResource = script;
+ if (script.resource)
+ target.resourceCollection.remove(script.resource);
+ }
+ }
+
+ if (isWebKitInternalScript(script.sourceURL)) {
+ this._internalWebKitScripts.push(script);
+ if (!WebInspector.isDebugUIEnabled())
+ return;
+ }
+
+ // Console expressions are not added to the UI by default.
+ if (isWebInspectorConsoleEvaluationScript(script.sourceURL))
+ return;
+
+ this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.ScriptAdded, {script});
+
+ if (target !== WebInspector.mainTarget && !script.isMainResource() && !script.resource)
+ target.addScript(script);
+ }
+
+ // Private
+
+ _sourceCodeLocationFromPayload(target, payload)
+ {
+ let targetData = this.dataForTarget(target);
+ let script = targetData.scriptForIdentifier(payload.scriptId);
+ if (!script)
+ return null;
+
+ return script.createSourceCodeLocation(payload.lineNumber, payload.columnNumber);
+ }
+
+ _scopeChainFromPayload(target, payload)
+ {
+ let scopeChain = [];
+ for (let i = 0; i < payload.length; ++i)
+ scopeChain.push(this._scopeChainNodeFromPayload(target, payload[i]));
+ return scopeChain;
+ }
+
+ _scopeChainNodeFromPayload(target, payload)
+ {
+ var type = null;
+ switch (payload.type) {
+ case DebuggerAgent.ScopeType.Global:
+ type = WebInspector.ScopeChainNode.Type.Global;
+ break;
+ case DebuggerAgent.ScopeType.With:
+ type = WebInspector.ScopeChainNode.Type.With;
+ break;
+ case DebuggerAgent.ScopeType.Closure:
+ type = WebInspector.ScopeChainNode.Type.Closure;
+ break;
+ case DebuggerAgent.ScopeType.Catch:
+ type = WebInspector.ScopeChainNode.Type.Catch;
+ break;
+ case DebuggerAgent.ScopeType.FunctionName:
+ type = WebInspector.ScopeChainNode.Type.FunctionName;
+ break;
+ case DebuggerAgent.ScopeType.NestedLexical:
+ type = WebInspector.ScopeChainNode.Type.Block;
+ break;
+ case DebuggerAgent.ScopeType.GlobalLexicalEnvironment:
+ type = WebInspector.ScopeChainNode.Type.GlobalLexicalEnvironment;
+ break;
+
+ // COMPATIBILITY (iOS 9): Debugger.ScopeType.Local used to be provided by the backend.
+ // Newer backends no longer send this enum value, it should be computed by the frontend.
+ // Map this to "Closure" type. The frontend can recalculate this when needed.
+ case DebuggerAgent.ScopeType.Local:
+ type = WebInspector.ScopeChainNode.Type.Closure;
+ break;
+
+ default:
+ console.error("Unknown type: " + payload.type);
+ }
+
+ let object = WebInspector.RemoteObject.fromPayload(payload.object, target);
+ return new WebInspector.ScopeChainNode(type, [object], payload.name, payload.location, payload.empty);
+ }
+
+ _pauseReasonFromPayload(payload)
+ {
+ // FIXME: Handle other backend pause reasons.
+ switch (payload) {
+ case DebuggerAgent.PausedReason.Assert:
+ return WebInspector.DebuggerManager.PauseReason.Assertion;
+ case DebuggerAgent.PausedReason.Breakpoint:
+ return WebInspector.DebuggerManager.PauseReason.Breakpoint;
+ case DebuggerAgent.PausedReason.CSPViolation:
+ return WebInspector.DebuggerManager.PauseReason.CSPViolation;
+ case DebuggerAgent.PausedReason.DebuggerStatement:
+ return WebInspector.DebuggerManager.PauseReason.DebuggerStatement;
+ case DebuggerAgent.PausedReason.Exception:
+ return WebInspector.DebuggerManager.PauseReason.Exception;
+ case DebuggerAgent.PausedReason.PauseOnNextStatement:
+ return WebInspector.DebuggerManager.PauseReason.PauseOnNextStatement;
+ default:
+ return WebInspector.DebuggerManager.PauseReason.Other;
+ }
+ }
+
+ _debuggerBreakpointActionType(type)
+ {
+ switch (type) {
+ case WebInspector.BreakpointAction.Type.Log:
+ return DebuggerAgent.BreakpointActionType.Log;
+ case WebInspector.BreakpointAction.Type.Evaluate:
+ return DebuggerAgent.BreakpointActionType.Evaluate;
+ case WebInspector.BreakpointAction.Type.Sound:
+ return DebuggerAgent.BreakpointActionType.Sound;
+ case WebInspector.BreakpointAction.Type.Probe:
+ return DebuggerAgent.BreakpointActionType.Probe;
+ default:
+ console.assert(false);
+ return DebuggerAgent.BreakpointActionType.Log;
+ }
+ }
+
+ _debuggerBreakpointOptions(breakpoint)
+ {
+ const templatePlaceholderRegex = /\$\{.*?\}/;
+
+ let options = breakpoint.options;
+ let invalidActions = [];
+
+ for (let action of options.actions) {
+ if (action.type !== WebInspector.BreakpointAction.Type.Log)
+ continue;
+
+ if (!templatePlaceholderRegex.test(action.data))
+ continue;
+
+ let lexer = new WebInspector.BreakpointLogMessageLexer;
+ let tokens = lexer.tokenize(action.data);
+ if (!tokens) {
+ invalidActions.push(action);
+ continue;
+ }
+
+ let templateLiteral = tokens.reduce((text, token) => {
+ if (token.type === WebInspector.BreakpointLogMessageLexer.TokenType.PlainText)
+ return text + token.data.escapeCharacters("`\\");
+ if (token.type === WebInspector.BreakpointLogMessageLexer.TokenType.Expression)
+ return text + "${" + token.data + "}";
+ return text;
+ }, "");
+
+ action.data = "console.log(`" + templateLiteral + "`)";
+ action.type = WebInspector.BreakpointAction.Type.Evaluate;
+ }
+
+ const onlyFirst = true;
+ for (let invalidAction of invalidActions)
+ options.actions.remove(invalidAction, onlyFirst);
+
+ return options;
+ }
+
+ _setBreakpoint(breakpoint, specificTarget, callback)
+ {
+ console.assert(!breakpoint.disabled);
+
+ if (breakpoint.disabled)
+ return;
+
+ if (!this._restoringBreakpoints && !this.breakpointsDisabledTemporarily) {
+ // Enable breakpoints since a breakpoint is being set. This eliminates
+ // a multi-step process for the user that can be confusing.
+ this.breakpointsEnabled = true;
+ }
+
+ function didSetBreakpoint(target, error, breakpointIdentifier, locations)
+ {
+ if (error)
+ return;
+
+ this._breakpointIdMap.set(breakpointIdentifier, breakpoint);
+
+ breakpoint.identifier = breakpointIdentifier;
+
+ // Debugger.setBreakpoint returns a single location.
+ if (!(locations instanceof Array))
+ locations = [locations];
+
+ for (let location of locations)
+ this.breakpointResolved(target, breakpointIdentifier, location);
+
+ if (typeof callback === "function")
+ callback();
+ }
+
+ // The breakpoint will be resolved again by calling DebuggerAgent, so mark it as unresolved.
+ // If something goes wrong it will stay unresolved and show up as such in the user interface.
+ // When setting for a new target, don't change the resolved target.
+ if (!specificTarget)
+ breakpoint.resolved = false;
+
+ // Convert BreakpointAction types to DebuggerAgent protocol types.
+ // NOTE: Breakpoint.options returns new objects each time, so it is safe to modify.
+ // COMPATIBILITY (iOS 7): Debugger.BreakpointActionType did not exist yet.
+ let options;
+ if (DebuggerAgent.BreakpointActionType) {
+ options = this._debuggerBreakpointOptions(breakpoint);
+ if (options.actions.length) {
+ for (let action of options.actions)
+ action.type = this._debuggerBreakpointActionType(action.type);
+ }
+ }
+
+ // COMPATIBILITY (iOS 7): iOS 7 and earlier, DebuggerAgent.setBreakpoint* took a "condition" string argument.
+ // This has been replaced with an "options" BreakpointOptions object.
+ if (breakpoint.contentIdentifier) {
+ let targets = specificTarget ? [specificTarget] : WebInspector.targets;
+ for (let target of targets) {
+ target.DebuggerAgent.setBreakpointByUrl.invoke({
+ lineNumber: breakpoint.sourceCodeLocation.lineNumber,
+ url: breakpoint.contentIdentifier,
+ urlRegex: undefined,
+ columnNumber: breakpoint.sourceCodeLocation.columnNumber,
+ condition: breakpoint.condition,
+ options
+ }, didSetBreakpoint.bind(this, target), target.DebuggerAgent);
+ }
+ } else if (breakpoint.scriptIdentifier) {
+ let target = breakpoint.target;
+ target.DebuggerAgent.setBreakpoint.invoke({
+ location: {scriptId: breakpoint.scriptIdentifier, lineNumber: breakpoint.sourceCodeLocation.lineNumber, columnNumber: breakpoint.sourceCodeLocation.columnNumber},
+ condition: breakpoint.condition,
+ options
+ }, didSetBreakpoint.bind(this, target), target.DebuggerAgent);
+ }
+ }
+
+ _removeBreakpoint(breakpoint, callback)
+ {
+ if (!breakpoint.identifier)
+ return;
+
+ function didRemoveBreakpoint(error)
+ {
+ if (error)
+ console.error(error);
+
+ this._breakpointIdMap.delete(breakpoint.identifier);
+
+ breakpoint.identifier = null;
+
+ // Don't reset resolved here since we want to keep disabled breakpoints looking like they
+ // are resolved in the user interface. They will get marked as unresolved in reset.
+
+ if (typeof callback === "function")
+ callback();
+ }
+
+ if (breakpoint.contentIdentifier) {
+ for (let target of WebInspector.targets)
+ target.DebuggerAgent.removeBreakpoint(breakpoint.identifier, didRemoveBreakpoint.bind(this));
+ } else if (breakpoint.scriptIdentifier) {
+ let target = breakpoint.target;
+ target.DebuggerAgent.removeBreakpoint(breakpoint.identifier, didRemoveBreakpoint.bind(this));
+ }
+ }
+
+ _breakpointDisplayLocationDidChange(event)
+ {
+ if (this._ignoreBreakpointDisplayLocationDidChangeEvent)
+ return;
+
+ let breakpoint = event.target;
+ if (!breakpoint.identifier || breakpoint.disabled)
+ return;
+
+ // Remove the breakpoint with its old id.
+ this._removeBreakpoint(breakpoint, breakpointRemoved.bind(this));
+
+ function breakpointRemoved()
+ {
+ // Add the breakpoint at its new lineNumber and get a new id.
+ this._setBreakpoint(breakpoint);
+
+ this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.BreakpointMoved, {breakpoint});
+ }
+ }
+
+ _breakpointDisabledStateDidChange(event)
+ {
+ this._saveBreakpoints();
+
+ let breakpoint = event.target;
+ if (breakpoint === this._allExceptionsBreakpoint) {
+ if (!breakpoint.disabled && !this.breakpointsDisabledTemporarily)
+ this.breakpointsEnabled = true;
+ this._allExceptionsBreakpointEnabledSetting.value = !breakpoint.disabled;
+ this._updateBreakOnExceptionsState();
+ for (let target of WebInspector.targets)
+ target.DebuggerAgent.setPauseOnExceptions(this._breakOnExceptionsState);
+ return;
+ }
+
+ if (breakpoint === this._allUncaughtExceptionsBreakpoint) {
+ if (!breakpoint.disabled && !this.breakpointsDisabledTemporarily)
+ this.breakpointsEnabled = true;
+ this._allUncaughtExceptionsBreakpointEnabledSetting.value = !breakpoint.disabled;
+ this._updateBreakOnExceptionsState();
+ for (let target of WebInspector.targets)
+ target.DebuggerAgent.setPauseOnExceptions(this._breakOnExceptionsState);
+ return;
+ }
+
+ if (breakpoint === this._assertionsBreakpoint) {
+ if (!breakpoint.disabled && !this.breakpointsDisabledTemporarily)
+ this.breakpointsEnabled = true;
+ this._assertionsBreakpointEnabledSetting.value = !breakpoint.disabled;
+ for (let target of WebInspector.targets)
+ target.DebuggerAgent.setPauseOnAssertions(this._assertionsBreakpointEnabledSetting.value);
+ return;
+ }
+
+ if (breakpoint.disabled)
+ this._removeBreakpoint(breakpoint);
+ else
+ this._setBreakpoint(breakpoint);
+ }
+
+ _breakpointEditablePropertyDidChange(event)
+ {
+ this._saveBreakpoints();
+
+ let breakpoint = event.target;
+ if (breakpoint.disabled)
+ return;
+
+ console.assert(this.isBreakpointEditable(breakpoint));
+ if (!this.isBreakpointEditable(breakpoint))
+ return;
+
+ // Remove the breakpoint with its old id.
+ this._removeBreakpoint(breakpoint, breakpointRemoved.bind(this));
+
+ function breakpointRemoved()
+ {
+ // Add the breakpoint with its new properties and get a new id.
+ this._setBreakpoint(breakpoint);
+ }
+ }
+
+ _startDisablingBreakpointsTemporarily()
+ {
+ console.assert(!this.breakpointsDisabledTemporarily, "Already temporarily disabling breakpoints.");
+ if (this.breakpointsDisabledTemporarily)
+ return;
+
+ this._temporarilyDisabledBreakpointsRestoreSetting.value = this._breakpointsEnabledSetting.value;
+
+ this.breakpointsEnabled = false;
+ }
+
+ _stopDisablingBreakpointsTemporarily()
+ {
+ console.assert(this.breakpointsDisabledTemporarily, "Was not temporarily disabling breakpoints.");
+ if (!this.breakpointsDisabledTemporarily)
+ return;
+
+ let restoreState = this._temporarilyDisabledBreakpointsRestoreSetting.value;
+ this._temporarilyDisabledBreakpointsRestoreSetting.value = null;
+
+ this.breakpointsEnabled = restoreState;
+ }
+
+ _timelineCapturingWillStart(event)
+ {
+ this._startDisablingBreakpointsTemporarily();
+
+ if (this.paused)
+ this.resume();
+ }
+
+ _timelineCapturingStopped(event)
+ {
+ this._stopDisablingBreakpointsTemporarily();
+ }
+
+ _targetRemoved(event)
+ {
+ let wasPaused = this.paused;
+
+ this._targetDebuggerDataMap.delete(event.data.target);
+
+ if (!this.paused && wasPaused)
+ this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.Resumed);
+ }
+
+ _mainResourceDidChange(event)
+ {
+ if (!event.target.isMainFrame())
+ return;
+
+ this._didResumeInternal(WebInspector.mainTarget);
+ }
+
+ _didResumeInternal(target)
+ {
+ if (!this.paused)
+ return;
+
+ if (this._delayedResumeTimeout) {
+ clearTimeout(this._delayedResumeTimeout);
+ this._delayedResumeTimeout = undefined;
+ }
+
+ let activeCallFrameDidChange = false;
+ if (this._activeCallFrame && this._activeCallFrame.target === target) {
+ this._activeCallFrame = null;
+ activeCallFrameDidChange = true;
+ }
+
+ this.dataForTarget(target).updateForResume();
+
+ if (!this.paused)
+ this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.Resumed);
+
+ this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.CallFramesDidChange, {target});
+
+ if (activeCallFrameDidChange)
+ this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.ActiveCallFrameDidChange);
+ }
+
+ _updateBreakOnExceptionsState()
+ {
+ let state = "none";
+
+ if (this._breakpointsEnabledSetting.value) {
+ if (!this._allExceptionsBreakpoint.disabled)
+ state = "all";
+ else if (!this._allUncaughtExceptionsBreakpoint.disabled)
+ state = "uncaught";
+ }
+
+ this._breakOnExceptionsState = state;
+
+ switch (state) {
+ case "all":
+ // Mark the uncaught breakpoint as unresolved since "all" includes "uncaught".
+ // That way it is clear in the user interface that the breakpoint is ignored.
+ this._allUncaughtExceptionsBreakpoint.resolved = false;
+ break;
+ case "uncaught":
+ case "none":
+ // Mark the uncaught breakpoint as resolved again.
+ this._allUncaughtExceptionsBreakpoint.resolved = true;
+ break;
+ }
+ }
+
+ _saveBreakpoints()
+ {
+ if (this._restoringBreakpoints)
+ return;
+
+ let breakpointsToSave = this._breakpoints.filter((breakpoint) => !!breakpoint.contentIdentifier);
+ let serializedBreakpoints = breakpointsToSave.map((breakpoint) => breakpoint.info);
+ this._breakpointsSetting.value = serializedBreakpoints;
+ }
+
+ _associateBreakpointsWithSourceCode(breakpoints, sourceCode)
+ {
+ this._ignoreBreakpointDisplayLocationDidChangeEvent = true;
+
+ for (let breakpoint of breakpoints) {
+ if (!breakpoint.sourceCodeLocation.sourceCode)
+ breakpoint.sourceCodeLocation.sourceCode = sourceCode;
+ // SourceCodes can be unequal if the SourceCodeLocation is associated with a Script and we are looking at the Resource.
+ console.assert(breakpoint.sourceCodeLocation.sourceCode === sourceCode || breakpoint.sourceCodeLocation.sourceCode.contentIdentifier === sourceCode.contentIdentifier);
+ }
+
+ this._ignoreBreakpointDisplayLocationDidChangeEvent = false;
+ }
+
+ _debugUIEnabledDidChange()
+ {
+ let eventType = WebInspector.isDebugUIEnabled() ? WebInspector.DebuggerManager.Event.ScriptAdded : WebInspector.DebuggerManager.Event.ScriptRemoved;
+ for (let script of this._internalWebKitScripts)
+ this.dispatchEventToListeners(eventType, {script});
+ }
+};
+
+WebInspector.DebuggerManager.Event = {
+ BreakpointAdded: "debugger-manager-breakpoint-added",
+ BreakpointRemoved: "debugger-manager-breakpoint-removed",
+ BreakpointMoved: "debugger-manager-breakpoint-moved",
+ WaitingToPause: "debugger-manager-waiting-to-pause",
+ Paused: "debugger-manager-paused",
+ Resumed: "debugger-manager-resumed",
+ CallFramesDidChange: "debugger-manager-call-frames-did-change",
+ ActiveCallFrameDidChange: "debugger-manager-active-call-frame-did-change",
+ ScriptAdded: "debugger-manager-script-added",
+ ScriptRemoved: "debugger-manager-script-removed",
+ ScriptsCleared: "debugger-manager-scripts-cleared",
+ BreakpointsEnabledDidChange: "debugger-manager-breakpoints-enabled-did-change"
+};
+
+WebInspector.DebuggerManager.PauseReason = {
+ Assertion: "assertion",
+ Breakpoint: "breakpoint",
+ CSPViolation: "CSP-violation",
+ DebuggerStatement: "debugger-statement",
+ Exception: "exception",
+ PauseOnNextStatement: "pause-on-next-statement",
+ Other: "other",
+};
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/DragToAdjustController.js b/Source/WebInspectorUI/UserInterface/Controllers/DragToAdjustController.js
new file mode 100644
index 000000000..4ad3a2ab3
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/DragToAdjustController.js
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2014 Antoine Quint
+ *
+ * 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.DragToAdjustController = class DragToAdjustController
+{
+ constructor(delegate)
+ {
+ this._delegate = delegate;
+
+ this._element = null;
+ this._active = false;
+ this._enabled = false;
+ this._dragging = false;
+ this._tracksMouseClickAndDrag = false;
+ }
+
+ // Public
+
+ get element()
+ {
+ return this._element;
+ }
+
+ set element(element)
+ {
+ this._element = element;
+ }
+
+ get enabled()
+ {
+ return this._enabled;
+ }
+
+ set enabled(enabled)
+ {
+ if (this._enabled === enabled)
+ return;
+
+ if (enabled) {
+ this._element.addEventListener("mouseenter", this);
+ this._element.addEventListener("mouseleave", this);
+ } else {
+ this._element.removeEventListener("mouseenter", this);
+ this._element.removeEventListener("mouseleave", this);
+ }
+ }
+
+ get active()
+ {
+ return this._active;
+ }
+
+ set active(active)
+ {
+ if (!this._element)
+ return;
+
+ if (this._active === active)
+ return;
+
+ if (active) {
+ WebInspector.notifications.addEventListener(WebInspector.Notification.GlobalModifierKeysDidChange, this._modifiersDidChange, this);
+ this._element.addEventListener("mousemove", this);
+ } else {
+ WebInspector.notifications.removeEventListener(WebInspector.Notification.GlobalModifierKeysDidChange, this._modifiersDidChange, this);
+ this._element.removeEventListener("mousemove", this);
+ this._setTracksMouseClickAndDrag(false);
+ }
+
+ this._active = active;
+
+ if (this._delegate && typeof this._delegate.dragToAdjustControllerActiveStateChanged === "function")
+ this._delegate.dragToAdjustControllerActiveStateChanged(this);
+ }
+
+ reset()
+ {
+ this._setTracksMouseClickAndDrag(false);
+ this._element.classList.remove(WebInspector.DragToAdjustController.StyleClassName);
+
+ if (this._delegate && typeof this._delegate.dragToAdjustControllerDidReset === "function")
+ this._delegate.dragToAdjustControllerDidReset(this);
+ }
+
+ // Protected
+
+ handleEvent(event)
+ {
+ switch (event.type) {
+ case "mouseenter":
+ if (!this._dragging) {
+ if (this._delegate && typeof this._delegate.dragToAdjustControllerCanBeActivated === "function")
+ this.active = this._delegate.dragToAdjustControllerCanBeActivated(this);
+ else
+ this.active = true;
+ }
+ break;
+ case "mouseleave":
+ if (!this._dragging)
+ this.active = false;
+ break;
+ case "mousemove":
+ if (this._dragging)
+ this._mouseWasDragged(event);
+ else
+ this._mouseMoved(event);
+ break;
+ case "mousedown":
+ this._mouseWasPressed(event);
+ break;
+ case "mouseup":
+ this._mouseWasReleased(event);
+ break;
+ case "contextmenu":
+ event.preventDefault();
+ break;
+ }
+ }
+
+ // Private
+
+ _setDragging(dragging)
+ {
+ if (this._dragging === dragging)
+ return;
+
+ console.assert(window.event);
+ if (dragging)
+ WebInspector.elementDragStart(this._element, this, this, window.event, "col-resize", window);
+ else
+ WebInspector.elementDragEnd(window.event);
+
+ this._dragging = dragging;
+ }
+
+ _setTracksMouseClickAndDrag(tracksMouseClickAndDrag)
+ {
+ if (this._tracksMouseClickAndDrag === tracksMouseClickAndDrag)
+ return;
+
+ if (tracksMouseClickAndDrag) {
+ this._element.classList.add(WebInspector.DragToAdjustController.StyleClassName);
+ window.addEventListener("mousedown", this, true);
+ window.addEventListener("contextmenu", this, true);
+ } else {
+ this._element.classList.remove(WebInspector.DragToAdjustController.StyleClassName);
+ window.removeEventListener("mousedown", this, true);
+ window.removeEventListener("contextmenu", this, true);
+ this._setDragging(false);
+ }
+
+ this._tracksMouseClickAndDrag = tracksMouseClickAndDrag;
+ }
+
+ _modifiersDidChange(event)
+ {
+ var canBeAdjusted = WebInspector.modifierKeys.altKey;
+ if (canBeAdjusted && this._delegate && typeof this._delegate.dragToAdjustControllerCanBeAdjusted === "function")
+ canBeAdjusted = this._delegate.dragToAdjustControllerCanBeAdjusted(this);
+
+ this._setTracksMouseClickAndDrag(canBeAdjusted);
+ }
+
+ _mouseMoved(event)
+ {
+ var canBeAdjusted = event.altKey;
+ if (canBeAdjusted && this._delegate && typeof this._delegate.dragToAdjustControllerCanAdjustObjectAtPoint === "function")
+ canBeAdjusted = this._delegate.dragToAdjustControllerCanAdjustObjectAtPoint(this, WebInspector.Point.fromEvent(event));
+
+ this._setTracksMouseClickAndDrag(canBeAdjusted);
+ }
+
+ _mouseWasPressed(event)
+ {
+ this._lastX = event.screenX;
+
+ this._setDragging(true);
+
+ event.preventDefault();
+ event.stopPropagation();
+ }
+
+ _mouseWasDragged(event)
+ {
+ var x = event.screenX;
+ var amount = x - this._lastX;
+
+ if (Math.abs(amount) < 1)
+ return;
+
+ this._lastX = x;
+
+ if (event.ctrlKey)
+ amount /= 10;
+ else if (event.shiftKey)
+ amount *= 10;
+
+ if (this._delegate && typeof this._delegate.dragToAdjustControllerWasAdjustedByAmount === "function")
+ this._delegate.dragToAdjustControllerWasAdjustedByAmount(this, amount);
+
+ event.preventDefault();
+ event.stopPropagation();
+ }
+
+ _mouseWasReleased(event)
+ {
+ this._setDragging(false);
+
+ event.preventDefault();
+ event.stopPropagation();
+
+ this.reset();
+ }
+};
+
+WebInspector.DragToAdjustController.StyleClassName = "drag-to-adjust";
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/Formatter.js b/Source/WebInspectorUI/UserInterface/Controllers/Formatter.js
new file mode 100644
index 000000000..2a1652733
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/Formatter.js
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2013 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.Formatter = class Formatter
+{
+ constructor(codeMirror, builder)
+ {
+ console.assert(codeMirror);
+ console.assert(builder);
+
+ this._codeMirror = codeMirror;
+ this._builder = builder;
+
+ this._lastToken = null;
+ this._lastContent = "";
+ }
+
+ // Public
+
+ format(from, to)
+ {
+ console.assert(this._builder.originalContent === null);
+ if (this._builder.originalContent !== null)
+ return;
+
+ var outerMode = this._codeMirror.getMode();
+ var content = this._codeMirror.getRange(from, to);
+ var state = CodeMirror.copyState(outerMode, this._codeMirror.getTokenAt(from).state);
+ this._builder.setOriginalContent(content);
+
+ var lineOffset = 0;
+ var lines = content.split("\n");
+ for (var i = 0; i < lines.length; ++i) {
+ var line = lines[i];
+ var startOfNewLine = true;
+ var firstTokenOnLine = true;
+ var stream = new CodeMirror.StringStream(line);
+ while (!stream.eol()) {
+ var innerMode = CodeMirror.innerMode(outerMode, state);
+ var token = outerMode.token(stream, state);
+ var isWhiteSpace = token === null && /^\s*$/.test(stream.current());
+ this._handleToken(innerMode.mode, token, state, stream, lineOffset + stream.start, isWhiteSpace, startOfNewLine, firstTokenOnLine);
+ stream.start = stream.pos;
+ startOfNewLine = false;
+ if (firstTokenOnLine && !isWhiteSpace)
+ firstTokenOnLine = false;
+ }
+
+ if (firstTokenOnLine)
+ this._handleEmptyLine();
+
+ lineOffset += line.length + 1; // +1 for the "\n" removed in split.
+ this._handleLineEnding(lineOffset - 1); // -1 for the index of the "\n".
+ }
+
+ this._builder.finish();
+ }
+
+ // Private
+
+ _handleToken(mode, token, state, stream, originalPosition, isWhiteSpace, startOfNewLine, firstTokenOnLine)
+ {
+ // String content of the token.
+ var content = stream.current();
+
+ // Start of a new line. Insert a newline to be safe if code was not-ASI safe. These are collapsed.
+ if (startOfNewLine)
+ this._builder.appendNewline();
+
+ // Whitespace. Remove all spaces or collapse to a single space.
+ if (isWhiteSpace) {
+ this._builder.appendSpace();
+ return;
+ }
+
+ // Avoid some hooks for content in comments.
+ var isComment = token && /\bcomment\b/.test(token);
+
+ if (mode.modifyStateForTokenPre)
+ mode.modifyStateForTokenPre(this._lastToken, this._lastContent, token, state, content, isComment);
+
+ // Should we remove the last whitespace?
+ if (this._builder.lastTokenWasWhitespace && mode.removeLastWhitespace(this._lastToken, this._lastContent, token, state, content, isComment))
+ this._builder.removeLastWhitespace();
+
+ // Should we remove the last newline?
+ if (this._builder.lastTokenWasNewline && mode.removeLastNewline(this._lastToken, this._lastContent, token, state, content, isComment, firstTokenOnLine))
+ this._builder.removeLastNewline();
+
+ // Add whitespace after the last token?
+ if (!this._builder.lastTokenWasWhitespace && mode.shouldHaveSpaceAfterLastToken(this._lastToken, this._lastContent, token, state, content, isComment))
+ this._builder.appendSpace();
+
+ // Add whitespace before this token?
+ if (!this._builder.lastTokenWasWhitespace && mode.shouldHaveSpaceBeforeToken(this._lastToken, this._lastContent, token, state, content, isComment))
+ this._builder.appendSpace();
+
+ // Should we dedent before this token?
+ var dedents = mode.dedentsBeforeToken(this._lastToken, this._lastContent, token, state, content, isComment);
+ while (dedents-- > 0)
+ this._builder.dedent();
+
+ // Should we add a newline before this token?
+ if (mode.newlineBeforeToken(this._lastToken, this._lastContent, token, state, content, isComment))
+ this._builder.appendNewline();
+
+ // Should we indent before this token?
+ if (mode.indentBeforeToken(this._lastToken, this._lastContent, token, state, content, isComment))
+ this._builder.indent();
+
+ // Append token.
+ this._builder.appendToken(content, originalPosition);
+
+ // Let the pretty printer update any state it keeps track of.
+ if (mode.modifyStateForTokenPost)
+ mode.modifyStateForTokenPost(this._lastToken, this._lastContent, token, state, content, isComment);
+
+ // Should we indent or dedent after this token?
+ if (!isComment && mode.indentAfterToken(this._lastToken, this._lastContent, token, state, content, isComment))
+ this._builder.indent();
+
+ // Should we add newlines after this token?
+ var newlines = mode.newlinesAfterToken(this._lastToken, this._lastContent, token, state, content, isComment);
+ if (newlines)
+ this._builder.appendMultipleNewlines(newlines);
+
+ // Record this token as the last token.
+ this._lastToken = token;
+ this._lastContent = content;
+ }
+
+ _handleEmptyLine()
+ {
+ // Preserve original whitespace only lines by adding a newline.
+ // However, don't do this if the builder just added multiple newlines.
+ if (!(this._builder.lastTokenWasNewline && this._builder.lastNewlineAppendWasMultiple))
+ this._builder.appendNewline(true);
+ }
+
+ _handleLineEnding(originalNewLinePosition)
+ {
+ // Record the original line ending.
+ this._builder.addOriginalLineEnding(originalNewLinePosition);
+ }
+};
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/FormatterSourceMap.js b/Source/WebInspectorUI/UserInterface/Controllers/FormatterSourceMap.js
new file mode 100644
index 000000000..e320ba1ce
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/FormatterSourceMap.js
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2013 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.FormatterSourceMap = class FormatterSourceMap extends WebInspector.Object
+{
+ constructor(originalLineEndings, formattedLineEndings, mapping)
+ {
+ super();
+
+ this._originalLineEndings = originalLineEndings;
+ this._formattedLineEndings = formattedLineEndings;
+ this._mapping = mapping;
+ }
+
+ // Static
+
+ static fromSourceMapData({originalLineEndings, formattedLineEndings, mapping})
+ {
+ return new WebInspector.FormatterSourceMap(originalLineEndings, formattedLineEndings, mapping);
+ }
+
+ // Public
+
+ originalToFormatted(lineNumber, columnNumber)
+ {
+ var originalPosition = this._locationToPosition(this._originalLineEndings, lineNumber || 0, columnNumber || 0);
+ return this.originalPositionToFormatted(originalPosition);
+ }
+
+ originalPositionToFormatted(originalPosition)
+ {
+ var formattedPosition = this._convertPosition(this._mapping.original, this._mapping.formatted, originalPosition);
+ return this._positionToLocation(this._formattedLineEndings, formattedPosition);
+ }
+
+ formattedToOriginal(lineNumber, columnNumber)
+ {
+ var originalPosition = this.formattedToOriginalOffset(lineNumber, columnNumber);
+ return this._positionToLocation(this._originalLineEndings, originalPosition);
+ }
+
+ formattedToOriginalOffset(lineNumber, columnNumber)
+ {
+ var formattedPosition = this._locationToPosition(this._formattedLineEndings, lineNumber || 0, columnNumber || 0);
+ var originalPosition = this._convertPosition(this._mapping.formatted, this._mapping.original, formattedPosition);
+ return originalPosition;
+ }
+
+ // Private
+
+ _locationToPosition(lineEndings, lineNumber, columnNumber)
+ {
+ var lineOffset = lineNumber ? lineEndings[lineNumber - 1] + 1 : 0;
+ return lineOffset + columnNumber;
+ }
+
+ _positionToLocation(lineEndings, position)
+ {
+ var lineNumber = lineEndings.upperBound(position - 1);
+ if (!lineNumber)
+ var columnNumber = position;
+ else
+ var columnNumber = position - lineEndings[lineNumber - 1] - 1;
+ return {lineNumber, columnNumber};
+ }
+
+ _convertPosition(positions1, positions2, positionInPosition1)
+ {
+ var index = positions1.upperBound(positionInPosition1) - 1;
+ var convertedPosition = positions2[index] + positionInPosition1 - positions1[index];
+ if (index < positions2.length - 1 && convertedPosition > positions2[index + 1])
+ convertedPosition = positions2[index + 1];
+ return convertedPosition;
+ }
+};
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/FrameResourceManager.js b/Source/WebInspectorUI/UserInterface/Controllers/FrameResourceManager.js
new file mode 100644
index 000000000..2fa0c936f
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/FrameResourceManager.js
@@ -0,0 +1,640 @@
+/*
+ * Copyright (C) 2013 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.FrameResourceManager = class FrameResourceManager extends WebInspector.Object
+{
+ constructor()
+ {
+ super();
+
+ if (window.PageAgent)
+ PageAgent.enable();
+ if (window.NetworkAgent)
+ NetworkAgent.enable();
+
+ WebInspector.notifications.addEventListener(WebInspector.Notification.ExtraDomainsActivated, this._extraDomainsActivated, this);
+
+ this.initialize();
+ }
+
+ // Public
+
+ initialize()
+ {
+ var oldMainFrame = this._mainFrame;
+
+ this._frameIdentifierMap = new Map;
+ this._mainFrame = null;
+ this._resourceRequestIdentifierMap = new Map;
+ this._orphanedResources = new Map;
+
+ if (this._mainFrame !== oldMainFrame)
+ this._mainFrameDidChange(oldMainFrame);
+
+ this._waitingForMainFrameResourceTreePayload = true;
+ if (window.PageAgent)
+ PageAgent.getResourceTree(this._processMainFrameResourceTreePayload.bind(this));
+ }
+
+ get mainFrame()
+ {
+ return this._mainFrame;
+ }
+
+ get frames()
+ {
+ return [...this._frameIdentifierMap.values()];
+ }
+
+ frameForIdentifier(frameId)
+ {
+ return this._frameIdentifierMap.get(frameId) || null;
+ }
+
+ frameDidNavigate(framePayload)
+ {
+ // Called from WebInspector.PageObserver.
+
+ // Ignore this while waiting for the whole frame/resource tree.
+ if (this._waitingForMainFrameResourceTreePayload)
+ return;
+
+ var frameWasLoadedInstantly = false;
+
+ var frame = this.frameForIdentifier(framePayload.id);
+ if (!frame) {
+ // If the frame wasn't known before now, then the main resource was loaded instantly (about:blank, etc.)
+ // Make a new resource (which will make the frame). Mark will mark it as loaded at the end too since we
+ // don't expect any more events about the load finishing for these frames.
+ var frameResource = this._addNewResourceToFrameOrTarget(null, framePayload.id, framePayload.loaderId, framePayload.url, null, null, null, null, null, framePayload.name, framePayload.securityOrigin);
+ frame = frameResource.parentFrame;
+ frameWasLoadedInstantly = true;
+
+ console.assert(frame);
+ if (!frame)
+ return;
+ }
+
+ if (framePayload.loaderId === frame.provisionalLoaderIdentifier) {
+ // There was a provisional load in progress, commit it.
+ frame.commitProvisionalLoad(framePayload.securityOrigin);
+ } else {
+ if (frame.mainResource.url !== framePayload.url || frame.loaderIdentifier !== framePayload.loaderId) {
+ // Navigations like back/forward do not have provisional loads, so create a new main resource here.
+ var mainResource = new WebInspector.Resource(framePayload.url, framePayload.mimeType, null, framePayload.loaderId);
+ } else {
+ // The main resource is already correct, so reuse it.
+ var mainResource = frame.mainResource;
+ }
+
+ frame.initialize(framePayload.name, framePayload.securityOrigin, framePayload.loaderId, mainResource);
+ }
+
+ var oldMainFrame = this._mainFrame;
+
+ if (framePayload.parentId) {
+ var parentFrame = this.frameForIdentifier(framePayload.parentId);
+ console.assert(parentFrame);
+
+ if (frame === this._mainFrame)
+ this._mainFrame = null;
+
+ if (frame.parentFrame !== parentFrame)
+ parentFrame.addChildFrame(frame);
+ } else {
+ if (frame.parentFrame)
+ frame.parentFrame.removeChildFrame(frame);
+ this._mainFrame = frame;
+ }
+
+ if (this._mainFrame !== oldMainFrame)
+ this._mainFrameDidChange(oldMainFrame);
+
+ if (frameWasLoadedInstantly)
+ frame.mainResource.markAsFinished();
+ }
+
+ frameDidDetach(frameId)
+ {
+ // Called from WebInspector.PageObserver.
+
+ // Ignore this while waiting for the whole frame/resource tree.
+ if (this._waitingForMainFrameResourceTreePayload)
+ return;
+
+ var frame = this.frameForIdentifier(frameId);
+ if (!frame)
+ return;
+
+ if (frame.parentFrame)
+ frame.parentFrame.removeChildFrame(frame);
+
+ this._frameIdentifierMap.delete(frame.id);
+
+ var oldMainFrame = this._mainFrame;
+
+ if (frame === this._mainFrame)
+ this._mainFrame = null;
+
+ frame.clearExecutionContexts();
+
+ this.dispatchEventToListeners(WebInspector.FrameResourceManager.Event.FrameWasRemoved, {frame});
+
+ if (this._mainFrame !== oldMainFrame)
+ this._mainFrameDidChange(oldMainFrame);
+ }
+
+ resourceRequestWillBeSent(requestIdentifier, frameIdentifier, loaderIdentifier, request, type, redirectResponse, timestamp, initiator, targetId)
+ {
+ // Called from WebInspector.NetworkObserver.
+
+ // Ignore this while waiting for the whole frame/resource tree.
+ if (this._waitingForMainFrameResourceTreePayload)
+ return;
+
+ // COMPATIBILITY (iOS 8): Timeline timestamps for legacy backends are computed
+ // dynamically from the first backend timestamp received. For navigations we
+ // need to reset that base timestamp, and an appropriate timestamp to use is
+ // the new main resource's will be sent timestamp. So save this value on the
+ // resource in case it becomes a main resource.
+ var originalRequestWillBeSentTimestamp = timestamp;
+
+ var elapsedTime = WebInspector.timelineManager.computeElapsedTime(timestamp);
+ let resource = this._resourceRequestIdentifierMap.get(requestIdentifier);
+ if (resource) {
+ // This is an existing request which is being redirected, update the resource.
+ console.assert(redirectResponse);
+ console.assert(!targetId);
+ resource.updateForRedirectResponse(request.url, request.headers, elapsedTime);
+ return;
+ }
+
+ var initiatorSourceCodeLocation = this._initiatorSourceCodeLocationFromPayload(initiator);
+
+ // This is a new request, make a new resource and add it to the right frame.
+ resource = this._addNewResourceToFrameOrTarget(requestIdentifier, frameIdentifier, loaderIdentifier, request.url, type, request.method, request.headers, request.postData, elapsedTime, null, null, initiatorSourceCodeLocation, originalRequestWillBeSentTimestamp, targetId);
+
+ // Associate the resource with the requestIdentifier so it can be found in future loading events.
+ this._resourceRequestIdentifierMap.set(requestIdentifier, resource);
+ }
+
+ markResourceRequestAsServedFromMemoryCache(requestIdentifier)
+ {
+ // Called from WebInspector.NetworkObserver.
+
+ // Ignore this while waiting for the whole frame/resource tree.
+ if (this._waitingForMainFrameResourceTreePayload)
+ return;
+
+ let resource = this._resourceRequestIdentifierMap.get(requestIdentifier);
+
+ // We might not have a resource if the inspector was opened during the page load (after resourceRequestWillBeSent is called).
+ // We don't want to assert in this case since we do likely have the resource, via PageAgent.getResourceTree. The Resource
+ // just doesn't have a requestIdentifier for us to look it up.
+ if (!resource)
+ return;
+
+ resource.markAsCached();
+ }
+
+ resourceRequestWasServedFromMemoryCache(requestIdentifier, frameIdentifier, loaderIdentifier, cachedResourcePayload, timestamp, initiator)
+ {
+ // Called from WebInspector.NetworkObserver.
+
+ // Ignore this while waiting for the whole frame/resource tree.
+ if (this._waitingForMainFrameResourceTreePayload)
+ return;
+
+ console.assert(!this._resourceRequestIdentifierMap.has(requestIdentifier));
+
+ var elapsedTime = WebInspector.timelineManager.computeElapsedTime(timestamp);
+ var initiatorSourceCodeLocation = this._initiatorSourceCodeLocationFromPayload(initiator);
+ var response = cachedResourcePayload.response;
+ var resource = this._addNewResourceToFrameOrTarget(requestIdentifier, frameIdentifier, loaderIdentifier, cachedResourcePayload.url, cachedResourcePayload.type, "GET", null, null, elapsedTime, null, null, initiatorSourceCodeLocation);
+ resource.markAsCached();
+ resource.updateForResponse(cachedResourcePayload.url, response.mimeType, cachedResourcePayload.type, response.headers, response.status, response.statusText, elapsedTime, response.timing);
+ resource.increaseSize(cachedResourcePayload.bodySize, elapsedTime);
+ resource.increaseTransferSize(cachedResourcePayload.bodySize);
+ resource.markAsFinished(elapsedTime);
+
+ if (cachedResourcePayload.sourceMapURL)
+ WebInspector.sourceMapManager.downloadSourceMap(cachedResourcePayload.sourceMapURL, resource.url, resource);
+
+ // No need to associate the resource with the requestIdentifier, since this is the only event
+ // sent for memory cache resource loads.
+ }
+
+ resourceRequestDidReceiveResponse(requestIdentifier, frameIdentifier, loaderIdentifier, type, response, timestamp)
+ {
+ // Called from WebInspector.NetworkObserver.
+
+ // Ignore this while waiting for the whole frame/resource tree.
+ if (this._waitingForMainFrameResourceTreePayload)
+ return;
+
+ var elapsedTime = WebInspector.timelineManager.computeElapsedTime(timestamp);
+ let resource = this._resourceRequestIdentifierMap.get(requestIdentifier);
+
+ // We might not have a resource if the inspector was opened during the page load (after resourceRequestWillBeSent is called).
+ // We don't want to assert in this case since we do likely have the resource, via PageAgent.getResourceTree. The Resource
+ // just doesn't have a requestIdentifier for us to look it up, but we can try to look it up by its URL.
+ if (!resource) {
+ var frame = this.frameForIdentifier(frameIdentifier);
+ if (frame)
+ resource = frame.resourceForURL(response.url);
+
+ // If we find the resource this way we had marked it earlier as finished via PageAgent.getResourceTree.
+ // Associate the resource with the requestIdentifier so it can be found in future loading events.
+ // and roll it back to an unfinished state, we know now it is still loading.
+ if (resource) {
+ this._resourceRequestIdentifierMap.set(requestIdentifier, resource);
+ resource.revertMarkAsFinished();
+ }
+ }
+
+ // If we haven't found an existing Resource by now, then it is a resource that was loading when the inspector
+ // opened and we just missed the resourceRequestWillBeSent for it. So make a new resource and add it.
+ if (!resource) {
+ resource = this._addNewResourceToFrameOrTarget(requestIdentifier, frameIdentifier, loaderIdentifier, response.url, type, null, response.requestHeaders, null, elapsedTime, null, null, null);
+
+ // Associate the resource with the requestIdentifier so it can be found in future loading events.
+ this._resourceRequestIdentifierMap.set(requestIdentifier, resource);
+ }
+
+ if (response.fromDiskCache)
+ resource.markAsCached();
+
+ resource.updateForResponse(response.url, response.mimeType, type, response.headers, response.status, response.statusText, elapsedTime, response.timing);
+ }
+
+ resourceRequestDidReceiveData(requestIdentifier, dataLength, encodedDataLength, timestamp)
+ {
+ // Called from WebInspector.NetworkObserver.
+
+ // Ignore this while waiting for the whole frame/resource tree.
+ if (this._waitingForMainFrameResourceTreePayload)
+ return;
+
+ let resource = this._resourceRequestIdentifierMap.get(requestIdentifier);
+ var elapsedTime = WebInspector.timelineManager.computeElapsedTime(timestamp);
+
+ // We might not have a resource if the inspector was opened during the page load (after resourceRequestWillBeSent is called).
+ // We don't want to assert in this case since we do likely have the resource, via PageAgent.getResourceTree. The Resource
+ // just doesn't have a requestIdentifier for us to look it up.
+ if (!resource)
+ return;
+
+ resource.increaseSize(dataLength, elapsedTime);
+
+ if (encodedDataLength !== -1)
+ resource.increaseTransferSize(encodedDataLength);
+ }
+
+ resourceRequestDidFinishLoading(requestIdentifier, timestamp, sourceMapURL)
+ {
+ // Called from WebInspector.NetworkObserver.
+
+ // Ignore this while waiting for the whole frame/resource tree.
+ if (this._waitingForMainFrameResourceTreePayload)
+ return;
+
+ // By now we should always have the Resource. Either it was fetched when the inspector first opened with
+ // PageAgent.getResourceTree, or it was a currently loading resource that we learned about in resourceRequestDidReceiveResponse.
+ let resource = this._resourceRequestIdentifierMap.get(requestIdentifier);
+ console.assert(resource);
+ if (!resource)
+ return;
+
+ var elapsedTime = WebInspector.timelineManager.computeElapsedTime(timestamp);
+ resource.markAsFinished(elapsedTime);
+
+ if (sourceMapURL)
+ WebInspector.sourceMapManager.downloadSourceMap(sourceMapURL, resource.url, resource);
+
+ this._resourceRequestIdentifierMap.delete(requestIdentifier);
+ }
+
+ resourceRequestDidFailLoading(requestIdentifier, canceled, timestamp)
+ {
+ // Called from WebInspector.NetworkObserver.
+
+ // Ignore this while waiting for the whole frame/resource tree.
+ if (this._waitingForMainFrameResourceTreePayload)
+ return;
+
+ // By now we should always have the Resource. Either it was fetched when the inspector first opened with
+ // PageAgent.getResourceTree, or it was a currently loading resource that we learned about in resourceRequestDidReceiveResponse.
+ let resource = this._resourceRequestIdentifierMap.get(requestIdentifier);
+ console.assert(resource);
+ if (!resource)
+ return;
+
+ var elapsedTime = WebInspector.timelineManager.computeElapsedTime(timestamp);
+ resource.markAsFailed(canceled, elapsedTime);
+
+ if (resource === resource.parentFrame.provisionalMainResource)
+ resource.parentFrame.clearProvisionalLoad();
+
+ this._resourceRequestIdentifierMap.delete(requestIdentifier);
+ }
+
+ executionContextCreated(contextPayload)
+ {
+ // Called from WebInspector.RuntimeObserver.
+
+ var frame = this.frameForIdentifier(contextPayload.frameId);
+ console.assert(frame);
+ if (!frame)
+ return;
+
+ var displayName = contextPayload.name || frame.mainResource.displayName;
+ var executionContext = new WebInspector.ExecutionContext(WebInspector.mainTarget, contextPayload.id, displayName, contextPayload.isPageContext, frame);
+ frame.addExecutionContext(executionContext);
+ }
+
+ resourceForURL(url)
+ {
+ if (!this._mainFrame)
+ return null;
+
+ if (this._mainFrame.mainResource.url === url)
+ return this._mainFrame.mainResource;
+
+ return this._mainFrame.resourceForURL(url, true);
+ }
+
+ adoptOrphanedResourcesForTarget(target)
+ {
+ let resources = this._orphanedResources.take(target.identifier);
+ if (!resources)
+ return;
+
+ for (let resource of resources)
+ target.adoptResource(resource);
+ }
+
+ // Private
+
+ _addNewResourceToFrameOrTarget(requestIdentifier, frameIdentifier, loaderIdentifier, url, type, requestMethod, requestHeaders, requestData, elapsedTime, frameName, frameSecurityOrigin, initiatorSourceCodeLocation, originalRequestWillBeSentTimestamp, targetId)
+ {
+ console.assert(!this._waitingForMainFrameResourceTreePayload);
+
+ let resource = null;
+
+ let frame = this.frameForIdentifier(frameIdentifier);
+ if (frame) {
+ // This is a new request for an existing frame, which might be the main resource or a new resource.
+ if (frame.mainResource.url === url && frame.loaderIdentifier === loaderIdentifier)
+ resource = frame.mainResource;
+ else if (frame.provisionalMainResource && frame.provisionalMainResource.url === url && frame.provisionalLoaderIdentifier === loaderIdentifier)
+ resource = frame.provisionalMainResource;
+ else {
+ resource = new WebInspector.Resource(url, null, type, loaderIdentifier, targetId, requestIdentifier, requestMethod, requestHeaders, requestData, elapsedTime, initiatorSourceCodeLocation, originalRequestWillBeSentTimestamp);
+ if (resource.target === WebInspector.mainTarget)
+ this._addResourceToFrame(frame, resource);
+ else if (resource.target)
+ resource.target.addResource(resource);
+ else
+ this._addOrphanedResource(resource, targetId);
+ }
+ } else {
+ // This is a new request for a new frame, which is always the main resource.
+ console.assert(!targetId);
+ resource = new WebInspector.Resource(url, null, type, loaderIdentifier, targetId, requestIdentifier, requestMethod, requestHeaders, requestData, elapsedTime, initiatorSourceCodeLocation, originalRequestWillBeSentTimestamp);
+ frame = new WebInspector.Frame(frameIdentifier, frameName, frameSecurityOrigin, loaderIdentifier, resource);
+ this._frameIdentifierMap.set(frame.id, frame);
+
+ // If we don't have a main frame, assume this is it. This can change later in
+ // frameDidNavigate when the parent frame is known.
+ if (!this._mainFrame) {
+ this._mainFrame = frame;
+ this._mainFrameDidChange(null);
+ }
+
+ this._dispatchFrameWasAddedEvent(frame);
+ }
+
+ console.assert(resource);
+
+ return resource;
+ }
+
+ _addResourceToFrame(frame, resource)
+ {
+ console.assert(!this._waitingForMainFrameResourceTreePayload);
+ if (this._waitingForMainFrameResourceTreePayload)
+ return;
+
+ console.assert(frame);
+ console.assert(resource);
+
+ if (resource.loaderIdentifier !== frame.loaderIdentifier && !frame.provisionalLoaderIdentifier) {
+ // This is the start of a provisional load which happens before frameDidNavigate is called.
+ // This resource will be the new mainResource if frameDidNavigate is called.
+ frame.startProvisionalLoad(resource);
+ return;
+ }
+
+ // This is just another resource, either for the main loader or the provisional loader.
+ console.assert(resource.loaderIdentifier === frame.loaderIdentifier || resource.loaderIdentifier === frame.provisionalLoaderIdentifier);
+ frame.addResource(resource);
+ }
+
+ _addResourceToTarget(target, resource)
+ {
+ console.assert(target !== WebInspector.mainTarget);
+ console.assert(resource);
+
+ target.addResource(resource);
+ }
+
+ _initiatorSourceCodeLocationFromPayload(initiatorPayload)
+ {
+ if (!initiatorPayload)
+ return null;
+
+ var url = null;
+ var lineNumber = NaN;
+ var columnNumber = 0;
+
+ if (initiatorPayload.stackTrace && initiatorPayload.stackTrace.length) {
+ var stackTracePayload = initiatorPayload.stackTrace;
+ for (var i = 0; i < stackTracePayload.length; ++i) {
+ var callFramePayload = stackTracePayload[i];
+ if (!callFramePayload.url || callFramePayload.url === "[native code]")
+ continue;
+
+ url = callFramePayload.url;
+
+ // The lineNumber is 1-based, but we expect 0-based.
+ lineNumber = callFramePayload.lineNumber - 1;
+
+ columnNumber = callFramePayload.columnNumber;
+
+ break;
+ }
+ } else if (initiatorPayload.url) {
+ url = initiatorPayload.url;
+
+ // The lineNumber is 1-based, but we expect 0-based.
+ lineNumber = initiatorPayload.lineNumber - 1;
+ }
+
+ if (!url || isNaN(lineNumber) || lineNumber < 0)
+ return null;
+
+ var sourceCode = WebInspector.frameResourceManager.resourceForURL(url);
+ if (!sourceCode)
+ sourceCode = WebInspector.debuggerManager.scriptsForURL(url, WebInspector.mainTarget)[0];
+
+ if (!sourceCode)
+ return null;
+
+ return sourceCode.createSourceCodeLocation(lineNumber, columnNumber);
+ }
+
+ _processMainFrameResourceTreePayload(error, mainFramePayload)
+ {
+ console.assert(this._waitingForMainFrameResourceTreePayload);
+ delete this._waitingForMainFrameResourceTreePayload;
+
+ if (error) {
+ console.error(JSON.stringify(error));
+ return;
+ }
+
+ console.assert(mainFramePayload);
+ console.assert(mainFramePayload.frame);
+
+ this._resourceRequestIdentifierMap = new Map;
+ this._frameIdentifierMap = new Map;
+
+ var oldMainFrame = this._mainFrame;
+
+ this._mainFrame = this._addFrameTreeFromFrameResourceTreePayload(mainFramePayload, true);
+
+ if (this._mainFrame !== oldMainFrame)
+ this._mainFrameDidChange(oldMainFrame);
+ }
+
+ _createFrame(payload)
+ {
+ // If payload.url is missing or empty then this page is likely the special empty page. In that case
+ // we will just say it is "about:blank" so we have a URL, which is required for resources.
+ var mainResource = new WebInspector.Resource(payload.url || "about:blank", payload.mimeType, null, payload.loaderId);
+ var frame = new WebInspector.Frame(payload.id, payload.name, payload.securityOrigin, payload.loaderId, mainResource);
+
+ this._frameIdentifierMap.set(frame.id, frame);
+
+ mainResource.markAsFinished();
+
+ return frame;
+ }
+
+ _createResource(payload, framePayload)
+ {
+ var resource = new WebInspector.Resource(payload.url, payload.mimeType, payload.type, framePayload.loaderId, payload.targetId);
+
+ if (payload.sourceMapURL)
+ WebInspector.sourceMapManager.downloadSourceMap(payload.sourceMapURL, resource.url, resource);
+
+ return resource;
+ }
+
+ _addFrameTreeFromFrameResourceTreePayload(payload, isMainFrame)
+ {
+ var frame = this._createFrame(payload.frame);
+ if (isMainFrame)
+ frame.markAsMainFrame();
+
+ for (var i = 0; payload.childFrames && i < payload.childFrames.length; ++i)
+ frame.addChildFrame(this._addFrameTreeFromFrameResourceTreePayload(payload.childFrames[i], false));
+
+ for (var i = 0; payload.resources && i < payload.resources.length; ++i) {
+ var resourcePayload = payload.resources[i];
+
+ // The main resource is included as a resource. We can skip it since we already created
+ // a main resource when we created the Frame. The resource payload does not include anything
+ // didn't already get from the frame payload.
+ if (resourcePayload.type === "Document" && resourcePayload.url === payload.frame.url)
+ continue;
+
+ var resource = this._createResource(resourcePayload, payload);
+ if (resource.target === WebInspector.mainTarget)
+ frame.addResource(resource);
+ else if (resource.target)
+ resource.target.addResource(resource);
+ else
+ this._addOrphanedResource(resource, resourcePayload.targetId);
+
+ if (resourcePayload.failed || resourcePayload.canceled)
+ resource.markAsFailed(resourcePayload.canceled);
+ else
+ resource.markAsFinished();
+ }
+
+ this._dispatchFrameWasAddedEvent(frame);
+
+ return frame;
+ }
+
+ _addOrphanedResource(resource, targetId)
+ {
+ let resources = this._orphanedResources.get(targetId);
+ if (!resources) {
+ resources = [];
+ this._orphanedResources.set(targetId, resources);
+ }
+
+ resources.push(resource);
+ }
+
+ _dispatchFrameWasAddedEvent(frame)
+ {
+ this.dispatchEventToListeners(WebInspector.FrameResourceManager.Event.FrameWasAdded, {frame});
+ }
+
+ _mainFrameDidChange(oldMainFrame)
+ {
+ if (oldMainFrame)
+ oldMainFrame.unmarkAsMainFrame();
+ if (this._mainFrame)
+ this._mainFrame.markAsMainFrame();
+
+ this.dispatchEventToListeners(WebInspector.FrameResourceManager.Event.MainFrameDidChange, {oldMainFrame});
+ }
+
+ _extraDomainsActivated(event)
+ {
+ if (event.data.domains.includes("Page") && window.PageAgent)
+ PageAgent.getResourceTree(this._processMainFrameResourceTreePayload.bind(this));
+ }
+};
+
+WebInspector.FrameResourceManager.Event = {
+ FrameWasAdded: "frame-resource-manager-frame-was-added",
+ FrameWasRemoved: "frame-resource-manager-frame-was-removed",
+ MainFrameDidChange: "frame-resource-manager-main-frame-did-change",
+};
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/HeapManager.js b/Source/WebInspectorUI/UserInterface/Controllers/HeapManager.js
new file mode 100644
index 000000000..cc8449cf3
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/HeapManager.js
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.HeapManager = class HeapManager extends WebInspector.Object
+{
+ constructor()
+ {
+ super();
+
+ if (window.HeapAgent)
+ HeapAgent.enable();
+ }
+
+ // Public
+
+ garbageCollected(target, payload)
+ {
+ // Called from WebInspector.HeapObserver.
+
+ // FIXME: <https://webkit.org/b/167323> Web Inspector: Enable Memory profiling in Workers
+ if (target !== WebInspector.mainTarget)
+ return;
+
+ let collection = WebInspector.GarbageCollection.fromPayload(payload);
+ this.dispatchEventToListeners(WebInspector.HeapManager.Event.GarbageCollected, {collection});
+ }
+};
+
+WebInspector.HeapManager.Event = {
+ GarbageCollected: "heap-manager-garbage-collected"
+};
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/IssueManager.js b/Source/WebInspectorUI/UserInterface/Controllers/IssueManager.js
new file mode 100644
index 000000000..a589adb38
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/IssueManager.js
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2013 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.IssueManager = class IssueManager extends WebInspector.Object
+{
+ constructor()
+ {
+ super();
+
+ WebInspector.Frame.addEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
+ WebInspector.logManager.addEventListener(WebInspector.LogManager.Event.Cleared, this._logCleared, this);
+
+ this.initialize();
+ }
+
+ static issueMatchSourceCode(issue, sourceCode)
+ {
+ if (sourceCode instanceof WebInspector.SourceMapResource)
+ return issue.sourceCodeLocation && issue.sourceCodeLocation.displaySourceCode === sourceCode;
+ if (sourceCode instanceof WebInspector.Resource)
+ return issue.url === sourceCode.url && (!issue.sourceCodeLocation || issue.sourceCodeLocation.sourceCode === sourceCode);
+ if (sourceCode instanceof WebInspector.Script)
+ return issue.sourceCodeLocation && issue.sourceCodeLocation.sourceCode === sourceCode;
+ return false;
+ }
+
+ // Public
+
+ initialize()
+ {
+ this._issues = [];
+
+ this.dispatchEventToListeners(WebInspector.IssueManager.Event.Cleared);
+ }
+
+ issueWasAdded(consoleMessage)
+ {
+ let issue = new WebInspector.IssueMessage(consoleMessage);
+
+ this._issues.push(issue);
+
+ this.dispatchEventToListeners(WebInspector.IssueManager.Event.IssueWasAdded, {issue});
+ }
+
+ issuesForSourceCode(sourceCode)
+ {
+ var issues = [];
+
+ for (var i = 0; i < this._issues.length; ++i) {
+ var issue = this._issues[i];
+ if (WebInspector.IssueManager.issueMatchSourceCode(issue, sourceCode))
+ issues.push(issue);
+ }
+
+ return issues;
+ }
+
+ // Private
+
+ _logCleared(event)
+ {
+ this.initialize();
+ }
+
+ _mainResourceDidChange(event)
+ {
+ console.assert(event.target instanceof WebInspector.Frame);
+
+ if (!event.target.isMainFrame())
+ return;
+
+ this.initialize();
+ }
+};
+
+WebInspector.IssueManager.Event = {
+ IssueWasAdded: "issue-manager-issue-was-added",
+ Cleared: "issue-manager-cleared"
+};
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/JavaScriptLogViewController.js b/Source/WebInspectorUI/UserInterface/Controllers/JavaScriptLogViewController.js
new file mode 100644
index 000000000..72621324c
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/JavaScriptLogViewController.js
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2013 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.JavaScriptLogViewController = class JavaScriptLogViewController extends WebInspector.Object
+{
+ constructor(element, scrollElement, textPrompt, delegate, historySettingIdentifier)
+ {
+ super();
+
+ console.assert(textPrompt instanceof WebInspector.ConsolePrompt);
+ console.assert(historySettingIdentifier);
+
+ this._element = element;
+ this._scrollElement = scrollElement;
+
+ this._promptHistorySetting = new WebInspector.Setting(historySettingIdentifier, null);
+
+ this._prompt = textPrompt;
+ this._prompt.delegate = this;
+ this._prompt.history = this._promptHistorySetting.value;
+
+ this.delegate = delegate;
+
+ this._cleared = true;
+ this._previousMessageView = null;
+ this._lastCommitted = "";
+ this._repeatCountWasInterrupted = false;
+
+ this._sessions = [];
+
+ this.messagesAlternateClearKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.Control, "L", this.requestClearMessages.bind(this), this._element);
+
+ this._messagesFindNextKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl, "G", this._handleFindNextShortcut.bind(this), this._element);
+ this._messagesFindPreviousKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl | WebInspector.KeyboardShortcut.Modifier.Shift, "G", this._handleFindPreviousShortcut.bind(this), this._element);
+
+ this._promptAlternateClearKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.Control, "L", this.requestClearMessages.bind(this), this._prompt.element);
+ this._promptFindNextKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl, "G", this._handleFindNextShortcut.bind(this), this._prompt.element);
+ this._promptFindPreviousKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl | WebInspector.KeyboardShortcut.Modifier.Shift, "G", this._handleFindPreviousShortcut.bind(this), this._prompt.element);
+
+ this._pendingMessages = [];
+ this._scheduledRenderIdentifier = 0;
+
+ this.startNewSession();
+ }
+
+ // Public
+
+ get prompt()
+ {
+ return this._prompt;
+ }
+
+ get currentConsoleGroup()
+ {
+ return this._currentConsoleGroup;
+ }
+
+ clear()
+ {
+ this._cleared = true;
+
+ const clearPreviousSessions = true;
+ this.startNewSession(clearPreviousSessions, {newSessionReason: WebInspector.ConsoleSession.NewSessionReason.ConsoleCleared});
+ }
+
+ startNewSession(clearPreviousSessions = false, data = {})
+ {
+ if (this._sessions.length && clearPreviousSessions) {
+ for (var i = 0; i < this._sessions.length; ++i)
+ this._element.removeChild(this._sessions[i].element);
+
+ this._sessions = [];
+ this._currentConsoleGroup = null;
+ }
+
+ // First session shows the time when the console was opened.
+ if (!this._sessions.length)
+ data.timestamp = Date.now();
+
+ let lastSession = this._sessions.lastValue;
+
+ // Remove empty session.
+ if (lastSession && !lastSession.hasMessages()) {
+ this._sessions.pop();
+ lastSession.element.remove();
+ }
+
+ let consoleSession = new WebInspector.ConsoleSession(data);
+
+ this._previousMessageView = null;
+ this._lastCommitted = "";
+ this._repeatCountWasInterrupted = false;
+
+ this._sessions.push(consoleSession);
+ this._currentConsoleGroup = consoleSession;
+
+ this._element.appendChild(consoleSession.element);
+
+ // Make sure the new session is visible.
+ consoleSession.element.scrollIntoView();
+ }
+
+ appendImmediateExecutionWithResult(text, result, addSpecialUserLogClass, shouldRevealConsole)
+ {
+ console.assert(result instanceof WebInspector.RemoteObject);
+
+ var commandMessageView = new WebInspector.ConsoleCommandView(text, addSpecialUserLogClass ? "special-user-log" : null);
+ this._appendConsoleMessageView(commandMessageView, true);
+
+ function saveResultCallback(savedResultIndex)
+ {
+ let commandResultMessage = new WebInspector.ConsoleCommandResultMessage(result.target, result, false, savedResultIndex, shouldRevealConsole);
+ let commandResultMessageView = new WebInspector.ConsoleMessageView(commandResultMessage);
+ this._appendConsoleMessageView(commandResultMessageView, true);
+ }
+
+ WebInspector.runtimeManager.saveResult(result, saveResultCallback.bind(this));
+ }
+
+ appendConsoleMessage(consoleMessage)
+ {
+ var consoleMessageView = new WebInspector.ConsoleMessageView(consoleMessage);
+ this._appendConsoleMessageView(consoleMessageView);
+ return consoleMessageView;
+ }
+
+ updatePreviousMessageRepeatCount(count)
+ {
+ console.assert(this._previousMessageView);
+ if (!this._previousMessageView)
+ return false;
+
+ var previousIgnoredCount = this._previousMessageView[WebInspector.JavaScriptLogViewController.IgnoredRepeatCount] || 0;
+ var previousVisibleCount = this._previousMessageView.repeatCount;
+
+ if (!this._repeatCountWasInterrupted) {
+ this._previousMessageView.repeatCount = count - previousIgnoredCount;
+ return true;
+ }
+
+ var consoleMessage = this._previousMessageView.message;
+ var duplicatedConsoleMessageView = new WebInspector.ConsoleMessageView(consoleMessage);
+ duplicatedConsoleMessageView[WebInspector.JavaScriptLogViewController.IgnoredRepeatCount] = previousIgnoredCount + previousVisibleCount;
+ duplicatedConsoleMessageView.repeatCount = 1;
+ this._appendConsoleMessageView(duplicatedConsoleMessageView);
+
+ return true;
+ }
+
+ isScrolledToBottom()
+ {
+ // Lie about being scrolled to the bottom if we have a pending request to scroll to the bottom soon.
+ return this._scrollToBottomTimeout || this._scrollElement.isScrolledToBottom();
+ }
+
+ scrollToBottom()
+ {
+ if (this._scrollToBottomTimeout)
+ return;
+
+ function delayedWork()
+ {
+ this._scrollToBottomTimeout = null;
+ this._scrollElement.scrollTop = this._scrollElement.scrollHeight;
+ }
+
+ // Don't scroll immediately so we are not causing excessive layouts when there
+ // are many messages being added at once.
+ this._scrollToBottomTimeout = setTimeout(delayedWork.bind(this), 0);
+ }
+
+ requestClearMessages()
+ {
+ WebInspector.logManager.requestClearMessages();
+ }
+
+ // Protected
+
+ consolePromptHistoryDidChange(prompt)
+ {
+ this._promptHistorySetting.value = this.prompt.history;
+ }
+
+ consolePromptShouldCommitText(prompt, text, cursorIsAtLastPosition, handler)
+ {
+ // Always commit the text if we are not at the last position.
+ if (!cursorIsAtLastPosition) {
+ handler(true);
+ return;
+ }
+
+ function parseFinished(error, result, message, range)
+ {
+ handler(result !== RuntimeAgent.SyntaxErrorType.Recoverable);
+ }
+
+ WebInspector.runtimeManager.activeExecutionContext.target.RuntimeAgent.parse(text, parseFinished.bind(this));
+ }
+
+ consolePromptTextCommitted(prompt, text)
+ {
+ console.assert(text);
+
+ if (this._lastCommitted !== text) {
+ let commandMessageView = new WebInspector.ConsoleCommandView(text);
+ this._appendConsoleMessageView(commandMessageView, true);
+ this._lastCommitted = text;
+ }
+
+ function printResult(result, wasThrown, savedResultIndex)
+ {
+ if (!result || this._cleared)
+ return;
+
+ let shouldRevealConsole = true;
+ let commandResultMessage = new WebInspector.ConsoleCommandResultMessage(result.target, result, wasThrown, savedResultIndex, shouldRevealConsole);
+ let commandResultMessageView = new WebInspector.ConsoleMessageView(commandResultMessage);
+ this._appendConsoleMessageView(commandResultMessageView, true);
+ }
+
+ let options = {
+ objectGroup: WebInspector.RuntimeManager.ConsoleObjectGroup,
+ includeCommandLineAPI: true,
+ doNotPauseOnExceptionsAndMuteConsole: false,
+ returnByValue: false,
+ generatePreview: true,
+ saveResult: true,
+ sourceURLAppender: appendWebInspectorConsoleEvaluationSourceURL,
+ };
+
+ WebInspector.runtimeManager.evaluateInInspectedWindow(text, options, printResult.bind(this));
+ }
+
+ // Private
+
+ _handleFindNextShortcut()
+ {
+ this.delegate.highlightNextSearchMatch();
+ }
+
+ _handleFindPreviousShortcut()
+ {
+ this.delegate.highlightPreviousSearchMatch();
+ }
+
+ _appendConsoleMessageView(messageView, repeatCountWasInterrupted)
+ {
+ this._pendingMessages.push(messageView);
+
+ this._cleared = false;
+ this._repeatCountWasInterrupted = repeatCountWasInterrupted || false;
+
+ if (!repeatCountWasInterrupted)
+ this._previousMessageView = messageView;
+
+ if (messageView.message && messageView.message.source !== WebInspector.ConsoleMessage.MessageSource.JS)
+ this._lastCommitted = "";
+
+ if (WebInspector.consoleContentView.visible)
+ this.renderPendingMessagesSoon();
+
+ if (!WebInspector.isShowingConsoleTab() && messageView.message && messageView.message.shouldRevealConsole)
+ WebInspector.showSplitConsole();
+ }
+
+ renderPendingMessages()
+ {
+ if (this._scheduledRenderIdentifier) {
+ cancelAnimationFrame(this._scheduledRenderIdentifier);
+ this._scheduledRenderIdentifier = 0;
+ }
+
+ if (this._pendingMessages.length === 0)
+ return;
+
+ const maxMessagesPerFrame = 100;
+ let messages = this._pendingMessages.splice(0, maxMessagesPerFrame);
+
+ let lastMessageView = messages.lastValue;
+ let isCommandView = lastMessageView instanceof WebInspector.ConsoleCommandView;
+ let shouldScrollToBottom = isCommandView || lastMessageView.message.type === WebInspector.ConsoleMessage.MessageType.Result || this.isScrolledToBottom();
+
+ for (let messageView of messages) {
+ messageView.render();
+ this._didRenderConsoleMessageView(messageView);
+ }
+
+ if (shouldScrollToBottom)
+ this.scrollToBottom();
+
+ WebInspector.quickConsole.needsLayout();
+
+ if (this._pendingMessages.length > 0)
+ this.renderPendingMessagesSoon();
+ }
+
+ renderPendingMessagesSoon()
+ {
+ if (this._scheduledRenderIdentifier)
+ return;
+
+ this._scheduledRenderIdentifier = requestAnimationFrame(() => this.renderPendingMessages());
+ }
+
+ _didRenderConsoleMessageView(messageView)
+ {
+ var type = messageView instanceof WebInspector.ConsoleCommandView ? null : messageView.message.type;
+ if (type === WebInspector.ConsoleMessage.MessageType.EndGroup) {
+ var parentGroup = this._currentConsoleGroup.parentGroup;
+ if (parentGroup)
+ this._currentConsoleGroup = parentGroup;
+ } else {
+ if (type === WebInspector.ConsoleMessage.MessageType.StartGroup || type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed) {
+ var group = new WebInspector.ConsoleGroup(this._currentConsoleGroup);
+ var groupElement = group.render(messageView);
+ this._currentConsoleGroup.append(groupElement);
+ this._currentConsoleGroup = group;
+ } else
+ this._currentConsoleGroup.addMessageView(messageView);
+ }
+
+ if (this.delegate && typeof this.delegate.didAppendConsoleMessageView === "function")
+ this.delegate.didAppendConsoleMessageView(messageView);
+ }
+};
+
+WebInspector.JavaScriptLogViewController.CachedPropertiesDuration = 30000;
+WebInspector.JavaScriptLogViewController.IgnoredRepeatCount = Symbol("ignored-repeat-count");
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/JavaScriptRuntimeCompletionProvider.js b/Source/WebInspectorUI/UserInterface/Controllers/JavaScriptRuntimeCompletionProvider.js
new file mode 100644
index 000000000..134b2f032
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/JavaScriptRuntimeCompletionProvider.js
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2013 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+Object.defineProperty(WebInspector, "javaScriptRuntimeCompletionProvider",
+{
+ get: function()
+ {
+ if (!WebInspector.JavaScriptRuntimeCompletionProvider._instance)
+ WebInspector.JavaScriptRuntimeCompletionProvider._instance = new WebInspector.JavaScriptRuntimeCompletionProvider;
+ return WebInspector.JavaScriptRuntimeCompletionProvider._instance;
+ }
+});
+
+WebInspector.JavaScriptRuntimeCompletionProvider = class JavaScriptRuntimeCompletionProvider extends WebInspector.Object
+{
+ constructor()
+ {
+ super();
+
+ console.assert(!WebInspector.JavaScriptRuntimeCompletionProvider._instance);
+
+ WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.ActiveCallFrameDidChange, this._clearLastProperties, this);
+ }
+
+ // Protected
+
+ completionControllerCompletionsNeeded(completionController, defaultCompletions, base, prefix, suffix, forced)
+ {
+ // Don't allow non-forced empty prefix completions unless the base is that start of property access.
+ if (!forced && !prefix && !/[.[]$/.test(base)) {
+ completionController.updateCompletions(null);
+ return;
+ }
+
+ // If the base ends with an open parentheses or open curly bracket then treat it like there is
+ // no base so we get global object completions.
+ if (/[({]$/.test(base))
+ base = "";
+
+ var lastBaseIndex = base.length - 1;
+ var dotNotation = base[lastBaseIndex] === ".";
+ var bracketNotation = base[lastBaseIndex] === "[";
+
+ if (dotNotation || bracketNotation) {
+ base = base.substring(0, lastBaseIndex);
+
+ // Don't suggest anything for an empty base that is using dot notation.
+ // Bracket notation with an empty base will be treated as an array.
+ if (!base && dotNotation) {
+ completionController.updateCompletions(defaultCompletions);
+ return;
+ }
+
+ // Don't allow non-forced empty prefix completions if the user is entering a number, since it might be a float.
+ // But allow number completions if the base already has a decimal, so "10.0." will suggest Number properties.
+ if (!forced && !prefix && dotNotation && base.indexOf(".") === -1 && parseInt(base, 10) == base) {
+ completionController.updateCompletions(null);
+ return;
+ }
+
+ // An empty base with bracket notation is not property access, it is an array.
+ // Clear the bracketNotation flag so completions are not quoted.
+ if (!base && bracketNotation)
+ bracketNotation = false;
+ }
+
+ // If the base is the same as the last time, we can reuse the property names we have already gathered.
+ // Doing this eliminates delay caused by the async nature of the code below and it only calls getters
+ // and functions once instead of repetitively. Sure, there can be difference each time the base is evaluated,
+ // but this optimization gives us more of a win. We clear the cache after 30 seconds or when stepping in the
+ // debugger to make sure we don't use stale properties in most cases.
+ if (this._lastBase === base && this._lastPropertyNames) {
+ receivedPropertyNames.call(this, this._lastPropertyNames);
+ return;
+ }
+
+ this._lastBase = base;
+ this._lastPropertyNames = null;
+
+ var activeCallFrame = WebInspector.debuggerManager.activeCallFrame;
+ if (!base && activeCallFrame && !this._alwaysEvaluateInWindowContext)
+ activeCallFrame.collectScopeChainVariableNames(receivedPropertyNames.bind(this));
+ else {
+ let options = {objectGroup: "completion", includeCommandLineAPI: true, doNotPauseOnExceptionsAndMuteConsole: true, returnByValue: false, generatePreview: false, saveResult: false};
+ WebInspector.runtimeManager.evaluateInInspectedWindow(base, options, evaluated.bind(this));
+ }
+
+ function updateLastPropertyNames(propertyNames)
+ {
+ if (this._clearLastPropertiesTimeout)
+ clearTimeout(this._clearLastPropertiesTimeout);
+ this._clearLastPropertiesTimeout = setTimeout(this._clearLastProperties.bind(this), WebInspector.JavaScriptLogViewController.CachedPropertiesDuration);
+
+ this._lastPropertyNames = propertyNames || {};
+ }
+
+ function evaluated(result, wasThrown)
+ {
+ if (wasThrown || !result || result.type === "undefined" || (result.type === "object" && result.subtype === "null")) {
+ WebInspector.runtimeManager.activeExecutionContext.target.RuntimeAgent.releaseObjectGroup("completion");
+
+ updateLastPropertyNames.call(this, {});
+ completionController.updateCompletions(defaultCompletions);
+
+ return;
+ }
+
+ function inspectedPage_evalResult_getArrayCompletions(primitiveType)
+ {
+ var array = this;
+ var arrayLength;
+
+ var resultSet = {};
+ for (var o = array; o; o = o.__proto__) {
+ try {
+ if (o === array && o.length) {
+ // If the array type has a length, don't include a list of all the indexes.
+ // Include it at the end and the frontend can build the list.
+ arrayLength = o.length;
+ } else {
+ var names = Object.getOwnPropertyNames(o);
+ for (var i = 0; i < names.length; ++i)
+ resultSet[names[i]] = true;
+ }
+ } catch (e) {
+ // Ignore
+ }
+ }
+
+ if (arrayLength)
+ resultSet["length"] = arrayLength;
+
+ return resultSet;
+ }
+
+ function inspectedPage_evalResult_getCompletions(primitiveType)
+ {
+ var object;
+ if (primitiveType === "string")
+ object = new String("");
+ else if (primitiveType === "number")
+ object = new Number(0);
+ else if (primitiveType === "boolean")
+ object = new Boolean(false);
+ else if (primitiveType === "symbol")
+ object = Symbol();
+ else
+ object = this;
+
+ var resultSet = {};
+ for (var o = object; o; o = o.__proto__) {
+ try {
+ var names = Object.getOwnPropertyNames(o);
+ for (var i = 0; i < names.length; ++i)
+ resultSet[names[i]] = true;
+ } catch (e) {
+ // Ignore
+ }
+ }
+
+ return resultSet;
+ }
+
+ if (result.subtype === "array")
+ result.callFunctionJSON(inspectedPage_evalResult_getArrayCompletions, undefined, receivedArrayPropertyNames.bind(this));
+ else if (result.type === "object" || result.type === "function")
+ result.callFunctionJSON(inspectedPage_evalResult_getCompletions, undefined, receivedPropertyNames.bind(this));
+ else if (result.type === "string" || result.type === "number" || result.type === "boolean" || result.type === "symbol") {
+ let options = {objectGroup: "completion", includeCommandLineAPI: false, doNotPauseOnExceptionsAndMuteConsole: true, returnByValue: false, generatePreview: false, saveResult: false};
+ WebInspector.runtimeManager.evaluateInInspectedWindow("(" + inspectedPage_evalResult_getCompletions + ")(\"" + result.type + "\")", options, receivedPropertyNamesFromEvaluate.bind(this));
+ } else
+ console.error("Unknown result type: " + result.type);
+ }
+
+ function receivedPropertyNamesFromEvaluate(object, wasThrown, result)
+ {
+ receivedPropertyNames.call(this, result && !wasThrown ? result.value : null);
+ }
+
+ function receivedArrayPropertyNames(propertyNames)
+ {
+ // FIXME: <https://webkit.org/b/143589> Web Inspector: Better handling for large collections in Object Trees
+ // If there was an array like object, we generate autocompletion up to 1000 indexes, but this should
+ // handle a list with arbitrary length.
+ if (propertyNames && typeof propertyNames.length === "number") {
+ var max = Math.min(propertyNames.length, 1000);
+ for (var i = 0; i < max; ++i)
+ propertyNames[i] = true;
+ }
+
+ receivedPropertyNames.call(this, propertyNames);
+ }
+
+ function receivedPropertyNames(propertyNames)
+ {
+ propertyNames = propertyNames || {};
+
+ updateLastPropertyNames.call(this, propertyNames);
+
+ WebInspector.runtimeManager.activeExecutionContext.target.RuntimeAgent.releaseObjectGroup("completion");
+
+ if (!base) {
+ var commandLineAPI = ["$", "$$", "$x", "dir", "dirxml", "keys", "values", "profile", "profileEnd", "monitorEvents", "unmonitorEvents", "inspect", "copy", "clear", "getEventListeners", "$0", "$_"];
+ if (WebInspector.debuggerManager.paused) {
+ let targetData = WebInspector.debuggerManager.dataForTarget(WebInspector.runtimeManager.activeExecutionContext.target);
+ if (targetData.pauseReason === WebInspector.DebuggerManager.PauseReason.Exception)
+ commandLineAPI.push("$exception");
+ }
+ for (var i = 0; i < commandLineAPI.length; ++i)
+ propertyNames[commandLineAPI[i]] = true;
+
+ // FIXME: Due to caching, sometimes old $n values show up as completion results even though they are not available. We should clear that proactively.
+ for (var i = 1; i <= WebInspector.ConsoleCommandResultMessage.maximumSavedResultIndex; ++i)
+ propertyNames["$" + i] = true;
+ }
+
+ propertyNames = Object.keys(propertyNames);
+
+ var implicitSuffix = "";
+ if (bracketNotation) {
+ var quoteUsed = prefix[0] === "'" ? "'" : "\"";
+ if (suffix !== "]" && suffix !== quoteUsed)
+ implicitSuffix = "]";
+ }
+
+ var completions = defaultCompletions;
+ var knownCompletions = completions.keySet();
+
+ for (var i = 0; i < propertyNames.length; ++i) {
+ var property = propertyNames[i];
+
+ if (dotNotation && !/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(property))
+ continue;
+
+ if (bracketNotation) {
+ if (parseInt(property) != property)
+ property = quoteUsed + property.escapeCharacters(quoteUsed + "\\") + (suffix !== quoteUsed ? quoteUsed : "");
+ }
+
+ if (!property.startsWith(prefix) || property in knownCompletions)
+ continue;
+
+ completions.push(property);
+ knownCompletions[property] = true;
+ }
+
+ function compare(a, b)
+ {
+ // Try to sort in numerical order first.
+ let numericCompareResult = a - b;
+ if (!isNaN(numericCompareResult))
+ return numericCompareResult;
+
+ // Sort __defineGetter__, __lookupGetter__, and friends last.
+ let aRareProperty = a.startsWith("__") && a.endsWith("__");
+ let bRareProperty = b.startsWith("__") && b.endsWith("__");
+ if (aRareProperty && !bRareProperty)
+ return 1;
+ if (!aRareProperty && bRareProperty)
+ return -1;
+
+ // Not numbers, sort as strings.
+ return a.localeCompare(b);
+ }
+
+ completions.sort(compare);
+
+ completionController.updateCompletions(completions, implicitSuffix);
+ }
+ }
+
+ // Private
+
+ _clearLastProperties()
+ {
+ if (this._clearLastPropertiesTimeout) {
+ clearTimeout(this._clearLastPropertiesTimeout);
+ delete this._clearLastPropertiesTimeout;
+ }
+
+ // Clear the cache of property names so any changes while stepping or sitting idle get picked up if the same
+ // expression is evaluated again.
+ this._lastPropertyNames = null;
+ }
+};
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/LayerTreeManager.js b/Source/WebInspectorUI/UserInterface/Controllers/LayerTreeManager.js
new file mode 100644
index 000000000..48ec5f1d4
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/LayerTreeManager.js
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2013 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.LayerTreeManager = class LayerTreeManager extends WebInspector.Object
+{
+ constructor()
+ {
+ super();
+
+ this._supported = !!window.LayerTreeAgent;
+
+ if (this._supported)
+ LayerTreeAgent.enable();
+ }
+
+ // Public
+
+ get supported()
+ {
+ return this._supported;
+ }
+
+ layerTreeMutations(previousLayers, newLayers)
+ {
+ console.assert(this.supported);
+
+ if (isEmptyObject(previousLayers)) {
+ return {
+ preserved: [],
+ additions: newLayers,
+ removals: []
+ };
+ }
+
+ function nodeIdForLayer(layer)
+ {
+ return layer.isGeneratedContent ? layer.pseudoElementId : layer.nodeId;
+ }
+
+ var layerIdsInPreviousLayers = [];
+ var nodeIdsInPreviousLayers = [];
+ var nodeIdsForReflectionsInPreviousLayers = [];
+
+ previousLayers.forEach(function(layer) {
+ layerIdsInPreviousLayers.push(layer.layerId);
+
+ var nodeId = nodeIdForLayer(layer);
+ if (!nodeId)
+ return;
+
+ if (layer.isReflection)
+ nodeIdsForReflectionsInPreviousLayers.push(nodeId);
+ else
+ nodeIdsInPreviousLayers.push(nodeId);
+ });
+
+ var preserved = [];
+ var additions = [];
+
+ var layerIdsInNewLayers = [];
+ var nodeIdsInNewLayers = [];
+ var nodeIdsForReflectionsInNewLayers = [];
+
+ newLayers.forEach(function(layer) {
+ layerIdsInNewLayers.push(layer.layerId);
+
+ var existed = layerIdsInPreviousLayers.includes(layer.layerId);
+
+ var nodeId = nodeIdForLayer(layer);
+ if (!nodeId)
+ return;
+
+ if (layer.isReflection) {
+ nodeIdsForReflectionsInNewLayers.push(nodeId);
+ existed = existed || nodeIdsForReflectionsInPreviousLayers.includes(nodeId);
+ } else {
+ nodeIdsInNewLayers.push(nodeId);
+ existed = existed || nodeIdsInPreviousLayers.includes(nodeId);
+ }
+
+ if (existed)
+ preserved.push(layer);
+ else
+ additions.push(layer);
+ });
+
+ var removals = previousLayers.filter(function(layer) {
+ var nodeId = nodeIdForLayer(layer);
+
+ if (layer.isReflection)
+ return !nodeIdsForReflectionsInNewLayers.includes(nodeId);
+ else
+ return !nodeIdsInNewLayers.includes(nodeId) && !layerIdsInNewLayers.includes(layer.layerId);
+ });
+
+ return {preserved, additions, removals};
+ }
+
+ layersForNode(node, callback)
+ {
+ console.assert(this.supported);
+
+ LayerTreeAgent.layersForNode(node.id, function(error, layers) {
+ if (error || isEmptyObject(layers)) {
+ callback(null, []);
+ return;
+ }
+
+ var firstLayer = layers[0];
+ var layerForNode = firstLayer.nodeId === node.id && !firstLayer.isGeneratedContent ? layers.shift() : null;
+ callback(layerForNode, layers);
+ });
+ }
+
+ reasonsForCompositingLayer(layer, callback)
+ {
+ console.assert(this.supported);
+
+ LayerTreeAgent.reasonsForCompositingLayer(layer.layerId, function(error, reasons) {
+ callback(error ? 0 : reasons);
+ });
+ }
+
+ layerTreeDidChange()
+ {
+ this.dispatchEventToListeners(WebInspector.LayerTreeManager.Event.LayerTreeDidChange);
+ }
+};
+
+WebInspector.LayerTreeManager.Event = {
+ LayerTreeDidChange: "layer-tree-did-change"
+};
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/LogManager.js b/Source/WebInspectorUI/UserInterface/Controllers/LogManager.js
new file mode 100644
index 000000000..3b917bbbd
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/LogManager.js
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2013 Apple Inc. All rights reserved.
+ * Copyright (C) 2015 Tobias Reiss <tobi+webkit@basecode.de>
+ *
+ * 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.LogManager = class LogManager extends WebInspector.Object
+{
+ constructor()
+ {
+ super();
+
+ this._clearMessagesRequested = false;
+ this._isNewPageOrReload = false;
+
+ WebInspector.Frame.addEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
+ }
+
+ // Public
+
+ messageWasAdded(target, source, level, text, type, url, line, column, repeatCount, parameters, stackTrace, requestId)
+ {
+ // Called from WebInspector.ConsoleObserver.
+
+ // FIXME: Get a request from request ID.
+
+ if (parameters)
+ parameters = parameters.map((x) => WebInspector.RemoteObject.fromPayload(x, target));
+
+ let message = new WebInspector.ConsoleMessage(target, source, level, text, type, url, line, column, repeatCount, parameters, stackTrace, null);
+
+ this.dispatchEventToListeners(WebInspector.LogManager.Event.MessageAdded, {message});
+
+ if (message.level === "warning" || message.level === "error")
+ WebInspector.issueManager.issueWasAdded(message);
+ }
+
+ messagesCleared()
+ {
+ // Called from WebInspector.ConsoleObserver.
+
+ WebInspector.ConsoleCommandResultMessage.clearMaximumSavedResultIndex();
+
+ if (this._clearMessagesRequested) {
+ // Frontend requested "clear console" and Backend successfully completed the request.
+ this._clearMessagesRequested = false;
+ this.dispatchEventToListeners(WebInspector.LogManager.Event.Cleared);
+ } else {
+ // Received an unrequested clear console event.
+ // This could be for a navigation or other reasons (like console.clear()).
+ // If this was a reload, we may not want to dispatch WebInspector.LogManager.Event.Cleared.
+ // To detect if this is a reload we wait a turn and check if there was a main resource change reload.
+ setTimeout(this._delayedMessagesCleared.bind(this), 0);
+ }
+ }
+
+ _delayedMessagesCleared()
+ {
+ if (this._isNewPageOrReload) {
+ this._isNewPageOrReload = false;
+
+ if (!WebInspector.settings.clearLogOnNavigate.value)
+ return;
+ }
+
+ // A console.clear() or command line clear() happened.
+ this.dispatchEventToListeners(WebInspector.LogManager.Event.Cleared);
+ }
+
+ messageRepeatCountUpdated(count)
+ {
+ // Called from WebInspector.ConsoleObserver.
+
+ this.dispatchEventToListeners(WebInspector.LogManager.Event.PreviousMessageRepeatCountUpdated, {count});
+ }
+
+ requestClearMessages()
+ {
+ this._clearMessagesRequested = true;
+
+ for (let target of WebInspector.targets)
+ target.ConsoleAgent.clearMessages();
+ }
+
+ // Private
+
+ _mainResourceDidChange(event)
+ {
+ console.assert(event.target instanceof WebInspector.Frame);
+
+ if (!event.target.isMainFrame())
+ return;
+
+ this._isNewPageOrReload = true;
+
+ let timestamp = Date.now();
+ let wasReloaded = event.data.oldMainResource && event.data.oldMainResource.url === event.target.mainResource.url;
+ this.dispatchEventToListeners(WebInspector.LogManager.Event.SessionStarted, {timestamp, wasReloaded});
+
+ WebInspector.ConsoleCommandResultMessage.clearMaximumSavedResultIndex();
+ }
+};
+
+WebInspector.LogManager.Event = {
+ SessionStarted: "log-manager-session-was-started",
+ Cleared: "log-manager-cleared",
+ MessageAdded: "log-manager-message-added",
+ PreviousMessageRepeatCountUpdated: "log-manager-previous-message-repeat-count-updated"
+};
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/MemoryManager.js b/Source/WebInspectorUI/UserInterface/Controllers/MemoryManager.js
new file mode 100644
index 000000000..ecb07eacb
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/MemoryManager.js
@@ -0,0 +1,49 @@
+/*
+ * 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.MemoryManager = class MemoryManager extends WebInspector.Object
+{
+ constructor()
+ {
+ super();
+
+ if (window.MemoryAgent)
+ MemoryAgent.enable();
+ }
+
+ // Public
+
+ memoryPressure(timestamp, protocolSeverity)
+ {
+ // Called from WebInspector.MemoryObserver.
+
+ let memoryPressureEvent = WebInspector.MemoryPressureEvent.fromPayload(timestamp, protocolSeverity);
+ this.dispatchEventToListeners(WebInspector.MemoryManager.Event.MemoryPressure, {memoryPressureEvent});
+ }
+};
+
+WebInspector.MemoryManager.Event = {
+ MemoryPressure: "memory-manager-memory-pressure",
+};
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/ProbeManager.js b/Source/WebInspectorUI/UserInterface/Controllers/ProbeManager.js
new file mode 100644
index 000000000..b7f100fba
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/ProbeManager.js
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2013 University of Washington. All rights reserved.
+ * Copyright (C) 2014 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT
+ * HOLDER 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.
+ */
+
+WebInspector.ProbeManager = class ProbeManager extends WebInspector.Object
+{
+ constructor()
+ {
+ super();
+
+ // Used to detect deleted probe actions.
+ this._knownProbeIdentifiersForBreakpoint = new Map;
+
+ // Main lookup tables for probes and probe sets.
+ this._probesByIdentifier = new Map;
+ this._probeSetsByBreakpoint = new Map;
+
+ WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.BreakpointAdded, this._breakpointAdded, this);
+ WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.BreakpointRemoved, this._breakpointRemoved, this);
+ WebInspector.Breakpoint.addEventListener(WebInspector.Breakpoint.Event.ActionsDidChange, this._breakpointActionsChanged, this);
+
+ // Saved breakpoints should not be restored on the first event loop turn, because it
+ // makes manager initialization order very fragile. No breakpoints should be available.
+ console.assert(!WebInspector.debuggerManager.breakpoints.length, "No breakpoints should exist before all the managers are constructed.");
+ }
+
+ // Public
+
+ get probeSets()
+ {
+ return [...this._probeSetsByBreakpoint.values()];
+ }
+
+ probeForIdentifier(identifier)
+ {
+ return this._probesByIdentifier.get(identifier);
+ }
+
+ // Protected (called by WebInspector.DebuggerObserver)
+
+ didSampleProbe(target, sample)
+ {
+ console.assert(this._probesByIdentifier.has(sample.probeId), "Unknown probe identifier specified for sample: ", sample);
+ let probe = this._probesByIdentifier.get(sample.probeId);
+ let elapsedTime = WebInspector.timelineManager.computeElapsedTime(sample.timestamp);
+ let object = WebInspector.RemoteObject.fromPayload(sample.payload, target);
+ probe.addSample(new WebInspector.ProbeSample(sample.sampleId, sample.batchId, elapsedTime, object));
+ }
+
+ // Private
+
+ _breakpointAdded(breakpointOrEvent)
+ {
+ var breakpoint;
+ if (breakpointOrEvent instanceof WebInspector.Breakpoint)
+ breakpoint = breakpointOrEvent;
+ else
+ breakpoint = breakpointOrEvent.data.breakpoint;
+
+ console.assert(breakpoint instanceof WebInspector.Breakpoint, "Unknown object passed as breakpoint: ", breakpoint);
+
+ if (this._knownProbeIdentifiersForBreakpoint.has(breakpoint))
+ return;
+
+ this._knownProbeIdentifiersForBreakpoint.set(breakpoint, new Set);
+
+ this._breakpointActionsChanged(breakpoint);
+ }
+
+ _breakpointRemoved(event)
+ {
+ var breakpoint = event.data.breakpoint;
+ console.assert(this._knownProbeIdentifiersForBreakpoint.has(breakpoint));
+
+ this._breakpointActionsChanged(breakpoint);
+ this._knownProbeIdentifiersForBreakpoint.delete(breakpoint);
+ }
+
+ _breakpointActionsChanged(breakpointOrEvent)
+ {
+ var breakpoint;
+ if (breakpointOrEvent instanceof WebInspector.Breakpoint)
+ breakpoint = breakpointOrEvent;
+ else
+ breakpoint = breakpointOrEvent.target;
+
+ console.assert(breakpoint instanceof WebInspector.Breakpoint, "Unknown object passed as breakpoint: ", breakpoint);
+
+ // Sometimes actions change before the added breakpoint is fully dispatched.
+ if (!this._knownProbeIdentifiersForBreakpoint.has(breakpoint)) {
+ this._breakpointAdded(breakpoint);
+ return;
+ }
+
+ var knownProbeIdentifiers = this._knownProbeIdentifiersForBreakpoint.get(breakpoint);
+ var seenProbeIdentifiers = new Set;
+
+ breakpoint.probeActions.forEach(function(probeAction) {
+ var probeIdentifier = probeAction.id;
+ console.assert(probeIdentifier, "Probe added without breakpoint action identifier: ", breakpoint);
+
+ seenProbeIdentifiers.add(probeIdentifier);
+ if (!knownProbeIdentifiers.has(probeIdentifier)) {
+ // New probe; find or create relevant probe set.
+ knownProbeIdentifiers.add(probeIdentifier);
+ var probeSet = this._probeSetForBreakpoint(breakpoint);
+ var newProbe = new WebInspector.Probe(probeIdentifier, breakpoint, probeAction.data);
+ this._probesByIdentifier.set(probeIdentifier, newProbe);
+ probeSet.addProbe(newProbe);
+ return;
+ }
+
+ var probe = this._probesByIdentifier.get(probeIdentifier);
+ console.assert(probe, "Probe known but couldn't be found by identifier: ", probeIdentifier);
+ // Update probe expression; if it differed, change events will fire.
+ probe.expression = probeAction.data;
+ }, this);
+
+ // Look for missing probes based on what we saw last.
+ knownProbeIdentifiers.forEach(function(probeIdentifier) {
+ if (seenProbeIdentifiers.has(probeIdentifier))
+ return;
+
+ // The probe has gone missing, remove it.
+ var probeSet = this._probeSetForBreakpoint(breakpoint);
+ var probe = this._probesByIdentifier.get(probeIdentifier);
+ this._probesByIdentifier.delete(probeIdentifier);
+ knownProbeIdentifiers.delete(probeIdentifier);
+ probeSet.removeProbe(probe);
+
+ // Remove the probe set if it has become empty.
+ if (!probeSet.probes.length) {
+ this._probeSetsByBreakpoint.delete(probeSet.breakpoint);
+ probeSet.willRemove();
+ this.dispatchEventToListeners(WebInspector.ProbeManager.Event.ProbeSetRemoved, {probeSet});
+ }
+ }, this);
+ }
+
+ _probeSetForBreakpoint(breakpoint)
+ {
+ if (this._probeSetsByBreakpoint.has(breakpoint))
+ return this._probeSetsByBreakpoint.get(breakpoint);
+
+ var newProbeSet = new WebInspector.ProbeSet(breakpoint);
+ this._probeSetsByBreakpoint.set(breakpoint, newProbeSet);
+ this.dispatchEventToListeners(WebInspector.ProbeManager.Event.ProbeSetAdded, {probeSet: newProbeSet});
+ return newProbeSet;
+ }
+};
+
+WebInspector.ProbeManager.Event = {
+ ProbeSetAdded: "probe-manager-probe-set-added",
+ ProbeSetRemoved: "probe-manager-probe-set-removed",
+};
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/ReplayManager.js b/Source/WebInspectorUI/UserInterface/Controllers/ReplayManager.js
new file mode 100644
index 000000000..ab04ddf10
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/ReplayManager.js
@@ -0,0 +1,678 @@
+/*
+ * Copyright (C) 2013 University of Washington. All rights reserved.
+ * Copyright (C) 2014 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.ReplayManager = class ReplayManager extends WebInspector.Object
+{
+ constructor()
+ {
+ super();
+
+ this._sessionState = WebInspector.ReplayManager.SessionState.Inactive;
+ this._segmentState = WebInspector.ReplayManager.SegmentState.Unloaded;
+
+ this._activeSessionIdentifier = null;
+ this._activeSegmentIdentifier = null;
+ this._currentPosition = new WebInspector.ReplayPosition(0, 0);
+ this._initialized = false;
+
+ // These hold actual instances of sessions and segments.
+ this._sessions = new Map;
+ this._segments = new Map;
+ // These hold promises that resolve when the instance data is recieved.
+ this._sessionPromises = new Map;
+ this._segmentPromises = new Map;
+
+ // Playback speed is specified in replayToPosition commands, and persists
+ // for the duration of the playback command until another playback begins.
+ this._playbackSpeed = WebInspector.ReplayManager.PlaybackSpeed.RealTime;
+
+ if (window.ReplayAgent) {
+ var instance = this;
+ this._initializationPromise = ReplayAgent.currentReplayState()
+ .then(function(payload) {
+ console.assert(payload.sessionState in WebInspector.ReplayManager.SessionState, "Unknown session state: " + payload.sessionState);
+ console.assert(payload.segmentState in WebInspector.ReplayManager.SegmentState, "Unknown segment state: " + payload.segmentState);
+
+ instance._activeSessionIdentifier = payload.sessionIdentifier;
+ instance._activeSegmentIdentifier = payload.segmentIdentifier;
+ instance._sessionState = WebInspector.ReplayManager.SessionState[payload.sessionState];
+ instance._segmentState = WebInspector.ReplayManager.SegmentState[payload.segmentState];
+ instance._currentPosition = payload.replayPosition;
+
+ instance._initialized = true;
+ }).then(function() {
+ return ReplayAgent.getAvailableSessions();
+ }).then(function(payload) {
+ for (var sessionId of payload.ids)
+ instance.sessionCreated(sessionId);
+ }).catch(function(error) {
+ console.error("ReplayManager initialization failed: ", error);
+ throw error;
+ });
+ }
+ }
+
+ // Public
+
+ // The following state is invalid unless called from a function that's chained
+ // to the (resolved) ReplayManager.waitUntilInitialized promise.
+ get sessionState()
+ {
+ console.assert(this._initialized);
+ return this._sessionState;
+ }
+
+ get segmentState()
+ {
+ console.assert(this._initialized);
+ return this._segmentState;
+ }
+
+ get activeSessionIdentifier()
+ {
+ console.assert(this._initialized);
+ return this._activeSessionIdentifier;
+ }
+
+ get activeSegmentIdentifier()
+ {
+ console.assert(this._initialized);
+ return this._activeSegmentIdentifier;
+ }
+
+ get playbackSpeed()
+ {
+ console.assert(this._initialized);
+ return this._playbackSpeed;
+ }
+
+ set playbackSpeed(value)
+ {
+ console.assert(this._initialized);
+ this._playbackSpeed = value;
+ }
+
+ get currentPosition()
+ {
+ console.assert(this._initialized);
+ return this._currentPosition;
+ }
+
+ // These return promises even if the relevant instance is already created.
+ waitUntilInitialized() // --> ()
+ {
+ return this._initializationPromise;
+ }
+
+ // Return a promise that resolves to a session, if it exists.
+ getSession(sessionId) // --> (WebInspector.ReplaySession)
+ {
+ if (this._sessionPromises.has(sessionId))
+ return this._sessionPromises.get(sessionId);
+
+ var newPromise = ReplayAgent.getSessionData(sessionId)
+ .then(function(payload) {
+ return Promise.resolve(WebInspector.ReplaySession.fromPayload(sessionId, payload));
+ });
+
+ this._sessionPromises.set(sessionId, newPromise);
+ return newPromise;
+ }
+
+ // Return a promise that resolves to a session segment, if it exists.
+ getSegment(segmentId) // --> (WebInspector.ReplaySessionSegment)
+ {
+ if (this._segmentPromises.has(segmentId))
+ return this._segmentPromises.get(segmentId);
+
+ var newPromise = ReplayAgent.getSegmentData(segmentId)
+ .then(function(payload) {
+ return Promise.resolve(new WebInspector.ReplaySessionSegment(segmentId, payload));
+ });
+
+ this._segmentPromises.set(segmentId, newPromise);
+ return newPromise;
+ }
+
+ // Switch to the specified session.
+ // Returns a promise that resolves when the switch completes.
+ switchSession(sessionId) // --> ()
+ {
+ var manager = this;
+ var result = this.waitUntilInitialized();
+
+ if (this.sessionState === WebInspector.ReplayManager.SessionState.Capturing) {
+ result = result.then(function() {
+ return WebInspector.replayManager.stopCapturing();
+ });
+ }
+
+ if (this.sessionState === WebInspector.ReplayManager.SessionState.Replaying) {
+ result = result.then(function() {
+ return WebInspector.replayManager.cancelPlayback();
+ });
+ }
+
+ result = result.then(function() {
+ console.assert(manager.sessionState === WebInspector.ReplayManager.SessionState.Inactive);
+ console.assert(manager.segmentState === WebInspector.ReplayManager.SegmentState.Unloaded);
+
+ return manager.getSession(sessionId);
+ }).then(function ensureSessionDataIsLoaded(session) {
+ return ReplayAgent.switchSession(session.identifier);
+ }).catch(function(error) {
+ console.error("Failed to switch to session: ", error);
+ throw error;
+ });
+
+ return result;
+ }
+
+ // Start capturing into the current session as soon as possible.
+ // Returns a promise that resolves when capturing begins.
+ startCapturing() // --> ()
+ {
+ var manager = this;
+ var result = this.waitUntilInitialized();
+
+ if (this.sessionState === WebInspector.ReplayManager.SessionState.Capturing)
+ return result; // Already capturing.
+
+ if (this.sessionState === WebInspector.ReplayManager.SessionState.Replaying) {
+ result = result.then(function() {
+ return WebInspector.replayManager.cancelPlayback();
+ });
+ }
+
+ result = result.then(this._suppressBreakpointsAndResumeIfNeeded());
+
+ result = result.then(function() {
+ console.assert(manager.sessionState === WebInspector.ReplayManager.SessionState.Inactive);
+ console.assert(manager.segmentState === WebInspector.ReplayManager.SegmentState.Unloaded);
+
+ return ReplayAgent.startCapturing();
+ }).catch(function(error) {
+ console.error("Failed to start capturing: ", error);
+ throw error;
+ });
+
+ return result;
+ }
+
+ // Stop capturing into the current session as soon as possible.
+ // Returns a promise that resolves when capturing ends.
+ stopCapturing() // --> ()
+ {
+ console.assert(this.sessionState === WebInspector.ReplayManager.SessionState.Capturing, "Cannot stop capturing unless capture is active.");
+ console.assert(this.segmentState === WebInspector.ReplayManager.SegmentState.Appending);
+
+ return ReplayAgent.stopCapturing()
+ .catch(function(error) {
+ console.error("Failed to stop capturing: ", error);
+ throw error;
+ });
+ }
+
+ // Pause playback as soon as possible.
+ // Returns a promise that resolves when playback is paused.
+ pausePlayback() // --> ()
+ {
+ console.assert(this.sessionState !== WebInspector.ReplayManager.SessionState.Capturing, "Cannot pause playback while capturing.");
+
+ var manager = this;
+ var result = this.waitUntilInitialized();
+
+ if (this.sessionState === WebInspector.ReplayManager.SessionState.Inactive)
+ return result; // Already stopped.
+
+ if (this.segmentState !== WebInspector.ReplayManager.SegmentState.Dispatching)
+ return result; // Already stopped.
+
+ result = result.then(function() {
+ console.assert(manager.sessionState === WebInspector.ReplayManager.SessionState.Replaying);
+ console.assert(manager.segmentState === WebInspector.ReplayManager.SegmentState.Dispatching);
+
+ return ReplayAgent.pausePlayback();
+ }).catch(function(error) {
+ console.error("Failed to pause playback: ", error);
+ throw error;
+ });
+
+ return result;
+ }
+
+ // Pause playback and unload the current session segment as soon as possible.
+ // Returns a promise that resolves when the current segment is unloaded.
+ cancelPlayback() // --> ()
+ {
+ console.assert(this.sessionState !== WebInspector.ReplayManager.SessionState.Capturing, "Cannot stop playback while capturing.");
+
+ var manager = this;
+ var result = this.waitUntilInitialized();
+
+ if (this.sessionState === WebInspector.ReplayManager.SessionState.Inactive)
+ return result; // Already stopped.
+
+ result = result.then(function() {
+ console.assert(manager.sessionState === WebInspector.ReplayManager.SessionState.Replaying);
+ console.assert(manager.segmentState !== WebInspector.ReplayManager.SegmentState.Appending);
+
+ return ReplayAgent.cancelPlayback();
+ }).catch(function(error) {
+ console.error("Failed to stop playback: ", error);
+ throw error;
+ });
+
+ return result;
+ }
+
+ // Replay to the specified position as soon as possible using the current replay speed.
+ // Returns a promise that resolves when replay has begun (NOT when the position is reached).
+ replayToPosition(replayPosition) // --> ()
+ {
+ console.assert(replayPosition instanceof WebInspector.ReplayPosition, "Cannot replay to a position while capturing.");
+
+ var manager = this;
+ var result = this.waitUntilInitialized();
+
+ if (this.sessionState === WebInspector.ReplayManager.SessionState.Capturing) {
+ result = result.then(function() {
+ return WebInspector.replayManager.stopCapturing();
+ });
+ }
+
+ result = result.then(this._suppressBreakpointsAndResumeIfNeeded());
+
+ result = result.then(function() {
+ console.assert(manager.sessionState !== WebInspector.ReplayManager.SessionState.Capturing);
+ console.assert(manager.segmentState !== WebInspector.ReplayManager.SegmentState.Appending);
+
+ return ReplayAgent.replayToPosition(replayPosition, manager.playbackSpeed === WebInspector.ReplayManager.PlaybackSpeed.FastForward);
+ }).catch(function(error) {
+ console.error("Failed to start playback to position: ", replayPosition, error);
+ throw error;
+ });
+
+ return result;
+ }
+
+ // Replay to the end of the session as soon as possible using the current replay speed.
+ // Returns a promise that resolves when replay has begun (NOT when the end is reached).
+ replayToCompletion() // --> ()
+ {
+ var manager = this;
+ var result = this.waitUntilInitialized();
+
+ if (this.segmentState === WebInspector.ReplayManager.SegmentState.Dispatching)
+ return result; // Already running.
+
+ if (this.sessionState === WebInspector.ReplayManager.SessionState.Capturing) {
+ result = result.then(function() {
+ return WebInspector.replayManager.stopCapturing();
+ });
+ }
+
+ result = result.then(this._suppressBreakpointsAndResumeIfNeeded());
+
+ result = result.then(function() {
+ console.assert(manager.sessionState !== WebInspector.ReplayManager.SessionState.Capturing);
+ console.assert(manager.segmentState === WebInspector.ReplayManager.SegmentState.Loaded || manager.segmentState === WebInspector.ReplayManager.SegmentState.Unloaded);
+
+ return ReplayAgent.replayToCompletion(manager.playbackSpeed === WebInspector.ReplayManager.PlaybackSpeed.FastForward);
+ }).catch(function(error) {
+ console.error("Failed to start playback to completion: ", error);
+ throw error;
+ });
+
+ return result;
+ }
+
+ // Protected (called by ReplayObserver)
+
+ // Since these methods update session and segment state, they depend on the manager
+ // being properly initialized. So, each function body is prepended with a retry guard.
+ // This makes call sites simpler and avoids an extra event loop turn in the common case.
+
+ captureStarted()
+ {
+ if (!this._initialized)
+ return this.waitUntilInitialized().then(this.captureStarted.bind(this));
+
+ this._changeSessionState(WebInspector.ReplayManager.SessionState.Capturing);
+
+ this.dispatchEventToListeners(WebInspector.ReplayManager.Event.CaptureStarted);
+ }
+
+ captureStopped()
+ {
+ if (!this._initialized)
+ return this.waitUntilInitialized().then(this.captureStopped.bind(this));
+
+ this._changeSessionState(WebInspector.ReplayManager.SessionState.Inactive);
+ this._changeSegmentState(WebInspector.ReplayManager.SegmentState.Unloaded);
+
+ if (this._breakpointsWereSuppressed) {
+ delete this._breakpointsWereSuppressed;
+ WebInspector.debuggerManager.breakpointsEnabled = true;
+ }
+
+ this.dispatchEventToListeners(WebInspector.ReplayManager.Event.CaptureStopped);
+ }
+
+ playbackStarted()
+ {
+ if (!this._initialized)
+ return this.waitUntilInitialized().then(this.playbackStarted.bind(this));
+
+ if (this.sessionState === WebInspector.ReplayManager.SessionState.Inactive)
+ this._changeSessionState(WebInspector.ReplayManager.SessionState.Replaying);
+
+ this._changeSegmentState(WebInspector.ReplayManager.SegmentState.Dispatching);
+
+ this.dispatchEventToListeners(WebInspector.ReplayManager.Event.PlaybackStarted);
+ }
+
+ playbackHitPosition(replayPosition, timestamp)
+ {
+ if (!this._initialized)
+ return this.waitUntilInitialized().then(this.playbackHitPosition.bind(this, replayPosition, timestamp));
+
+ console.assert(this.sessionState === WebInspector.ReplayManager.SessionState.Replaying);
+ console.assert(this.segmentState === WebInspector.ReplayManager.SegmentState.Dispatching);
+ console.assert(replayPosition instanceof WebInspector.ReplayPosition);
+
+ this._currentPosition = replayPosition;
+ this.dispatchEventToListeners(WebInspector.ReplayManager.Event.PlaybackPositionChanged);
+ }
+
+ playbackPaused(position)
+ {
+ if (!this._initialized)
+ return this.waitUntilInitialized().then(this.playbackPaused.bind(this, position));
+
+ console.assert(this.sessionState === WebInspector.ReplayManager.SessionState.Replaying);
+ this._changeSegmentState(WebInspector.ReplayManager.SegmentState.Loaded);
+
+ if (this._breakpointsWereSuppressed) {
+ delete this._breakpointsWereSuppressed;
+ WebInspector.debuggerManager.breakpointsEnabled = true;
+ }
+
+ this.dispatchEventToListeners(WebInspector.ReplayManager.Event.PlaybackPaused);
+ }
+
+ playbackFinished()
+ {
+ if (!this._initialized)
+ return this.waitUntilInitialized().then(this.playbackFinished.bind(this));
+
+ this._changeSessionState(WebInspector.ReplayManager.SessionState.Inactive);
+ console.assert(this.segmentState === WebInspector.ReplayManager.SegmentState.Unloaded);
+
+ if (this._breakpointsWereSuppressed) {
+ delete this._breakpointsWereSuppressed;
+ WebInspector.debuggerManager.breakpointsEnabled = true;
+ }
+
+ this.dispatchEventToListeners(WebInspector.ReplayManager.Event.PlaybackFinished);
+ }
+
+ sessionCreated(sessionId)
+ {
+ if (!this._initialized)
+ return this.waitUntilInitialized().then(this.sessionCreated.bind(this, sessionId));
+
+ console.assert(!this._sessions.has(sessionId), "Tried to add duplicate session identifier:", sessionId);
+ var sessionMap = this._sessions;
+ this.getSession(sessionId)
+ .then(function(session) {
+ sessionMap.set(sessionId, session);
+ }).catch(function(error) {
+ console.error("Error obtaining session data: ", error);
+ throw error;
+ });
+
+ this.dispatchEventToListeners(WebInspector.ReplayManager.Event.SessionAdded, {sessionId});
+ }
+
+ sessionModified(sessionId)
+ {
+ if (!this._initialized)
+ return this.waitUntilInitialized().then(this.sessionModified.bind(this, sessionId));
+
+ this.getSession(sessionId).then(function(session) {
+ session.segmentsChanged();
+ });
+ }
+
+ sessionRemoved(sessionId)
+ {
+ if (!this._initialized)
+ return this.waitUntilInitialized().then(this.sessionRemoved.bind(this, sessionId));
+
+ console.assert(this._sessions.has(sessionId), "Unknown session identifier:", sessionId);
+
+ if (!this._sessionPromises.has(sessionId))
+ return;
+
+ var manager = this;
+
+ this.getSession(sessionId)
+ .catch(function(error) {
+ // Wait for any outstanding promise to settle so it doesn't get re-added.
+ }).then(function() {
+ manager._sessionPromises.delete(sessionId);
+ var removedSession = manager._sessions.take(sessionId);
+ console.assert(removedSession);
+ manager.dispatchEventToListeners(WebInspector.ReplayManager.Event.SessionRemoved, {removedSession});
+ });
+ }
+
+ segmentCreated(segmentId)
+ {
+ if (!this._initialized)
+ return this.waitUntilInitialized().then(this.segmentCreated.bind(this, segmentId));
+
+ console.assert(!this._segments.has(segmentId), "Tried to add duplicate segment identifier:", segmentId);
+
+ this._changeSegmentState(WebInspector.ReplayManager.SegmentState.Appending);
+
+ // Create a dummy segment, and don't try to load any data for it. It will
+ // be removed once the segment is complete, and then its data will be fetched.
+ var incompleteSegment = new WebInspector.IncompleteSessionSegment(segmentId);
+ this._segments.set(segmentId, incompleteSegment);
+ this._segmentPromises.set(segmentId, Promise.resolve(incompleteSegment));
+
+ this.dispatchEventToListeners(WebInspector.ReplayManager.Event.SessionSegmentAdded, {segmentIdentifier: segmentId});
+ }
+
+ segmentCompleted(segmentId)
+ {
+ if (!this._initialized)
+ return this.waitUntilInitialized().then(this.segmentCompleted.bind(this, segmentId));
+
+ var placeholderSegment = this._segments.take(segmentId);
+ console.assert(placeholderSegment instanceof WebInspector.IncompleteSessionSegment);
+ this._segmentPromises.delete(segmentId);
+
+ var segmentMap = this._segments;
+ this.getSegment(segmentId)
+ .then(function(segment) {
+ segmentMap.set(segmentId, segment);
+ }).catch(function(error) {
+ console.error("Error obtaining segment data: ", error);
+ throw error;
+ });
+ }
+
+ segmentRemoved(segmentId)
+ {
+ if (!this._initialized)
+ return this.waitUntilInitialized().then(this.segmentRemoved.bind(this, segmentId));
+
+ console.assert(this._segments.has(segmentId), "Unknown segment identifier:", segmentId);
+
+ if (!this._segmentPromises.has(segmentId))
+ return;
+
+ var manager = this;
+
+ // Wait for any outstanding promise to settle so it doesn't get re-added.
+ this.getSegment(segmentId)
+ .catch(function(error) {
+ return Promise.resolve();
+ }).then(function() {
+ manager._segmentPromises.delete(segmentId);
+ var removedSegment = manager._segments.take(segmentId);
+ console.assert(removedSegment);
+ manager.dispatchEventToListeners(WebInspector.ReplayManager.Event.SessionSegmentRemoved, {removedSegment});
+ });
+ }
+
+ segmentLoaded(segmentId)
+ {
+ if (!this._initialized)
+ return this.waitUntilInitialized().then(this.segmentLoaded.bind(this, segmentId));
+
+ console.assert(this._segments.has(segmentId), "Unknown segment identifier:", segmentId);
+
+ console.assert(this.sessionState !== WebInspector.ReplayManager.SessionState.Capturing);
+ this._changeSegmentState(WebInspector.ReplayManager.SegmentState.Loaded);
+
+ var previousIdentifier = this._activeSegmentIdentifier;
+ this._activeSegmentIdentifier = segmentId;
+ this.dispatchEventToListeners(WebInspector.ReplayManager.Event.ActiveSegmentChanged, {previousSegmentIdentifier: previousIdentifier});
+ }
+
+ segmentUnloaded()
+ {
+ if (!this._initialized)
+ return this.waitUntilInitialized().then(this.segmentUnloaded.bind(this));
+
+ console.assert(this.sessionState === WebInspector.ReplayManager.SessionState.Replaying);
+ this._changeSegmentState(WebInspector.ReplayManager.SegmentState.Unloaded);
+
+ var previousIdentifier = this._activeSegmentIdentifier;
+ this._activeSegmentIdentifier = null;
+ this.dispatchEventToListeners(WebInspector.ReplayManager.Event.ActiveSegmentChanged, {previousSegmentIdentifier: previousIdentifier});
+ }
+
+ // Private
+
+ _changeSessionState(newState)
+ {
+ // Warn about no-op state changes. We shouldn't be seeing them.
+ var isAllowed = this._sessionState !== newState;
+
+ switch (this._sessionState) {
+ case WebInspector.ReplayManager.SessionState.Capturing:
+ isAllowed &= newState === WebInspector.ReplayManager.SessionState.Inactive;
+ break;
+
+ case WebInspector.ReplayManager.SessionState.Replaying:
+ isAllowed &= newState === WebInspector.ReplayManager.SessionState.Inactive;
+ break;
+ }
+
+ console.assert(isAllowed, "Invalid session state change: ", this._sessionState, " to ", newState);
+ if (isAllowed)
+ this._sessionState = newState;
+ }
+
+ _changeSegmentState(newState)
+ {
+ // Warn about no-op state changes. We shouldn't be seeing them.
+ var isAllowed = this._segmentState !== newState;
+
+ switch (this._segmentState) {
+ case WebInspector.ReplayManager.SegmentState.Appending:
+ isAllowed &= newState === WebInspector.ReplayManager.SegmentState.Unloaded;
+ break;
+ case WebInspector.ReplayManager.SegmentState.Unloaded:
+ isAllowed &= newState === WebInspector.ReplayManager.SegmentState.Appending || newState === WebInspector.ReplayManager.SegmentState.Loaded;
+ break;
+ case WebInspector.ReplayManager.SegmentState.Loaded:
+ isAllowed &= newState === WebInspector.ReplayManager.SegmentState.Unloaded || newState === WebInspector.ReplayManager.SegmentState.Dispatching;
+ break;
+ case WebInspector.ReplayManager.SegmentState.Dispatching:
+ isAllowed &= newState === WebInspector.ReplayManager.SegmentState.Loaded;
+ break;
+ }
+
+ console.assert(isAllowed, "Invalid segment state change: ", this._segmentState, " to ", newState);
+ if (isAllowed)
+ this._segmentState = newState;
+ }
+
+ _suppressBreakpointsAndResumeIfNeeded()
+ {
+ var manager = this;
+
+ return new Promise(function(resolve, reject) {
+ manager._breakpointsWereSuppressed = WebInspector.debuggerManager.breakpointsEnabled;
+ WebInspector.debuggerManager.breakpointsEnabled = false;
+
+ return WebInspector.debuggerManager.resume();
+ });
+ }
+};
+
+WebInspector.ReplayManager.Event = {
+ CaptureStarted: "replay-manager-capture-started",
+ CaptureStopped: "replay-manager-capture-stopped",
+
+ PlaybackStarted: "replay-manager-playback-started",
+ PlaybackPaused: "replay-manager-playback-paused",
+ PlaybackFinished: "replay-manager-playback-finished",
+ PlaybackPositionChanged: "replay-manager-play-back-position-changed",
+
+ ActiveSessionChanged: "replay-manager-active-session-changed",
+ ActiveSegmentChanged: "replay-manager-active-segment-changed",
+
+ SessionSegmentAdded: "replay-manager-session-segment-added",
+ SessionSegmentRemoved: "replay-manager-session-segment-removed",
+
+ SessionAdded: "replay-manager-session-added",
+ SessionRemoved: "replay-manager-session-removed",
+};
+
+WebInspector.ReplayManager.SessionState = {
+ Capturing: "replay-manager-session-state-capturing",
+ Inactive: "replay-manager-session-state-inactive",
+ Replaying: "replay-manager-session-state-replaying",
+};
+
+WebInspector.ReplayManager.SegmentState = {
+ Appending: "replay-manager-segment-state-appending",
+ Unloaded: "replay-manager-segment-state-unloaded",
+ Loaded: "replay-manager-segment-state-loaded",
+ Dispatching: "replay-manager-segment-state-dispatching",
+};
+
+WebInspector.ReplayManager.PlaybackSpeed = {
+ RealTime: "replay-manager-playback-speed-real-time",
+ FastForward: "replay-manager-playback-speed-fast-forward",
+};
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/ResourceQueryController.js b/Source/WebInspectorUI/UserInterface/Controllers/ResourceQueryController.js
new file mode 100644
index 000000000..171165f32
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/ResourceQueryController.js
@@ -0,0 +1,203 @@
+/*
+ * 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.ResourceQueryController = class ResourceQueryController extends WebInspector.Object
+{
+ constructor()
+ {
+ super();
+
+ this._resourceDataMap = new Map;
+ }
+
+ // Public
+
+ addResource(resource)
+ {
+ this._resourceDataMap.set(resource, {});
+ }
+
+ removeResource(resource)
+ {
+ this._resourceDataMap.delete(resource);
+ }
+
+ reset()
+ {
+ this._resourceDataMap.clear();
+ }
+
+ executeQuery(query)
+ {
+ if (!query || !this._resourceDataMap.size)
+ return [];
+
+ query = query.removeWhitespace().toLowerCase();
+
+ let cookie = null;
+ if (query.includes(":")) {
+ let [newQuery, lineNumber, columnNumber] = query.split(":");
+ query = newQuery;
+ lineNumber = lineNumber ? parseInt(lineNumber, 10) - 1 : 0;
+ columnNumber = columnNumber ? parseInt(columnNumber, 10) - 1 : 0;
+ cookie = {lineNumber, columnNumber};
+ }
+
+ let results = [];
+ for (let [resource, cachedData] of this._resourceDataMap) {
+ if (!cachedData.searchString) {
+ let displayName = resource.displayName;
+ cachedData.searchString = displayName.toLowerCase();
+ cachedData.specialCharacterIndices = this._findSpecialCharacterIndices(displayName);
+ }
+
+ let matches = this._findQueryMatches(query, cachedData.searchString, cachedData.specialCharacterIndices);
+ if (matches.length)
+ results.push(new WebInspector.ResourceQueryResult(resource, matches, cookie));
+ }
+
+ // Resources are sorted in descending order by rank. Resources of equal
+ // rank are sorted by display name.
+ return results.sort((a, b) => {
+ if (a.rank === b.rank)
+ return a.resource.displayName.localeCompare(b.resource.displayName);
+ return b.rank - a.rank;
+ });
+ }
+
+ // Private
+
+ _findQueryMatches(query, searchString, specialCharacterIndices)
+ {
+ let matches = [];
+ let queryIndex = 0;
+ let searchIndex = 0;
+ let specialIndex = 0;
+ let deadBranches = new Array(query.length).fill(Infinity);
+ let type = WebInspector.ResourceQueryMatch.Type.Special;
+
+ function pushMatch(index)
+ {
+ matches.push(new WebInspector.ResourceQueryMatch(type, index, queryIndex));
+ searchIndex = index + 1;
+ queryIndex++;
+ }
+
+ function matchNextSpecialCharacter()
+ {
+ if (specialIndex >= specialCharacterIndices.length)
+ return false;
+
+ let originalSpecialIndex = specialIndex;
+ while (specialIndex < specialCharacterIndices.length) {
+ // Normal character matching can move past special characters,
+ // so advance the special character index if it's before the
+ // current search string position.
+ let index = specialCharacterIndices[specialIndex++];
+ if (index < searchIndex)
+ continue;
+
+ if (query[queryIndex] === searchString[index]) {
+ pushMatch(index);
+ return true;
+ }
+ }
+
+ specialIndex = originalSpecialIndex;
+ return false;
+ }
+
+ function backtrack()
+ {
+ while (matches.length) {
+ queryIndex--;
+
+ let lastMatch = matches.pop();
+ if (lastMatch.type !== WebInspector.ResourceQueryMatch.Type.Special)
+ continue;
+
+ deadBranches[lastMatch.queryIndex] = lastMatch.index;
+ searchIndex = matches.lastValue ? matches.lastValue.index + 1 : 0;
+ return true;
+ }
+
+ return false;
+ }
+
+ while (queryIndex < query.length && searchIndex < searchString.length) {
+ if (type === WebInspector.ResourceQueryMatch.Type.Special && !matchNextSpecialCharacter())
+ type = WebInspector.ResourceQueryMatch.Type.Normal;
+
+ if (type === WebInspector.ResourceQueryMatch.Type.Normal) {
+ let index = searchString.indexOf(query[queryIndex], searchIndex);
+ if (index >= 0 && index < deadBranches[queryIndex]) {
+ pushMatch(index);
+ type = WebInspector.ResourceQueryMatch.Type.Special;
+ } else if (!backtrack())
+ return [];
+ }
+ }
+
+ if (queryIndex < query.length)
+ return [];
+
+ return matches;
+ }
+
+ _findSpecialCharacterIndices(string)
+ {
+ if (!string.length)
+ return [];
+
+ const filenameSeparators = "_.-";
+
+ // Special characters include the following:
+ // 1. The first character.
+ // 2. Uppercase characters that follow a lowercase letter.
+ // 3. Filename separators and the first character following the separator.
+ let indices = [0];
+
+ for (let i = 1; i < string.length; ++i) {
+ let character = string[i];
+ let isSpecial = false;
+
+ if (filenameSeparators.includes(character))
+ isSpecial = true;
+ else {
+ let previousCharacter = string[i - 1];
+ let previousCharacterIsSeparator = filenameSeparators.includes(previousCharacter);
+ if (previousCharacterIsSeparator)
+ isSpecial = true;
+ else if (character.isUpperCase() && previousCharacter.isLowerCase())
+ isSpecial = true;
+ }
+
+ if (isSpecial)
+ indices.push(i);
+ }
+
+ return indices;
+ }
+};
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/RuntimeManager.js b/Source/WebInspectorUI/UserInterface/Controllers/RuntimeManager.js
new file mode 100644
index 000000000..a10541422
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/RuntimeManager.js
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2013 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.RuntimeManager = class RuntimeManager extends WebInspector.Object
+{
+ constructor()
+ {
+ super();
+
+ // Enable the RuntimeAgent to receive notification of execution contexts.
+ RuntimeAgent.enable();
+
+ this._activeExecutionContext = WebInspector.mainTarget.executionContext;
+
+ WebInspector.Frame.addEventListener(WebInspector.Frame.Event.ExecutionContextsCleared, this._frameExecutionContextsCleared, this);
+ }
+
+ // Public
+
+ get activeExecutionContext()
+ {
+ return this._activeExecutionContext;
+ }
+
+ set activeExecutionContext(executionContext)
+ {
+ if (this._activeExecutionContext === executionContext)
+ return;
+
+ this._activeExecutionContext = executionContext;
+
+ this.dispatchEventToListeners(WebInspector.RuntimeManager.Event.ActiveExecutionContextChanged);
+ }
+
+ evaluateInInspectedWindow(expression, options, callback)
+ {
+ let {objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, returnByValue, generatePreview, saveResult, sourceURLAppender} = options;
+
+ includeCommandLineAPI = includeCommandLineAPI || false;
+ doNotPauseOnExceptionsAndMuteConsole = doNotPauseOnExceptionsAndMuteConsole || false;
+ returnByValue = returnByValue || false;
+ generatePreview = generatePreview || false;
+ saveResult = saveResult || false;
+ sourceURLAppender = sourceURLAppender || appendWebInspectorSourceURL;
+
+ console.assert(objectGroup, "RuntimeManager.evaluateInInspectedWindow should always be called with an objectGroup");
+ console.assert(typeof sourceURLAppender === "function");
+
+ if (!expression) {
+ // There is no expression, so the completion should happen against global properties.
+ expression = "this";
+ } else if (/^\s*\{/.test(expression) && /\}\s*$/.test(expression)) {
+ // Transform {a:1} to ({a:1}) so it is treated like an object literal instead of a block with a label.
+ expression = "(" + expression + ")";
+ } else if (/\bawait\b/.test(expression)) {
+ // Transform `await <expr>` into an async function assignment.
+ expression = this._tryApplyAwaitConvenience(expression);
+ }
+
+ expression = sourceURLAppender(expression);
+
+ let target = this._activeExecutionContext.target;
+ let executionContextId = this._activeExecutionContext.id;
+
+ if (WebInspector.debuggerManager.activeCallFrame) {
+ target = WebInspector.debuggerManager.activeCallFrame.target;
+ executionContextId = target.executionContext.id;
+ }
+
+ function evalCallback(error, result, wasThrown, savedResultIndex)
+ {
+ this.dispatchEventToListeners(WebInspector.RuntimeManager.Event.DidEvaluate, {objectGroup});
+
+ if (error) {
+ console.error(error);
+ callback(null, false);
+ return;
+ }
+
+ if (returnByValue)
+ callback(null, wasThrown, wasThrown ? null : result, savedResultIndex);
+ else
+ callback(WebInspector.RemoteObject.fromPayload(result, target), wasThrown, savedResultIndex);
+ }
+
+ if (WebInspector.debuggerManager.activeCallFrame) {
+ // COMPATIBILITY (iOS 8): "saveResult" did not exist.
+ target.DebuggerAgent.evaluateOnCallFrame.invoke({callFrameId: WebInspector.debuggerManager.activeCallFrame.id, expression, objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, returnByValue, generatePreview, saveResult}, evalCallback.bind(this), target.DebuggerAgent);
+ return;
+ }
+
+ // COMPATIBILITY (iOS 8): "saveResult" did not exist.
+ target.RuntimeAgent.evaluate.invoke({expression, objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, contextId: executionContextId, returnByValue, generatePreview, saveResult}, evalCallback.bind(this), target.RuntimeAgent);
+ }
+
+ saveResult(remoteObject, callback)
+ {
+ console.assert(remoteObject instanceof WebInspector.RemoteObject);
+
+ // COMPATIBILITY (iOS 8): Runtime.saveResult did not exist.
+ if (!RuntimeAgent.saveResult) {
+ callback(undefined);
+ return;
+ }
+
+ function mycallback(error, savedResultIndex)
+ {
+ callback(savedResultIndex);
+ }
+
+ let target = this._activeExecutionContext.target;
+ let executionContextId = this._activeExecutionContext.id;
+
+ if (remoteObject.objectId)
+ target.RuntimeAgent.saveResult(remoteObject.asCallArgument(), mycallback);
+ else
+ target.RuntimeAgent.saveResult(remoteObject.asCallArgument(), executionContextId, mycallback);
+ }
+
+ getPropertiesForRemoteObject(objectId, callback)
+ {
+ this._activeExecutionContext.target.RuntimeAgent.getProperties(objectId, function(error, result) {
+ if (error) {
+ callback(error);
+ return;
+ }
+
+ let properties = new Map;
+ for (let property of result)
+ properties.set(property.name, property);
+
+ callback(null, properties);
+ });
+ }
+
+ // Private
+
+ _frameExecutionContextsCleared(event)
+ {
+ let contexts = event.data.contexts || [];
+
+ let currentContextWasDestroyed = contexts.some((context) => context.id === this._activeExecutionContext.id);
+ if (currentContextWasDestroyed)
+ this.activeExecutionContext = WebInspector.mainTarget.executionContext;
+ }
+
+ _tryApplyAwaitConvenience(originalExpression)
+ {
+ let esprimaSyntaxTree;
+
+ // Do not transform if the original code parses just fine.
+ try {
+ esprima.parse(originalExpression);
+ return originalExpression;
+ } catch (error) { }
+
+ // Do not transform if the async function version does not parse.
+ try {
+ esprimaSyntaxTree = esprima.parse("(async function(){" + originalExpression + "})");
+ } catch (error) {
+ return originalExpression;
+ }
+
+ // Assert expected AST produced by our wrapping code.
+ console.assert(esprimaSyntaxTree.type === "Program");
+ console.assert(esprimaSyntaxTree.body.length === 1);
+ console.assert(esprimaSyntaxTree.body[0].type === "ExpressionStatement");
+ console.assert(esprimaSyntaxTree.body[0].expression.type === "FunctionExpression");
+ console.assert(esprimaSyntaxTree.body[0].expression.async);
+ console.assert(esprimaSyntaxTree.body[0].expression.body.type === "BlockStatement");
+
+ // Do not transform if there is more than one statement.
+ let asyncFunctionBlock = esprimaSyntaxTree.body[0].expression.body;
+ if (asyncFunctionBlock.body.length !== 1)
+ return originalExpression;
+
+ // Extract the variable name for transformation.
+ let variableName;
+ let anonymous = false;
+ let declarationKind = "var";
+ let awaitPortion;
+ let statement = asyncFunctionBlock.body[0];
+ if (statement.type === "ExpressionStatement"
+ && statement.expression.type === "AwaitExpression") {
+ // await <expr>
+ anonymous = true;
+ } else if (statement.type === "ExpressionStatement"
+ && statement.expression.type === "AssignmentExpression"
+ && statement.expression.right.type === "AwaitExpression"
+ && statement.expression.left.type === "Identifier") {
+ // x = await <expr>
+ variableName = statement.expression.left.name;
+ awaitPortion = originalExpression.substring(originalExpression.indexOf("await"));
+ } else if (statement.type === "VariableDeclaration"
+ && statement.declarations.length === 1
+ && statement.declarations[0].init.type === "AwaitExpression"
+ && statement.declarations[0].id.type === "Identifier") {
+ // var x = await <expr>
+ variableName = statement.declarations[0].id.name;
+ declarationKind = statement.kind;
+ awaitPortion = originalExpression.substring(originalExpression.indexOf("await"));
+ } else {
+ // Do not transform if this was not one of the simple supported syntaxes.
+ return originalExpression;
+ }
+
+ if (anonymous) {
+ return `
+(async function() {
+ try {
+ let result = ${originalExpression};
+ console.info("%o", result);
+ } catch (e) {
+ console.error(e);
+ }
+})();
+undefined`;
+ }
+
+ return `${declarationKind} ${variableName};
+(async function() {
+ try {
+ ${variableName} = ${awaitPortion};
+ console.info("%o", ${variableName});
+ } catch (e) {
+ console.error(e);
+ }
+})();
+undefined;`;
+ }
+};
+
+WebInspector.RuntimeManager.ConsoleObjectGroup = "console";
+WebInspector.RuntimeManager.TopLevelExecutionContextIdentifier = undefined;
+
+WebInspector.RuntimeManager.Event = {
+ DidEvaluate: Symbol("runtime-manager-did-evaluate"),
+ DefaultExecutionContextChanged: Symbol("runtime-manager-default-execution-context-changed"),
+ ActiveExecutionContextChanged: Symbol("runtime-manager-active-execution-context-changed"),
+};
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/SourceMapManager.js b/Source/WebInspectorUI/UserInterface/Controllers/SourceMapManager.js
new file mode 100644
index 000000000..6bb8cefba
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/SourceMapManager.js
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2013 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.SourceMapManager = class SourceMapManager extends WebInspector.Object
+{
+ constructor()
+ {
+ super();
+
+ this._sourceMapURLMap = {};
+ this._downloadingSourceMaps = {};
+
+ WebInspector.Frame.addEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
+ }
+
+ // Public
+
+ sourceMapForURL(sourceMapURL)
+ {
+ return this._sourceMapURLMap[sourceMapURL];
+ }
+
+ downloadSourceMap(sourceMapURL, baseURL, originalSourceCode)
+ {
+ // The baseURL could have come from a "//# sourceURL". Attempt to get a
+ // reasonable absolute URL for the base by using the main resource's URL.
+ if (WebInspector.frameResourceManager.mainFrame)
+ baseURL = absoluteURL(baseURL, WebInspector.frameResourceManager.mainFrame.url);
+
+ if (sourceMapURL.startsWith("data:")) {
+ this._loadAndParseSourceMap(sourceMapURL, baseURL, originalSourceCode);
+ return;
+ }
+
+ sourceMapURL = absoluteURL(sourceMapURL, baseURL);
+ if (!sourceMapURL)
+ return;
+
+ console.assert(originalSourceCode.url);
+ if (!originalSourceCode.url)
+ return;
+
+ // FIXME: <rdar://problem/13265694> Source Maps: Better handle when multiple resources reference the same SourceMap
+
+ if (sourceMapURL in this._sourceMapURLMap)
+ return;
+
+ if (sourceMapURL in this._downloadingSourceMaps)
+ return;
+
+ function loadAndParseSourceMap()
+ {
+ this._loadAndParseSourceMap(sourceMapURL, baseURL, originalSourceCode);
+ }
+
+ if (!WebInspector.frameResourceManager.mainFrame) {
+ // If we don't have a main frame, then we are likely in the middle of building the resource tree.
+ // Delaying until the next runloop is enough in this case to then start loading the source map.
+ setTimeout(loadAndParseSourceMap.bind(this), 0);
+ return;
+ }
+
+ loadAndParseSourceMap.call(this);
+ }
+
+ // Private
+
+ _loadAndParseSourceMap(sourceMapURL, baseURL, originalSourceCode)
+ {
+ this._downloadingSourceMaps[sourceMapURL] = true;
+
+ function sourceMapLoaded(error, content, mimeType, statusCode)
+ {
+ if (error || statusCode >= 400) {
+ this._loadAndParseFailed(sourceMapURL);
+ return;
+ }
+
+ if (content.slice(0, 3) === ")]}") {
+ var firstNewlineIndex = content.indexOf("\n");
+ if (firstNewlineIndex === -1) {
+ this._loadAndParseFailed(sourceMapURL);
+ return;
+ }
+
+ content = content.substring(firstNewlineIndex);
+ }
+
+ try {
+ var payload = JSON.parse(content);
+ var baseURL = sourceMapURL.startsWith("data:") ? originalSourceCode.url : sourceMapURL;
+ var sourceMap = new WebInspector.SourceMap(baseURL, payload, originalSourceCode);
+ this._loadAndParseSucceeded(sourceMapURL, sourceMap);
+ } catch (e) {
+ this._loadAndParseFailed(sourceMapURL);
+ }
+ }
+
+ if (sourceMapURL.startsWith("data:")) {
+ let {mimeType, base64, data} = parseDataURL(sourceMapURL);
+ let content = base64 ? atob(data) : data;
+ sourceMapLoaded.call(this, null, content, mimeType, 0);
+ return;
+ }
+
+ // COMPATIBILITY (iOS 7): Network.loadResource did not exist.
+ // Also, JavaScript Debuggable may reach this.
+ if (!window.NetworkAgent || !NetworkAgent.loadResource) {
+ this._loadAndParseFailed(sourceMapURL);
+ return;
+ }
+
+ var frameIdentifier = null;
+ if (originalSourceCode instanceof WebInspector.Resource && originalSourceCode.parentFrame)
+ frameIdentifier = originalSourceCode.parentFrame.id;
+
+ if (!frameIdentifier)
+ frameIdentifier = WebInspector.frameResourceManager.mainFrame.id;
+
+ NetworkAgent.loadResource(frameIdentifier, sourceMapURL, sourceMapLoaded.bind(this));
+ }
+
+ _loadAndParseFailed(sourceMapURL)
+ {
+ delete this._downloadingSourceMaps[sourceMapURL];
+ }
+
+ _loadAndParseSucceeded(sourceMapURL, sourceMap)
+ {
+ if (!(sourceMapURL in this._downloadingSourceMaps))
+ return;
+
+ delete this._downloadingSourceMaps[sourceMapURL];
+
+ this._sourceMapURLMap[sourceMapURL] = sourceMap;
+
+ var sources = sourceMap.sources();
+ for (var i = 0; i < sources.length; ++i) {
+ var sourceMapResource = new WebInspector.SourceMapResource(sources[i], sourceMap);
+ sourceMap.addResource(sourceMapResource);
+ }
+
+ // Associate the SourceMap with the originalSourceCode.
+ sourceMap.originalSourceCode.addSourceMap(sourceMap);
+
+ // If the originalSourceCode was not a Resource, be sure to also associate with the Resource if one exists.
+ // FIXME: We should try to use the right frame instead of a global lookup by URL.
+ if (!(sourceMap.originalSourceCode instanceof WebInspector.Resource)) {
+ console.assert(sourceMap.originalSourceCode instanceof WebInspector.Script);
+ var resource = sourceMap.originalSourceCode.resource;
+ if (resource)
+ resource.addSourceMap(sourceMap);
+ }
+ }
+
+ _mainResourceDidChange(event)
+ {
+ if (!event.target.isMainFrame())
+ return;
+
+ this._sourceMapURLMap = {};
+ this._downloadingSourceMaps = {};
+ }
+};
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/StorageManager.js b/Source/WebInspectorUI/UserInterface/Controllers/StorageManager.js
new file mode 100644
index 000000000..a267dcb85
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/StorageManager.js
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2013 Apple Inc. All rights reserved.
+ * Copyright (C) 2013 Samsung Electronics. 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.StorageManager = class StorageManager extends WebInspector.Object
+{
+ constructor()
+ {
+ super();
+
+ if (window.DOMStorageAgent)
+ DOMStorageAgent.enable();
+ if (window.DatabaseAgent)
+ DatabaseAgent.enable();
+ if (window.IndexedDBAgent)
+ IndexedDBAgent.enable();
+
+ WebInspector.Frame.addEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
+ WebInspector.Frame.addEventListener(WebInspector.Frame.Event.SecurityOriginDidChange, this._securityOriginDidChange, this);
+
+ this.initialize();
+ }
+
+ // Public
+
+ initialize()
+ {
+ this._domStorageObjects = [];
+ this._databaseObjects = [];
+ this._indexedDatabases = [];
+ this._cookieStorageObjects = {};
+ }
+
+ get domStorageObjects()
+ {
+ return this._domStorageObjects;
+ }
+
+ get databases()
+ {
+ return this._databaseObjects;
+ }
+
+ get indexedDatabases()
+ {
+ return this._indexedDatabases;
+ }
+
+ get cookieStorageObjects()
+ {
+ var cookieStorageObjects = [];
+ for (var host in this._cookieStorageObjects)
+ cookieStorageObjects.push(this._cookieStorageObjects[host]);
+ return cookieStorageObjects;
+ }
+
+ domStorageWasAdded(id, host, isLocalStorage)
+ {
+ var domStorage = new WebInspector.DOMStorageObject(id, host, isLocalStorage);
+
+ this._domStorageObjects.push(domStorage);
+ this.dispatchEventToListeners(WebInspector.StorageManager.Event.DOMStorageObjectWasAdded, {domStorage});
+ }
+
+ databaseWasAdded(id, host, name, version)
+ {
+ var database = new WebInspector.DatabaseObject(id, host, name, version);
+
+ this._databaseObjects.push(database);
+ this.dispatchEventToListeners(WebInspector.StorageManager.Event.DatabaseWasAdded, {database});
+ }
+
+ itemsCleared(storageId)
+ {
+ let domStorage = this._domStorageForIdentifier(storageId);
+ if (domStorage)
+ domStorage.itemsCleared(storageId);
+ }
+
+ itemRemoved(storageId, key)
+ {
+ let domStorage = this._domStorageForIdentifier(storageId);
+ if (domStorage)
+ domStorage.itemRemoved(key);
+ }
+
+ itemAdded(storageId, key, value)
+ {
+ let domStorage = this._domStorageForIdentifier(storageId);
+ if (domStorage)
+ domStorage.itemAdded(key, value);
+ }
+
+ itemUpdated(storageId, key, oldValue, value)
+ {
+ let domStorage = this._domStorageForIdentifier(storageId);
+ if (domStorage)
+ domStorage.itemUpdated(key, oldValue, value);
+ }
+
+ inspectDatabase(id)
+ {
+ var database = this._databaseForIdentifier(id);
+ console.assert(database);
+ if (!database)
+ return;
+ this.dispatchEventToListeners(WebInspector.StorageManager.Event.DatabaseWasInspected, {database});
+ }
+
+ inspectDOMStorage(id)
+ {
+ var domStorage = this._domStorageForIdentifier(id);
+ console.assert(domStorage);
+ if (!domStorage)
+ return;
+ this.dispatchEventToListeners(WebInspector.StorageManager.Event.DOMStorageObjectWasInspected, {domStorage});
+ }
+
+ requestIndexedDatabaseData(objectStore, objectStoreIndex, startEntryIndex, maximumEntryCount, callback)
+ {
+ console.assert(window.IndexedDBAgent);
+ console.assert(objectStore);
+ console.assert(callback);
+
+ function processData(error, entryPayloads, moreAvailable)
+ {
+ if (error) {
+ callback(null, false);
+ return;
+ }
+
+ var entries = [];
+
+ for (var entryPayload of entryPayloads) {
+ var entry = {};
+ entry.primaryKey = WebInspector.RemoteObject.fromPayload(entryPayload.primaryKey);
+ entry.key = WebInspector.RemoteObject.fromPayload(entryPayload.key);
+ entry.value = WebInspector.RemoteObject.fromPayload(entryPayload.value);
+ entries.push(entry);
+ }
+
+ callback(entries, moreAvailable);
+ }
+
+ var requestArguments = {
+ securityOrigin: objectStore.parentDatabase.securityOrigin,
+ databaseName: objectStore.parentDatabase.name,
+ objectStoreName: objectStore.name,
+ indexName: objectStoreIndex && objectStoreIndex.name || "",
+ skipCount: startEntryIndex || 0,
+ pageSize: maximumEntryCount || 100
+ };
+
+ IndexedDBAgent.requestData.invoke(requestArguments, processData);
+ }
+
+ clearObjectStore(objectStore)
+ {
+ let securityOrigin = objectStore.parentDatabase.securityOrigin;
+ let databaseName = objectStore.parentDatabase.name;
+ let objectStoreName = objectStore.name;
+
+ IndexedDBAgent.clearObjectStore(securityOrigin, databaseName, objectStoreName);
+ }
+
+ // Private
+
+ _domStorageForIdentifier(id)
+ {
+ for (var storageObject of this._domStorageObjects) {
+ // The id is an object, so we need to compare the properties using Object.shallowEqual.
+ if (Object.shallowEqual(storageObject.id, id))
+ return storageObject;
+ }
+
+ return null;
+ }
+
+ _mainResourceDidChange(event)
+ {
+ console.assert(event.target instanceof WebInspector.Frame);
+
+ if (event.target.isMainFrame()) {
+ // If we are dealing with the main frame, we want to clear our list of objects, because we are navigating to a new page.
+ this.initialize();
+ this.dispatchEventToListeners(WebInspector.StorageManager.Event.Cleared);
+
+ this._addDOMStorageIfNeeded(event.target);
+ this._addIndexedDBDatabasesIfNeeded(event.target);
+ }
+
+ // Add the host of the frame that changed the main resource to the list of hosts there could be cookies for.
+ var host = parseURL(event.target.url).host;
+ if (!host)
+ return;
+
+ if (this._cookieStorageObjects[host])
+ return;
+
+ this._cookieStorageObjects[host] = new WebInspector.CookieStorageObject(host);
+ this.dispatchEventToListeners(WebInspector.StorageManager.Event.CookieStorageObjectWasAdded, {cookieStorage: this._cookieStorageObjects[host]});
+ }
+
+ _addDOMStorageIfNeeded(frame)
+ {
+ if (!window.DOMStorageAgent)
+ return;
+
+ // Don't show storage if we don't have a security origin (about:blank).
+ if (!frame.securityOrigin || frame.securityOrigin === "://")
+ return;
+
+ // FIXME: Consider passing the other parts of the origin along to domStorageWasAdded.
+
+ var localStorageIdentifier = {securityOrigin: frame.securityOrigin, isLocalStorage: true};
+ if (!this._domStorageForIdentifier(localStorageIdentifier))
+ this.domStorageWasAdded(localStorageIdentifier, frame.mainResource.urlComponents.host, true);
+
+ var sessionStorageIdentifier = {securityOrigin: frame.securityOrigin, isLocalStorage: false};
+ if (!this._domStorageForIdentifier(sessionStorageIdentifier))
+ this.domStorageWasAdded(sessionStorageIdentifier, frame.mainResource.urlComponents.host, false);
+ }
+
+ _addIndexedDBDatabasesIfNeeded(frame)
+ {
+ if (!window.IndexedDBAgent)
+ return;
+
+ var securityOrigin = frame.securityOrigin;
+
+ // Don't show storage if we don't have a security origin (about:blank).
+ if (!securityOrigin || securityOrigin === "://")
+ return;
+
+ function processDatabaseNames(error, names)
+ {
+ if (error || !names)
+ return;
+
+ for (var name of names)
+ IndexedDBAgent.requestDatabase(securityOrigin, name, processDatabase.bind(this));
+ }
+
+ function processDatabase(error, databasePayload)
+ {
+ if (error || !databasePayload)
+ return;
+
+ var objectStores = databasePayload.objectStores.map(processObjectStore);
+ var indexedDatabase = new WebInspector.IndexedDatabase(databasePayload.name, securityOrigin, databasePayload.version, objectStores);
+
+ this._indexedDatabases.push(indexedDatabase);
+ this.dispatchEventToListeners(WebInspector.StorageManager.Event.IndexedDatabaseWasAdded, {indexedDatabase});
+ }
+
+ function processKeyPath(keyPathPayload)
+ {
+ switch (keyPathPayload.type) {
+ case IndexedDBAgent.KeyPathType.Null:
+ return null;
+ case IndexedDBAgent.KeyPathType.String:
+ return keyPathPayload.string;
+ case IndexedDBAgent.KeyPathType.Array:
+ return keyPathPayload.array;
+ default:
+ console.error("Unknown KeyPath type:", keyPathPayload.type);
+ return null;
+ }
+ }
+
+ function processObjectStore(objectStorePayload)
+ {
+ var keyPath = processKeyPath(objectStorePayload.keyPath);
+ var indexes = objectStorePayload.indexes.map(processObjectStoreIndex);
+ return new WebInspector.IndexedDatabaseObjectStore(objectStorePayload.name, keyPath, objectStorePayload.autoIncrement, indexes);
+ }
+
+ function processObjectStoreIndex(objectStoreIndexPayload)
+ {
+ var keyPath = processKeyPath(objectStoreIndexPayload.keyPath);
+ return new WebInspector.IndexedDatabaseObjectStoreIndex(objectStoreIndexPayload.name, keyPath, objectStoreIndexPayload.unique, objectStoreIndexPayload.multiEntry);
+ }
+
+ IndexedDBAgent.requestDatabaseNames(securityOrigin, processDatabaseNames.bind(this));
+ }
+
+ _securityOriginDidChange(event)
+ {
+ console.assert(event.target instanceof WebInspector.Frame);
+
+ this._addDOMStorageIfNeeded(event.target);
+ this._addIndexedDBDatabasesIfNeeded(event.target);
+ }
+
+ _databaseForIdentifier(id)
+ {
+ for (var i = 0; i < this._databaseObjects.length; ++i) {
+ if (this._databaseObjects[i].id === id)
+ return this._databaseObjects[i];
+ }
+
+ return null;
+ }
+};
+
+WebInspector.StorageManager.Event = {
+ CookieStorageObjectWasAdded: "storage-manager-cookie-storage-object-was-added",
+ DOMStorageObjectWasAdded: "storage-manager-dom-storage-object-was-added",
+ DOMStorageObjectWasInspected: "storage-dom-object-was-inspected",
+ DatabaseWasAdded: "storage-manager-database-was-added",
+ DatabaseWasInspected: "storage-object-was-inspected",
+ IndexedDatabaseWasAdded: "storage-manager-indexed-database-was-added",
+ Cleared: "storage-manager-cleared"
+};
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/TargetManager.js b/Source/WebInspectorUI/UserInterface/Controllers/TargetManager.js
new file mode 100644
index 000000000..7e987d4dd
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/TargetManager.js
@@ -0,0 +1,75 @@
+/*
+ * 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.TargetManager = class TargetManager extends WebInspector.Object
+{
+ constructor()
+ {
+ super();
+
+ console.assert(WebInspector.mainTarget);
+
+ this._targets = new Set([WebInspector.mainTarget]);
+ }
+
+ // Public
+
+ get targets()
+ {
+ return this._targets;
+ }
+
+ targetForIdentifier(targetId)
+ {
+ if (!targetId)
+ return null;
+
+ for (let target of this._targets) {
+ if (target.identifier === targetId)
+ return target;
+ }
+
+ return null;
+ }
+
+ addTarget(target)
+ {
+ this._targets.add(target);
+
+ this.dispatchEventToListeners(WebInspector.TargetManager.Event.TargetAdded, {target});
+ }
+
+ removeTarget(target)
+ {
+ this._targets.delete(target);
+
+ this.dispatchEventToListeners(WebInspector.TargetManager.Event.TargetRemoved, {target});
+ }
+};
+
+WebInspector.TargetManager.Event = {
+ TargetAdded: Symbol("target-manager-target-added"),
+ TargetRemoved: Symbol("target-manager-target-removed"),
+};
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/TimelineManager.js b/Source/WebInspectorUI/UserInterface/Controllers/TimelineManager.js
new file mode 100644
index 000000000..e03ba9ba2
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/TimelineManager.js
@@ -0,0 +1,1091 @@
+/*
+ * Copyright (C) 2013, 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.TimelineManager = class TimelineManager extends WebInspector.Object
+{
+ constructor()
+ {
+ super();
+
+ WebInspector.Frame.addEventListener(WebInspector.Frame.Event.ProvisionalLoadStarted, this._provisionalLoadStarted, this);
+ WebInspector.Frame.addEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
+ WebInspector.Frame.addEventListener(WebInspector.Frame.Event.ResourceWasAdded, this._resourceWasAdded, this);
+ WebInspector.Target.addEventListener(WebInspector.Target.Event.ResourceAdded, this._resourceWasAdded, this);
+
+ WebInspector.heapManager.addEventListener(WebInspector.HeapManager.Event.GarbageCollected, this._garbageCollected, this);
+ WebInspector.memoryManager.addEventListener(WebInspector.MemoryManager.Event.MemoryPressure, this._memoryPressure, this);
+
+ this._enabledTimelineTypesSetting = new WebInspector.Setting("enabled-instrument-types", WebInspector.TimelineManager.defaultTimelineTypes());
+ this._updateAutoCaptureInstruments();
+
+ this._persistentNetworkTimeline = new WebInspector.NetworkTimeline;
+
+ this._isCapturing = false;
+ this._initiatedByBackendStart = false;
+ this._initiatedByBackendStop = false;
+ this._waitingForCapturingStartedEvent = false;
+ this._isCapturingPageReload = false;
+ this._autoCaptureOnPageLoad = false;
+ this._mainResourceForAutoCapturing = null;
+ this._shouldSetAutoCapturingMainResource = false;
+ this._boundStopCapturing = this.stopCapturing.bind(this);
+
+ this._webTimelineScriptRecordsExpectingScriptProfilerEvents = null;
+ this._scriptProfilerRecords = null;
+
+ this._stopCapturingTimeout = undefined;
+ this._deadTimeTimeout = undefined;
+ this._lastDeadTimeTickle = 0;
+
+ this.reset();
+ }
+
+ // Static
+
+ static defaultTimelineTypes()
+ {
+ if (WebInspector.debuggableType === WebInspector.DebuggableType.JavaScript) {
+ let defaultTypes = [WebInspector.TimelineRecord.Type.Script];
+ if (WebInspector.HeapAllocationsInstrument.supported())
+ defaultTypes.push(WebInspector.TimelineRecord.Type.HeapAllocations);
+ return defaultTypes;
+ }
+
+ let defaultTypes = [
+ WebInspector.TimelineRecord.Type.Network,
+ WebInspector.TimelineRecord.Type.Layout,
+ WebInspector.TimelineRecord.Type.Script,
+ ];
+
+ if (WebInspector.FPSInstrument.supported())
+ defaultTypes.push(WebInspector.TimelineRecord.Type.RenderingFrame);
+
+ return defaultTypes;
+ }
+
+ static availableTimelineTypes()
+ {
+ let types = WebInspector.TimelineManager.defaultTimelineTypes();
+ if (WebInspector.debuggableType === WebInspector.DebuggableType.JavaScript)
+ return types;
+
+ if (WebInspector.MemoryInstrument.supported())
+ types.push(WebInspector.TimelineRecord.Type.Memory);
+
+ if (WebInspector.HeapAllocationsInstrument.supported())
+ types.push(WebInspector.TimelineRecord.Type.HeapAllocations);
+
+ return types;
+ }
+
+ // Public
+
+ reset()
+ {
+ if (this._isCapturing)
+ this.stopCapturing();
+
+ this._recordings = [];
+ this._activeRecording = null;
+ this._nextRecordingIdentifier = 1;
+
+ this._loadNewRecording();
+ }
+
+ // The current recording that new timeline records will be appended to, if any.
+ get activeRecording()
+ {
+ console.assert(this._activeRecording || !this._isCapturing);
+ return this._activeRecording;
+ }
+
+ get persistentNetworkTimeline()
+ {
+ return this._persistentNetworkTimeline;
+ }
+
+ get recordings()
+ {
+ return this._recordings.slice();
+ }
+
+ get autoCaptureOnPageLoad()
+ {
+ return this._autoCaptureOnPageLoad;
+ }
+
+ set autoCaptureOnPageLoad(autoCapture)
+ {
+ autoCapture = !!autoCapture;
+
+ if (this._autoCaptureOnPageLoad === autoCapture)
+ return;
+
+ this._autoCaptureOnPageLoad = autoCapture;
+
+ if (window.TimelineAgent && TimelineAgent.setAutoCaptureEnabled)
+ TimelineAgent.setAutoCaptureEnabled(this._autoCaptureOnPageLoad);
+ }
+
+ get enabledTimelineTypes()
+ {
+ let availableTimelineTypes = WebInspector.TimelineManager.availableTimelineTypes();
+ return this._enabledTimelineTypesSetting.value.filter((type) => availableTimelineTypes.includes(type));
+ }
+
+ set enabledTimelineTypes(x)
+ {
+ this._enabledTimelineTypesSetting.value = x || [];
+
+ this._updateAutoCaptureInstruments();
+ }
+
+ isCapturing()
+ {
+ return this._isCapturing;
+ }
+
+ isCapturingPageReload()
+ {
+ return this._isCapturingPageReload;
+ }
+
+ startCapturing(shouldCreateRecording)
+ {
+ console.assert(!this._isCapturing, "TimelineManager is already capturing.");
+
+ if (!this._activeRecording || shouldCreateRecording)
+ this._loadNewRecording();
+
+ this._waitingForCapturingStartedEvent = true;
+
+ this.dispatchEventToListeners(WebInspector.TimelineManager.Event.CapturingWillStart);
+
+ this._activeRecording.start(this._initiatedByBackendStart);
+ }
+
+ stopCapturing()
+ {
+ console.assert(this._isCapturing, "TimelineManager is not capturing.");
+
+ this._activeRecording.stop(this._initiatedByBackendStop);
+
+ // NOTE: Always stop immediately instead of waiting for a Timeline.recordingStopped event.
+ // This way the UI feels as responsive to a stop as possible.
+ // FIXME: <https://webkit.org/b/152904> Web Inspector: Timeline UI should keep up with processing all incoming records
+ this.capturingStopped();
+ }
+
+ unloadRecording()
+ {
+ if (!this._activeRecording)
+ return;
+
+ if (this._isCapturing)
+ this.stopCapturing();
+
+ this._activeRecording.unloaded();
+ this._activeRecording = null;
+ }
+
+ computeElapsedTime(timestamp)
+ {
+ if (!this._activeRecording)
+ return 0;
+
+ return this._activeRecording.computeElapsedTime(timestamp);
+ }
+
+ scriptProfilerIsTracking()
+ {
+ return this._scriptProfilerRecords !== null;
+ }
+
+ // Protected
+
+ capturingStarted(startTime)
+ {
+ // Called from WebInspector.TimelineObserver.
+
+ if (this._isCapturing)
+ return;
+
+ this._waitingForCapturingStartedEvent = false;
+ this._isCapturing = true;
+
+ this._lastDeadTimeTickle = 0;
+
+ if (startTime)
+ this.activeRecording.initializeTimeBoundsIfNecessary(startTime);
+
+ this._webTimelineScriptRecordsExpectingScriptProfilerEvents = [];
+
+ this.dispatchEventToListeners(WebInspector.TimelineManager.Event.CapturingStarted, {startTime});
+ }
+
+ capturingStopped(endTime)
+ {
+ // Called from WebInspector.TimelineObserver.
+
+ if (!this._isCapturing)
+ return;
+
+ if (this._stopCapturingTimeout) {
+ clearTimeout(this._stopCapturingTimeout);
+ this._stopCapturingTimeout = undefined;
+ }
+
+ if (this._deadTimeTimeout) {
+ clearTimeout(this._deadTimeTimeout);
+ this._deadTimeTimeout = undefined;
+ }
+
+ this._isCapturing = false;
+ this._isCapturingPageReload = false;
+ this._shouldSetAutoCapturingMainResource = false;
+ this._mainResourceForAutoCapturing = null;
+ this._initiatedByBackendStart = false;
+ this._initiatedByBackendStop = false;
+
+ this.dispatchEventToListeners(WebInspector.TimelineManager.Event.CapturingStopped, {endTime});
+ }
+
+ autoCaptureStarted()
+ {
+ // Called from WebInspector.TimelineObserver.
+
+ if (this._isCapturing)
+ this.stopCapturing();
+
+ this._initiatedByBackendStart = true;
+
+ // We may already have an fresh TimelineRecording created if autoCaptureStarted is received
+ // between sending the Timeline.start command and receiving Timeline.capturingStarted event.
+ // In that case, there is no need to call startCapturing again. Reuse the fresh recording.
+ if (!this._waitingForCapturingStartedEvent) {
+ const createNewRecording = true;
+ this.startCapturing(createNewRecording);
+ }
+
+ this._shouldSetAutoCapturingMainResource = true;
+ }
+
+ programmaticCaptureStarted()
+ {
+ // Called from WebInspector.TimelineObserver.
+
+ this._initiatedByBackendStart = true;
+
+ this._activeRecording.addScriptInstrumentForProgrammaticCapture();
+
+ const createNewRecording = false;
+ this.startCapturing(createNewRecording);
+ }
+
+ programmaticCaptureStopped()
+ {
+ // Called from WebInspector.TimelineObserver.
+
+ this._initiatedByBackendStop = true;
+
+ // FIXME: This is purely to avoid a noisy assert. Previously
+ // it was impossible to stop without stopping from the UI.
+ console.assert(!this._isCapturing);
+ this._isCapturing = true;
+
+ this.stopCapturing();
+ }
+
+ eventRecorded(recordPayload)
+ {
+ // Called from WebInspector.TimelineObserver.
+
+ if (!this._isCapturing)
+ return;
+
+ var records = [];
+
+ // Iterate over the records tree using a stack. Doing this recursively has
+ // been known to cause a call stack overflow. https://webkit.org/b/79106
+ var stack = [{array: [recordPayload], parent: null, parentRecord: null, index: 0}];
+ while (stack.length) {
+ var entry = stack.lastValue;
+ var recordPayloads = entry.array;
+
+ if (entry.index < recordPayloads.length) {
+ var recordPayload = recordPayloads[entry.index];
+ var record = this._processEvent(recordPayload, entry.parent);
+ if (record) {
+ record.parent = entry.parentRecord;
+ records.push(record);
+ if (entry.parentRecord)
+ entry.parentRecord.children.push(record);
+ }
+
+ if (recordPayload.children && recordPayload.children.length)
+ stack.push({array: recordPayload.children, parent: recordPayload, parentRecord: record || entry.parentRecord, index: 0});
+ ++entry.index;
+ } else
+ stack.pop();
+ }
+
+ for (var record of records) {
+ if (record.type === WebInspector.TimelineRecord.Type.RenderingFrame) {
+ if (!record.children.length)
+ continue;
+ record.setupFrameIndex();
+ }
+
+ this._addRecord(record);
+ }
+ }
+
+ // Protected
+
+ pageDOMContentLoadedEventFired(timestamp)
+ {
+ // Called from WebInspector.PageObserver.
+
+ console.assert(this._activeRecording);
+ console.assert(isNaN(WebInspector.frameResourceManager.mainFrame.domContentReadyEventTimestamp));
+
+ let computedTimestamp = this.activeRecording.computeElapsedTime(timestamp);
+
+ WebInspector.frameResourceManager.mainFrame.markDOMContentReadyEvent(computedTimestamp);
+
+ let eventMarker = new WebInspector.TimelineMarker(computedTimestamp, WebInspector.TimelineMarker.Type.DOMContentEvent);
+ this._activeRecording.addEventMarker(eventMarker);
+ }
+
+ pageLoadEventFired(timestamp)
+ {
+ // Called from WebInspector.PageObserver.
+
+ console.assert(this._activeRecording);
+ console.assert(isNaN(WebInspector.frameResourceManager.mainFrame.loadEventTimestamp));
+
+ let computedTimestamp = this.activeRecording.computeElapsedTime(timestamp);
+
+ WebInspector.frameResourceManager.mainFrame.markLoadEvent(computedTimestamp);
+
+ let eventMarker = new WebInspector.TimelineMarker(computedTimestamp, WebInspector.TimelineMarker.Type.LoadEvent);
+ this._activeRecording.addEventMarker(eventMarker);
+
+ this._stopAutoRecordingSoon();
+ }
+
+ memoryTrackingStart(timestamp)
+ {
+ // Called from WebInspector.MemoryObserver.
+
+ this.capturingStarted(timestamp);
+ }
+
+ memoryTrackingUpdate(event)
+ {
+ // Called from WebInspector.MemoryObserver.
+
+ if (!this._isCapturing)
+ return;
+
+ this._addRecord(new WebInspector.MemoryTimelineRecord(event.timestamp, event.categories));
+ }
+
+ memoryTrackingComplete()
+ {
+ // Called from WebInspector.MemoryObserver.
+ }
+
+ heapTrackingStarted(timestamp, snapshot)
+ {
+ // Called from WebInspector.HeapObserver.
+
+ this._addRecord(new WebInspector.HeapAllocationsTimelineRecord(timestamp, snapshot));
+
+ this.capturingStarted(timestamp);
+ }
+
+ heapTrackingCompleted(timestamp, snapshot)
+ {
+ // Called from WebInspector.HeapObserver.
+
+ this._addRecord(new WebInspector.HeapAllocationsTimelineRecord(timestamp, snapshot));
+ }
+
+ heapSnapshotAdded(timestamp, snapshot)
+ {
+ // Called from WebInspector.HeapAllocationsInstrument.
+
+ this._addRecord(new WebInspector.HeapAllocationsTimelineRecord(timestamp, snapshot));
+ }
+
+ // Private
+
+ _processRecord(recordPayload, parentRecordPayload)
+ {
+ var startTime = this.activeRecording.computeElapsedTime(recordPayload.startTime);
+ var endTime = this.activeRecording.computeElapsedTime(recordPayload.endTime);
+ var callFrames = this._callFramesFromPayload(recordPayload.stackTrace);
+
+ var significantCallFrame = null;
+ if (callFrames) {
+ for (var i = 0; i < callFrames.length; ++i) {
+ if (callFrames[i].nativeCode)
+ continue;
+ significantCallFrame = callFrames[i];
+ break;
+ }
+ }
+
+ var sourceCodeLocation = significantCallFrame && significantCallFrame.sourceCodeLocation;
+
+ switch (recordPayload.type) {
+ case TimelineAgent.EventType.ScheduleStyleRecalculation:
+ console.assert(isNaN(endTime));
+
+ // Pass the startTime as the endTime since this record type has no duration.
+ return new WebInspector.LayoutTimelineRecord(WebInspector.LayoutTimelineRecord.EventType.InvalidateStyles, startTime, startTime, callFrames, sourceCodeLocation);
+
+ case TimelineAgent.EventType.RecalculateStyles:
+ return new WebInspector.LayoutTimelineRecord(WebInspector.LayoutTimelineRecord.EventType.RecalculateStyles, startTime, endTime, callFrames, sourceCodeLocation);
+
+ case TimelineAgent.EventType.InvalidateLayout:
+ console.assert(isNaN(endTime));
+
+ // Pass the startTime as the endTime since this record type has no duration.
+ return new WebInspector.LayoutTimelineRecord(WebInspector.LayoutTimelineRecord.EventType.InvalidateLayout, startTime, startTime, callFrames, sourceCodeLocation);
+
+ case TimelineAgent.EventType.Layout:
+ var layoutRecordType = sourceCodeLocation ? WebInspector.LayoutTimelineRecord.EventType.ForcedLayout : WebInspector.LayoutTimelineRecord.EventType.Layout;
+ var quad = new WebInspector.Quad(recordPayload.data.root);
+ return new WebInspector.LayoutTimelineRecord(layoutRecordType, startTime, endTime, callFrames, sourceCodeLocation, quad);
+
+ case TimelineAgent.EventType.Paint:
+ var quad = new WebInspector.Quad(recordPayload.data.clip);
+ return new WebInspector.LayoutTimelineRecord(WebInspector.LayoutTimelineRecord.EventType.Paint, startTime, endTime, callFrames, sourceCodeLocation, quad);
+
+ case TimelineAgent.EventType.Composite:
+ return new WebInspector.LayoutTimelineRecord(WebInspector.LayoutTimelineRecord.EventType.Composite, startTime, endTime, callFrames, sourceCodeLocation);
+
+ case TimelineAgent.EventType.RenderingFrame:
+ if (!recordPayload.children || !recordPayload.children.length)
+ return null;
+
+ return new WebInspector.RenderingFrameTimelineRecord(startTime, endTime);
+
+ case TimelineAgent.EventType.EvaluateScript:
+ if (!sourceCodeLocation) {
+ var mainFrame = WebInspector.frameResourceManager.mainFrame;
+ var scriptResource = mainFrame.url === recordPayload.data.url ? mainFrame.mainResource : mainFrame.resourceForURL(recordPayload.data.url, true);
+ if (scriptResource) {
+ // The lineNumber is 1-based, but we expect 0-based.
+ var lineNumber = recordPayload.data.lineNumber - 1;
+
+ // FIXME: No column number is provided.
+ sourceCodeLocation = scriptResource.createSourceCodeLocation(lineNumber, 0);
+ }
+ }
+
+ var profileData = recordPayload.data.profile;
+
+ var record;
+ switch (parentRecordPayload && parentRecordPayload.type) {
+ case TimelineAgent.EventType.TimerFire:
+ record = new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.TimerFired, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.timerId, profileData);
+ break;
+ default:
+ record = new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.ScriptEvaluated, startTime, endTime, callFrames, sourceCodeLocation, null, profileData);
+ break;
+ }
+
+ this._webTimelineScriptRecordsExpectingScriptProfilerEvents.push(record);
+ return record;
+
+ case TimelineAgent.EventType.ConsoleProfile:
+ var profileData = recordPayload.data.profile;
+ // COMPATIBILITY (iOS 9): With the Sampling Profiler, profiles no longer include legacy profile data.
+ console.assert(profileData || TimelineAgent.setInstruments);
+ return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.ConsoleProfileRecorded, startTime, endTime, callFrames, sourceCodeLocation, recordPayload.data.title, profileData);
+
+ case TimelineAgent.EventType.TimerFire:
+ case TimelineAgent.EventType.EventDispatch:
+ case TimelineAgent.EventType.FireAnimationFrame:
+ // These are handled when the parent of FunctionCall or EvaluateScript.
+ break;
+
+ case TimelineAgent.EventType.FunctionCall:
+ // FunctionCall always happens as a child of another record, and since the FunctionCall record
+ // has useful info we just make the timeline record here (combining the data from both records).
+ if (!parentRecordPayload) {
+ console.warn("Unexpectedly received a FunctionCall timeline record without a parent record");
+ break;
+ }
+
+ if (!sourceCodeLocation) {
+ var mainFrame = WebInspector.frameResourceManager.mainFrame;
+ var scriptResource = mainFrame.url === recordPayload.data.scriptName ? mainFrame.mainResource : mainFrame.resourceForURL(recordPayload.data.scriptName, true);
+ if (scriptResource) {
+ // The lineNumber is 1-based, but we expect 0-based.
+ var lineNumber = recordPayload.data.scriptLine - 1;
+
+ // FIXME: No column number is provided.
+ sourceCodeLocation = scriptResource.createSourceCodeLocation(lineNumber, 0);
+ }
+ }
+
+ var profileData = recordPayload.data.profile;
+
+ var record;
+ switch (parentRecordPayload.type) {
+ case TimelineAgent.EventType.TimerFire:
+ record = new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.TimerFired, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.timerId, profileData);
+ break;
+ case TimelineAgent.EventType.EventDispatch:
+ record = new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.EventDispatched, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.type, profileData);
+ break;
+ case TimelineAgent.EventType.FireAnimationFrame:
+ record = new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.AnimationFrameFired, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.id, profileData);
+ break;
+ case TimelineAgent.EventType.FunctionCall:
+ record = new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.ScriptEvaluated, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.id, profileData);
+ break;
+ case TimelineAgent.EventType.RenderingFrame:
+ record = new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.ScriptEvaluated, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.id, profileData);
+ break;
+
+ default:
+ console.assert(false, "Missed FunctionCall embedded inside of: " + parentRecordPayload.type);
+ break;
+ }
+
+ if (record) {
+ this._webTimelineScriptRecordsExpectingScriptProfilerEvents.push(record);
+ return record;
+ }
+ break;
+
+ case TimelineAgent.EventType.ProbeSample:
+ // Pass the startTime as the endTime since this record type has no duration.
+ sourceCodeLocation = WebInspector.probeManager.probeForIdentifier(recordPayload.data.probeId).breakpoint.sourceCodeLocation;
+ return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.ProbeSampleRecorded, startTime, startTime, callFrames, sourceCodeLocation, recordPayload.data.probeId);
+
+ case TimelineAgent.EventType.TimerInstall:
+ console.assert(isNaN(endTime));
+
+ // Pass the startTime as the endTime since this record type has no duration.
+ var timerDetails = {timerId: recordPayload.data.timerId, timeout: recordPayload.data.timeout, repeating: !recordPayload.data.singleShot};
+ return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.TimerInstalled, startTime, startTime, callFrames, sourceCodeLocation, timerDetails);
+
+ case TimelineAgent.EventType.TimerRemove:
+ console.assert(isNaN(endTime));
+
+ // Pass the startTime as the endTime since this record type has no duration.
+ return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.TimerRemoved, startTime, startTime, callFrames, sourceCodeLocation, recordPayload.data.timerId);
+
+ case TimelineAgent.EventType.RequestAnimationFrame:
+ console.assert(isNaN(endTime));
+
+ // Pass the startTime as the endTime since this record type has no duration.
+ return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.AnimationFrameRequested, startTime, startTime, callFrames, sourceCodeLocation, recordPayload.data.id);
+
+ case TimelineAgent.EventType.CancelAnimationFrame:
+ console.assert(isNaN(endTime));
+
+ // Pass the startTime as the endTime since this record type has no duration.
+ return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.AnimationFrameCanceled, startTime, startTime, callFrames, sourceCodeLocation, recordPayload.data.id);
+
+ default:
+ console.error("Missing handling of Timeline Event Type: " + recordPayload.type);
+ }
+
+ return null;
+ }
+
+ _processEvent(recordPayload, parentRecordPayload)
+ {
+ switch (recordPayload.type) {
+ case TimelineAgent.EventType.TimeStamp:
+ var timestamp = this.activeRecording.computeElapsedTime(recordPayload.startTime);
+ var eventMarker = new WebInspector.TimelineMarker(timestamp, WebInspector.TimelineMarker.Type.TimeStamp, recordPayload.data.message);
+ this._activeRecording.addEventMarker(eventMarker);
+ break;
+
+ case TimelineAgent.EventType.Time:
+ case TimelineAgent.EventType.TimeEnd:
+ // FIXME: <https://webkit.org/b/150690> Web Inspector: Show console.time/timeEnd ranges in Timeline
+ // FIXME: Make use of "message" payload properties.
+ break;
+
+ default:
+ return this._processRecord(recordPayload, parentRecordPayload);
+ }
+
+ return null;
+ }
+
+ _loadNewRecording()
+ {
+ if (this._activeRecording && this._activeRecording.isEmpty())
+ return;
+
+ let instruments = this.enabledTimelineTypes.map((type) => WebInspector.Instrument.createForTimelineType(type));
+ let identifier = this._nextRecordingIdentifier++;
+ let newRecording = new WebInspector.TimelineRecording(identifier, WebInspector.UIString("Timeline Recording %d").format(identifier), instruments);
+
+ this._recordings.push(newRecording);
+ this.dispatchEventToListeners(WebInspector.TimelineManager.Event.RecordingCreated, {recording: newRecording});
+
+ if (this._isCapturing)
+ this.stopCapturing();
+
+ var oldRecording = this._activeRecording;
+ if (oldRecording)
+ oldRecording.unloaded();
+
+ this._activeRecording = newRecording;
+
+ // COMPATIBILITY (iOS 8): When using Legacy timestamps, a navigation will have computed
+ // the main resource's will send request timestamp in terms of the last page's base timestamp.
+ // Now that we have navigated, we should reset the legacy base timestamp and the
+ // will send request timestamp for the new main resource. This way, all new timeline
+ // records will be computed relative to the new navigation.
+ if (this._mainResourceForAutoCapturing && WebInspector.TimelineRecording.isLegacy) {
+ console.assert(this._mainResourceForAutoCapturing.originalRequestWillBeSentTimestamp);
+ this._activeRecording.setLegacyBaseTimestamp(this._mainResourceForAutoCapturing.originalRequestWillBeSentTimestamp);
+ this._mainResourceForAutoCapturing._requestSentTimestamp = 0;
+ }
+
+ this.dispatchEventToListeners(WebInspector.TimelineManager.Event.RecordingLoaded, {oldRecording});
+ }
+
+ _callFramesFromPayload(payload)
+ {
+ if (!payload)
+ return null;
+
+ return payload.map((x) => WebInspector.CallFrame.fromPayload(WebInspector.assumingMainTarget(), x));
+ }
+
+ _addRecord(record)
+ {
+ this._activeRecording.addRecord(record);
+
+ // Only worry about dead time after the load event.
+ if (WebInspector.frameResourceManager.mainFrame && isNaN(WebInspector.frameResourceManager.mainFrame.loadEventTimestamp))
+ this._resetAutoRecordingDeadTimeTimeout();
+ }
+
+ _attemptAutoCapturingForFrame(frame)
+ {
+ if (!this._autoCaptureOnPageLoad)
+ return false;
+
+ if (!frame.isMainFrame())
+ return false;
+
+ // COMPATIBILITY (iOS 9): Timeline.setAutoCaptureEnabled did not exist.
+ // Perform auto capture in the frontend.
+ if (!TimelineAgent.setAutoCaptureEnabled)
+ return this._legacyAttemptStartAutoCapturingForFrame(frame);
+
+ if (!this._shouldSetAutoCapturingMainResource)
+ return false;
+
+ console.assert(this._isCapturing, "We saw autoCaptureStarted so we should already be capturing");
+
+ let mainResource = frame.provisionalMainResource || frame.mainResource;
+ if (mainResource === this._mainResourceForAutoCapturing)
+ return false;
+
+ let oldMainResource = frame.mainResource || null;
+ this._isCapturingPageReload = oldMainResource !== null && oldMainResource.url === mainResource.url;
+
+ this._mainResourceForAutoCapturing = mainResource;
+
+ this._addRecord(new WebInspector.ResourceTimelineRecord(mainResource));
+
+ this._resetAutoRecordingMaxTimeTimeout();
+
+ this._shouldSetAutoCapturingMainResource = false;
+
+ return true;
+ }
+
+ _legacyAttemptStartAutoCapturingForFrame(frame)
+ {
+ if (this._isCapturing && !this._mainResourceForAutoCapturing)
+ return false;
+
+ let mainResource = frame.provisionalMainResource || frame.mainResource;
+ if (mainResource === this._mainResourceForAutoCapturing)
+ return false;
+
+ let oldMainResource = frame.mainResource || null;
+ this._isCapturingPageReload = oldMainResource !== null && oldMainResource.url === mainResource.url;
+
+ if (this._isCapturing)
+ this.stopCapturing();
+
+ this._mainResourceForAutoCapturing = mainResource;
+
+ this._loadNewRecording();
+
+ this.startCapturing();
+
+ this._addRecord(new WebInspector.ResourceTimelineRecord(mainResource));
+
+ this._resetAutoRecordingMaxTimeTimeout();
+
+ return true;
+ }
+
+ _stopAutoRecordingSoon()
+ {
+ // Only auto stop when auto capturing.
+ if (!this._isCapturing || !this._mainResourceForAutoCapturing)
+ return;
+
+ if (this._stopCapturingTimeout)
+ clearTimeout(this._stopCapturingTimeout);
+ this._stopCapturingTimeout = setTimeout(this._boundStopCapturing, WebInspector.TimelineManager.MaximumAutoRecordDurationAfterLoadEvent);
+ }
+
+ _resetAutoRecordingMaxTimeTimeout()
+ {
+ if (this._stopCapturingTimeout)
+ clearTimeout(this._stopCapturingTimeout);
+ this._stopCapturingTimeout = setTimeout(this._boundStopCapturing, WebInspector.TimelineManager.MaximumAutoRecordDuration);
+ }
+
+ _resetAutoRecordingDeadTimeTimeout()
+ {
+ // Only monitor dead time when auto capturing.
+ if (!this._isCapturing || !this._mainResourceForAutoCapturing)
+ return;
+
+ // Avoid unnecessary churning of timeout identifier by not tickling until 10ms have passed.
+ let now = Date.now();
+ if (now <= this._lastDeadTimeTickle)
+ return;
+ this._lastDeadTimeTickle = now + 10;
+
+ if (this._deadTimeTimeout)
+ clearTimeout(this._deadTimeTimeout);
+ this._deadTimeTimeout = setTimeout(this._boundStopCapturing, WebInspector.TimelineManager.DeadTimeRequiredToStopAutoRecordingEarly);
+ }
+
+ _provisionalLoadStarted(event)
+ {
+ this._attemptAutoCapturingForFrame(event.target);
+ }
+
+ _mainResourceDidChange(event)
+ {
+ let frame = event.target;
+ if (frame.isMainFrame() && WebInspector.settings.clearNetworkOnNavigate.value)
+ this._persistentNetworkTimeline.reset();
+
+ let mainResource = frame.mainResource;
+ let record = new WebInspector.ResourceTimelineRecord(mainResource);
+ if (!isNaN(record.startTime))
+ this._persistentNetworkTimeline.addRecord(record);
+
+ // Ignore resource events when there isn't a main frame yet. Those events are triggered by
+ // loading the cached resources when the inspector opens, and they do not have timing information.
+ if (!WebInspector.frameResourceManager.mainFrame)
+ return;
+
+ if (this._attemptAutoCapturingForFrame(frame))
+ return;
+
+ if (!this._isCapturing)
+ return;
+
+ if (mainResource === this._mainResourceForAutoCapturing)
+ return;
+
+ this._addRecord(record);
+ }
+
+ _resourceWasAdded(event)
+ {
+ var record = new WebInspector.ResourceTimelineRecord(event.data.resource);
+ if (!isNaN(record.startTime))
+ this._persistentNetworkTimeline.addRecord(record);
+
+ // Ignore resource events when there isn't a main frame yet. Those events are triggered by
+ // loading the cached resources when the inspector opens, and they do not have timing information.
+ if (!WebInspector.frameResourceManager.mainFrame)
+ return;
+
+ if (!this._isCapturing)
+ return;
+
+ this._addRecord(record);
+ }
+
+ _garbageCollected(event)
+ {
+ if (!this._isCapturing)
+ return;
+
+ let collection = event.data.collection;
+ this._addRecord(new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.GarbageCollected, collection.startTime, collection.endTime, null, null, collection));
+ }
+
+ _memoryPressure(event)
+ {
+ if (!this._isCapturing)
+ return;
+
+ this.activeRecording.addMemoryPressureEvent(event.data.memoryPressureEvent);
+ }
+
+ _scriptProfilerTypeToScriptTimelineRecordType(type)
+ {
+ switch (type) {
+ case ScriptProfilerAgent.EventType.API:
+ return WebInspector.ScriptTimelineRecord.EventType.APIScriptEvaluated;
+ case ScriptProfilerAgent.EventType.Microtask:
+ return WebInspector.ScriptTimelineRecord.EventType.MicrotaskDispatched;
+ case ScriptProfilerAgent.EventType.Other:
+ return WebInspector.ScriptTimelineRecord.EventType.ScriptEvaluated;
+ }
+ }
+
+ scriptProfilerProgrammaticCaptureStarted()
+ {
+ // FIXME: <https://webkit.org/b/158753> Generalize the concept of Instruments on the backend to work equally for JSContext and Web inspection
+ console.assert(WebInspector.debuggableType === WebInspector.DebuggableType.JavaScript);
+ console.assert(!this._isCapturing);
+
+ this.programmaticCaptureStarted();
+ }
+
+ scriptProfilerProgrammaticCaptureStopped()
+ {
+ // FIXME: <https://webkit.org/b/158753> Generalize the concept of Instruments on the backend to work equally for JSContext and Web inspection
+ console.assert(WebInspector.debuggableType === WebInspector.DebuggableType.JavaScript);
+ console.assert(this._isCapturing);
+
+ this.programmaticCaptureStopped();
+ }
+
+ scriptProfilerTrackingStarted(timestamp)
+ {
+ this._scriptProfilerRecords = [];
+
+ this.capturingStarted(timestamp);
+ }
+
+ scriptProfilerTrackingUpdated(event)
+ {
+ let {startTime, endTime, type} = event;
+ let scriptRecordType = this._scriptProfilerTypeToScriptTimelineRecordType(type);
+ let record = new WebInspector.ScriptTimelineRecord(scriptRecordType, startTime, endTime, null, null, null, null);
+ record.__scriptProfilerType = type;
+ this._scriptProfilerRecords.push(record);
+
+ // "Other" events, generated by Web content, will have wrapping Timeline records
+ // and need to be merged. Non-Other events, generated purely by the JavaScript
+ // engine or outside of the page via APIs, will not have wrapping Timeline
+ // records, so these records can just be added right now.
+ if (type !== ScriptProfilerAgent.EventType.Other)
+ this._addRecord(record);
+ }
+
+ scriptProfilerTrackingCompleted(samples)
+ {
+ console.assert(!this._webTimelineScriptRecordsExpectingScriptProfilerEvents || this._scriptProfilerRecords.length >= this._webTimelineScriptRecordsExpectingScriptProfilerEvents.length);
+
+ if (samples) {
+ let {stackTraces} = samples;
+ let topDownCallingContextTree = this.activeRecording.topDownCallingContextTree;
+ let bottomUpCallingContextTree = this.activeRecording.bottomUpCallingContextTree;
+ let topFunctionsTopDownCallingContextTree = this.activeRecording.topFunctionsTopDownCallingContextTree;
+ let topFunctionsBottomUpCallingContextTree = this.activeRecording.topFunctionsBottomUpCallingContextTree;
+
+ // Calculate a per-sample duration.
+ let timestampIndex = 0;
+ let timestampCount = stackTraces.length;
+ let sampleDurations = new Array(timestampCount);
+ let sampleDurationIndex = 0;
+ const defaultDuration = 1 / 1000; // 1ms.
+ for (let i = 0; i < this._scriptProfilerRecords.length; ++i) {
+ let record = this._scriptProfilerRecords[i];
+
+ // Use a default duration for timestamps recorded outside of ScriptProfiler events.
+ while (timestampIndex < timestampCount && stackTraces[timestampIndex].timestamp < record.startTime) {
+ sampleDurations[sampleDurationIndex++] = defaultDuration;
+ timestampIndex++;
+ }
+
+ // Average the duration per sample across all samples during the record.
+ let samplesInRecord = 0;
+ while (timestampIndex < timestampCount && stackTraces[timestampIndex].timestamp < record.endTime) {
+ timestampIndex++;
+ samplesInRecord++;
+ }
+ if (samplesInRecord) {
+ let averageDuration = (record.endTime - record.startTime) / samplesInRecord;
+ sampleDurations.fill(averageDuration, sampleDurationIndex, sampleDurationIndex + samplesInRecord);
+ sampleDurationIndex += samplesInRecord;
+ }
+ }
+
+ // Use a default duration for timestamps recorded outside of ScriptProfiler events.
+ if (timestampIndex < timestampCount)
+ sampleDurations.fill(defaultDuration, sampleDurationIndex);
+
+ for (let i = 0; i < stackTraces.length; i++) {
+ topDownCallingContextTree.updateTreeWithStackTrace(stackTraces[i], sampleDurations[i]);
+ bottomUpCallingContextTree.updateTreeWithStackTrace(stackTraces[i], sampleDurations[i]);
+ topFunctionsTopDownCallingContextTree.updateTreeWithStackTrace(stackTraces[i], sampleDurations[i]);
+ topFunctionsBottomUpCallingContextTree.updateTreeWithStackTrace(stackTraces[i], sampleDurations[i]);
+ }
+
+ // FIXME: This transformation should not be needed after introducing ProfileView.
+ // Once we eliminate ProfileNodeTreeElements and ProfileNodeDataGridNodes.
+ // <https://webkit.org/b/154973> Web Inspector: Timelines UI redesign: Remove TimelineSidebarPanel
+ for (let i = 0; i < this._scriptProfilerRecords.length; ++i) {
+ let record = this._scriptProfilerRecords[i];
+ record.profilePayload = topDownCallingContextTree.toCPUProfilePayload(record.startTime, record.endTime);
+ }
+ }
+
+ // Associate the ScriptProfiler created records with Web Timeline records.
+ // Filter out the already added ScriptProfiler events which should not have been wrapped.
+ if (WebInspector.debuggableType !== WebInspector.DebuggableType.JavaScript) {
+ this._scriptProfilerRecords = this._scriptProfilerRecords.filter((x) => x.__scriptProfilerType === ScriptProfilerAgent.EventType.Other);
+ this._mergeScriptProfileRecords();
+ }
+
+ this._scriptProfilerRecords = null;
+
+ let timeline = this.activeRecording.timelineForRecordType(WebInspector.TimelineRecord.Type.Script);
+ timeline.refresh();
+ }
+
+ _mergeScriptProfileRecords()
+ {
+ let nextRecord = function(list) { return list.shift() || null; };
+ let nextWebTimelineRecord = nextRecord.bind(null, this._webTimelineScriptRecordsExpectingScriptProfilerEvents);
+ let nextScriptProfilerRecord = nextRecord.bind(null, this._scriptProfilerRecords);
+ let recordEnclosesRecord = function(record1, record2) {
+ return record1.startTime <= record2.startTime && record1.endTime >= record2.endTime;
+ };
+
+ let webRecord = nextWebTimelineRecord();
+ let profilerRecord = nextScriptProfilerRecord();
+
+ while (webRecord && profilerRecord) {
+ // Skip web records with parent web records. For example an EvaluateScript with an EvaluateScript parent.
+ if (webRecord.parent instanceof WebInspector.ScriptTimelineRecord) {
+ console.assert(recordEnclosesRecord(webRecord.parent, webRecord), "Timeline Record incorrectly wrapping another Timeline Record");
+ webRecord = nextWebTimelineRecord();
+ continue;
+ }
+
+ // Normal case of a Web record wrapping a Script record.
+ if (recordEnclosesRecord(webRecord, profilerRecord)) {
+ webRecord.profilePayload = profilerRecord.profilePayload;
+ profilerRecord = nextScriptProfilerRecord();
+
+ // If there are more script profile records in the same time interval, add them
+ // as individual script evaluated records with profiles. This can happen with
+ // web microtask checkpoints that are technically inside of other web records.
+ // FIXME: <https://webkit.org/b/152903> Web Inspector: Timeline Cleanup: Better Timeline Record for Microtask Checkpoints
+ while (profilerRecord && recordEnclosesRecord(webRecord, profilerRecord)) {
+ this._addRecord(profilerRecord);
+ profilerRecord = nextScriptProfilerRecord();
+ }
+
+ webRecord = nextWebTimelineRecord();
+ continue;
+ }
+
+ // Profiler Record is entirely after the Web Record. This would mean an empty web record.
+ if (profilerRecord.startTime > webRecord.endTime) {
+ console.warn("Unexpected case of a Timeline record not containing a ScriptProfiler event and profile data");
+ webRecord = nextWebTimelineRecord();
+ continue;
+ }
+
+ // Non-wrapped profiler record.
+ console.warn("Unexpected case of a ScriptProfiler event not being contained by a Timeline record");
+ this._addRecord(profilerRecord);
+ profilerRecord = nextScriptProfilerRecord();
+ }
+
+ // Skipping the remaining ScriptProfiler events to match the current UI for handling Timeline records.
+ // However, the remaining ScriptProfiler records are valid and could be shown.
+ // FIXME: <https://webkit.org/b/152904> Web Inspector: Timeline UI should keep up with processing all incoming records
+ }
+
+ _updateAutoCaptureInstruments()
+ {
+ if (!window.TimelineAgent)
+ return;
+
+ if (!TimelineAgent.setInstruments)
+ return;
+
+ let instrumentSet = new Set;
+ let enabledTimelineTypes = this._enabledTimelineTypesSetting.value;
+
+ for (let timelineType of enabledTimelineTypes) {
+ switch (timelineType) {
+ case WebInspector.TimelineRecord.Type.Script:
+ instrumentSet.add(TimelineAgent.Instrument.ScriptProfiler);
+ break;
+ case WebInspector.TimelineRecord.Type.HeapAllocations:
+ instrumentSet.add(TimelineAgent.Instrument.Heap);
+ break;
+ case WebInspector.TimelineRecord.Type.Network:
+ case WebInspector.TimelineRecord.Type.RenderingFrame:
+ case WebInspector.TimelineRecord.Type.Layout:
+ instrumentSet.add(TimelineAgent.Instrument.Timeline);
+ break;
+ case WebInspector.TimelineRecord.Type.Memory:
+ instrumentSet.add(TimelineAgent.Instrument.Memory);
+ break;
+ }
+ }
+
+ TimelineAgent.setInstruments([...instrumentSet]);
+ }
+};
+
+WebInspector.TimelineManager.Event = {
+ RecordingCreated: "timeline-manager-recording-created",
+ RecordingLoaded: "timeline-manager-recording-loaded",
+ CapturingWillStart: "timeline-manager-capturing-will-start",
+ CapturingStarted: "timeline-manager-capturing-started",
+ CapturingStopped: "timeline-manager-capturing-stopped"
+};
+
+WebInspector.TimelineManager.MaximumAutoRecordDuration = 90000; // 90 seconds
+WebInspector.TimelineManager.MaximumAutoRecordDurationAfterLoadEvent = 10000; // 10 seconds
+WebInspector.TimelineManager.DeadTimeRequiredToStopAutoRecordingEarly = 2000; // 2 seconds
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/TypeTokenAnnotator.js b/Source/WebInspectorUI/UserInterface/Controllers/TypeTokenAnnotator.js
new file mode 100644
index 000000000..7b820bbec
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/TypeTokenAnnotator.js
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2014, 2015 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.TypeTokenAnnotator = class TypeTokenAnnotator extends WebInspector.Annotator
+{
+ constructor(sourceCodeTextEditor, script)
+ {
+ super(sourceCodeTextEditor);
+
+ this._script = script;
+ this._typeTokenNodes = [];
+ this._typeTokenBookmarks = [];
+ }
+
+ // Protected
+
+ insertAnnotations()
+ {
+ if (!this.isActive())
+ return;
+
+ var scriptSyntaxTree = this._script.scriptSyntaxTree;
+
+ if (!scriptSyntaxTree) {
+ this._script.requestScriptSyntaxTree((syntaxTree) => {
+ // After requesting the tree, we still might get a null tree from a parse error.
+ if (syntaxTree)
+ this.insertAnnotations();
+ });
+ return;
+ }
+
+ if (!scriptSyntaxTree.parsedSuccessfully)
+ return;
+
+ var {startOffset, endOffset} = this.sourceCodeTextEditor.visibleRangeOffsets();
+
+ var startTime = Date.now();
+ var allNodesInRange = scriptSyntaxTree.filterByRange(startOffset, endOffset);
+ scriptSyntaxTree.updateTypes(allNodesInRange, (nodesWithUpdatedTypes) => {
+ // Because this is an asynchronous call, we could have been deactivated before the callback function is called.
+ if (!this.isActive())
+ return;
+
+ nodesWithUpdatedTypes.forEach(this._insertTypeToken, this);
+
+ let totalTime = Date.now() - startTime;
+ let timeoutTime = Number.constrain(8 * totalTime, 500, 2000);
+ this._timeoutIdentifier = setTimeout(() => {
+ this._timeoutIdentifier = null;
+ this.insertAnnotations();
+ }, timeoutTime);
+ });
+ }
+
+ clearAnnotations()
+ {
+ this._clearTypeTokens();
+ }
+
+ // Private
+
+ _insertTypeToken(node)
+ {
+ if (node.type === WebInspector.ScriptSyntaxTree.NodeType.Identifier) {
+ if (!node.attachments.__typeToken && node.attachments.types && node.attachments.types.valid)
+ this._insertToken(node.range[0], node, false, WebInspector.TypeTokenView.TitleType.Variable, node.name);
+
+ if (node.attachments.__typeToken)
+ node.attachments.__typeToken.update(node.attachments.types);
+
+ return;
+ }
+
+ console.assert(node.type === WebInspector.ScriptSyntaxTree.NodeType.FunctionDeclaration || node.type === WebInspector.ScriptSyntaxTree.NodeType.FunctionExpression || node.type === WebInspector.ScriptSyntaxTree.NodeType.ArrowFunctionExpression);
+
+ var functionReturnType = node.attachments.returnTypes;
+ if (!functionReturnType || !functionReturnType.valid)
+ return;
+
+ // If a function does not have an explicit return statement with an argument (i.e, "return x;" instead of "return;")
+ // then don't show a return type unless we think it's a constructor.
+ var scriptSyntaxTree = this._script._scriptSyntaxTree;
+ if (!node.attachments.__typeToken && (scriptSyntaxTree.containsNonEmptyReturnStatement(node.body) || !functionReturnType.typeSet.isContainedIn(WebInspector.TypeSet.TypeBit.Undefined))) {
+ var functionName = node.id ? node.id.name : null;
+ this._insertToken(node.typeProfilingReturnDivot, node, true, WebInspector.TypeTokenView.TitleType.ReturnStatement, functionName);
+ }
+
+ if (node.attachments.__typeToken)
+ node.attachments.__typeToken.update(node.attachments.returnTypes);
+ }
+
+ _insertToken(originalOffset, node, shouldTranslateOffsetToAfterParameterList, typeTokenTitleType, functionOrVariableName)
+ {
+ var tokenPosition = this.sourceCodeTextEditor.originalOffsetToCurrentPosition(originalOffset);
+ var currentOffset = this.sourceCodeTextEditor.currentPositionToCurrentOffset(tokenPosition);
+ var sourceString = this.sourceCodeTextEditor.string;
+
+ if (shouldTranslateOffsetToAfterParameterList) {
+ // Translate the position to the closing parenthesis of the function arguments:
+ // translate from: [type-token] function foo() {} => to: function foo() [type-token] {}
+ currentOffset = this._translateToOffsetAfterFunctionParameterList(node, currentOffset, sourceString);
+ tokenPosition = this.sourceCodeTextEditor.currentOffsetToCurrentPosition(currentOffset);
+ }
+
+ // Note: bookmarks render to the left of the character they're being displayed next to.
+ // This is why right margin checks the current offset. And this is okay to do because JavaScript can't be written right-to-left.
+ var isSpaceRegexp = /\s/;
+ var shouldHaveLeftMargin = currentOffset !== 0 && !isSpaceRegexp.test(sourceString[currentOffset - 1]);
+ var shouldHaveRightMargin = !isSpaceRegexp.test(sourceString[currentOffset]);
+ var typeToken = new WebInspector.TypeTokenView(this, shouldHaveRightMargin, shouldHaveLeftMargin, typeTokenTitleType, functionOrVariableName);
+ var bookmark = this.sourceCodeTextEditor.setInlineWidget(tokenPosition, typeToken.element);
+ node.attachments.__typeToken = typeToken;
+ this._typeTokenNodes.push(node);
+ this._typeTokenBookmarks.push(bookmark);
+ }
+
+ _translateToOffsetAfterFunctionParameterList(node, offset, sourceString)
+ {
+ // The assumption here is that we get the offset starting at the function keyword (or after the get/set keywords).
+ // We will return the offset for the closing parenthesis in the function declaration.
+ // All this code is just a way to find this parenthesis while ignoring comments.
+
+ var isMultiLineComment = false;
+ var isSingleLineComment = false;
+ var shouldIgnore = false;
+ const isArrowFunction = node.type === WebInspector.ScriptSyntaxTree.NodeType.ArrowFunctionExpression;
+
+ function isLineTerminator(char)
+ {
+ // Reference EcmaScript 5 grammar for single line comments and line terminators:
+ // http://www.ecma-international.org/ecma-262/5.1/#sec-7.3
+ // http://www.ecma-international.org/ecma-262/5.1/#sec-7.4
+ return char === "\n" || char === "\r" || char === "\u2028" || char === "\u2029";
+ }
+
+ while (((!isArrowFunction && sourceString[offset] !== ")")
+ || (isArrowFunction && sourceString[offset] !== ">")
+ || shouldIgnore)
+ && offset < sourceString.length) {
+ if (isSingleLineComment && isLineTerminator(sourceString[offset])) {
+ isSingleLineComment = false;
+ shouldIgnore = false;
+ } else if (isMultiLineComment && sourceString[offset] === "*" && sourceString[offset + 1] === "/") {
+ isMultiLineComment = false;
+ shouldIgnore = false;
+ offset++;
+ } else if (!shouldIgnore && sourceString[offset] === "/") {
+ offset++;
+ if (sourceString[offset] === "*")
+ isMultiLineComment = true;
+ else if (sourceString[offset] === "/")
+ isSingleLineComment = true;
+ else
+ throw new Error("Bad parsing. Couldn't parse comment preamble.");
+ shouldIgnore = true;
+ }
+
+ offset++;
+ }
+
+ return offset + 1;
+ }
+
+ _clearTypeTokens()
+ {
+ this._typeTokenNodes.forEach(function(node) {
+ node.attachments.__typeToken = null;
+ });
+ this._typeTokenBookmarks.forEach(function(bookmark) {
+ bookmark.clear();
+ });
+
+ this._typeTokenNodes = [];
+ this._typeTokenBookmarks = [];
+ }
+};
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/VisualStyleCompletionsController.js b/Source/WebInspectorUI/UserInterface/Controllers/VisualStyleCompletionsController.js
new file mode 100644
index 000000000..d2a40b9d4
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/VisualStyleCompletionsController.js
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2015 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.VisualStyleCompletionsController = class VisualStyleCompletionsController extends WebInspector.Object
+{
+ constructor(delegate)
+ {
+ super();
+
+ this._delegate = delegate || null;
+ this._suggestionsView = new WebInspector.CompletionSuggestionsView(this);
+ this._completions = null;
+ this._currentCompletions = [];
+ this._selectedCompletionIndex = 0;
+ }
+
+ // Public
+
+ get visible()
+ {
+ return this._completions && this._currentCompletions.length && this._suggestionsView.visible;
+ }
+
+ get hasCompletions()
+ {
+ return !!this._completions;
+ }
+
+ get currentCompletion()
+ {
+ if (!this.hasCompletions)
+ return null;
+
+ return this._currentCompletions[this._selectedCompletionIndex] || null;
+ }
+
+ set completions(completions)
+ {
+ this._completions = completions || null;
+ }
+
+ completionSuggestionsViewCustomizeCompletionElement(suggestionsView, element, item)
+ {
+ if (this._delegate && typeof this._delegate.visualStyleCompletionsControllerCustomizeCompletionElement === "function")
+ this._delegate.visualStyleCompletionsControllerCustomizeCompletionElement(suggestionsView, element, item);
+ }
+
+ completionSuggestionsClickedCompletion(suggestionsView, text)
+ {
+ suggestionsView.hide();
+ this.dispatchEventToListeners(WebInspector.VisualStyleCompletionsController.Event.CompletionSelected, {text});
+ }
+
+ previous()
+ {
+ this._suggestionsView.selectPrevious();
+ this._selectedCompletionIndex = this._suggestionsView.selectedIndex;
+ }
+
+ next()
+ {
+ this._suggestionsView.selectNext();
+ this._selectedCompletionIndex = this._suggestionsView.selectedIndex;
+ }
+
+ update(value)
+ {
+ if (!this.hasCompletions)
+ return false;
+
+ this._currentCompletions = this._completions.startsWith(value);
+
+ var currentCompletionsLength = this._currentCompletions.length;
+ if (currentCompletionsLength) {
+ if (currentCompletionsLength === 1 && this._currentCompletions[0] === value) {
+ this.hide();
+ return false;
+ }
+
+ if (this._selectedCompletionIndex >= currentCompletionsLength)
+ this._selectedCompletionIndex = 0;
+
+ this._suggestionsView.update(this._currentCompletions, this._selectedCompletionIndex);
+ return true;
+ }
+
+ this.hide();
+ return false;
+ }
+
+ show(bounds, padding)
+ {
+ if (!bounds)
+ return;
+
+ this._suggestionsView.show(bounds.pad(padding || 0));
+ }
+
+ hide()
+ {
+ if (this._suggestionsView.isHandlingClickEvent())
+ return;
+
+ this._suggestionsView.hide();
+ }
+};
+
+WebInspector.VisualStyleCompletionsController.Event = {
+ CompletionSelected: "visual-style-completions-controller-completion-selected"
+};
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/WorkerManager.js b/Source/WebInspectorUI/UserInterface/Controllers/WorkerManager.js
new file mode 100644
index 000000000..99cd3f6ee
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/WorkerManager.js
@@ -0,0 +1,69 @@
+/*
+ * 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+WebInspector.WorkerManager = class WorkerManager extends WebInspector.Object
+{
+ constructor()
+ {
+ super();
+
+ this._connections = new Map;
+
+ if (window.WorkerAgent)
+ WorkerAgent.enable();
+ }
+
+ // Public
+
+ workerCreated(workerId, url)
+ {
+ let connection = new InspectorBackend.WorkerConnection(workerId);
+ let workerTarget = new WebInspector.WorkerTarget(workerId, url, connection);
+ WebInspector.targetManager.addTarget(workerTarget);
+
+ this._connections.set(workerId, connection);
+
+ // Unpause the worker now that we have sent all initialization messages.
+ WorkerAgent.initialized(workerId);
+ }
+
+ workerTerminated(workerId)
+ {
+ let connection = this._connections.take(workerId);
+
+ WebInspector.targetManager.removeTarget(connection.target);
+ }
+
+ dispatchMessageFromWorker(workerId, message)
+ {
+ let connection = this._connections.get(workerId);
+
+ console.assert(connection);
+ if (!connection)
+ return;
+
+ connection.dispatch(message);
+ }
+};