Follow-up review comment to r237652.
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Controllers / DebuggerManager.js
index e473a51..a5c3faf 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013, 2014 Apple Inc. All rights reserved.
+ * 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
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-WebInspector.DebuggerManager = function()
+WI.DebuggerManager = class DebuggerManager extends WI.Object
 {
-    WebInspector.Object.call(this);
+    constructor()
+    {
+        super();
 
-    if (window.DebuggerAgent)
-        DebuggerAgent.enable();
+        WI.notifications.addEventListener(WI.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.AutoContinueDidChange, this._breakpointEditablePropertyDidChange, this);
-    WebInspector.Breakpoint.addEventListener(WebInspector.Breakpoint.Event.ActionsDidChange, this._breakpointEditablePropertyDidChange, this);
+        WI.Breakpoint.addEventListener(WI.Breakpoint.Event.DisplayLocationDidChange, this._breakpointDisplayLocationDidChange, this);
+        WI.Breakpoint.addEventListener(WI.Breakpoint.Event.DisabledStateDidChange, this._breakpointDisabledStateDidChange, this);
+        WI.Breakpoint.addEventListener(WI.Breakpoint.Event.ConditionDidChange, this._breakpointEditablePropertyDidChange, this);
+        WI.Breakpoint.addEventListener(WI.Breakpoint.Event.IgnoreCountDidChange, this._breakpointEditablePropertyDidChange, this);
+        WI.Breakpoint.addEventListener(WI.Breakpoint.Event.AutoContinueDidChange, this._breakpointEditablePropertyDidChange, this);
+        WI.Breakpoint.addEventListener(WI.Breakpoint.Event.ActionsDidChange, this._handleBreakpointActionsDidChange, this);
 
-    window.addEventListener("pagehide", this._inspectorClosing.bind(this));
+        WI.timelineManager.addEventListener(WI.TimelineManager.Event.CapturingWillStart, this._timelineCapturingWillStart, this);
+        WI.timelineManager.addEventListener(WI.TimelineManager.Event.CapturingStopped, this._timelineCapturingStopped, this);
 
-    this._allExceptionsBreakpointEnabledSetting = new WebInspector.Setting("break-on-all-exceptions", false);
-    this._allUncaughtExceptionsBreakpointEnabledSetting = new WebInspector.Setting("break-on-all-uncaught-exceptions", false);
+        WI.targetManager.addEventListener(WI.TargetManager.Event.TargetRemoved, this._targetRemoved, this);
 
-    var specialBreakpointLocation = new WebInspector.SourceCodeLocation(null, Infinity, Infinity);
+        WI.settings.pauseForInternalScripts.addEventListener(WI.Setting.Event.Changed, this._pauseForInternalScriptsDidChange, this);
 
-    this._allExceptionsBreakpoint = new WebInspector.Breakpoint(specialBreakpointLocation, !this._allExceptionsBreakpointEnabledSetting.value);
-    this._allExceptionsBreakpoint.resolved = true;
+        WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
 
-    this._allUncaughtExceptionsBreakpoint = new WebInspector.Breakpoint(specialBreakpointLocation, !this._allUncaughtExceptionsBreakpointEnabledSetting.value);
+        this._breakpointsSetting = new WI.Setting("breakpoints", []);
+        this._breakpointsEnabledSetting = new WI.Setting("breakpoints-enabled", true);
+        this._allExceptionsBreakpointEnabledSetting = new WI.Setting("break-on-all-exceptions", false);
+        this._uncaughtExceptionsBreakpointEnabledSetting = new WI.Setting("break-on-uncaught-exceptions", false);
+        this._assertionFailuresBreakpointEnabledSetting = new WI.Setting("break-on-assertion-failures", false);
+        this._asyncStackTraceDepthSetting = new WI.Setting("async-stack-trace-depth", 200);
 
-    this._breakpoints = [];
-    this._breakpointURLMap = {};
-    this._breakpointScriptIdentifierMap = {};
-    this._breakpointIdMap = {};
+        let specialBreakpointLocation = new WI.SourceCodeLocation(null, Infinity, Infinity);
 
-    this._nextBreakpointActionIdentifier = 1;
+        this._allExceptionsBreakpoint = new WI.Breakpoint(specialBreakpointLocation, !this._allExceptionsBreakpointEnabledSetting.value);
+        this._allExceptionsBreakpoint.resolved = true;
 
-    this._scriptIdMap = {};
-    this._scriptURLMap = {};
+        this._uncaughtExceptionsBreakpoint = new WI.Breakpoint(specialBreakpointLocation, !this._uncaughtExceptionsBreakpointEnabledSetting.value);
 
-    this._breakpointsSetting = new WebInspector.Setting("breakpoints", []);
-    this._breakpointsEnabledSetting = new WebInspector.Setting("breakpoints-enabled", true);
+        this._assertionFailuresBreakpoint = new WI.Breakpoint(specialBreakpointLocation, !this._assertionFailuresBreakpointEnabledSetting.value);
+        this._assertionFailuresBreakpoint.resolved = true;
 
-    if (window.DebuggerAgent)
-        DebuggerAgent.setBreakpointsActive(this._breakpointsEnabledSetting.value);
+        this._breakpoints = [];
+        this._breakpointContentIdentifierMap = new Map;
+        this._breakpointScriptIdentifierMap = new Map;
+        this._breakpointIdMap = new Map;
 
-    this._updateBreakOnExceptionsState();
+        this._breakOnExceptionsState = "none";
+        this._updateBreakOnExceptionsState();
 
-    function restoreBreakpointsSoon() {
-        for (cookie of this._breakpointsSetting.value)
-            this.addBreakpoint(new WebInspector.Breakpoint(cookie));
-    }
+        this._nextBreakpointActionIdentifier = 1;
 
-    // Ensure that all managers learn about restored breakpoints,
-    // regardless of their initialization order.
-    setTimeout(restoreBreakpointsSoon.bind(this), 0);
-};
+        this._activeCallFrame = null;
 
-WebInspector.DebuggerManager.Event = {
-    BreakpointAdded: "debugger-manager-breakpoint-added",
-    BreakpointRemoved: "debugger-manager-breakpoint-removed",
-    BreakpointMoved: "debugger-manager-breakpoint-moved",
-    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",
-    ScriptsCleared: "debugger-manager-scripts-cleared",
-    BreakpointsEnabledDidChange: "debugger-manager-breakpoints-enabled-did-change"
-};
+        this._internalWebKitScripts = [];
+        this._targetDebuggerDataMap = new Map;
 
-WebInspector.DebuggerManager.prototype = {
-    constructor: WebInspector.DebuggerManager,
+        // Used to detect deleted probe actions.
+        this._knownProbeIdentifiersForBreakpoint = new Map;
 
-    // Public
+        // Main lookup tables for probes and probe sets.
+        this._probesByIdentifier = new Map;
+        this._probeSetsByBreakpoint = new Map;
 
-    get breakpointsEnabled()
-    {
-        return this._breakpointsEnabledSetting.value;
-    },
+        // Restore the correct breakpoints enabled setting if Web Inspector had
+        // previously been left in a state where breakpoints were temporarily disabled.
+        this._temporarilyDisabledBreakpointsRestoreSetting = new WI.Setting("temporarily-disabled-breakpoints-restore", null);
+        if (this._temporarilyDisabledBreakpointsRestoreSetting.value !== null) {
+            this._breakpointsEnabledSetting.value = this._temporarilyDisabledBreakpointsRestoreSetting.value;
+            this._temporarilyDisabledBreakpointsRestoreSetting.value = null;
+        }
 
-    set breakpointsEnabled(enabled)
+        this._ignoreBreakpointDisplayLocationDidChangeEvent = false;
+
+        // Ensure that all managers learn about restored breakpoints,
+        // regardless of their initialization order.
+        setTimeout(() => {
+            this._restoringBreakpoints = true;
+            for (let cookie of this._breakpointsSetting.value)
+                this.addBreakpoint(new WI.Breakpoint(cookie));
+            this._restoringBreakpoints = false;
+        });
+    }
+
+    // Target
+
+    initializeTarget(target)
     {
-        if (this._breakpointsEnabled === enabled)
-            return;
+        let targetData = this.dataForTarget(target);
 
-        this._breakpointsEnabledSetting.value = enabled;
+        // Initialize global state.
+        target.DebuggerAgent.enable();
+        target.DebuggerAgent.setBreakpointsActive(this._breakpointsEnabledSetting.value);
+        target.DebuggerAgent.setPauseOnExceptions(this._breakOnExceptionsState);
 
-        this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.BreakpointsEnabledDidChange);
+        // COMPATIBILITY (iOS 10): DebuggerAgent.setPauseOnAssertions did not exist yet.
+        if (DebuggerAgent.setPauseOnAssertions)
+            target.DebuggerAgent.setPauseOnAssertions(this._assertionFailuresBreakpointEnabledSetting.value);
 
-        this._allExceptionsBreakpoint.dispatchEventToListeners(WebInspector.Breakpoint.Event.ResolvedStateDidChange);
-        this._allUncaughtExceptionsBreakpoint.dispatchEventToListeners(WebInspector.Breakpoint.Event.ResolvedStateDidChange);
+        // COMPATIBILITY (iOS 10): Debugger.setAsyncStackTraceDepth did not exist yet.
+        if (DebuggerAgent.setAsyncStackTraceDepth)
+            target.DebuggerAgent.setAsyncStackTraceDepth(this._asyncStackTraceDepthSetting.value);
 
-        for (var i = 0; i < this._breakpoints.length; ++i)
-            this._breakpoints[i].dispatchEventToListeners(WebInspector.Breakpoint.Event.ResolvedStateDidChange);
+        // COMPATIBILITY (iOS 12): DebuggerAgent.setPauseForInternalScripts did not exist yet.
+        if (DebuggerAgent.setPauseForInternalScripts)
+            target.DebuggerAgent.setPauseForInternalScripts(WI.settings.pauseForInternalScripts.value);
 
-        DebuggerAgent.setBreakpointsActive(enabled);
+        if (this.paused)
+            targetData.pauseIfNeeded();
 
-        this._updateBreakOnExceptionsState();
-    },
+        // 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;
+    }
+
+    // Public
 
     get paused()
     {
-        return this._paused;
-    },
+        for (let [target, targetData] of this._targetDebuggerDataMap) {
+            if (targetData.paused)
+                return true;
+        }
 
-    get callFrames()
-    {
-        return this._callFrames;
-    },
+        return false;
+    }
 
     get activeCallFrame()
     {
         return this._activeCallFrame;
-    },
+    }
 
     set activeCallFrame(callFrame)
     {
@@ -141,124 +168,341 @@ WebInspector.DebuggerManager.prototype = {
 
         this._activeCallFrame = callFrame || null;
 
-        this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.ActiveCallFrameDidChange);
-    },
+        this.dispatchEventToListeners(WI.DebuggerManager.Event.ActiveCallFrameDidChange);
+    }
 
-    pause: function()
+    dataForTarget(target)
     {
-        DebuggerAgent.pause();
-    },
+        let targetData = this._targetDebuggerDataMap.get(target);
+        if (targetData)
+            return targetData;
+
+        targetData = new WI.DebuggerData(target);
+        this._targetDebuggerDataMap.set(target, targetData);
+        return targetData;
+    }
 
-    resume: function()
+    get allExceptionsBreakpoint() { return this._allExceptionsBreakpoint; }
+    get uncaughtExceptionsBreakpoint() { return this._uncaughtExceptionsBreakpoint; }
+    get assertionFailuresBreakpoint() { return this._assertionFailuresBreakpoint; }
+    get breakpoints() { return this._breakpoints; }
+
+    breakpointForIdentifier(id)
     {
-        DebuggerAgent.resume();
-    },
+        return this._breakpointIdMap.get(id) || null;
+    }
 
-    stepOver: function()
+    breakpointsForSourceCode(sourceCode)
     {
-        DebuggerAgent.stepOver();
-    },
+        console.assert(sourceCode instanceof WI.Resource || sourceCode instanceof WI.Script);
+
+        if (sourceCode instanceof WI.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 WI.Script) {
+            let scriptIdentifierBreakpoints = this._breakpointScriptIdentifierMap.get(sourceCode.id);
+            if (scriptIdentifierBreakpoints) {
+                this._associateBreakpointsWithSourceCode(scriptIdentifierBreakpoints, sourceCode);
+                return scriptIdentifierBreakpoints;
+            }
+        }
+
+        return [];
+    }
 
-    stepInto: function()
+    breakpointForSourceCodeLocation(sourceCodeLocation)
     {
-        DebuggerAgent.stepInto();
-    },
+        console.assert(sourceCodeLocation instanceof WI.SourceCodeLocation);
 
-    stepOut: function()
+        for (let breakpoint of this.breakpointsForSourceCode(sourceCodeLocation.sourceCode)) {
+            if (breakpoint.sourceCodeLocation.isEqual(sourceCodeLocation))
+                return breakpoint;
+        }
+
+        return null;
+    }
+
+    isBreakpointRemovable(breakpoint)
     {
-        DebuggerAgent.stepOut();
-    },
+        return breakpoint !== this._allExceptionsBreakpoint
+            && breakpoint !== this._uncaughtExceptionsBreakpoint;
+    }
 
-    get allExceptionsBreakpoint()
+    isBreakpointSpecial(breakpoint)
     {
-        return this._allExceptionsBreakpoint;
-    },
+        return !this.isBreakpointRemovable(breakpoint)
+            || breakpoint === this._assertionFailuresBreakpoint;
+    }
 
-    get allUncaughtExceptionsBreakpoint()
+    isBreakpointEditable(breakpoint)
     {
-        return this._allUncaughtExceptionsBreakpoint;
-    },
+        return !this.isBreakpointSpecial(breakpoint);
+    }
 
-    get breakpoints()
+    get breakpointsEnabled()
     {
-        return this._breakpoints;
-    },
+        return this._breakpointsEnabledSetting.value;
+    }
 
-    breakpointsForSourceCode: function(sourceCode)
+    set breakpointsEnabled(enabled)
     {
-        console.assert(sourceCode instanceof WebInspector.Resource || sourceCode instanceof WebInspector.Script);
+        if (this._breakpointsEnabledSetting.value === enabled)
+            return;
 
-        if (sourceCode instanceof WebInspector.SourceMapResource) {
-            var mappedResourceBreakpoints = [];
-            var originalSourceCodeBreakpoints = this.breakpointsForSourceCode(sourceCode.sourceMap.originalSourceCode);
-            return originalSourceCodeBreakpoints.filter(function(breakpoint) {
-                return breakpoint.sourceCodeLocation.displaySourceCode === sourceCode;
-            });
-        }
+        console.assert(!(enabled && this.breakpointsDisabledTemporarily), "Should not enable breakpoints when we are temporarily disabling breakpoints.");
+        if (enabled && this.breakpointsDisabledTemporarily)
+            return;
 
-        if (sourceCode.url in this._breakpointURLMap) {
-            var urlBreakpoint = this._breakpointURLMap[sourceCode.url] || [];
-            this._associateBreakpointsWithSourceCode(urlBreakpoint, sourceCode);
-            return urlBreakpoint;
-        }
+        this._breakpointsEnabledSetting.value = enabled;
+
+        this._updateBreakOnExceptionsState();
 
-        if (sourceCode instanceof WebInspector.Script && sourceCode.id in this._breakpointScriptIdentifierMap) {
-            var scriptIdentifierBreakpoints = this._breakpointScriptIdentifierMap[sourceCode.id] || [];
-            this._associateBreakpointsWithSourceCode(scriptIdentifierBreakpoints, sourceCode);
-            return scriptIdentifierBreakpoints;
+        for (let target of WI.targets) {
+            target.DebuggerAgent.setBreakpointsActive(enabled);
+            target.DebuggerAgent.setPauseOnExceptions(this._breakOnExceptionsState);
         }
 
-        return [];
-    },
+        this.dispatchEventToListeners(WI.DebuggerManager.Event.BreakpointsEnabledDidChange);
+    }
 
-    scriptForIdentifier: function(id)
+    get breakpointsDisabledTemporarily()
     {
-        return this._scriptIdMap[id] || null;
-    },
+        return this._temporarilyDisabledBreakpointsRestoreSetting.value !== null;
+    }
+
+    scriptForIdentifier(id, target)
+    {
+        console.assert(target instanceof WI.Target);
+        return this.dataForTarget(target).scriptForIdentifier(id);
+    }
 
-    scriptsForURL: function(url)
+    scriptsForURL(url, target)
     {
         // FIXME: This may not be safe. A Resource's URL may differ from a Script's URL.
-        return this._scriptURLMap[url] || [];
-    },
+        console.assert(target instanceof WI.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 (isWebInspectorConsoleEvaluationScript(script.sourceURL))
+                    continue;
+                if (!WI.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 WI.targets)
+            target.DebuggerAgent.setAsyncStackTraceDepth(this._asyncStackTraceDepthSetting.value);
+    }
+
+    get probeSets()
+    {
+        return [...this._probeSetsByBreakpoint.values()];
+    }
+
+    probeForIdentifier(identifier)
+    {
+        return this._probesByIdentifier.get(identifier);
+    }
+
+    pause()
+    {
+        if (this.paused)
+            return Promise.resolve();
+
+        this.dispatchEventToListeners(WI.DebuggerManager.Event.WaitingToPause);
+
+        let listener = new WI.EventListener(this, true);
+
+        let managerResult = new Promise(function(resolve, reject) {
+            listener.connect(WI.debuggerManager, WI.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 WI.EventListener(this, true);
+
+        let managerResult = new Promise(function(resolve, reject) {
+            listener.connect(WI.debuggerManager, WI.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 WI.EventListener(this, true);
+
+        let managerResult = new Promise(function(resolve, reject) {
+            listener.connect(WI.debuggerManager, WI.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 WI.EventListener(this, true);
+
+        let managerResult = new Promise(function(resolve, reject) {
+            listener.connect(WI.debuggerManager, WI.DebuggerManager.Event.ActiveCallFrameDidChange, resolve);
+        });
 
-    continueToLocation: function(scriptIdentifier, lineNumber, columnNumber)
+        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()
     {
-        DebuggerAgent.continueToLocation({scriptId: scriptIdentifier, lineNumber: lineNumber, columnNumber: columnNumber});
-    },
+        if (!this.paused)
+            return Promise.reject(new Error("Cannot step out because debugger is not paused."));
+
+        let listener = new WI.EventListener(this, true);
+
+        let managerResult = new Promise(function(resolve, reject) {
+            listener.connect(WI.debuggerManager, WI.DebuggerManager.Event.ActiveCallFrameDidChange, resolve);
+        });
 
-    addBreakpoint: function(breakpoint, skipEventDispatch)
+        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)
     {
-        console.assert(breakpoint instanceof WebInspector.Breakpoint, "Bad argument to DebuggerManger.addBreakpoint: ", breakpoint);
+        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 WI.Breakpoint);
         if (!breakpoint)
             return;
 
-        if (breakpoint.url) {
-            var urlBreakpoints = this._breakpointURLMap[breakpoint.url];
-            if (!urlBreakpoints)
-                urlBreakpoints = this._breakpointURLMap[breakpoint.url] = [];
-            urlBreakpoints.push(breakpoint);
+        if (this.isBreakpointSpecial(breakpoint)) {
+            this.dispatchEventToListeners(WI.DebuggerManager.Event.BreakpointAdded, {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) {
-            var scriptIdentifierBreakpoints = this._breakpointScriptIdentifierMap[breakpoint.scriptIdentifier];
-            if (!scriptIdentifierBreakpoints)
-                scriptIdentifierBreakpoints = this._breakpointScriptIdentifierMap[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)
-            this._setBreakpoint(breakpoint);
+        if (!breakpoint.disabled) {
+            const specificTarget = undefined;
+            this._setBreakpoint(breakpoint, specificTarget, () => {
+                if (shouldSpeculativelyResolve)
+                    breakpoint.resolved = true;
+            });
+        }
+
+        this._saveBreakpoints();
 
-        if (!skipEventDispatch)
-            this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.BreakpointAdded, {breakpoint: breakpoint});
-    },
+        this._addProbesForBreakpoint(breakpoint);
 
-    removeBreakpoint: function(breakpoint)
+        this.dispatchEventToListeners(WI.DebuggerManager.Event.BreakpointAdded, {breakpoint});
+    }
+
+    removeBreakpoint(breakpoint)
     {
-        console.assert(breakpoint);
+        console.assert(breakpoint instanceof WI.Breakpoint);
         if (!breakpoint)
             return;
 
@@ -266,26 +510,32 @@ WebInspector.DebuggerManager.prototype = {
         if (!this.isBreakpointRemovable(breakpoint))
             return;
 
+        if (this.isBreakpointSpecial(breakpoint)) {
+            breakpoint.disabled = true;
+            this.dispatchEventToListeners(WI.DebuggerManager.Event.BreakpointRemoved, {breakpoint});
+            return;
+        }
+
         this._breakpoints.remove(breakpoint);
 
         if (breakpoint.identifier)
             this._removeBreakpoint(breakpoint);
 
-        if (breakpoint.url) {
-            var urlBreakpoints = this._breakpointURLMap[breakpoint.url];
-            if (urlBreakpoints) {
-                urlBreakpoints.remove(breakpoint);
-                if (!urlBreakpoints.length)
-                    delete this._breakpointURLMap[breakpoint.url];
+        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) {
-            var scriptIdentifierBreakpoints = this._breakpointScriptIdentifierMap[breakpoint.scriptIdentifier];
+            let scriptIdentifierBreakpoints = this._breakpointScriptIdentifierMap.get(breakpoint.scriptIdentifier);
             if (scriptIdentifierBreakpoints) {
                 scriptIdentifierBreakpoints.remove(breakpoint);
                 if (!scriptIdentifierBreakpoints.length)
-                    delete this._breakpointScriptIdentifierMap[breakpoint.scriptIdentifier];
+                    this._breakpointScriptIdentifierMap.delete(breakpoint.scriptIdentifier);
             }
         }
 
@@ -293,246 +543,412 @@ WebInspector.DebuggerManager.prototype = {
         breakpoint.disabled = true;
         breakpoint.clearActions();
 
-        this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.BreakpointRemoved, {breakpoint: breakpoint});
-    },
+        this._saveBreakpoints();
+
+        this._removeProbesForBreakpoint(breakpoint);
 
-    breakpointResolved: function(breakpointIdentifier, location)
+        this.dispatchEventToListeners(WI.DebuggerManager.Event.BreakpointRemoved, {breakpoint});
+    }
+
+    nextBreakpointActionIdentifier()
     {
-        // Called from WebInspector.DebuggerObserver.
+        return this._nextBreakpointActionIdentifier++;
+    }
+
+    // Protected (Called from WI.DebuggerObserver)
 
-        var breakpoint = this._breakpointIdMap[breakpointIdentifier];
+    breakpointResolved(target, breakpointIdentifier, location)
+    {
+        // Called from WI.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: function()
+    reset()
     {
-        // Called from WebInspector.DebuggerObserver.
+        // Called from WI.DebuggerObserver.
 
-        var wasPaused = this._paused;
+        let wasPaused = this.paused;
 
-        WebInspector.Script.resetUniqueDisplayNameNumbers();
+        WI.Script.resetUniqueDisplayNameNumbers();
 
-        this._paused = false;
-        this._scriptIdMap = {};
-        this._scriptURLMap = {};
+        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 (var i = 0; i < this._breakpoints.length; ++i) {
-            var breakpoint = this._breakpoints[i];
+        for (let breakpoint of this._breakpoints) {
             breakpoint.resolved = false;
             if (breakpoint.sourceCodeLocation.sourceCode)
                 breakpoint.sourceCodeLocation.sourceCode = null;
         }
 
-        delete this._ignoreBreakpointDisplayLocationDidChangeEvent;
+        this._ignoreBreakpointDisplayLocationDidChangeEvent = false;
 
-        this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.ScriptsCleared);
+        this.dispatchEventToListeners(WI.DebuggerManager.Event.ScriptsCleared);
 
         if (wasPaused)
-            this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.Resumed);
-    },
+            this.dispatchEventToListeners(WI.DebuggerManager.Event.Resumed);
+    }
 
-    debuggerDidPause: function(callFramesPayload)
+    debuggerDidPause(target, callFramesPayload, reason, data, asyncStackTracePayload)
     {
-        // Called from WebInspector.DebuggerObserver.
+        // Called from WI.DebuggerObserver.
 
         if (this._delayedResumeTimeout) {
             clearTimeout(this._delayedResumeTimeout);
-            delete this._delayedResumeTimeout;
+            this._delayedResumeTimeout = undefined;
         }
 
-        var wasStillPaused = this._paused;
+        let wasPaused = this.paused;
+        let targetData = this._targetDebuggerDataMap.get(target);
 
-        this._paused = true;
-        this._callFrames = [];
+        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(callFramePayload.location);
+            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 (!sourceCodeLocation || !sourceCodeLocation._sourceCode || !sourceCodeLocation._sourceCode._url || sourceCodeLocation._sourceCode._url.indexOf("__WebInspector") === 0)
+            if (!WI.isDebugUIEnabled() && isWebKitInternalScript(sourceCodeLocation.sourceCode.sourceURL))
                 continue;
-            var thisObject = WebInspector.RemoteObject.fromPayload(callFramePayload.this);
-            var scopeChain = this._scopeChainFromPayload(callFramePayload.scopeChain);
-            var callFrame = new WebInspector.CallFrame(callFramePayload.callFrameId, sourceCodeLocation, callFramePayload.functionName, thisObject, scopeChain);
-            this._callFrames.push(callFrame);
+
+            let scopeChain = this._scopeChainFromPayload(target, callFramePayload.scopeChain);
+            let callFrame = WI.CallFrame.fromDebuggerPayload(target, callFramePayload, scopeChain, sourceCodeLocation);
+            callFrames.push(callFrame);
         }
 
-        if (!this._callFrames.length) {
-            this.resume();
+        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;
         }
 
-        this._activeCallFrame = this._callFrames[0];
+        let asyncStackTrace = WI.StackTrace.fromPayload(target, asyncStackTracePayload);
+        targetData.updateForPause(callFrames, pauseReason, pauseData, asyncStackTrace);
 
-        if (!wasStillPaused)
-            this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.Paused);
-        this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.CallFramesDidChange);
-        this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.ActiveCallFrameDidChange);
-    },
+        // 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();
 
-    debuggerDidResume: function()
-    {
-        // Called from WebInspector.DebuggerObserver.
+        let activeCallFrameDidChange = this._activeCallFrame && this._activeCallFrame.target === target;
+        if (activeCallFrameDidChange)
+            this._activeCallFrame = activeCallFrame;
+        else if (!wasPaused) {
+            this._activeCallFrame = activeCallFrame;
+            activeCallFrameDidChange = true;
+        }
 
-        function delayedWork()
-        {
-            delete this._delayedResumeTimeout;
+        if (!wasPaused)
+            this.dispatchEventToListeners(WI.DebuggerManager.Event.Paused);
 
-            this._paused = false;
-            this._callFrames = null;
-            this._activeCallFrame = null;
+        this.dispatchEventToListeners(WI.DebuggerManager.Event.CallFramesDidChange, {target});
 
-            this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.Resumed);
-            this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.CallFramesDidChange);
-            this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.ActiveCallFrameDidChange);
+        if (activeCallFrameDidChange)
+            this.dispatchEventToListeners(WI.DebuggerManager.Event.ActiveCallFrameDidChange);
+    }
+
+    debuggerDidResume(target)
+    {
+        // Called from WI.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;
         }
 
-        // We delay clearing the state and firing events so the user interface does not flash
-        // between brief steps or successive breakpoints.
-        this._delayedResumeTimeout = setTimeout(delayedWork.bind(this), 50);
-    },
+        this._didResumeInternal(target);
+    }
 
-    playBreakpointActionSound: function(breakpointActionIdentifier)
+    playBreakpointActionSound(breakpointActionIdentifier)
     {
+        // Called from WI.DebuggerObserver.
+
         InspectorFrontendHost.beep();
-    },
+    }
 
-    scriptDidParse: function(scriptIdentifier, url, isContentScript, startLine, startColumn, endLine, endColumn, sourceMapURL)
+    scriptDidParse(target, scriptIdentifier, url, startLine, startColumn, endLine, endColumn, isModule, isContentScript, sourceURL, sourceMapURL)
     {
+        // Called from WI.DebuggerObserver.
+
         // Don't add the script again if it is already known.
-        if (this._scriptIdMap[scriptIdentifier]) {
-            console.assert(this._scriptIdMap[scriptIdentifier].url === (url || null));
-            console.assert(this._scriptIdMap[scriptIdentifier].range.startLine === startLine);
-            console.assert(this._scriptIdMap[scriptIdentifier].range.startColumn === startColumn);
-            console.assert(this._scriptIdMap[scriptIdentifier].range.endLine === endLine);
-            console.assert(this._scriptIdMap[scriptIdentifier].range.endColumn === endColumn);
+        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;
         }
 
-        var script = new WebInspector.Script(scriptIdentifier, new WebInspector.TextRange(startLine, startColumn, endLine, endColumn), url, isContentScript, sourceMapURL);
+        if (!WI.isDebugUIEnabled() && isWebKitInternalScript(sourceURL))
+            return;
 
-        this._scriptIdMap[scriptIdentifier] = script;
+        let range = new WI.TextRange(startLine, startColumn, endLine, endColumn);
+        let sourceType = isModule ? WI.Script.SourceType.Module : WI.Script.SourceType.Program;
+        let script = new WI.Script(target, scriptIdentifier, range, url, sourceType, isContentScript, sourceURL, sourceMapURL);
+
+        targetData.addScript(script);
+
+        // 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 (WI.sharedApp.debuggableType === WI.DebuggableType.ServiceWorker) {
+            // A ServiceWorker starts with a LocalScript for the main resource but we can replace it during initialization.
+            if (target.mainResource instanceof WI.LocalScript) {
+                if (script.url === target.name)
+                    target.mainResource = script;
+            }
+        } else if (!target.mainResource && target !== WI.mainTarget) {
+            // A Worker starts without a main resource and we insert one.
+            if (script.url === target.name) {
+                target.mainResource = script;
+                if (script.resource)
+                    target.resourceCollection.remove(script.resource);
+            }
+        }
 
-        if (script.url) {
-            var scripts = this._scriptURLMap[script.url];
-            if (!scripts)
-                scripts = this._scriptURLMap[script.url] = [];
-            scripts.push(script);
+        if (isWebKitInternalScript(script.sourceURL)) {
+            this._internalWebKitScripts.push(script);
+            if (!WI.isDebugUIEnabled())
+                return;
         }
 
-        this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.ScriptAdded, {script: script});
-    },
+        // Console expressions are not added to the UI by default.
+        if (isWebInspectorConsoleEvaluationScript(script.sourceURL))
+            return;
 
-    isBreakpointRemovable: function(breakpoint)
-    {
-        return breakpoint !== this._allExceptionsBreakpoint && breakpoint !== this._allUncaughtExceptionsBreakpoint;
-    },
+        this.dispatchEventToListeners(WI.DebuggerManager.Event.ScriptAdded, {script});
 
-    isBreakpointEditable: function(breakpoint)
-    {
-        return this.isBreakpointRemovable(breakpoint);
-    },
+        if ((target !== WI.mainTarget || WI.sharedApp.debuggableType === WI.DebuggableType.ServiceWorker) && !script.isMainResource() && !script.resource)
+            target.addScript(script);
+    }
 
-    get nextBreakpointActionIdentifier()
+    didSampleProbe(target, sample)
     {
-        return this._nextBreakpointActionIdentifier++;
-    },
+        console.assert(this._probesByIdentifier.has(sample.probeId), "Unknown probe identifier specified for sample: ", sample);
+        let probe = this._probesByIdentifier.get(sample.probeId);
+        let elapsedTime = WI.timelineManager.computeElapsedTime(sample.timestamp);
+        let object = WI.RemoteObject.fromPayload(sample.payload, target);
+        probe.addSample(new WI.ProbeSample(sample.sampleId, sample.batchId, elapsedTime, object));
+    }
 
     // Private
 
-    _sourceCodeLocationFromPayload: function(payload)
+    _sourceCodeLocationFromPayload(target, payload)
     {
-        var script = this._scriptIdMap[payload.scriptId];
-        console.assert(script);
+        let targetData = this.dataForTarget(target);
+        let script = targetData.scriptForIdentifier(payload.scriptId);
         if (!script)
             return null;
 
         return script.createSourceCodeLocation(payload.lineNumber, payload.columnNumber);
-    },
+    }
 
-    _scopeChainFromPayload: function(payload)
+    _scopeChainFromPayload(target, payload)
     {
-        var scopeChain = [];
-        for (var i = 0; i < payload.length; ++i)
-            scopeChain.push(this._scopeChainNodeFromPayload(payload[i]));
+        let scopeChain = [];
+        for (let i = 0; i < payload.length; ++i)
+            scopeChain.push(this._scopeChainNodeFromPayload(target, payload[i]));
         return scopeChain;
-    },
+    }
 
-    _scopeChainNodeFromPayload: function(payload)
+    _scopeChainNodeFromPayload(target, payload)
     {
         var type = null;
         switch (payload.type) {
-        case "local":
-            type = WebInspector.ScopeChainNode.Type.Local;
+        case DebuggerAgent.ScopeType.Global:
+            type = WI.ScopeChainNode.Type.Global;
+            break;
+        case DebuggerAgent.ScopeType.With:
+            type = WI.ScopeChainNode.Type.With;
+            break;
+        case DebuggerAgent.ScopeType.Closure:
+            type = WI.ScopeChainNode.Type.Closure;
             break;
-        case "global":
-            type = WebInspector.ScopeChainNode.Type.Global;
+        case DebuggerAgent.ScopeType.Catch:
+            type = WI.ScopeChainNode.Type.Catch;
             break;
-        case "with":
-            type = WebInspector.ScopeChainNode.Type.With;
+        case DebuggerAgent.ScopeType.FunctionName:
+            type = WI.ScopeChainNode.Type.FunctionName;
             break;
-        case "closure":
-            type = WebInspector.ScopeChainNode.Type.Closure;
+        case DebuggerAgent.ScopeType.NestedLexical:
+            type = WI.ScopeChainNode.Type.Block;
             break;
-        case "catch":
-            type = WebInspector.ScopeChainNode.Type.Catch;
+        case DebuggerAgent.ScopeType.GlobalLexicalEnvironment:
+            type = WI.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 = WI.ScopeChainNode.Type.Closure;
             break;
+
         default:
             console.error("Unknown type: " + payload.type);
         }
 
-        var object = WebInspector.RemoteObject.fromPayload(payload.object);
-        return new WebInspector.ScopeChainNode(type, object);
-    },
+        let object = WI.RemoteObject.fromPayload(payload.object, target);
+        return new WI.ScopeChainNode(type, [object], payload.name, payload.location, payload.empty);
+    }
 
-    _debuggerBreakpointActionType: function(type)
+    _pauseReasonFromPayload(payload)
+    {
+        // FIXME: Handle other backend pause reasons.
+        switch (payload) {
+        case DebuggerAgent.PausedReason.AnimationFrame:
+            return WI.DebuggerManager.PauseReason.AnimationFrame;
+        case DebuggerAgent.PausedReason.Assert:
+            return WI.DebuggerManager.PauseReason.Assertion;
+        case DebuggerAgent.PausedReason.Breakpoint:
+            return WI.DebuggerManager.PauseReason.Breakpoint;
+        case DebuggerAgent.PausedReason.CSPViolation:
+            return WI.DebuggerManager.PauseReason.CSPViolation;
+        case DebuggerAgent.PausedReason.DOM:
+            return WI.DebuggerManager.PauseReason.DOM;
+        case DebuggerAgent.PausedReason.DebuggerStatement:
+            return WI.DebuggerManager.PauseReason.DebuggerStatement;
+        case DebuggerAgent.PausedReason.EventListener:
+            return WI.DebuggerManager.PauseReason.EventListener;
+        case DebuggerAgent.PausedReason.Exception:
+            return WI.DebuggerManager.PauseReason.Exception;
+        case DebuggerAgent.PausedReason.PauseOnNextStatement:
+            return WI.DebuggerManager.PauseReason.PauseOnNextStatement;
+        case DebuggerAgent.PausedReason.Timer:
+            return WI.DebuggerManager.PauseReason.Timer;
+        case DebuggerAgent.PausedReason.XHR:
+            return WI.DebuggerManager.PauseReason.XHR;
+        default:
+            return WI.DebuggerManager.PauseReason.Other;
+        }
+    }
+
+    _debuggerBreakpointActionType(type)
     {
         switch (type) {
-        case WebInspector.BreakpointAction.Type.Log:
+        case WI.BreakpointAction.Type.Log:
             return DebuggerAgent.BreakpointActionType.Log;
-        case WebInspector.BreakpointAction.Type.Evaluate:
+        case WI.BreakpointAction.Type.Evaluate:
             return DebuggerAgent.BreakpointActionType.Evaluate;
-        case WebInspector.BreakpointAction.Type.Sound:
+        case WI.BreakpointAction.Type.Sound:
             return DebuggerAgent.BreakpointActionType.Sound;
-        case WebInspector.BreakpointAction.Type.Probe:
+        case WI.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 !== WI.BreakpointAction.Type.Log)
+                continue;
+
+            if (!templatePlaceholderRegex.test(action.data))
+                continue;
+
+            let lexer = new WI.BreakpointLogMessageLexer;
+            let tokens = lexer.tokenize(action.data);
+            if (!tokens) {
+                invalidActions.push(action);
+                continue;
+            }
+
+            let templateLiteral = tokens.reduce((text, token) => {
+                if (token.type === WI.BreakpointLogMessageLexer.TokenType.PlainText)
+                    return text + token.data.escapeCharacters("`\\");
+                if (token.type === WI.BreakpointLogMessageLexer.TokenType.Expression)
+                    return text + "${" + token.data + "}";
+                return text;
+            }, "");
+
+            action.data = "console.log(`" + templateLiteral + "`)";
+            action.type = WI.BreakpointAction.Type.Evaluate;
+        }
+
+        for (let invalidAction of invalidActions)
+            options.actions.remove(invalidAction);
 
-    _setBreakpoint: function(breakpoint, callback)
+        return options;
+    }
+
+    _setBreakpoint(breakpoint, specificTarget, callback)
     {
-        console.assert(!breakpoint.identifier);
         console.assert(!breakpoint.disabled);
 
-        if (breakpoint.identifier || breakpoint.disabled)
+        if (breakpoint.disabled)
             return;
 
-        // Enable breakpoints since a breakpoint is being set. This eliminates
-        // a multi-step process for the user that can be confusing.
-        this.breakpointsEnabled = true;
+        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(error, breakpointIdentifier)
+        function didSetBreakpoint(target, error, breakpointIdentifier, locations)
         {
             if (error)
                 return;
 
-            this._breakpointIdMap[breakpointIdentifier] = breakpoint;
+            this._breakpointIdMap.set(breakpointIdentifier, breakpoint);
 
             breakpoint.identifier = breakpointIdentifier;
-            breakpoint.resolved = true;
+
+            // 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();
@@ -540,40 +956,39 @@ WebInspector.DebuggerManager.prototype = {
 
         // 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.
-        breakpoint.resolved = false;
+        // 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.
-        var options;
-        if (DebuggerAgent.BreakpointActionType) {
-            options = breakpoint.options;
-            if (options.actions.length) {
-                for (var i = 0; i < options.actions.length; ++i)
-                    options.actions[i].type = this._debuggerBreakpointActionType(options.actions[i].type);
-            }
+        let 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.url) {
-            DebuggerAgent.setBreakpointByUrl.invoke({
-                lineNumber: breakpoint.sourceCodeLocation.lineNumber,
-                url: breakpoint.url,
-                urlRegex: undefined,
-                columnNumber: breakpoint.sourceCodeLocation.columnNumber,
-                condition: breakpoint.condition,
-                options: options
-            }, didSetBreakpoint.bind(this));
+        if (breakpoint.contentIdentifier) {
+            let targets = specificTarget ? [specificTarget] : WI.targets;
+            for (let target of targets) {
+                target.DebuggerAgent.setBreakpointByUrl.invoke({
+                    lineNumber: breakpoint.sourceCodeLocation.lineNumber,
+                    url: breakpoint.contentIdentifier,
+                    urlRegex: undefined,
+                    columnNumber: breakpoint.sourceCodeLocation.columnNumber,
+                    options
+                }, didSetBreakpoint.bind(this, target), target.DebuggerAgent);
+            }
         } else if (breakpoint.scriptIdentifier) {
-            DebuggerAgent.setBreakpoint.invoke({
+            let target = breakpoint.target;
+            target.DebuggerAgent.setBreakpoint.invoke({
                 location: {scriptId: breakpoint.scriptIdentifier, lineNumber: breakpoint.sourceCodeLocation.lineNumber, columnNumber: breakpoint.sourceCodeLocation.columnNumber},
-                condition: breakpoint.condition,
-                options: options
-            }, didSetBreakpoint.bind(this));
+                options
+            }, didSetBreakpoint.bind(this, target), target.DebuggerAgent);
         }
-    },
+    }
 
-    _removeBreakpoint: function(breakpoint, callback)
+    _removeBreakpoint(breakpoint, callback)
     {
         if (!breakpoint.identifier)
             return;
@@ -583,7 +998,7 @@ WebInspector.DebuggerManager.prototype = {
             if (error)
                 console.error(error);
 
-            delete this._breakpointIdMap[breakpoint.identifier];
+            this._breakpointIdMap.delete(breakpoint.identifier);
 
             breakpoint.identifier = null;
 
@@ -594,15 +1009,21 @@ WebInspector.DebuggerManager.prototype = {
                 callback();
         }
 
-        DebuggerAgent.removeBreakpoint(breakpoint.identifier, didRemoveBreakpoint.bind(this));
-    },
+        if (breakpoint.contentIdentifier) {
+            for (let target of WI.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: function(event)
+    _breakpointDisplayLocationDidChange(event)
     {
         if (this._ignoreBreakpointDisplayLocationDidChangeEvent)
             return;
 
-        var breakpoint = event.target;
+        let breakpoint = event.target;
         if (!breakpoint.identifier || breakpoint.disabled)
             return;
 
@@ -614,27 +1035,41 @@ WebInspector.DebuggerManager.prototype = {
             // Add the breakpoint at its new lineNumber and get a new id.
             this._setBreakpoint(breakpoint);
 
-            this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.BreakpointMoved, {breakpoint: breakpoint});
+            this.dispatchEventToListeners(WI.DebuggerManager.Event.BreakpointMoved, {breakpoint});
         }
-    },
+    }
 
-    _breakpointDisabledStateDidChange: function(event)
+    _breakpointDisabledStateDidChange(event)
     {
-        var breakpoint = event.target;
+        this._saveBreakpoints();
 
+        let breakpoint = event.target;
         if (breakpoint === this._allExceptionsBreakpoint) {
-            if (!breakpoint.disabled)
+            if (!breakpoint.disabled && !this.breakpointsDisabledTemporarily)
                 this.breakpointsEnabled = true;
             this._allExceptionsBreakpointEnabledSetting.value = !breakpoint.disabled;
             this._updateBreakOnExceptionsState();
+            for (let target of WI.targets)
+                target.DebuggerAgent.setPauseOnExceptions(this._breakOnExceptionsState);
             return;
         }
 
-        if (breakpoint === this._allUncaughtExceptionsBreakpoint) {
-            if (!breakpoint.disabled)
+        if (breakpoint === this._uncaughtExceptionsBreakpoint) {
+            if (!breakpoint.disabled && !this.breakpointsDisabledTemporarily)
                 this.breakpointsEnabled = true;
-            this._allUncaughtExceptionsBreakpointEnabledSetting.value = !breakpoint.disabled;
+            this._uncaughtExceptionsBreakpointEnabledSetting.value = !breakpoint.disabled;
             this._updateBreakOnExceptionsState();
+            for (let target of WI.targets)
+                target.DebuggerAgent.setPauseOnExceptions(this._breakOnExceptionsState);
+            return;
+        }
+
+        if (breakpoint === this._assertionFailuresBreakpoint) {
+            if (!breakpoint.disabled && !this.breakpointsDisabledTemporarily)
+                this.breakpointsEnabled = true;
+            this._assertionFailuresBreakpointEnabledSetting.value = !breakpoint.disabled;
+            for (let target of WI.targets)
+                target.DebuggerAgent.setPauseOnAssertions(this._assertionFailuresBreakpointEnabledSetting.value);
             return;
         }
 
@@ -642,11 +1077,13 @@ WebInspector.DebuggerManager.prototype = {
             this._removeBreakpoint(breakpoint);
         else
             this._setBreakpoint(breakpoint);
-    },
+    }
 
-    _breakpointEditablePropertyDidChange: function(event)
+    _breakpointEditablePropertyDidChange(event)
     {
-        var breakpoint = event.target;
+        this._saveBreakpoints();
+
+        let breakpoint = event.target;
         if (breakpoint.disabled)
             return;
 
@@ -659,74 +1096,277 @@ WebInspector.DebuggerManager.prototype = {
 
         function breakpointRemoved()
         {
-            // Add the breakpoint with its new condition and get a new id.
+            // Add the breakpoint with its new properties and get a new id.
             this._setBreakpoint(breakpoint);
         }
-    },
+    }
+
+    _handleBreakpointActionsDidChange(event)
+    {
+        this._breakpointEditablePropertyDidChange(event);
+
+        this._updateProbesForBreakpoint(event.target);
+    }
+
+    _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(WI.DebuggerManager.Event.Resumed);
+    }
+
+    _pauseForInternalScriptsDidChange(event)
+    {
+        // COMPATIBILITY (iOS 12): DebuggerAgent.setPauseForInternalScripts did not exist yet.
+        console.assert(DebuggerAgent.setPauseForInternalScripts);
+
+        for (let target of WI.targets)
+            target.DebuggerAgent.setPauseForInternalScripts(WI.settings.pauseForInternalScripts.value);
+    }
+
+    _mainResourceDidChange(event)
+    {
+        if (!event.target.isMainFrame())
+            return;
+
+        this._didResumeInternal(WI.mainTarget);
+    }
+
+    _didResumeInternal(target)
+    {
+        if (!this.paused)
+            return;
+
+        if (this._delayedResumeTimeout) {
+            clearTimeout(this._delayedResumeTimeout);
+            this._delayedResumeTimeout = undefined;
+        }
 
-    _updateBreakOnExceptionsState: function()
+        let activeCallFrameDidChange = false;
+        if (this._activeCallFrame && this._activeCallFrame.target === target) {
+            this._activeCallFrame = null;
+            activeCallFrameDidChange = true;
+        }
+
+        this.dataForTarget(target).updateForResume();
+
+        if (!this.paused)
+            this.dispatchEventToListeners(WI.DebuggerManager.Event.Resumed);
+
+        this.dispatchEventToListeners(WI.DebuggerManager.Event.CallFramesDidChange, {target});
+
+        if (activeCallFrameDidChange)
+            this.dispatchEventToListeners(WI.DebuggerManager.Event.ActiveCallFrameDidChange);
+    }
+
+    _updateBreakOnExceptionsState()
     {
-        var state = "none";
+        let state = "none";
 
         if (this._breakpointsEnabledSetting.value) {
             if (!this._allExceptionsBreakpoint.disabled)
                 state = "all";
-            else if (!this._allUncaughtExceptionsBreakpoint.disabled)
+            else if (!this._uncaughtExceptionsBreakpoint.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;
+            this._uncaughtExceptionsBreakpoint.resolved = false;
             break;
         case "uncaught":
         case "none":
             // Mark the uncaught breakpoint as resolved again.
-            this._allUncaughtExceptionsBreakpoint.resolved = true;
+            this._uncaughtExceptionsBreakpoint.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);
+        }
 
-        DebuggerAgent.setPauseOnExceptions(state);
-    },
+        this._ignoreBreakpointDisplayLocationDidChangeEvent = false;
+    }
 
-    _inspectorClosing: function(event)
+    _addProbesForBreakpoint(breakpoint)
     {
-        this._saveBreakpoints();
-    },
+        if (this._knownProbeIdentifiersForBreakpoint.has(breakpoint))
+            return;
+
+        this._knownProbeIdentifiersForBreakpoint.set(breakpoint, new Set);
 
-    _saveBreakpoints: function()
+        this._updateProbesForBreakpoint(breakpoint);
+    }
+
+    _removeProbesForBreakpoint(breakpoint)
     {
-        var savedBreakpoints = [];
+        console.assert(this._knownProbeIdentifiersForBreakpoint.has(breakpoint));
 
-        for (var i = 0; i < this._breakpoints.length; ++i) {
-            var breakpoint = this._breakpoints[i];
+        this._updateProbesForBreakpoint(breakpoint);
+        this._knownProbeIdentifiersForBreakpoint.delete(breakpoint);
+    }
 
-            // Only breakpoints with URLs can be saved. Breakpoints for transient scripts can't.
-            if (!breakpoint.url)
-                continue;
+    _updateProbesForBreakpoint(breakpoint)
+    {
+        let knownProbeIdentifiers = this._knownProbeIdentifiersForBreakpoint.get(breakpoint);
+        if (!knownProbeIdentifiers) {
+            // Sometimes actions change before the added breakpoint is fully dispatched.
+            this._addProbesForBreakpoint(breakpoint);
+            return;
+        }
 
-            savedBreakpoints.push(breakpoint.info);
+        let seenProbeIdentifiers = new Set;
+
+        for (let probeAction of breakpoint.probeActions) {
+            let 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);
+                let probeSet = this._probeSetForBreakpoint(breakpoint);
+                let newProbe = new WI.Probe(probeIdentifier, breakpoint, probeAction.data);
+                this._probesByIdentifier.set(probeIdentifier, newProbe);
+                probeSet.addProbe(newProbe);
+                break;
+            }
+
+            let 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._breakpointsSetting.value = savedBreakpoints;
-    },
+        // Look for missing probes based on what we saw last.
+        for (let probeIdentifier of knownProbeIdentifiers) {
+            if (seenProbeIdentifiers.has(probeIdentifier))
+                break;
+
+            // The probe has gone missing, remove it.
+            let probeSet = this._probeSetForBreakpoint(breakpoint);
+            let 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(WI.DebuggerManager.Event.ProbeSetRemoved, {probeSet});
+            }
+        }
+    }
 
-    _associateBreakpointsWithSourceCode: function(breakpoints, sourceCode)
+    _probeSetForBreakpoint(breakpoint)
     {
-        this._ignoreBreakpointDisplayLocationDidChangeEvent = true;
-
-        for (var i = 0; i < breakpoints.length; ++i) {
-            var breakpoint = breakpoints[i];
-            if (breakpoint.sourceCodeLocation.sourceCode === null)
-                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.url === sourceCode.url);
+        let probeSet = this._probeSetsByBreakpoint.get(breakpoint);
+        if (!probeSet) {
+            probeSet = new WI.ProbeSet(breakpoint);
+            this._probeSetsByBreakpoint.set(breakpoint, probeSet);
+            this.dispatchEventToListeners(WI.DebuggerManager.Event.ProbeSetAdded, {probeSet});
         }
+        return probeSet;
+    }
 
-        delete this._ignoreBreakpointDisplayLocationDidChangeEvent;
+    _debugUIEnabledDidChange()
+    {
+        let eventType = WI.isDebugUIEnabled() ? WI.DebuggerManager.Event.ScriptAdded : WI.DebuggerManager.Event.ScriptRemoved;
+        for (let script of this._internalWebKitScripts)
+            this.dispatchEventToListeners(eventType, {script});
     }
 };
 
-WebInspector.DebuggerManager.prototype.__proto__ = WebInspector.Object.prototype;
+WI.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",
+    ProbeSetAdded: "debugger-manager-probe-set-added",
+    ProbeSetRemoved: "debugger-manager-probe-set-removed",
+};
+
+WI.DebuggerManager.PauseReason = {
+    AnimationFrame: "animation-frame",
+    Assertion: "assertion",
+    Breakpoint: "breakpoint",
+    CSPViolation: "CSP-violation",
+    DebuggerStatement: "debugger-statement",
+    DOM: "DOM",
+    EventListener: "event-listener",
+    Exception: "exception",
+    PauseOnNextStatement: "pause-on-next-statement",
+    Timer: "timer",
+    XHR: "xhr",
+    Other: "other",
+};