Bug 1200798 - refactor sources and breakpoints in debugger to use redux r=ejpbruel
authorJames Long <longster@gmail.com>
Tue, 01 Dec 2015 16:59:00 -0500
changeset 309163 0e47cb06470145725732f0f362c46048969126d8
parent 309162 15522bc2931a52ec435f22e233f747264b90f647
child 309164 62ff0d59cd62619bede132718cf524f2a142646d
push id5513
push userraliiev@mozilla.com
push dateMon, 25 Jan 2016 13:55:34 +0000
treeherdermozilla-beta@5ee97dd05b5c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersejpbruel
bugs1200798
milestone45.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1200798 - refactor sources and breakpoints in debugger to use redux r=ejpbruel
devtools/client/debugger/content/actions/breakpoints.js
devtools/client/debugger/content/actions/moz.build
devtools/client/debugger/content/actions/sources.js
devtools/client/debugger/content/constants.js
devtools/client/debugger/content/globalActions.js
devtools/client/debugger/content/moz.build
devtools/client/debugger/content/queries.js
devtools/client/debugger/content/reducers/async-requests.js
devtools/client/debugger/content/reducers/breakpoints.js
devtools/client/debugger/content/reducers/event-listeners.js
devtools/client/debugger/content/reducers/index.js
devtools/client/debugger/content/reducers/moz.build
devtools/client/debugger/content/reducers/sources.js
devtools/client/debugger/content/utils.js
devtools/client/debugger/content/views/event-listeners-view.js
devtools/client/debugger/content/views/moz.build
devtools/client/debugger/content/views/sources-view.js
devtools/client/debugger/debugger-commands.js
devtools/client/debugger/debugger-controller.js
devtools/client/debugger/debugger-view.js
devtools/client/debugger/debugger.css
devtools/client/debugger/debugger.xul
devtools/client/debugger/panel.js
devtools/client/debugger/test/mochitest/browser.ini
devtools/client/debugger/test/mochitest/browser_dbg_auto-pretty-print-02.js
devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-01.js
devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-02.js
devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-05.js
devtools/client/debugger/test/mochitest/browser_dbg_breadcrumbs-access.js
devtools/client/debugger/test/mochitest/browser_dbg_break-in-anon.js
devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-01.js
devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-02.js
devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-03.js
devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-04.js
devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-05.js
devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-06.js
devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-08.js
devtools/client/debugger/test/mochitest/browser_dbg_break-on-next-console.js
devtools/client/debugger/test/mochitest/browser_dbg_break-on-next.js
devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-actual-location.js
devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-actual-location2.js
devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js
devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-button-01.js
devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-button-02.js
devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-condition-thrown-message.js
devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-contextmenu-add.js
devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-contextmenu.js
devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-disabled-reload.js
devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-editor.js
devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-eval.js
devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-highlight.js
devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-new-script.js
devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-other-tabs.js
devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-pane.js
devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-reload.js
devtools/client/debugger/test/mochitest/browser_dbg_cmd-blackbox.js
devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-01.js
devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-02.js
devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-03.js
devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-04.js
devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-05.js
devtools/client/debugger/test/mochitest/browser_dbg_console-eval.js
devtools/client/debugger/test/mochitest/browser_dbg_controller-evaluate-01.js
devtools/client/debugger/test/mochitest/browser_dbg_controller-evaluate-02.js
devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-04.js
devtools/client/debugger/test/mochitest/browser_dbg_file-reload.js
devtools/client/debugger/test/mochitest/browser_dbg_hide-toolbar-buttons.js
devtools/client/debugger/test/mochitest/browser_dbg_interrupts.js
devtools/client/debugger/test/mochitest/browser_dbg_location-changes-01-simple.js
devtools/client/debugger/test/mochitest/browser_dbg_location-changes-02-blank.js
devtools/client/debugger/test/mochitest/browser_dbg_location-changes-03-new.js
devtools/client/debugger/test/mochitest/browser_dbg_location-changes-04-breakpoint.js
devtools/client/debugger/test/mochitest/browser_dbg_no-page-sources.js
devtools/client/debugger/test/mochitest/browser_dbg_pause-exceptions-02.js
devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-01.js
devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-02.js
devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-03.js
devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-04.js
devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-05.js
devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-06.js
devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-10.js
devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-12.js
devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-13.js
devtools/client/debugger/test/mochitest/browser_dbg_progress-listener-bug.js
devtools/client/debugger/test/mochitest/browser_dbg_reload-same-script.js
devtools/client/debugger/test/mochitest/browser_dbg_scripts-switching-01.js
devtools/client/debugger/test/mochitest/browser_dbg_scripts-switching-02.js
devtools/client/debugger/test/mochitest/browser_dbg_scripts-switching-03.js
devtools/client/debugger/test/mochitest/browser_dbg_search-basic-02.js
devtools/client/debugger/test/mochitest/browser_dbg_search-basic-03.js
devtools/client/debugger/test/mochitest/browser_dbg_search-global-02.js
devtools/client/debugger/test/mochitest/browser_dbg_search-global-05.js
devtools/client/debugger/test/mochitest/browser_dbg_search-popup-jank.js
devtools/client/debugger/test/mochitest/browser_dbg_searchbox-help-popup-02.js
devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-01.js
devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-02.js
devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-03.js
devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-04.js
devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-05.js
devtools/client/debugger/test/mochitest/browser_dbg_sources-bookmarklet.js
devtools/client/debugger/test/mochitest/browser_dbg_sources-cache.js
devtools/client/debugger/test/mochitest/browser_dbg_sources-eval-02.js
devtools/client/debugger/test/mochitest/browser_dbg_sources-labels.js
devtools/client/debugger/test/mochitest/browser_dbg_split-console-paused-reload.js
devtools/client/debugger/test/mochitest/browser_dbg_stack-02.js
devtools/client/debugger/test/mochitest/browser_dbg_stack-05.js
devtools/client/debugger/test/mochitest/browser_dbg_stack-07.js
devtools/client/debugger/test/mochitest/browser_dbg_terminate-on-tab-close.js
devtools/client/debugger/test/mochitest/browser_dbg_variables-view-reexpand-01.js
devtools/client/debugger/test/mochitest/browser_dbg_variables-view-reexpand-02.js
devtools/client/debugger/test/mochitest/browser_dbg_variables-view-reexpand-03.js
devtools/client/debugger/test/mochitest/head.js
devtools/client/debugger/utils.js
devtools/client/debugger/views/filter-view.js
devtools/client/debugger/views/global-search-view.js
devtools/client/debugger/views/sources-view.js
devtools/client/jar.mn
devtools/client/shared/browser-loader.js
devtools/client/shared/redux/create-store.js
devtools/client/shared/redux/middleware/promise.js
devtools/client/shared/redux/middleware/thunk.js
devtools/client/shared/redux/middleware/wait-service.js
devtools/client/shared/redux/moz.build
devtools/client/shared/redux/non-react-subscriber.js
devtools/client/shared/redux/reducers.js
devtools/client/shared/source-utils.js
devtools/client/shared/vendor/moz.build
devtools/client/shared/vendor/react.js
devtools/client/shared/vendor/seamless-immutable.js
devtools/client/shared/widgets/ViewHelpers.jsm
devtools/client/sourceeditor/debugger.js
devtools/client/webconsole/test/head.js
devtools/shared/DevToolsUtils.js
devtools/shared/client/main.js
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/content/actions/breakpoints.js
@@ -0,0 +1,169 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const constants = require('../constants');
+const promise = require('promise');
+const { asPaused, rdpInvoke } = require('../utils');
+const { PROMISE } = require('devtools/client/shared/redux/middleware/promise');
+const {
+  getSource, getBreakpoint, getBreakpoints, makeLocationId
+} = require('../queries');
+
+// Because breakpoints are just simple data structures, we still need
+// a way to lookup the actual client instance to talk to the server.
+// We keep an internal database of clients based off of actor ID.
+const BREAKPOINT_CLIENT_STORE = new Map();
+
+function setBreakpointClient(actor, client) {
+  BREAKPOINT_CLIENT_STORE.set(actor, client);
+}
+
+function getBreakpointClient(actor) {
+  return BREAKPOINT_CLIENT_STORE.get(actor);
+}
+
+function enableBreakpoint(location) {
+  // Enabling is exactly the same as adding. It will use the existing
+  // breakpoint that still stored.
+  return addBreakpoint(location);
+}
+
+function _breakpointExists(state, location) {
+  const currentBp = getBreakpoint(state, location);
+  return currentBp && !currentBp.disabled;
+}
+
+function _getOrCreateBreakpoint(state, location, condition) {
+  return getBreakpoint(state, location) || { location, condition };
+}
+
+function addBreakpoint(location, condition) {
+  return (dispatch, getState) => {
+    if (_breakpointExists(getState(), location)) {
+      return;
+    }
+
+    const bp = _getOrCreateBreakpoint(getState(), location, condition);
+
+    return dispatch({
+      type: constants.ADD_BREAKPOINT,
+      breakpoint: bp,
+      condition: condition,
+      [PROMISE]: Task.spawn(function*() {
+        const sourceClient = gThreadClient.source(
+          getSource(getState(), bp.location.actor)
+        );
+        const [response, bpClient] = yield rdpInvoke(sourceClient, sourceClient.setBreakpoint, {
+          line: bp.location.line,
+          column: bp.location.column,
+          condition: bp.condition
+        });
+        const { isPending, actualLocation } = response;
+
+        // Save the client instance
+        setBreakpointClient(bpClient.actor, bpClient);
+
+        return {
+          text: DebuggerView.editor.getText(bp.location.line - 1).trim(),
+
+          // If the breakpoint response has an "actualLocation" attached, then
+          // the original requested placement for the breakpoint wasn't
+          // accepted.
+          actualLocation: isPending ? null : actualLocation,
+          actor: bpClient.actor
+        };
+      })
+    });
+  }
+}
+
+function disableBreakpoint(location) {
+  return _removeOrDisableBreakpoint(location, true);
+}
+
+function removeBreakpoint(location) {
+  return _removeOrDisableBreakpoint(location);
+}
+
+function _removeOrDisableBreakpoint(location, isDisabled) {
+  return (dispatch, getState) => {
+    let bp = getBreakpoint(getState(), location);
+    if (!bp) {
+      throw new Error('attempt to remove breakpoint that does not exist');
+    }
+    if (bp.loading) {
+      // TODO(jwl): make this wait until the breakpoint is saved if it
+      // is still loading
+      throw new Error('attempt to remove unsaved breakpoint');
+    }
+
+    const bpClient = getBreakpointClient(bp.actor);
+
+    return dispatch({
+      type: constants.REMOVE_BREAKPOINT,
+      breakpoint: bp,
+      disabled: isDisabled,
+      [PROMISE]: rdpInvoke(bpClient, bpClient.remove)
+    });
+  }
+}
+
+function removeAllBreakpoints() {
+  return (dispatch, getState) => {
+    const breakpoints = getBreakpoints(getState());
+    const activeBreakpoints = breakpoints.filter(bp => !bp.disabled);
+    activeBreakpoints.forEach(bp => removeBreakpoint(bp.location));
+  }
+}
+
+/**
+ * Update the condition of a breakpoint.
+ *
+ * @param object aLocation
+ *        @see DebuggerController.Breakpoints.addBreakpoint
+ * @param string aClients
+ *        The condition to set on the breakpoint
+ * @return object
+ *         A promise that will be resolved with the breakpoint client
+ */
+function setBreakpointCondition(location, condition) {
+  return (dispatch, getState) => {
+    const bp = getBreakpoint(getState(), location);
+    if (!bp) {
+      throw new Error("Breakpoint does not exist at the specified location");
+    }
+    if (bp.loading){
+      // TODO(jwl): when this function is called, make sure the action
+      // creator waits for the breakpoint to exist
+      throw new Error("breakpoint must be saved");
+    }
+
+    const bpClient = getBreakpointClient(bp.actor);
+
+    return dispatch({
+      type: constants.SET_BREAKPOINT_CONDITION,
+      breakpoint: bp,
+      condition: condition,
+      [PROMISE]: Task.spawn(function*() {
+        const newClient = yield bpClient.setCondition(gThreadClient, condition);
+
+        // Remove the old instance and save the new one
+        setBreakpointClient(bpClient.actor, null);
+        setBreakpointClient(newClient.actor, newClient);
+
+        return { actor: newClient.actor };
+      })
+    });
+  };
+}
+
+module.exports = {
+  enableBreakpoint,
+  addBreakpoint,
+  disableBreakpoint,
+  removeBreakpoint,
+  removeAllBreakpoints,
+  setBreakpointCondition
+}
--- a/devtools/client/debugger/content/actions/moz.build
+++ b/devtools/client/debugger/content/actions/moz.build
@@ -1,8 +1,10 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
+    'breakpoints.js',
     'event-listeners.js',
+    'sources.js'
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/content/actions/sources.js
@@ -0,0 +1,283 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const constants = require('../constants');
+const promise = require('promise');
+const { rdpInvoke } = require('../utils');
+const { dumpn } = require("devtools/shared/DevToolsUtils");
+const { PROMISE, HISTOGRAM_ID } = require('devtools/client/shared/redux/middleware/promise');
+const { getSource, getSourceText } = require('../queries');
+
+const NEW_SOURCE_IGNORED_URLS = ["debugger eval code", "XStringBundle"];
+const FETCH_SOURCE_RESPONSE_DELAY = 200; // ms
+
+function getSourceClient(source) {
+  return gThreadClient.source(source);
+}
+
+/**
+ * Handler for the debugger client's unsolicited newSource notification.
+ */
+function newSource(source) {
+  return dispatch => {
+    // Ignore bogus scripts, e.g. generated from 'clientEvaluate' packets.
+    if (NEW_SOURCE_IGNORED_URLS.indexOf(source.url) != -1) {
+      return;
+    }
+
+    // Signal that a new source has been added.
+    window.emit(EVENTS.NEW_SOURCE);
+
+    return dispatch({
+      type: constants.ADD_SOURCE,
+      source: source
+    });
+  };
+}
+
+function selectSource(source, opts) {
+  return (dispatch, getState) => {
+    if (!gThreadClient) {
+      // No connection, do nothing. This happens when the debugger is
+      // shut down too fast and it tries to display a default source.
+      return;
+    }
+
+    source = getSource(getState(), source.actor);
+
+    // Make sure to start a request to load the source text.
+    dispatch(loadSourceText(source));
+
+    dispatch({
+      type: constants.SELECT_SOURCE,
+      source: source,
+      opts: opts
+    });
+  };
+}
+
+function loadSources() {
+  return {
+    type: constants.LOAD_SOURCES,
+    [PROMISE]: Task.spawn(function*() {
+      const response = yield rdpInvoke(gThreadClient, gThreadClient.getSources);
+
+      // Top-level breakpoints may pause the entire loading process
+      // because scripts are executed as they are loaded, so the
+      // engine may pause in the middle of loading all the sources.
+      // This is relatively harmless, as individual `newSource`
+      // notifications are fired for each script and they will be
+      // added to the UI through that.
+      if (!response.sources) {
+        dumpn(
+          "Error getting sources, probably because a top-level " +
+          "breakpoint was hit while executing them"
+        );
+        return;
+      }
+
+      // Ignore bogus scripts, e.g. generated from 'clientEvaluate' packets.
+      return response.sources.filter(source => {
+        return NEW_SOURCE_IGNORED_URLS.indexOf(source.url) === -1;
+      });
+    })
+  }
+}
+
+/**
+ * Set the black boxed status of the given source.
+ *
+ * @param Object aSource
+ *        The source form.
+ * @param bool aBlackBoxFlag
+ *        True to black box the source, false to un-black box it.
+ * @returns Promise
+ *          A promize that resolves to [aSource, isBlackBoxed] or rejects to
+ *          [aSource, error].
+ */
+function blackbox(source, shouldBlackBox) {
+  const client = getSourceClient(source);
+
+  return {
+    type: constants.BLACKBOX,
+    source: source,
+    [PROMISE]: Task.spawn(function*() {
+      yield rdpInvoke(client,
+                      shouldBlackBox ? client.blackBox : client.unblackBox);
+      return {
+        isBlackBoxed: shouldBlackBox
+      }
+    })
+  };
+}
+
+/**
+ * Toggle the pretty printing of a source's text. All subsequent calls to
+ * |getText| will return the pretty-toggled text. Nothing will happen for
+ * non-javascript files.
+ *
+ * @param Object aSource
+ *        The source form from the RDP.
+ * @returns Promise
+ *          A promise that resolves to [aSource, prettyText] or rejects to
+ *          [aSource, error].
+ */
+function togglePrettyPrint(source) {
+  return (dispatch, getState) => {
+    const sourceClient = getSourceClient(source);
+    const wantPretty = !source.isPrettyPrinted;
+
+    return dispatch({
+      type: constants.TOGGLE_PRETTY_PRINT,
+      source: source,
+      [PROMISE]: Task.spawn(function*() {
+        let response;
+
+        // Only attempt to pretty print JavaScript sources.
+        const sourceText = getSourceText(getState(), source.actor);
+        const contentType = sourceText ? sourceText.contentType : null;
+        if (!SourceUtils.isJavaScript(source.url, contentType)) {
+          throw new Error("Can't prettify non-javascript files.");
+        }
+
+        if (wantPretty) {
+          response = yield rdpInvoke(sourceClient,
+                                     sourceClient.prettyPrint,
+                                     Prefs.editorTabSize);
+        }
+        else {
+          response = yield rdpInvoke(sourceClient,
+                                     sourceClient.disablePrettyPrint);
+        }
+
+        // Remove the cached source AST from the Parser, to avoid getting
+        // wrong locations when searching for functions.
+        DebuggerController.Parser.clearSource(source.url);
+
+        return {
+          isPrettyPrinted: wantPretty,
+          text: response.source,
+          contentType: response.contentType
+        };
+      })
+    });
+  };
+}
+
+function loadSourceText(source) {
+  return (dispatch, getState) => {
+    // Fetch the source text only once.
+    let textInfo = getSourceText(getState(), source.actor);
+    if (textInfo) {
+      // It's already loaded or is loading
+      return promise.resolve(textInfo);
+    }
+
+    const sourceClient = getSourceClient(source);
+
+    return dispatch({
+      type: constants.LOAD_SOURCE_TEXT,
+      source: source,
+      [PROMISE]: Task.spawn(function*() {
+        let transportType = gClient.localTransport ? "_LOCAL" : "_REMOTE";
+        let histogramId = "DEVTOOLS_DEBUGGER_DISPLAY_SOURCE" + transportType + "_MS";
+        let histogram = Services.telemetry.getHistogramById(histogramId);
+        let startTime = Date.now();
+
+        const response = yield rdpInvoke(sourceClient, sourceClient.source);
+
+        histogram.add(Date.now() - startTime);
+
+        // Automatically pretty print if enabled and the test is
+        // detected to be "minified"
+        if (Prefs.autoPrettyPrint &&
+            !source.isPrettyPrinted &&
+            SourceUtils.isMinified(source.actor, response.source)) {
+          dispatch(togglePrettyPrint(source));
+        }
+
+        return { text: response.source,
+                 contentType: response.contentType };
+      })
+    });
+  }
+}
+
+/**
+ * Starts fetching all the sources, silently.
+ *
+ * @param array aUrls
+ *        The urls for the sources to fetch. If fetching a source's text
+ *        takes too long, it will be discarded.
+ * @return object
+ *         A promise that is resolved after source texts have been fetched.
+ */
+function getTextForSources(actors) {
+  return (dispatch, getState) => {
+    let deferred = promise.defer();
+    let pending = new Set(actors);
+    let fetched = [];
+
+    // Can't use promise.all, because if one fetch operation is rejected, then
+    // everything is considered rejected, thus no other subsequent source will
+    // be getting fetched. We don't want that. Something like Q's allSettled
+    // would work like a charm here.
+
+    // Try to fetch as many sources as possible.
+    for (let actor of actors) {
+      let source = getSource(getState(), actor);
+      dispatch(loadSourceText(source)).then(({ text, contentType }) => {
+        onFetch([source, text, contentType]);
+      }, err => {
+        onError(source, err);
+      });
+    }
+
+    setTimeout(onTimeout, FETCH_SOURCE_RESPONSE_DELAY);
+
+    /* Called if fetching a source takes too long. */
+    function onTimeout() {
+      pending = new Set();
+      maybeFinish();
+    }
+
+    /* Called if fetching a source finishes successfully. */
+    function onFetch([aSource, aText, aContentType]) {
+      // If fetching the source has previously timed out, discard it this time.
+      if (!pending.has(aSource.actor)) {
+        return;
+      }
+      pending.delete(aSource.actor);
+      fetched.push([aSource.actor, aText, aContentType]);
+      maybeFinish();
+    }
+
+    /* Called if fetching a source failed because of an error. */
+    function onError([aSource, aError]) {
+      pending.delete(aSource.actor);
+      maybeFinish();
+    }
+
+    /* Called every time something interesting happens while fetching sources. */
+    function maybeFinish() {
+      if (pending.size == 0) {
+        // Sort the fetched sources alphabetically by their url.
+        deferred.resolve(fetched.sort(([aFirst], [aSecond]) => aFirst > aSecond));
+      }
+    }
+
+    return deferred.promise;
+  };
+}
+
+module.exports = {
+  newSource,
+  selectSource,
+  loadSources,
+  blackbox,
+  togglePrettyPrint,
+  loadSourceText,
+  getTextForSources
+};
--- a/devtools/client/debugger/content/constants.js
+++ b/devtools/client/debugger/content/constants.js
@@ -1,7 +1,23 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 exports.UPDATE_EVENT_BREAKPOINTS = 'UPDATE_EVENT_BREAKPOINTS';
 exports.FETCH_EVENT_LISTENERS = 'FETCH_EVENT_LISTENERS';
+
+exports.TOGGLE_PRETTY_PRINT = 'TOGGLE_PRETTY_PRINT';
+exports.BLACKBOX = 'BLACKBOX';
+
+exports.ADD_BREAKPOINT = 'ADD_BREAKPOINT';
+exports.REMOVE_BREAKPOINT = 'REMOVE_BREAKPOINT';
+exports.ENABLE_BREAKPOINT = 'ENABLE_BREAKPOINT';
+exports.DISABLE_BREAKPOINT = 'DISABLE_BREAKPOINT';
+exports.SET_BREAKPOINT_CONDITION = 'SET_BREAKPOINT_CONDITION'
+
+exports.ADD_SOURCE = 'ADD_SOURCE';
+exports.LOAD_SOURCES = 'LOAD_SOURCES';
+exports.LOAD_SOURCE_TEXT = 'LOAD_SOURCE_TEXT';
+exports.SELECT_SOURCE = 'SELECT_SOURCE';
+exports.UNLOAD = 'UNLOAD';
+exports.RELOAD = 'RELOAD';
--- a/devtools/client/debugger/content/globalActions.js
+++ b/devtools/client/debugger/content/globalActions.js
@@ -1,9 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const constants = require('./constants');
 
-// No global actions right now, but I'm sure there will be soon.
-module.exports = {};
+// Fired when the page is being unloaded, for example when it's being
+// navigated away from.
+function unload() {
+  return {
+    type: constants.UNLOAD
+  }
+}
+
+module.exports = { unload };
--- a/devtools/client/debugger/content/moz.build
+++ b/devtools/client/debugger/content/moz.build
@@ -7,10 +7,11 @@ DIRS += [
     'actions',
     'reducers',
     'views',
 ]
 
 DevToolsModules(
     'constants.js',
     'globalActions.js',
+    'queries.js',
     'utils.js'
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/content/queries.js
@@ -0,0 +1,70 @@
+
+function getSource(state, actor) {
+  return state.sources.sources[actor];
+}
+
+function getSources(state) {
+  return state.sources.sources;
+}
+
+function getSourceCount(state) {
+  return Object.keys(state.sources.sources).length;
+}
+
+function getSourceByURL(state, url) {
+  for(let k in state.sources.sources) {
+    const source = state.sources.sources[k];
+    if (source.url === url) {
+      return source;
+    }
+  }
+}
+
+function getSourceByActor(state, actor) {
+  for(let k in state.sources.sources) {
+    const source = state.sources.sources[k];
+    if (source.actor === actor) {
+      return source;
+    }
+  }
+}
+
+function getSelectedSource(state) {
+  return state.sources.sources[state.sources.selectedSource];
+}
+
+function getSelectedSourceOpts(state) {
+  return state.sources.selectedSourceOpts;
+}
+
+function getSourceText(state, actor) {
+  return state.sources.sourcesText[actor];
+}
+
+function getBreakpoints(state) {
+  return Object.keys(state.breakpoints.breakpoints).map(k => {
+    return state.breakpoints.breakpoints[k];
+  });
+}
+
+function getBreakpoint(state, location) {
+  return state.breakpoints.breakpoints[makeLocationId(location)];
+}
+
+function makeLocationId(location) {
+  return location.actor + ':' + location.line.toString();
+}
+
+module.exports = {
+  getSource,
+  getSources,
+  getSourceCount,
+  getSourceByURL,
+  getSourceByActor,
+  getSelectedSource,
+  getSelectedSourceOpts,
+  getSourceText,
+  getBreakpoint,
+  getBreakpoints,
+  makeLocationId
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/content/reducers/async-requests.js
@@ -0,0 +1,31 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const constants = require('../constants');
+const initialState = [];
+
+function update(state = initialState, action, emitChange) {
+  const { seqId } = action;
+
+  if (action.type === constants.UNLOAD) {
+    return initialState;
+  }
+  else if (seqId) {
+    let newState;
+    if (action.status === 'start') {
+      newState = [...state, seqId];
+    }
+    else if (action.status === 'error' || action.status === 'done') {
+      newState = state.filter(id => id !== seqId);
+    }
+
+    emitChange('open-requests', newState);
+    return newState;
+  }
+
+  return state;
+}
+
+module.exports = update;
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/content/reducers/breakpoints.js
@@ -0,0 +1,128 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const constants = require('../constants');
+const Immutable = require('devtools/client/shared/vendor/seamless-immutable');
+const { mergeIn, setIn, deleteIn } = require('../utils');
+const { makeLocationId } = require('../queries');
+
+const initialState = Immutable({
+  breakpoints: {}
+});
+
+function update(state = initialState, action, emitChange) {
+  switch(action.type) {
+  case constants.ADD_BREAKPOINT: {
+    const id = makeLocationId(action.breakpoint.location);
+
+    if (action.status === 'start') {
+      const existingBp = state.breakpoints[id];
+      const bp = existingBp || Immutable(action.breakpoint);
+
+      state = setIn(state, ['breakpoints', id], bp.merge({
+        disabled: false,
+        loading: true,
+        condition: action.condition || bp.condition || undefined
+      }));
+
+      emitChange(existingBp ? "breakpoint-enabled" : "breakpoint-added",
+                 state.breakpoints[id]);
+      return state;
+    }
+    else if (action.status === 'done') {
+      const { actor, text } = action.value;
+      let { actualLocation } = action.value;
+
+      // If the breakpoint moved, update the map
+      if (actualLocation) {
+        // XXX Bug 1227417: The `setBreakpoint` RDP request rdp
+        // request returns an `actualLocation` field that doesn't
+        // conform to the regular { actor, line } location shape, but
+        // it has a `source` field. We should fix that.
+        actualLocation = { actor: actualLocation.source.actor,
+                           line: actualLocation.line };
+
+        state = deleteIn(state, ['breakpoints', id]);
+
+        const movedId = makeLocationId(actualLocation);
+        const currentBp = state.breakpoints[movedId] || Immutable(action.breakpoint);
+        const prevLocation = action.breakpoint.location;
+        const newBp = currentBp.merge({ location: actualLocation });
+        state = setIn(state, ['breakpoints', movedId], newBp);
+
+        emitChange('breakpoint-moved', {
+          breakpoint: newBp,
+          prevLocation: prevLocation
+        });
+      }
+
+      const finalLocation = (
+        actualLocation ? actualLocation : action.breakpoint.location
+      );
+      const finalLocationId = makeLocationId(finalLocation);
+      state = mergeIn(state, ['breakpoints', finalLocationId], {
+        disabled: false,
+        loading: false,
+        actor: actor,
+        text: text
+      });
+      emitChange('breakpoint-updated', state.breakpoints[finalLocationId]);
+      return state;
+    }
+    else if (action.status === 'error') {
+      // Remove the optimistic update
+      emitChange('breakpoint-removed', state.breakpoints[id]);
+      return deleteIn(state, ['breakpoints', id]);
+    }
+      break;
+  }
+
+  case constants.REMOVE_BREAKPOINT: {
+    if (action.status === 'done') {
+      const id = makeLocationId(action.breakpoint.location);
+      const bp = state.breakpoints[id];
+
+      if (action.disabled) {
+        state = mergeIn(state, ['breakpoints', id],
+                        { loading: false, disabled: true });
+        emitChange('breakpoint-disabled', state.breakpoints[id]);
+        return state;
+      }
+
+      state = deleteIn(state, ['breakpoints', id]);
+      emitChange('breakpoint-removed', bp);
+      return state;
+    }
+    break;
+  }
+
+  case constants.SET_BREAKPOINT_CONDITION: {
+    const id = makeLocationId(action.breakpoint.location);
+    const bp = state.breakpoints[id];
+
+    if (action.status === 'start') {
+      return mergeIn(state, ['breakpoints', id], {
+        loading: true,
+        condition: action.condition
+      });
+    }
+    else if (action.status === 'done') {
+      return mergeIn(state, ['breakpoints', id], {
+        loading: false,
+        // Setting a condition creates a new breakpoint client as of
+        // now, so we need to update the actor
+        actor: action.value.actor
+      });
+    }
+    else if (action.status === 'error') {
+      return deleteIn(state, ['breakpoints', id]);
+    }
+    break;
+  }}
+
+  return state;
+}
+
+module.exports = update;
--- a/devtools/client/debugger/content/reducers/event-listeners.js
+++ b/devtools/client/debugger/content/reducers/event-listeners.js
@@ -12,26 +12,26 @@ const initialState = {
   listeners: [],
   fetchingListeners: false,
 };
 
 function update(state = initialState, action, emit) {
   switch(action.type) {
   case constants.UPDATE_EVENT_BREAKPOINTS:
     state.activeEventNames = action.eventNames;
-    emit("@redux:activeEventNames", state.activeEventNames);
+    emit("activeEventNames", state.activeEventNames);
     break;
   case constants.FETCH_EVENT_LISTENERS:
     if (action.status === "begin") {
       state.fetchingListeners = true;
     }
     else if (action.status === "done") {
       state.fetchingListeners = false;
       state.listeners = action.listeners;
-      emit("@redux:listeners", state.listeners);
+      emit("event-listeners", state.listeners);
     }
     break;
   }
 
   return state;
 }
 
 module.exports = update;
--- a/devtools/client/debugger/content/reducers/index.js
+++ b/devtools/client/debugger/content/reducers/index.js
@@ -1,8 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const eventListeners = require('./event-listeners');
+const sources = require('./sources');
+const breakpoints = require('./breakpoints');
+const asyncRequests = require('./async-requests');
 
-exports.eventListeners = eventListeners;
+module.exports = {
+  eventListeners,
+  sources,
+  breakpoints,
+  asyncRequests
+};
--- a/devtools/client/debugger/content/reducers/moz.build
+++ b/devtools/client/debugger/content/reducers/moz.build
@@ -1,9 +1,12 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
+    'async-requests.js',
+    'breakpoints.js',
     'event-listeners.js',
-    'index.js'
+    'index.js',
+    'sources.js'
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/content/reducers/sources.js
@@ -0,0 +1,117 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const constants = require('../constants');
+const Immutable = require('devtools/client/shared/vendor/seamless-immutable');
+const { mergeIn, setIn } = require('../utils');
+
+const initialState = Immutable({
+  sources: {},
+  selectedSource: null,
+  selectedSourceOpts: null,
+  sourcesText: {}
+});
+
+function update(state = initialState, action, emitChange) {
+  switch(action.type) {
+  case constants.ADD_SOURCE:
+    emitChange('source', action.source);
+    return mergeIn(state, ['sources', action.source.actor], action.source);
+
+  case constants.LOAD_SOURCES:
+    if (action.status === 'done') {
+      // We don't need to actually load the sources into the state.
+      // Loading sources actually forces the server to emit several
+      // individual newSources packets which will eventually fire
+      // ADD_SOURCE actions.
+      //
+      // We still emit this event so that the UI can show an "empty
+      // text" label if no sources were loaded.
+      emitChange('sources', state.sources);
+    }
+    break;
+
+  case constants.SELECT_SOURCE:
+    emitChange('source-selected', action.source);
+    return state.merge({
+      selectedSource: action.source.actor,
+      selectedSourceOpts: action.opts
+    });
+
+  case constants.LOAD_SOURCE_TEXT: {
+    const s = _updateText(state, action);
+    emitChange('source-text-loaded', s.sources[action.source.actor]);
+    return s;
+  }
+
+  case constants.BLACKBOX:
+    if (action.status === 'done') {
+      const s = mergeIn(state,
+                        ['sources', action.source.actor, 'isBlackBoxed'],
+                        action.value.isBlackBoxed);
+      emitChange('blackboxed', s.sources[action.source.actor]);
+      return s;
+    }
+    break;
+
+  case constants.TOGGLE_PRETTY_PRINT:
+    let s = state;
+    if (action.status === "error") {
+      s = mergeIn(state, ['sourcesText', action.source.actor], {
+        loading: false
+      });
+
+      // If it errored, just display the source as it way before.
+      emitChange('prettyprinted', s.sources[action.source.actor]);
+    }
+    else {
+      s = _updateText(state, action);
+      // Don't do this yet, the progress bar is still imperatively shown
+      // from the source view. We will fix in the next iteration.
+      // emitChange('source-text-loaded', s.sources[action.source.actor]);
+
+      if (action.status === 'done') {
+        s = mergeIn(s,
+                    ['sources', action.source.actor, 'isPrettyPrinted'],
+                    action.value.isPrettyPrinted);
+        emitChange('prettyprinted', s.sources[action.source.actor]);
+      }
+    }
+    return s;
+
+  case constants.UNLOAD:
+    // Reset the entire state to just the initial state, a blank state
+    // if you will.
+    return initialState;
+  }
+
+  return state;
+}
+
+function _updateText(state, action) {
+  const { source } = action;
+
+  if (action.status === 'start') {
+    // Merge this in, don't set it. That way the previous value is
+    // still stored here, and we can retrieve it if whatever we're
+    // doing fails.
+    return mergeIn(state, ['sourcesText', source.actor], {
+      loading: true
+    });
+  }
+  else if (action.status === 'error') {
+    return setIn(state, ['sourcesText', source.actor], {
+      error: action.error
+    });
+  }
+  else {
+    return setIn(state, ['sourcesText', source.actor], {
+      text: action.value.text,
+      contentType: action.value.contentType
+    });
+  }
+}
+
+module.exports = update;
--- a/devtools/client/debugger/content/utils.js
+++ b/devtools/client/debugger/content/utils.js
@@ -1,26 +1,31 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const { promiseInvoke } = require("devtools/shared/async-utils");
 const { reportException } = require("devtools/shared/DevToolsUtils");
 
+// RDP utils
+
 function rdpInvoke(client, method, ...args) {
-  return promiseInvoke(client, method, ...args)
+  return (promiseInvoke(client, method, ...args)
     .then((packet) => {
-      let { error, message } = packet;
-      if (error) {
-        throw new Error(error + ": " + message);
+      if (packet.error) {
+        let { error, message } = packet;
+        const err = new Error(error + ": " + message);
+        err.rdpError = error;
+        err.rdpMessage = message;
+        throw err;
       }
 
       return packet;
-    });
+    }));
 }
 
 function asPaused(client, func) {
   if (client.state != "paused") {
     return Task.spawn(function*() {
       yield rdpInvoke(client, client.interrupt);
       let result;
 
@@ -37,9 +42,63 @@ function asPaused(client, func) {
       yield rdpInvoke(client, client.resume);
       return result;
     });
   } else {
     return func();
   }
 }
 
-module.exports = { rdpInvoke, asPaused };
+function handleError(err) {
+  reportException("promise", err.toString());
+}
+
+function onReducerEvents(controller, listeners, thisContext) {
+  Object.keys(listeners).forEach(name => {
+    const listener = listeners[name];
+    controller.onChange(name, payload => {
+      listener.call(thisContext, payload);
+    });
+  });
+}
+
+function _getIn(destObj, path) {
+  return path.reduce(function(acc, name) {
+    return acc[name];
+  }, destObj);
+}
+
+function mergeIn(destObj, path, value) {
+  path = [...path];
+  path.reverse();
+  var obj = path.reduce(function(acc, name) {
+    return { [name]: acc };
+  }, value);
+
+  return destObj.merge(obj, { deep: true });
+}
+
+function setIn(destObj, path, value) {
+  destObj = mergeIn(destObj, path, null);
+  return mergeIn(destObj, path, value);
+}
+
+function updateIn(destObj, path, fn) {
+  return setIn(destObj, path, fn(_getIn(destObj, path)));
+}
+
+function deleteIn(destObj, path) {
+  const objPath = path.slice(0, -1);
+  const propName = path[path.length - 1];
+  const obj = _getIn(destObj, objPath);
+  return setIn(destObj, objPath, obj.without(propName));
+}
+
+module.exports = {
+  rdpInvoke,
+  asPaused,
+  handleError,
+  onReducerEvents,
+  mergeIn,
+  setIn,
+  updateIn,
+  deleteIn
+};
--- a/devtools/client/debugger/content/views/event-listeners-view.js
+++ b/devtools/client/debugger/content/views/event-listeners-view.js
@@ -4,29 +4,26 @@
 "use strict";
 
 const actions = require('../actions/event-listeners');
 const { bindActionCreators } = require('devtools/client/shared/vendor/redux');
 
 /**
  * Functions handling the event listeners UI.
  */
-function EventListenersView(store, DebuggerController) {
+function EventListenersView(controller) {
   dumpn("EventListenersView was instantiated");
 
-  this.actions = bindActionCreators(actions, store.dispatch);
-  this.getState = () => store.getState().eventListeners;
+  this.actions = bindActionCreators(actions, controller.dispatch);
+  this.getState = () => controller.getState().eventListeners;
 
   this._onCheck = this._onCheck.bind(this);
   this._onClick = this._onClick.bind(this);
-  this._onListeners = this._onListeners.bind(this);
 
-  this.Breakpoints = DebuggerController.Breakpoints;
-  this.controller = DebuggerController;
-  this.controller.on("@redux:listeners", this._onListeners);
+  controller.onChange("event-listeners", this.renderListeners.bind(this));
 }
 
 EventListenersView.prototype = Heritage.extend(WidgetMethods, {
   /**
    * Initialization function, called when the debugger is started.
    */
   initialize: function() {
     dumpn("Initializing the EventListenersView");
@@ -47,20 +44,18 @@ EventListenersView.prototype = Heritage.
   },
 
   /**
    * Destruction function, called when the debugger is closed.
    */
   destroy: function() {
     dumpn("Destroying the EventListenersView");
 
-    this.controller.off("@redux:listeners", this._onListeners);
     this.widget.removeEventListener("check", this._onCheck, false);
     this.widget.removeEventListener("click", this._onClick, false);
-    this.controller = this.Breakpoints = null;
   },
 
   renderListeners: function(listeners) {
     listeners.forEach(listener => {
       this.addListener(listener, { staged: true });
     });
 
     // Flushes all the prepared events into the event listeners container.
@@ -281,22 +276,15 @@ EventListenersView.prototype = Heritage.
     // when retrieving the target's item, to ignore the checkbox.
     let eventItem = this.getItemForElement(target, { noSiblings: true });
     if (eventItem) {
       let newState = eventItem.attachment.checkboxState ^= 1;
       this.callMethod("checkItem", eventItem.target, newState);
     }
   },
 
-  /**
-   * Called when listeners change.
-   */
-  _onListeners: function(_, listeners) {
-    this.renderListeners(listeners);
-  },
-
   _eventCheckboxTooltip: "",
   _onSelectorString: "",
   _inSourceString: "",
   _inNativeCodeString: ""
 });
 
 module.exports = EventListenersView;
--- a/devtools/client/debugger/content/views/moz.build
+++ b/devtools/client/debugger/content/views/moz.build
@@ -1,8 +1,9 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
-    'event-listeners-view.js'
+    'event-listeners-view.js',
+    'sources-view.js'
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/content/views/sources-view.js
@@ -0,0 +1,1317 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const utils = require('../utils');
+const {
+  getSelectedSource,
+  getSourceByURL,
+  getBreakpoint,
+  getBreakpoints,
+  makeLocationId
+} = require('../queries');
+const actions = Object.assign(
+  {},
+  require('../actions/sources'),
+  require('../actions/breakpoints')
+);
+const { bindActionCreators } = require('devtools/client/shared/vendor/redux');
+
+const NEW_SOURCE_DISPLAY_DELAY = 200; // ms
+const FUNCTION_SEARCH_POPUP_POSITION = "topcenter bottomleft";
+const BREAKPOINT_LINE_TOOLTIP_MAX_LENGTH = 1000; // chars
+const BREAKPOINT_CONDITIONAL_POPUP_POSITION = "before_start";
+const BREAKPOINT_CONDITIONAL_POPUP_OFFSET_X = 7; // px
+const BREAKPOINT_CONDITIONAL_POPUP_OFFSET_Y = -3; // px
+
+/**
+ * Functions handling the sources UI.
+ */
+function SourcesView(controller, DebuggerView) {
+  dumpn("SourcesView was instantiated");
+
+  utils.onReducerEvents(controller, {
+    "sources": this.renderSources,
+    "source": this.renderSource,
+    "blackboxed": this.renderBlackBoxed,
+    "prettyprinted": this.updateToolbarButtonsState,
+    "source-selected": this.renderSourceSelected,
+    "breakpoint-updated": bp => this.renderBreakpoint(bp),
+    "breakpoint-enabled": bp => this.renderBreakpoint(bp),
+    "breakpoint-disabled": bp => this.renderBreakpoint(bp),
+    "breakpoint-removed": bp => this.renderBreakpoint(bp, true),
+  }, this);
+
+  this.getState = controller.getState;
+  this.actions = bindActionCreators(actions, controller.dispatch);
+  this.DebuggerView = DebuggerView;
+  this.Parser = DebuggerController.Parser;
+
+  this.togglePrettyPrint = this.togglePrettyPrint.bind(this);
+  this.toggleBlackBoxing = this.toggleBlackBoxing.bind(this);
+  this.toggleBreakpoints = this.toggleBreakpoints.bind(this);
+
+  this._onEditorCursorActivity = this._onEditorCursorActivity.bind(this);
+  this._onMouseDown = this._onMouseDown.bind(this);
+  this._onSourceSelect = this._onSourceSelect.bind(this);
+  this._onStopBlackBoxing = this._onStopBlackBoxing.bind(this);
+  this._onBreakpointRemoved = this._onBreakpointRemoved.bind(this);
+  this._onBreakpointClick = this._onBreakpointClick.bind(this);
+  this._onBreakpointCheckboxClick = this._onBreakpointCheckboxClick.bind(this);
+  this._onConditionalPopupShowing = this._onConditionalPopupShowing.bind(this);
+  this._onConditionalPopupShown = this._onConditionalPopupShown.bind(this);
+  this._onConditionalPopupHiding = this._onConditionalPopupHiding.bind(this);
+  this._onConditionalTextboxKeyPress = this._onConditionalTextboxKeyPress.bind(this);
+  this._onCopyUrlCommand = this._onCopyUrlCommand.bind(this);
+  this._onNewTabCommand = this._onNewTabCommand.bind(this);
+}
+
+SourcesView.prototype = Heritage.extend(WidgetMethods, {
+  /**
+   * Initialization function, called when the debugger is started.
+   */
+  initialize: function() {
+    dumpn("Initializing the SourcesView");
+
+    this.widget = new SideMenuWidget(document.getElementById("sources"), {
+      contextMenu: document.getElementById("debuggerSourcesContextMenu"),
+      showArrows: true
+    });
+
+    this._preferredSourceURL = null;
+    this._unnamedSourceIndex = 0;
+    this.emptyText = L10N.getStr("noSourcesText");
+    this._blackBoxCheckboxTooltip = L10N.getStr("blackBoxCheckboxTooltip");
+
+    this._commandset = document.getElementById("debuggerCommands");
+    this._popupset = document.getElementById("debuggerPopupset");
+    this._cmPopup = document.getElementById("sourceEditorContextMenu");
+    this._cbPanel = document.getElementById("conditional-breakpoint-panel");
+    this._cbTextbox = document.getElementById("conditional-breakpoint-panel-textbox");
+    this._blackBoxButton = document.getElementById("black-box");
+    this._stopBlackBoxButton = document.getElementById("black-boxed-message-button");
+    this._prettyPrintButton = document.getElementById("pretty-print");
+    this._toggleBreakpointsButton = document.getElementById("toggle-breakpoints");
+    this._newTabMenuItem = document.getElementById("debugger-sources-context-newtab");
+    this._copyUrlMenuItem = document.getElementById("debugger-sources-context-copyurl");
+
+    this._noResultsFoundToolTip = new Tooltip(document);
+    this._noResultsFoundToolTip.defaultPosition = FUNCTION_SEARCH_POPUP_POSITION;
+
+    if (Prefs.prettyPrintEnabled) {
+      this._prettyPrintButton.removeAttribute("hidden");
+    }
+
+    this._editorContainer = document.getElementById("editor");
+    this._editorContainer.addEventListener("mousedown", this._onMouseDown, false);
+
+    this.widget.addEventListener("select", this._onSourceSelect, false);
+
+    this._stopBlackBoxButton.addEventListener("click", this._onStopBlackBoxing, false);
+    this._cbPanel.addEventListener("popupshowing", this._onConditionalPopupShowing, false);
+    this._cbPanel.addEventListener("popupshown", this._onConditionalPopupShown, false);
+    this._cbPanel.addEventListener("popuphiding", this._onConditionalPopupHiding, false);
+    this._cbTextbox.addEventListener("keypress", this._onConditionalTextboxKeyPress, false);
+    this._copyUrlMenuItem.addEventListener("command", this._onCopyUrlCommand, false);
+    this._newTabMenuItem.addEventListener("command", this._onNewTabCommand, false);
+
+    this.allowFocusOnRightClick = true;
+    this.autoFocusOnSelection = false;
+    this.autoFocusOnFirstItem = false;
+
+    // Sort the contents by the displayed label.
+    this.sortContents((aFirst, aSecond) => {
+      return +(aFirst.attachment.label.toLowerCase() >
+               aSecond.attachment.label.toLowerCase());
+    });
+
+    // Sort known source groups towards the end of the list
+    this.widget.groupSortPredicate = function(a, b) {
+      if ((a in KNOWN_SOURCE_GROUPS) == (b in KNOWN_SOURCE_GROUPS)) {
+        return a.localeCompare(b);
+      }
+      return (a in KNOWN_SOURCE_GROUPS) ? 1 : -1;
+    };
+
+    this._addCommands();
+  },
+
+  /**
+   * Destruction function, called when the debugger is closed.
+   */
+  destroy: function() {
+    dumpn("Destroying the SourcesView");
+
+    this.widget.removeEventListener("select", this._onSourceSelect, false);
+    this._stopBlackBoxButton.removeEventListener("click", this._onStopBlackBoxing, false);
+    this._cbPanel.removeEventListener("popupshowing", this._onConditionalPopupShowing, false);
+    this._cbPanel.removeEventListener("popupshowing", this._onConditionalPopupShown, false);
+    this._cbPanel.removeEventListener("popuphiding", this._onConditionalPopupHiding, false);
+    this._cbTextbox.removeEventListener("keypress", this._onConditionalTextboxKeyPress, false);
+    this._copyUrlMenuItem.removeEventListener("command", this._onCopyUrlCommand, false);
+    this._newTabMenuItem.removeEventListener("command", this._onNewTabCommand, false);
+  },
+
+  empty: function() {
+    WidgetMethods.empty.call(this);
+    this._unnamedSourceIndex = 0;
+    this._selectedBreakpoint = null;
+  },
+
+  /**
+   * Add commands that XUL can fire.
+   */
+  _addCommands: function() {
+    XULUtils.addCommands(this._commandset, {
+      addBreakpointCommand: e => this._onCmdAddBreakpoint(e),
+      addConditionalBreakpointCommand: e => this._onCmdAddConditionalBreakpoint(e),
+      blackBoxCommand: () => this.toggleBlackBoxing(),
+      unBlackBoxButton: () => this._onStopBlackBoxing(),
+      prettyPrintCommand: () => this.togglePrettyPrint(),
+      toggleBreakpointsCommand: () =>this.toggleBreakpoints(),
+      togglePromiseDebuggerCommand: () => this.togglePromiseDebugger(),
+      nextSourceCommand: () => this.selectNextItem(),
+      prevSourceCommand: () => this.selectPrevItem()
+    });
+  },
+
+  /**
+   * Sets the preferred location to be selected in this sources container.
+   * @param string aUrl
+   */
+  set preferredSource(aUrl) {
+    this._preferredValue = aUrl;
+
+    // Selects the element with the specified value in this sources container,
+    // if already inserted.
+    if (this.containsValue(aUrl)) {
+      this.selectedValue = aUrl;
+    }
+  },
+
+  sourcesDidUpdate: function() {
+    if (!getSelectedSource(this.getState())) {
+      let url = this._preferredSourceURL;
+      let source = url && getSourceByURL(this.getState(), url);
+      if (source) {
+        this.actions.selectSource(source);
+      }
+      else {
+        setNamedTimeout("new-source", NEW_SOURCE_DISPLAY_DELAY, () => {
+          if (!getSelectedSource(this.getState()) && this.itemCount > 0) {
+            this.actions.selectSource(this.getItemAtIndex(0).attachment.source);
+          }
+        });
+      }
+    }
+  },
+
+  renderSource: function(source) {
+    this.addSource(source, { staged: false });
+    for(let bp of getBreakpoints(this.getState())) {
+      if (bp.location.actor === source.actor) {
+        this.renderBreakpoint(bp);
+      }
+    }
+    this.sourcesDidUpdate();
+  },
+
+  renderSources: function(sources) {
+    if (Object.keys(sources).length === 0) {
+      this.emptyText = L10N.getStr("noSourcesText");
+    }
+  },
+
+  /**
+   * Adds a source to this sources container.
+   *
+   * @param object aSource
+   *        The source object coming from the active thread.
+   * @param object aOptions [optional]
+   *        Additional options for adding the source. Supported options:
+   *        - staged: true to stage the item to be appended later
+   */
+  addSource: function(aSource, aOptions = {}) {
+    if (!aSource.url && !aOptions.force) {
+      // We don't show any unnamed eval scripts yet (see bug 1124106)
+      return;
+    }
+
+    let { label, group, unicodeUrl } = this._parseUrl(aSource);
+
+    let contents = document.createElement("label");
+    contents.className = "plain dbg-source-item";
+    contents.setAttribute("value", label);
+    contents.setAttribute("crop", "start");
+    contents.setAttribute("flex", "1");
+    contents.setAttribute("tooltiptext", unicodeUrl);
+
+    // If the source is blackboxed, apply the appropriate style.
+    if (gThreadClient.source(aSource).isBlackBoxed) {
+      contents.classList.add("black-boxed");
+    }
+
+    // Append a source item to this container.
+    this.push([contents, aSource.actor], {
+      staged: aOptions.staged, /* stage the item to be appended later? */
+      attachment: {
+        label: label,
+        group: group,
+        checkboxState: !aSource.isBlackBoxed,
+        checkboxTooltip: this._blackBoxCheckboxTooltip,
+        source: aSource
+      }
+    });
+  },
+
+  _parseUrl: function(aSource) {
+    let fullUrl = aSource.url;
+    let url, unicodeUrl, label, group;
+
+    if (!fullUrl) {
+      unicodeUrl = 'SCRIPT' + this._unnamedSourceIndex++;
+      label = unicodeUrl;
+      group = L10N.getStr("anonymousSourcesLabel");
+    }
+    else {
+      let url = fullUrl.split(" -> ").pop();
+      label = aSource.addonPath ? aSource.addonPath : SourceUtils.getSourceLabel(url);
+      group = aSource.addonID ? aSource.addonID : SourceUtils.getSourceGroup(url);
+      unicodeUrl = NetworkHelper.convertToUnicode(unescape(fullUrl));
+    }
+
+    return {
+      label: label,
+      group: group,
+      unicodeUrl: unicodeUrl
+    };
+  },
+
+  renderBreakpoint: function(breakpoint, removed) {
+    if (removed) {
+      // Be defensive about the breakpoint not existing.
+      if (this._getBreakpoint(breakpoint)) {
+        this._removeBreakpoint(breakpoint)
+      }
+    }
+    else {
+      if (this._getBreakpoint(breakpoint)) {
+        this._updateBreakpointStatus(breakpoint);
+      }
+      else {
+        this._addBreakpoint(breakpoint);
+      }
+    }
+  },
+
+  /**
+   * Adds a breakpoint to this sources container.
+   *
+   * @param object aBreakpointClient
+   *               See Breakpoints.prototype._showBreakpoint
+   * @param object aOptions [optional]
+   *        @see DebuggerController.Breakpoints.addBreakpoint
+   */
+  _addBreakpoint: function(breakpoint, options = {}) {
+    let disabled = breakpoint.disabled;
+    let location = breakpoint.location;
+
+    // Get the source item to which the breakpoint should be attached.
+    let sourceItem = this.getItemByValue(location.actor);
+    if (!sourceItem) {
+      return;
+    }
+
+    // Create the element node and menu popup for the breakpoint item.
+    let breakpointArgs = Heritage.extend(breakpoint.asMutable(), options);
+    let breakpointView = this._createBreakpointView.call(this, breakpointArgs);
+    let contextMenu = this._createContextMenu.call(this, breakpointArgs);
+
+    // Append a breakpoint child item to the corresponding source item.
+    sourceItem.append(breakpointView.container, {
+      attachment: Heritage.extend(breakpointArgs, {
+        actor: location.actor,
+        line: location.line,
+        view: breakpointView,
+        popup: contextMenu
+      }),
+      attributes: [
+        ["contextmenu", contextMenu.menupopupId]
+      ],
+      // Make sure that when the breakpoint item is removed, the corresponding
+      // menupopup and commandset are also destroyed.
+      finalize: this._onBreakpointRemoved
+    });
+
+    if (typeof breakpoint.condition === "string") {
+      this.highlightBreakpoint(breakpoint.location, {
+        openPopup: true,
+        noEditorUpdate: true
+      });
+    }
+
+    window.emit(EVENTS.BREAKPOINT_SHOWN_IN_PANE);
+  },
+
+  /**
+   * Removes a breakpoint from this sources container.
+   * It does not also remove the breakpoint from the controller. Be careful.
+   *
+   * @param object aLocation
+   *        @see DebuggerController.Breakpoints.addBreakpoint
+   */
+  _removeBreakpoint: function(breakpoint) {
+    // When a parent source item is removed, all the child breakpoint items are
+    // also automagically removed.
+    let sourceItem = this.getItemByValue(breakpoint.location.actor);
+    if (!sourceItem) {
+      return;
+    }
+
+    // Clear the breakpoint view.
+    sourceItem.remove(this._getBreakpoint(breakpoint));
+
+    if (this._selectedBreakpoint &&
+       (queries.makeLocationId(this._selectedBreakpoint.location) ===
+        queries.makeLocationId(breakpoint.location))) {
+      this._selectedBreakpoint = null;
+    }
+
+    window.emit(EVENTS.BREAKPOINT_HIDDEN_IN_PANE);
+  },
+
+  _getBreakpoint: function(bp) {
+    return this.getItemForPredicate(item => {
+      return item.attachment.actor === bp.location.actor &&
+        item.attachment.line === bp.location.line;
+    });
+  },
+
+  /**
+   * Updates a breakpoint.
+   *
+   * @param object breakpoint
+   */
+  _updateBreakpointStatus: function(breakpoint) {
+    let location = breakpoint.location;
+    let breakpointItem = this._getBreakpoint(getBreakpoint(this.getState(), location));
+    if (!breakpointItem) {
+      return promise.reject(new Error("No breakpoint found."));
+    }
+
+    // Breakpoint will now be enabled.
+    let attachment = breakpointItem.attachment;
+
+    // Update the corresponding menu items to reflect the enabled state.
+    let prefix = "bp-cMenu-"; // "breakpoints context menu"
+    let identifier = makeLocationId(location);
+    let enableSelfId = prefix + "enableSelf-" + identifier + "-menuitem";
+    let disableSelfId = prefix + "disableSelf-" + identifier + "-menuitem";
+    let enableSelf = document.getElementById(enableSelfId);
+    let disableSelf = document.getElementById(disableSelfId);
+
+    if (breakpoint.disabled) {
+      enableSelf.removeAttribute("hidden");
+      disableSelf.setAttribute("hidden", true);
+      attachment.view.checkbox.removeAttribute("checked");
+    }
+    else {
+      enableSelf.setAttribute("hidden", true);
+      disableSelf.removeAttribute("hidden");
+      attachment.view.checkbox.setAttribute("checked", "true");
+
+      // Update the breakpoint toggle button checked state.
+      this._toggleBreakpointsButton.removeAttribute("checked");
+    }
+
+  },
+
+  /**
+   * Highlights a breakpoint in this sources container.
+   *
+   * @param object aLocation
+   *        @see DebuggerController.Breakpoints.addBreakpoint
+   * @param object aOptions [optional]
+   *        An object containing some of the following boolean properties:
+   *          - openPopup: tells if the expression popup should be shown.
+   *          - noEditorUpdate: tells if you want to skip editor updates.
+   */
+  highlightBreakpoint: function(aLocation, aOptions = {}) {
+    let breakpoint = getBreakpoint(this.getState(), aLocation);
+    if (!breakpoint) {
+      return;
+    }
+
+    // Breakpoint will now be selected.
+    this._selectBreakpoint(breakpoint);
+
+    // Update the editor location if necessary.
+    if (!aOptions.noEditorUpdate) {
+      this.DebuggerView.setEditorLocation(aLocation.actor, aLocation.line, { noDebug: true });
+    }
+
+    // If the breakpoint requires a new conditional expression, display
+    // the panel to input the corresponding expression.
+    if (aOptions.openPopup) {
+      return this._openConditionalPopup();
+    } else {
+      return this._hideConditionalPopup();
+    }
+  },
+
+  /**
+   * Highlight the breakpoint on the current currently focused line/column
+   * if it exists.
+   */
+  highlightBreakpointAtCursor: function() {
+    let actor = this.selectedValue;
+    let line = this.DebuggerView.editor.getCursor().line + 1;
+
+    let location = { actor: actor, line: line };
+    this.highlightBreakpoint(location, { noEditorUpdate: true });
+  },
+
+  /**
+   * Unhighlights the current breakpoint in this sources container.
+   */
+  unhighlightBreakpoint: function() {
+    this._hideConditionalPopup();
+    this._unselectBreakpoint();
+  },
+
+   /**
+    * Display the message thrown on breakpoint condition
+    */
+  showBreakpointConditionThrownMessage: function(aLocation, aMessage = "") {
+    let breakpointItem = this._getBreakpoint(getBreakpoint(this.getState(), aLocation));
+    if (!breakpointItem) {
+      return;
+    }
+    let attachment = breakpointItem.attachment;
+    attachment.view.container.classList.add("dbg-breakpoint-condition-thrown");
+    attachment.view.message.setAttribute("value", aMessage);
+  },
+
+  /**
+   * Update the checked/unchecked and enabled/disabled states of the buttons in
+   * the sources toolbar based on the currently selected source's state.
+   */
+  updateToolbarButtonsState: function(source) {
+    if (source.isBlackBoxed) {
+      this._blackBoxButton.setAttribute("checked", true);
+      this._prettyPrintButton.setAttribute("checked", true);
+    } else {
+      this._blackBoxButton.removeAttribute("checked");
+      this._prettyPrintButton.removeAttribute("checked");
+    }
+
+    if (source.isPrettyPrinted) {
+      this._prettyPrintButton.setAttribute("checked", true);
+    } else {
+      this._prettyPrintButton.removeAttribute("checked");
+    }
+  },
+
+  /**
+   * Toggle the pretty printing of the selected source.
+   */
+  togglePrettyPrint: function() {
+    if (this._prettyPrintButton.hasAttribute("disabled")) {
+      return;
+    }
+
+    this.DebuggerView.showProgressBar();
+    const source = getSelectedSource(this.getState());
+    const sourceClient = gThreadClient.source(source);
+    const shouldPrettyPrint = !source.isPrettyPrinted;
+
+    // This is only here to give immediate feedback,
+    // `renderPrettyPrinted` will set the final status of the buttons
+    if (shouldPrettyPrint) {
+      this._prettyPrintButton.setAttribute("checked", true);
+    } else {
+      this._prettyPrintButton.removeAttribute("checked");
+    }
+
+    this.actions.togglePrettyPrint(source);
+  },
+
+  /**
+   * Toggle the black boxed state of the selected source.
+   */
+  toggleBlackBoxing: Task.async(function*() {
+    const source = getSelectedSource(this.getState());
+    const shouldBlackBox = !source.isBlackBoxed;
+
+    // Be optimistic that the (un-)black boxing will succeed, so
+    // enable/disable the pretty print button and check/uncheck the
+    // black box button immediately.
+    if (shouldBlackBox) {
+      this._prettyPrintButton.setAttribute("disabled", true);
+      this._blackBoxButton.setAttribute("checked", true);
+    } else {
+      this._prettyPrintButton.removeAttribute("disabled");
+      this._blackBoxButton.removeAttribute("checked");
+    }
+
+    this.actions.blackbox(source, shouldBlackBox);
+  }),
+
+  renderBlackBoxed: function(source) {
+    const sourceItem = this.getItemByValue(source.actor);
+    sourceItem.prebuiltNode.classList.toggle(
+      "black-boxed",
+      source.isBlackBoxed
+    );
+
+    if (getSelectedSource(this.getState()).actor === source.actor) {
+      this.updateToolbarButtonsState(source);
+    }
+  },
+
+  /**
+   * Toggles all breakpoints enabled/disabled.
+   */
+  toggleBreakpoints: function() {
+    let breakpoints = getBreakpoints(this.getState());
+    let hasBreakpoints = breakpoints.length > 0;
+    let hasEnabledBreakpoints = breakpoints.some(bp => !bp.disabled);
+
+    if (hasBreakpoints && hasEnabledBreakpoints) {
+      this._toggleBreakpointsButton.setAttribute("checked", true);
+      this._onDisableAll();
+    } else {
+      this._toggleBreakpointsButton.removeAttribute("checked");
+      this._onEnableAll();
+    }
+  },
+
+  togglePromiseDebugger: function() {
+    if (Prefs.promiseDebuggerEnabled) {
+      let promisePane = this.DebuggerView._promisePane;
+      promisePane.hidden = !promisePane.hidden;
+
+      if (!this.DebuggerView._promiseDebuggerIframe) {
+        this.DebuggerView._initializePromiseDebugger();
+      }
+    }
+  },
+
+  hidePrettyPrinting: function() {
+    this._prettyPrintButton.style.display = 'none';
+
+    if (this._blackBoxButton.style.display === 'none') {
+      let sep = document.querySelector('#sources-toolbar .devtools-separator');
+      sep.style.display = 'none';
+    }
+  },
+
+  hideBlackBoxing: function() {
+    this._blackBoxButton.style.display = 'none';
+
+    if (this._prettyPrintButton.style.display === 'none') {
+      let sep = document.querySelector('#sources-toolbar .devtools-separator');
+      sep.style.display = 'none';
+    }
+  },
+
+  getDisplayURL: function(source) {
+    if (!source.url) {
+      return this.getItemByValue(source.actor).attachment.label;
+    }
+    return NetworkHelper.convertToUnicode(unescape(source.url))
+  },
+
+  /**
+   * Marks a breakpoint as selected in this sources container.
+   *
+   * @param object aItem
+   *        The breakpoint item to select.
+   */
+  _selectBreakpoint: function(bp) {
+    if (this._selectedBreakpoint === bp) {
+      return;
+    }
+    this._unselectBreakpoint();
+    this._selectedBreakpoint = bp;
+
+    const item = this._getBreakpoint(bp);
+    item.target.classList.add("selected");
+
+    // Ensure the currently selected breakpoint is visible.
+    this.widget.ensureElementIsVisible(item.target);
+  },
+
+  /**
+   * Marks the current breakpoint as unselected in this sources container.
+   */
+  _unselectBreakpoint: function() {
+    if (!this._selectedBreakpoint) {
+      return;
+    }
+
+    const item = this._getBreakpoint(this._selectedBreakpoint);
+    item.target.classList.remove("selected");
+
+    this._selectedBreakpoint = null;
+  },
+
+  /**
+   * Opens a conditional breakpoint's expression input popup.
+   */
+  _openConditionalPopup: function() {
+    let breakpointItem = this._getBreakpoint(this._selectedBreakpoint);
+    let attachment = breakpointItem.attachment;
+    // Check if this is an enabled conditional breakpoint, and if so,
+    // retrieve the current conditional epression.
+    let bp = getBreakpoint(this.getState(), attachment);
+    let expr = (bp ? (bp.condition || "") : "");
+
+    // Update the conditional expression textbox. If no expression was
+    // previously set, revert to using an empty string by default.
+    this._cbTextbox.value = expr;
+
+    // Show the conditional expression panel. The popup arrow should be pointing
+    // at the line number node in the breakpoint item view.
+    this._cbPanel.hidden = false;
+    this._cbPanel.openPopup(breakpointItem.attachment.view.lineNumber,
+                            BREAKPOINT_CONDITIONAL_POPUP_POSITION,
+                            BREAKPOINT_CONDITIONAL_POPUP_OFFSET_X,
+                            BREAKPOINT_CONDITIONAL_POPUP_OFFSET_Y);
+  },
+
+  /**
+   * Hides a conditional breakpoint's expression input popup.
+   */
+  _hideConditionalPopup: function() {
+    this._cbPanel.hidden = true;
+
+    // Sometimes this._cbPanel doesn't have hidePopup method which doesn't
+    // break anything but simply outputs an exception to the console.
+    if (this._cbPanel.hidePopup) {
+      this._cbPanel.hidePopup();
+    }
+  },
+
+  /**
+   * Customization function for creating a breakpoint item's UI.
+   *
+   * @param object aOptions
+   *        A couple of options or flags supported by this operation:
+   *          - location: the breakpoint's source location and line number
+   *          - disabled: the breakpoint's disabled state, boolean
+   *          - text: the breakpoint's line text to be displayed
+   *          - message: thrown string when the breakpoint condition throws
+   * @return object
+   *         An object containing the breakpoint container, checkbox,
+   *         line number and line text nodes.
+   */
+  _createBreakpointView: function(aOptions) {
+    let { location, disabled, text, message } = aOptions;
+    let identifier = makeLocationId(location);
+
+    let checkbox = document.createElement("checkbox");
+    if (!disabled) {
+      checkbox.setAttribute("checked", true);
+    }
+    checkbox.className = "dbg-breakpoint-checkbox";
+
+    let lineNumberNode = document.createElement("label");
+    lineNumberNode.className = "plain dbg-breakpoint-line";
+    lineNumberNode.setAttribute("value", location.line);
+
+    let lineTextNode = document.createElement("label");
+    lineTextNode.className = "plain dbg-breakpoint-text";
+    lineTextNode.setAttribute("value", text);
+    lineTextNode.setAttribute("crop", "end");
+    lineTextNode.setAttribute("flex", "1");
+
+    let tooltip = text ? text.substr(0, BREAKPOINT_LINE_TOOLTIP_MAX_LENGTH) : "";
+    lineTextNode.setAttribute("tooltiptext", tooltip);
+
+    let thrownNode = document.createElement("label");
+    thrownNode.className = "plain dbg-breakpoint-condition-thrown-message dbg-breakpoint-text";
+    thrownNode.setAttribute("value", message);
+    thrownNode.setAttribute("crop", "end");
+    thrownNode.setAttribute("flex", "1");
+
+    let bpLineContainer = document.createElement("hbox");
+    bpLineContainer.className = "plain dbg-breakpoint-line-container";
+    bpLineContainer.setAttribute("flex", "1");
+
+    bpLineContainer.appendChild(lineNumberNode);
+    bpLineContainer.appendChild(lineTextNode);
+
+    let bpDetailContainer = document.createElement("vbox");
+    bpDetailContainer.className = "plain dbg-breakpoint-detail-container";
+    bpDetailContainer.setAttribute("flex", "1");
+
+    bpDetailContainer.appendChild(bpLineContainer);
+    bpDetailContainer.appendChild(thrownNode);
+
+    let container = document.createElement("hbox");
+    container.id = "breakpoint-" + identifier;
+    container.className = "dbg-breakpoint side-menu-widget-item-other";
+    container.classList.add("devtools-monospace");
+    container.setAttribute("align", "center");
+    container.setAttribute("flex", "1");
+
+    container.addEventListener("click", this._onBreakpointClick, false);
+    checkbox.addEventListener("click", this._onBreakpointCheckboxClick, false);
+
+    container.appendChild(checkbox);
+    container.appendChild(bpDetailContainer);
+
+    return {
+      container: container,
+      checkbox: checkbox,
+      lineNumber: lineNumberNode,
+      lineText: lineTextNode,
+      message: thrownNode
+    };
+  },
+
+  /**
+   * Creates a context menu for a breakpoint element.
+   *
+   * @param object aOptions
+   *        A couple of options or flags supported by this operation:
+   *          - location: the breakpoint's source location and line number
+   *          - disabled: the breakpoint's disabled state, boolean
+   * @return object
+   *         An object containing the breakpoint commandset and menu popup ids.
+   */
+  _createContextMenu: function(aOptions) {
+    let { location, disabled } = aOptions;
+    let identifier = makeLocationId(location);
+
+    let commandset = document.createElement("commandset");
+    let menupopup = document.createElement("menupopup");
+    commandset.id = "bp-cSet-" + identifier;
+    menupopup.id = "bp-mPop-" + identifier;
+
+    createMenuItem.call(this, "enableSelf", !disabled);
+    createMenuItem.call(this, "disableSelf", disabled);
+    createMenuItem.call(this, "deleteSelf");
+    createMenuSeparator();
+    createMenuItem.call(this, "setConditional");
+    createMenuSeparator();
+    createMenuItem.call(this, "enableOthers");
+    createMenuItem.call(this, "disableOthers");
+    createMenuItem.call(this, "deleteOthers");
+    createMenuSeparator();
+    createMenuItem.call(this, "enableAll");
+    createMenuItem.call(this, "disableAll");
+    createMenuSeparator();
+    createMenuItem.call(this, "deleteAll");
+
+    this._popupset.appendChild(menupopup);
+    this._commandset.appendChild(commandset);
+
+    return {
+      commandsetId: commandset.id,
+      menupopupId: menupopup.id
+    };
+
+    /**
+     * Creates a menu item specified by a name with the appropriate attributes
+     * (label and handler).
+     *
+     * @param string aName
+     *        A global identifier for the menu item.
+     * @param boolean aHiddenFlag
+     *        True if this menuitem should be hidden.
+     */
+    function createMenuItem(aName, aHiddenFlag) {
+      let menuitem = document.createElement("menuitem");
+      let command = document.createElement("command");
+
+      let prefix = "bp-cMenu-"; // "breakpoints context menu"
+      let commandId = prefix + aName + "-" + identifier + "-command";
+      let menuitemId = prefix + aName + "-" + identifier + "-menuitem";
+
+      let label = L10N.getStr("breakpointMenuItem." + aName);
+      let func = "_on" + aName.charAt(0).toUpperCase() + aName.slice(1);
+
+      command.id = commandId;
+      command.setAttribute("label", label);
+      command.addEventListener("command", () => this[func](location), false);
+
+      menuitem.id = menuitemId;
+      menuitem.setAttribute("command", commandId);
+      aHiddenFlag && menuitem.setAttribute("hidden", "true");
+
+      commandset.appendChild(command);
+      menupopup.appendChild(menuitem);
+    }
+
+    /**
+     * Creates a simple menu separator element and appends it to the current
+     * menupopup hierarchy.
+     */
+    function createMenuSeparator() {
+      let menuseparator = document.createElement("menuseparator");
+      menupopup.appendChild(menuseparator);
+    }
+  },
+
+  /**
+   * Copy the source url from the currently selected item.
+   */
+  _onCopyUrlCommand: function() {
+    let selected = this.selectedItem && this.selectedItem.attachment;
+    if (!selected) {
+      return;
+    }
+    clipboardHelper.copyString(selected.source.url);
+  },
+
+  /**
+   * Opens selected item source in a new tab.
+   */
+  _onNewTabCommand: function() {
+    let win = Services.wm.getMostRecentWindow("navigator:browser");
+    let selected = this.selectedItem.attachment;
+    win.openUILinkIn(selected.source.url, "tab", { relatedToCurrent: true });
+  },
+
+  /**
+   * Function called each time a breakpoint item is removed.
+   *
+   * @param object aItem
+   *        The corresponding item.
+   */
+  _onBreakpointRemoved: function(aItem) {
+    dumpn("Finalizing breakpoint item: " + aItem.stringify());
+
+    // Destroy the context menu for the breakpoint.
+    let contextMenu = aItem.attachment.popup;
+    document.getElementById(contextMenu.commandsetId).remove();
+    document.getElementById(contextMenu.menupopupId).remove();
+  },
+
+  _onMouseDown: function(e) {
+    this.hideNoResultsTooltip();
+
+    if (!e.metaKey) {
+      return;
+    }
+
+    let editor = this.DebuggerView.editor;
+    let identifier = this._findIdentifier(e.clientX, e.clientY);
+
+    if (!identifier) {
+        return;
+    }
+
+    let foundDefinitions = this._getFunctionDefinitions(identifier);
+
+    if (!foundDefinitions || !foundDefinitions.definitions) {
+      return;
+    }
+
+    this._showFunctionDefinitionResults(identifier, foundDefinitions.definitions, editor);
+  },
+
+  /**
+   * Searches for function definition of a function in a given source file
+   */
+
+  _findDefinition: function(parsedSource, aName) {
+    let functionDefinitions = parsedSource.getNamedFunctionDefinitions(aName);
+
+    let resultList = [];
+
+    if (!functionDefinitions || !functionDefinitions.length || !functionDefinitions[0].length) {
+      return {
+        definitions: resultList
+      };
+    }
+
+    // functionDefinitions is a list with an object full of metadata,
+    // extract the data and use to construct a more useful, less
+    // cluttered, contextual list
+    for (let i = 0; i < functionDefinitions.length; i++) {
+      let functionDefinition = {
+        source: functionDefinitions[i].sourceUrl,
+        startLine: functionDefinitions[i][0].functionLocation.start.line,
+        startColumn: functionDefinitions[i][0].functionLocation.start.column,
+        name: functionDefinitions[i][0].functionName
+      }
+
+      resultList.push(functionDefinition)
+    }
+
+    return {
+     definitions: resultList
+    };
+  },
+
+  /**
+   * Searches for an identifier underneath the specified position in the
+   * source editor.
+   *
+   * @param number x, y
+   *        The left/top coordinates where to look for an identifier.
+   */
+  _findIdentifier: function(x, y) {
+    let parsedSource = SourceUtils.parseSource(this.DebuggerView, this.Parser);
+    let identifierInfo = SourceUtils.findIdentifier(this.DebuggerView.editor, parsedSource, x, y);
+
+    // Not hovering over an identifier
+    if (!identifierInfo) {
+        return;
+    }
+
+    return identifierInfo;
+  },
+
+  /**
+   * The selection listener for the source editor.
+   */
+  _onEditorCursorActivity: function(e) {
+    let editor = this.DebuggerView.editor;
+    let start = editor.getCursor("start").line + 1;
+    let end = editor.getCursor().line + 1;
+    let source = getSelectedSource(this.getState());
+
+    if (source) {
+      let location = { actor: source.actor, line: start };
+      if (getBreakpoint(this.getState(), location) && start == end) {
+        this.highlightBreakpoint(location, { noEditorUpdate: true });
+      } else {
+        this.unhighlightBreakpoint();
+      }
+    }
+  },
+
+  /*
+   * Uses function definition data to perform actions in different
+   * cases of how many locations were found: zero, one, or multiple definitions
+   */
+  _showFunctionDefinitionResults: function(aHoveredFunction, aDefinitionList, aEditor) {
+    let definitions = aDefinitionList;
+    let hoveredFunction = aHoveredFunction;
+
+    //show a popup saying no results were found
+    if (definitions.length == 0) {
+      this._noResultsFoundToolTip.setTextContent({
+          messages: [L10N.getStr("noMatchingStringsText")]
+      });
+
+      this._markedIdentifier = aEditor.markText(
+        { line: hoveredFunction.location.start.line - 1, ch: hoveredFunction.location.start.column },
+        { line: hoveredFunction.location.end.line - 1, ch: hoveredFunction.location.end.column });
+
+      this._noResultsFoundToolTip.show(this._markedIdentifier.anchor);
+
+    } else if (definitions.length == 1) {
+      this.DebuggerView.setEditorLocation(definitions[0].source, definitions[0].startLine);
+    } else {
+      // TODO: multiple definitions found, do something else
+      this.DebuggerView.setEditorLocation(definitions[0].source, definitions[0].startLine);
+    }
+},
+
+  /**
+   * Hides the tooltip and clear marked text popup.
+   */
+  hideNoResultsTooltip: function() {
+    this._noResultsFoundToolTip.hide();
+    if (this._markedIdentifier) {
+      this._markedIdentifier.clear();
+      this._markedIdentifier = null;
+    }
+  },
+
+  /*
+   * Gets the definition locations from function metadata
+   */
+  _getFunctionDefinitions: function(aIdentifierInfo) {
+    let parsedSource = SourceUtils.parseSource(this.DebuggerView, this.Parser);
+    let definition_info = this._findDefinition(parsedSource, aIdentifierInfo.name);
+
+    //Did not find any definitions for the identifier
+    if (!definition_info) {
+      return;
+    }
+
+    return definition_info;
+  },
+
+  /**
+   * The select listener for the sources container.
+   */
+  _onSourceSelect: function({ detail: sourceItem }) {
+    if (!sourceItem) {
+      return;
+    }
+
+    const { source } = sourceItem.attachment;
+    this.actions.selectSource(source);
+  },
+
+  renderSourceSelected: function(source) {
+    // Set window title. No need to split the url by " -> " here,
+    // because it was already sanitized when the source was added.
+    document.title = L10N.getFormatStr("DebuggerWindowScriptTitle", source.url);
+
+    if (source.url) {
+      this._preferredSourceURL = source.url;
+    }
+    this.updateToolbarButtonsState(source);
+    this._selectItem(this.getItemByValue(source.actor));
+  },
+
+  /**
+   * The click listener for the "stop black boxing" button.
+   */
+  _onStopBlackBoxing: Task.async(function*() {
+    this.actions.blackbox(getSelectedSource(this.getState()), false);
+  }),
+
+  /**
+   * The click listener for a breakpoint container.
+   */
+  _onBreakpointClick: function(e) {
+    let sourceItem = this.getItemForElement(e.target);
+    let breakpointItem = this.getItemForElement.call(sourceItem, e.target);
+    let attachment = breakpointItem.attachment;
+    let bp = getBreakpoint(this.getState(), attachment);
+    if (bp) {
+      this.highlightBreakpoint(bp.location, {
+        openPopup: bp.condition && e.button == 0
+      });
+    } else {
+      this.highlightBreakpoint(bp.location);
+    }
+  },
+
+  /**
+   * The click listener for a breakpoint checkbox.
+   */
+  _onBreakpointCheckboxClick: function(e) {
+    let sourceItem = this.getItemForElement(e.target);
+    let breakpointItem = this.getItemForElement.call(sourceItem, e.target);
+    let bp = getBreakpoint(this.getState(), breakpointItem.attachment);
+
+    if (bp.disabled) {
+      this.actions.enableBreakpoint(bp.location);
+    }
+    else {
+      this.actions.disableBreakpoint(bp.location);
+    }
+
+    // Don't update the editor location (avoid propagating into _onBreakpointClick).
+    e.preventDefault();
+    e.stopPropagation();
+  },
+
+  /**
+   * The popup showing listener for the breakpoints conditional expression panel.
+   */
+  _onConditionalPopupShowing: function() {
+    this._conditionalPopupVisible = true; // Used in tests.
+    window.emit(EVENTS.CONDITIONAL_BREAKPOINT_POPUP_SHOWING);
+  },
+
+  /**
+   * The popup shown listener for the breakpoints conditional expression panel.
+   */
+  _onConditionalPopupShown: function() {
+    this._cbTextbox.focus();
+    this._cbTextbox.select();
+  },
+
+  /**
+   * The popup hiding listener for the breakpoints conditional expression panel.
+   */
+  _onConditionalPopupHiding: function() {
+    this._conditionalPopupVisible = false; // Used in tests.
+
+    // Check if this is an enabled conditional breakpoint, and if so,
+    // save the current conditional expression.
+    let bp = this._selectedBreakpoint;
+    if (bp) {
+      let condition = this._cbTextbox.value;
+      this.actions.setBreakpointCondition(bp.location, condition);
+    }
+  },
+
+  /**
+   * The keypress listener for the breakpoints conditional expression textbox.
+   */
+  _onConditionalTextboxKeyPress: function(e) {
+    if (e.keyCode == e.DOM_VK_RETURN) {
+      this._hideConditionalPopup();
+    }
+  },
+
+  /**
+   * Called when the add breakpoint key sequence was pressed.
+   */
+  _onCmdAddBreakpoint: function(e) {
+    let actor = this.selectedValue;
+    let line = (e && e.sourceEvent.target.tagName == 'menuitem' ?
+                this.DebuggerView.clickedLine + 1 :
+                this.DebuggerView.editor.getCursor().line + 1);
+    let location = { actor, line };
+    let bp = getBreakpoint(this.getState(), location);
+
+    // If a breakpoint already existed, remove it now.
+    if (bp) {
+      this.actions.removeBreakpoint(bp.location);
+    }
+    // No breakpoint existed at the required location, add one now.
+    else {
+      this.actions.addBreakpoint(location);
+    }
+  },
+
+  /**
+   * Called when the add conditional breakpoint key sequence was pressed.
+   */
+  _onCmdAddConditionalBreakpoint: function(e) {
+    let actor = this.selectedValue;
+    let line = (e && e.sourceEvent.target.tagName == 'menuitem' ?
+                this.DebuggerView.clickedLine + 1 :
+                this.DebuggerView.editor.getCursor().line + 1);
+    let location = { actor, line };
+    let bp = getBreakpoint(this.getState(), location);
+
+    // If a breakpoint already existed or wasn't a conditional, morph it now.
+    if (bp) {
+      this.highlightBreakpoint(bp.location, { openPopup: true });
+    }
+    // No breakpoint existed at the required location, add one now.
+    else {
+      this.actions.addBreakpoint(location, "");
+    }
+  },
+
+  getOtherBreakpoints: function(location) {
+    const bps = getBreakpoints(this.getState());
+    if (location) {
+      return bps.filter(bp => {
+        return (bp.location.actor !== location.actor ||
+                bp.location.line !== location.line);
+      });
+    }
+    return bps;
+  },
+
+  /**
+   * Function invoked on the "setConditional" menuitem command.
+   *
+   * @param object aLocation
+   *        @see DebuggerController.Breakpoints.addBreakpoint
+   */
+  _onSetConditional: function(aLocation) {
+    // Highlight the breakpoint and show a conditional expression popup.
+    this.highlightBreakpoint(aLocation, { openPopup: true });
+  },
+
+  /**
+   * Function invoked on the "enableSelf" menuitem command.
+   *
+   * @param object aLocation
+   *        @see DebuggerController.Breakpoints.addBreakpoint
+   */
+  _onEnableSelf: function(aLocation) {
+    // Enable the breakpoint, in this container and the controller store.
+    this.actions.enableBreakpoint(aLocation);
+  },
+
+  /**
+   * Function invoked on the "disableSelf" menuitem command.
+   *
+   * @param object aLocation
+   *        @see DebuggerController.Breakpoints.addBreakpoint
+   */
+  _onDisableSelf: function(aLocation) {
+    const bp = getBreakpoint(this.getState(), aLocation);
+    if (!bp.disabled) {
+      this.actions.disableBreakpoint(aLocation);
+    }
+  },
+
+  /**
+   * Function invoked on the "deleteSelf" menuitem command.
+   *
+   * @param object aLocation
+   *        @see DebuggerController.Breakpoints.addBreakpoint
+   */
+  _onDeleteSelf: function(aLocation) {
+    this.actions.removeBreakpoint(aLocation);
+  },
+
+  /**
+   * Function invoked on the "enableOthers" menuitem command.
+   *
+   * @param object aLocation
+   *        @see DebuggerController.Breakpoints.addBreakpoint
+   */
+  _onEnableOthers: function(aLocation) {
+    let other = this.getOtherBreakpoints(aLocation);
+    // TODO(jwl): batch these and interrupt the thread for all of them
+    other.forEach(bp => this._onEnableSelf(bp.location));
+  },
+
+  /**
+   * Function invoked on the "disableOthers" menuitem command.
+   *
+   * @param object aLocation
+   *        @see DebuggerController.Breakpoints.addBreakpoint
+   */
+  _onDisableOthers: function(aLocation) {
+    let other = this.getOtherBreakpoints(aLocation);
+    other.forEach(bp => this._onDisableSelf(bp.location));
+  },
+
+  /**
+   * Function invoked on the "deleteOthers" menuitem command.
+   *
+   * @param object aLocation
+   *        @see DebuggerController.Breakpoints.addBreakpoint
+   */
+  _onDeleteOthers: function(aLocation) {
+    let other = this.getOtherBreakpoints(aLocation);
+    other.forEach(bp => this._onDeleteSelf(bp.location));
+  },
+
+  /**
+   * Function invoked on the "enableAll" menuitem command.
+   */
+  _onEnableAll: function() {
+    this._onEnableOthers(undefined);
+  },
+
+  /**
+   * Function invoked on the "disableAll" menuitem command.
+   */
+  _onDisableAll: function() {
+    this._onDisableOthers(undefined);
+  },
+
+  /**
+   * Function invoked on the "deleteAll" menuitem command.
+   */
+  _onDeleteAll: function() {
+    this._onDeleteOthers(undefined);
+  },
+
+  _commandset: null,
+  _popupset: null,
+  _cmPopup: null,
+  _cbPanel: null,
+  _cbTextbox: null,
+  _selectedBreakpointItem: null,
+  _conditionalPopupVisible: false,
+  _noResultsFoundToolTip: null,
+  _markedIdentifier: null,
+  _selectedBreakpoint: null,
+  _conditionalPopupVisible: false
+});
+
+module.exports = SourcesView;
--- a/devtools/client/debugger/debugger-commands.js
+++ b/devtools/client/debugger/debugger-commands.js
@@ -532,27 +532,25 @@ exports.items.push({
       }
 
       // Send the black box request to each source we are black boxing. As we
       // get responses, accumulate the results in `blackBoxed`.
 
       const blackBoxed = [];
 
       for (let source of toBlackBox) {
-        activeThread.source(source)[cmd.clientMethod](function({ error }) {
-          if (error) {
-            blackBoxed.push(lookup("ErrorDesc") + " " + source.url);
-          } else {
-            blackBoxed.push(source.url);
-          }
-
+        dbg.blackbox(source, cmd.clientMethod === "blackBox").then(() => {
+          blackBoxed.push(source.url);
+        }, err => {
+          blackBoxed.push(lookup("ErrorDesc") + " " + source.url);
+        }).then(() => {
           if (toBlackBox.length === blackBoxed.length) {
             displayResults();
           }
-        });
+        })
       }
 
       // List the results for the user.
 
       function displayResults() {
         const results = doc.createElement("div");
         results.textContent = lookup("NonEmptyDesc");
 
--- a/devtools/client/debugger/debugger-controller.js
+++ b/devtools/client/debugger/debugger-controller.js
@@ -98,28 +98,57 @@ Cu.import("resource://gre/modules/XPCOMU
 Cu.import("resource://devtools/shared/event-emitter.js");
 Cu.import("resource://devtools/client/shared/widgets/SimpleListWidget.jsm");
 Cu.import("resource://devtools/client/shared/widgets/BreadcrumbsWidget.jsm");
 Cu.import("resource://devtools/client/shared/widgets/SideMenuWidget.jsm");
 Cu.import("resource://devtools/client/shared/widgets/VariablesView.jsm");
 Cu.import("resource://devtools/client/shared/widgets/VariablesViewController.jsm");
 Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
 
+/**
+ * Localization convenience methods.
+ */
+var L10N = new ViewHelpers.L10N(DBG_STRINGS_URI);
+
 Cu.import("resource://devtools/client/shared/browser-loader.js");
 const require = BrowserLoader("resource://devtools/client/debugger/", this).require;
 XPCOMUtils.defineConstant(this, "require", require);
 
-const {TargetFactory} = require("devtools/client/framework/target");
-const {Toolbox} = require("devtools/client/framework/toolbox");
-const DevToolsUtils = require("devtools/shared/DevToolsUtils");
-const promise = require("devtools/shared/deprecated-sync-thenables");
-const Editor = require("devtools/client/sourceeditor/editor");
-const DebuggerEditor = require("devtools/client/sourceeditor/debugger");
-const {Tooltip} = require("devtools/client/shared/widgets/Tooltip");
-const FastListWidget = require("devtools/client/shared/widgets/FastListWidget");
+// React
+const React = require("devtools/client/shared/vendor/react");
+const ReactDOM = require("devtools/client/shared/vendor/react-dom");
+const { Provider } = require("devtools/client/shared/vendor/react-redux");
+
+// Used to create the Redux store
+const createStore = require("devtools/client/shared/redux/create-store")({
+  getTargetClient: () => DebuggerController.client,
+  log: false
+});
+const {
+  makeStateBroadcaster,
+  enhanceStoreWithBroadcaster,
+  combineBroadcastingReducers
+} = require("devtools/client/shared/redux/non-react-subscriber");
+const { bindActionCreators } = require('devtools/client/shared/vendor/redux');
+const reducers = require("./content/reducers/index");
+const { onReducerEvents } = require("./content/utils");
+
+const waitUntilService = require("devtools/client/shared/redux/middleware/wait-service");
+var services = {
+  WAIT_UNTIL: waitUntilService.NAME
+};
+
+var {TargetFactory} = require("devtools/client/framework/target");
+var {Toolbox} = require("devtools/client/framework/toolbox");
+var DevToolsUtils = require("devtools/shared/DevToolsUtils");
+var promise = require("devtools/shared/deprecated-sync-thenables");
+var Editor = require("devtools/client/sourceeditor/editor");
+var DebuggerEditor = require("devtools/client/sourceeditor/debugger");
+var {Tooltip} = require("devtools/client/shared/widgets/Tooltip");
+var FastListWidget = require("devtools/client/shared/widgets/FastListWidget");
 
 XPCOMUtils.defineConstant(this, "EVENTS", EVENTS);
 
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
   "resource://gre/modules/Task.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Parser",
   "resource://devtools/shared/Parser.jsm");
@@ -145,148 +174,221 @@ var DebuggerController = {
   /**
    * Initializes the debugger controller.
    */
   initialize: function() {
     dumpn("Initializing the DebuggerController");
 
     this.startupDebugger = this.startupDebugger.bind(this);
     this.shutdownDebugger = this.shutdownDebugger.bind(this);
-    this._onTabNavigated = this._onTabNavigated.bind(this);
+    this._onNavigate = this._onNavigate.bind(this);
+    this._onWillNavigate = this._onWillNavigate.bind(this);
     this._onTabDetached = this._onTabDetached.bind(this);
+
+    const broadcaster = makeStateBroadcaster(() => !!this.activeThread);
+    const reducer = combineBroadcastingReducers(
+      reducers,
+      broadcaster.emitChange
+    );
+    // TODO: Bug 1228867, clean this up and probably abstract it out
+    // better.
+    //
+    // We only want to process async event that are appropriate for
+    // this page. The devtools are open across page reloads, so async
+    // requests from the last page might bleed through if reloading
+    // fast enough. We check to make sure the async action is part of
+    // a current request, and ignore it if not.
+    let store = createStore((state, action) => {
+      if (action.seqId &&
+         (action.status === 'done' || action.status === 'error') &&
+         state && state.asyncRequests.indexOf(action.seqId) === -1) {
+        return state;
+      }
+      return reducer(state, action);
+    });
+    store = enhanceStoreWithBroadcaster(store, broadcaster);
+
+    // This controller right now acts as the store that's globally
+    // available, so just copy the Redux API onto it.
+    Object.keys(store).forEach(name => {
+      this[name] = store[name];
+    });
   },
 
   /**
    * Initializes the view.
    *
    * @return object
    *         A promise that is resolved when the debugger finishes startup.
    */
   startupDebugger: Task.async(function*() {
     if (this._startup) {
       return;
     }
 
-    yield DebuggerView.initialize();
+    DebuggerView.initialize();
     this._startup = true;
   }),
 
   /**
    * Destroys the view and disconnects the debugger client from the server.
    *
    * @return object
    *         A promise that is resolved when the debugger finishes shutdown.
    */
   shutdownDebugger: Task.async(function*() {
     if (this._shutdown) {
       return;
     }
 
-    yield DebuggerView.destroy();
-    this.SourceScripts.disconnect();
+    yield this._settleAllRequests();
+
+    DebuggerView.destroy();
     this.StackFrames.disconnect();
     this.ThreadState.disconnect();
     if (this._target.isTabActor) {
       this.Workers.disconnect();
     }
+
     this.disconnect();
 
     this._shutdown = true;
   }),
 
+  _settleAllRequests: function() {
+    const requests = this.getState().asyncRequests;
+
+    if (requests.length > 0) {
+      const deferred = promise.defer();
+      this.onChange('open-requests', function checkSettled(reqs) {
+        if (reqs.length === 0) {
+          deferred.resolve();
+        }
+
+        this.offChange('open-requests', checkSettled);
+      }.bind(this));
+      return deferred.promise;
+    }
+
+    return promise.resolve();
+  },
+
   /**
    * Initiates remote debugging based on the current target, wiring event
    * handlers as necessary.
    *
    * @return object
    *         A promise that is resolved when the debugger finishes connecting.
    */
   connect: Task.async(function*() {
     if (this._connected) {
       return;
     }
 
     let target = this._target;
     let { client, form: { chromeDebugger, actor } } = target;
     target.on("close", this._onTabDetached);
-    target.on("navigate", this._onTabNavigated);
-    target.on("will-navigate", this._onTabNavigated);
+    target.on("navigate", this._onNavigate);
+    target.on("will-navigate", this._onWillNavigate);
     this.client = client;
 
     if (target.isAddon) {
       yield this._startAddonDebugging(actor);
     } else if (!target.isTabActor) {
       // Some actors like AddonActor or RootActor for chrome debugging
       // do not support attach/detach and can be used directly
       yield this._startChromeDebugging(chromeDebugger);
     } else {
       yield this._startDebuggingTab();
     }
 
     this._hideUnsupportedFeatures();
   }),
 
+  connectThread: function() {
+    const { newSource, fetchEventListeners } = bindActionCreators(actions, this.dispatch);
+
+    // TODO: bug 806775, update the globals list using aPacket.hostAnnotations
+    // from bug 801084.
+    // this.client.addListener("newGlobal", ...);
+
+    this.activeThread.addListener("newSource", (event, packet) => {
+      newSource(packet.source);
+
+      // Make sure the events listeners are up to date.
+      if (DebuggerView.instrumentsPaneTab == "events-tab") {
+        fetchEventListeners();
+      }
+    });
+
+    this.Workers.connect();
+    this.ThreadState.connect();
+    this.StackFrames.connect();
+
+    this._onNavigate();
+  },
+
   /**
    * Disconnects the debugger client and removes event handlers as necessary.
    */
   disconnect: function() {
     // Return early if the client didn't even have a chance to instantiate.
     if (!this.client) {
       return;
     }
 
+    this.client.removeListener("newGlobal");
+    this.activeThread.removeListener("newSource");
+    this.activeThread.removeListener("blackboxchange");
+
     this._connected = false;
     this.client = null;
     this.activeThread = null;
   },
 
   _hideUnsupportedFeatures: function() {
     if (this.client.mainRoot.traits.noPrettyPrinting) {
       DebuggerView.Sources.hidePrettyPrinting();
     }
 
     if (this.client.mainRoot.traits.noBlackBoxing) {
       DebuggerView.Sources.hideBlackBoxing();
     }
   },
 
-  /**
-   * Called for each location change in the debugged tab.
-   *
-   * @param string aType
-   *        Packet type.
-   * @param object aPacket
-   *        Packet received from the server.
-   */
-  _onTabNavigated: function(aType, aPacket) {
-    switch (aType) {
-      case "will-navigate": {
-        // Reset UI.
-        DebuggerView.handleTabNavigation();
+  _onWillNavigate: function(opts={}) {
+    // Reset UI.
+    DebuggerView.handleTabNavigation();
+    if (!opts.noUnload) {
+      this.dispatch(actions.unload());
+    }
+
+    // Discard all the cached parsed sources *before* the target
+    // starts navigating. Sources may be fetched during navigation, in
+    // which case we don't want to hang on to the old source contents.
+    DebuggerController.Parser.clearCache();
+    SourceUtils.clearCache();
 
-        // Discard all the cached sources *before* the target starts navigating.
-        // Sources may be fetched during navigation, in which case we don't
-        // want to hang on to the old source contents.
-        DebuggerController.SourceScripts.clearCache();
-        DebuggerController.Parser.clearCache();
-        SourceUtils.clearCache();
+    // Prevent performing any actions that were scheduled before
+    // navigation.
+    clearNamedTimeout("new-source");
+    clearNamedTimeout("event-breakpoints-update");
+    clearNamedTimeout("event-listeners-fetch");
+  },
 
-        // Prevent performing any actions that were scheduled before navigation.
-        clearNamedTimeout("new-source");
-        clearNamedTimeout("event-breakpoints-update");
-        clearNamedTimeout("event-listeners-fetch");
-        break;
-      }
-      case "navigate": {
-        this.ThreadState.handleTabNavigation();
-        this.StackFrames.handleTabNavigation();
-        this.SourceScripts.handleTabNavigation();
-        break;
-      }
-    }
+  _onNavigate: function() {
+    this.ThreadState.handleTabNavigation();
+    this.StackFrames.handleTabNavigation();
+
+    // Load all of the sources. Note that the server will actually
+    // emit individual `newSource` notifications, which trigger
+    // separate actions, so this won't do anything other than force
+    // the server to traverse sources.
+
+    this.dispatch(actions.loadSources());
   },
 
   /**
    * Called when the debugged tab is closed.
    */
   _onTabDetached: function() {
     this.shutdownDebugger();
   },
@@ -314,20 +416,17 @@ var DebuggerController = {
     };
 
     this._target.activeTab.attachThread(threadOptions, (aResponse, aThreadClient) => {
       if (!aThreadClient) {
         deferred.reject(new Error("Couldn't attach to thread: " + aResponse.error));
         return;
       }
       this.activeThread = aThreadClient;
-      this.Workers.connect();
-      this.ThreadState.connect();
-      this.StackFrames.connect();
-      this.SourceScripts.connect();
+      this.connectThread();
 
       if (aThreadClient.paused) {
         aThreadClient.resume(res => {
           this._ensureResumptionOrder(res)
         });
       }
 
       deferred.resolve();
@@ -370,19 +469,17 @@ var DebuggerController = {
     };
 
     this.client.attachThread(aChromeDebugger, (aResponse, aThreadClient) => {
       if (!aThreadClient) {
         deferred.reject(new Error("Couldn't attach to thread: " + aResponse.error));
         return;
       }
       this.activeThread = aThreadClient;
-      this.ThreadState.connect();
-      this.StackFrames.connect();
-      this.SourceScripts.connect();
+      this.connectThread();
 
       if (aThreadClient.paused) {
         aThreadClient.resume(this._ensureResumptionOrder);
       }
 
       deferred.resolve();
     }, threadOptions);
 
@@ -402,26 +499,49 @@ var DebuggerController = {
         let msg = "Couldn't reconfigure thread: " + aResponse.message;
         Cu.reportError(msg);
         dumpn(msg);
         return;
       }
 
       // Reset the view and fetch all the sources again.
       DebuggerView.handleTabNavigation();
-      this.SourceScripts.handleTabNavigation();
+      this.dispatch(actions.unload());
+      this.dispatch(actions.loadSources());
 
       // Update the stack frame list.
       if (this.activeThread.paused) {
         this.activeThread._clearFrames();
         this.activeThread.fillFrames(CALL_STACK_PAGE_SIZE);
       }
     });
   },
 
+  waitForSourcesLoaded: function() {
+    const deferred = promise.defer();
+    this.dispatch({
+      type: services.WAIT_UNTIL,
+      predicate: action => (action.type === constants.LOAD_SOURCES &&
+                            action.status === "done"),
+      run: deferred.resolve
+    });
+    return deferred.promise;
+  },
+
+  waitForSourceShown: function(name) {
+    const deferred = promise.defer();
+    window.on(EVENTS.SOURCE_SHOWN, function onShown(_, source) {
+      if (source.url.includes(name)) {
+        window.off(EVENTS.SOURCE_SHOWN, onShown);
+        deferred.resolve();
+      }
+    });
+    return deferred.promise;
+  },
+
   _startup: false,
   _shutdown: false,
   _connected: false,
   client: null,
   activeThread: null
 };
 
 function Workers() {
@@ -1009,36 +1129,36 @@ StackFrames.prototype = {
     if (gClient.mainRoot.traits.conditionalBreakpoints) {
       return;
     }
     let breakLocation = this._currentBreakpointLocation;
     if (!breakLocation) {
       return;
     }
 
-    let breakpointPromise = DebuggerController.Breakpoints._getAdded(breakLocation);
-    if (!breakpointPromise) {
-      return;
-    }
-    let breakpointClient = yield breakpointPromise;
-    let conditionalExpression = breakpointClient.conditionalExpression;
+    let bp = queries.getBreakpoint(DebuggerController.getState(), {
+      actor: breakLocation.source.actor,
+      line: breakLocation.line
+    });
+    let conditionalExpression = bp.condition;
     if (!conditionalExpression) {
       return;
     }
 
     // Evaluating the current breakpoint's conditional expression will
     // cause the stack frames to be cleared and active thread to pause,
     // sending a 'clientEvaluated' packed and adding the frames again.
     let evaluationOptions = { depth: 0, meta: FRAME_TYPE.CONDITIONAL_BREAKPOINT_EVAL };
     yield this.evaluate(conditionalExpression, evaluationOptions);
     this._currentFrameDescription = FRAME_TYPE.NORMAL;
 
-    // If the breakpoint's conditional expression evaluation is falsy,
-    // automatically resume execution.
-    if (VariablesView.isFalsy({ value: this._currentEvaluation.return })) {
+    // If the breakpoint's conditional expression evaluation is falsy
+    // and there is no exception, automatically resume execution.
+    if (!this._currentEvaluation.throw &&
+        VariablesView.isFalsy({ value: this._currentEvaluation.return })) {
       this.activeThread.resume(DebuggerController._ensureResumptionOrder);
     }
   }),
 
   /**
    * Handles watch expressions when the debugger pauses and the stackframes
    * are received.
    *
@@ -1156,879 +1276,16 @@ StackFrames.prototype = {
     }
 
     this.currentFrameDepth = -1;
     return this._onFrames();
   }
 };
 
 /**
- * Keeps the source script list up-to-date, using the thread client's
- * source script cache.
- */
-function SourceScripts() {
-  this._onNewGlobal = this._onNewGlobal.bind(this);
-  this._onNewSource = this._onNewSource.bind(this);
-  this._onSourcesAdded = this._onSourcesAdded.bind(this);
-  this._onBlackBoxChange = this._onBlackBoxChange.bind(this);
-  this._onPrettyPrintChange = this._onPrettyPrintChange.bind(this);
-}
-
-SourceScripts.prototype = {
-  get activeThread() {
-    return DebuggerController.activeThread;
-  },
-
-  get debuggerClient() {
-    return DebuggerController.client;
-  },
-
-  _cache: new Map(),
-
-  /**
-   * Connect to the current thread client.
-   */
-  connect: function() {
-    dumpn("SourceScripts is connecting...");
-    this.debuggerClient.addListener("newGlobal", this._onNewGlobal);
-    this.activeThread.addListener("newSource", this._onNewSource);
-    this.activeThread.addListener("blackboxchange", this._onBlackBoxChange);
-    this.activeThread.addListener("prettyprintchange", this._onPrettyPrintChange);
-    this.handleTabNavigation();
-  },
-
-  /**
-   * Disconnect from the client.
-   */
-  disconnect: function() {
-    if (!this.activeThread) {
-      return;
-    }
-    dumpn("SourceScripts is disconnecting...");
-    this.debuggerClient.removeListener("newGlobal", this._onNewGlobal);
-    this.activeThread.removeListener("newSource", this._onNewSource);
-    this.activeThread.removeListener("blackboxchange", this._onBlackBoxChange);
-    this.activeThread.addListener("prettyprintchange", this._onPrettyPrintChange);
-  },
-
-  /**
-   * Clears all the cached source contents.
-   */
-  clearCache: function() {
-    this._cache.clear();
-  },
-
-  /**
-   * Handles any initialization on a tab navigation event issued by the client.
-   */
-  handleTabNavigation: function() {
-    if (!this.activeThread) {
-      return;
-    }
-    dumpn("Handling tab navigation in the SourceScripts");
-
-    // Retrieve the list of script sources known to the server from before
-    // the client was ready to handle "newSource" notifications.
-    this.activeThread.getSources(this._onSourcesAdded);
-  },
-
-  /**
-   * Handler for the debugger client's unsolicited newGlobal notification.
-   */
-  _onNewGlobal: function(aNotification, aPacket) {
-    // TODO: bug 806775, update the globals list using aPacket.hostAnnotations
-    // from bug 801084.
-  },
-
-  /**
-   * Handler for the debugger client's unsolicited newSource notification.
-   */
-  _onNewSource: function(aNotification, aPacket) {
-    // Ignore bogus scripts, e.g. generated from 'clientEvaluate' packets.
-    if (NEW_SOURCE_IGNORED_URLS.indexOf(aPacket.source.url) != -1) {
-      return;
-    }
-
-    // Add the source in the debugger view sources container.
-    DebuggerView.Sources.addSource(aPacket.source, { staged: false });
-
-    // Select this source if it's the preferred one.
-    let preferredValue = DebuggerView.Sources.preferredValue;
-    if (aPacket.source.url == preferredValue) {
-      DebuggerView.Sources.selectedValue = preferredValue;
-    }
-    // ..or the first entry if there's none selected yet after a while
-    else {
-      setNamedTimeout("new-source", NEW_SOURCE_DISPLAY_DELAY, () => {
-        // If after a certain delay the preferred source still wasn't received,
-        // just give up on waiting and display the first entry.
-        if (!DebuggerView.Sources.selectedValue) {
-          DebuggerView.Sources.selectedIndex = 0;
-        }
-      });
-    }
-
-    // If there are any stored breakpoints for this source, display them again,
-    // both in the editor and the breakpoints pane.
-    DebuggerController.Breakpoints.updatePaneBreakpoints();
-    DebuggerController.Breakpoints.updateEditorBreakpoints();
-
-    // Make sure the events listeners are up to date.
-    if (DebuggerView.instrumentsPaneTab == "events-tab") {
-      store.dispatch(actions.fetchEventListeners());
-    }
-
-    // Signal that a new source has been added.
-    window.emit(EVENTS.NEW_SOURCE);
-  },
-
-  /**
-   * Callback for the debugger's active thread getSources() method.
-   */
-  _onSourcesAdded: function(aResponse) {
-    if (aResponse.error || !aResponse.sources) {
-      let msg = "Error getting sources: " + aResponse.message;
-      Cu.reportError(msg);
-      dumpn(msg);
-      return;
-    }
-
-    if (aResponse.sources.length === 0) {
-      DebuggerView.Sources.emptyText = L10N.getStr("noSourcesText");
-      window.emit(EVENTS.SOURCES_ADDED);
-      return;
-    }
-
-    // Add all the sources in the debugger view sources container.
-    for (let source of aResponse.sources) {
-      // Ignore bogus scripts, e.g. generated from 'clientEvaluate' packets.
-      if (NEW_SOURCE_IGNORED_URLS.indexOf(source.url) == -1) {
-        DebuggerView.Sources.addSource(source, { staged: true });
-      }
-    }
-
-    // Flushes all the prepared sources into the sources container.
-    DebuggerView.Sources.commit({ sorted: true });
-
-    // Select the preferred source if it exists and was part of the response.
-    let preferredValue = DebuggerView.Sources.preferredValue;
-    if (DebuggerView.Sources.containsValue(preferredValue)) {
-      DebuggerView.Sources.selectedValue = preferredValue;
-    }
-    // ..or the first entry if there's no one selected yet.
-    else if (!DebuggerView.Sources.selectedValue) {
-      DebuggerView.Sources.selectedIndex = 0;
-    }
-
-    // If there are any stored breakpoints for the sources, display them again,
-    // both in the editor and the breakpoints pane.
-    DebuggerController.Breakpoints.updatePaneBreakpoints();
-    DebuggerController.Breakpoints.updateEditorBreakpoints();
-
-    // Signal that sources have been added.
-    window.emit(EVENTS.SOURCES_ADDED);
-  },
-
-  /**
-   * Handler for the debugger client's 'blackboxchange' notification.
-   */
-  _onBlackBoxChange: function (aEvent, { actor, isBlackBoxed }) {
-    const item = DebuggerView.Sources.getItemByValue(actor);
-    if (item) {
-      item.prebuiltNode.classList.toggle("black-boxed", isBlackBoxed);
-    }
-    DebuggerView.Sources.updateToolbarButtonsState();
-    DebuggerView.maybeShowBlackBoxMessage();
-  },
-
-  /**
-   * Set the black boxed status of the given source.
-   *
-   * @param Object aSource
-   *        The source form.
-   * @param bool aBlackBoxFlag
-   *        True to black box the source, false to un-black box it.
-   * @returns Promise
-   *          A promize that resolves to [aSource, isBlackBoxed] or rejects to
-   *          [aSource, error].
-   */
-  setBlackBoxing: function(aSource, aBlackBoxFlag) {
-    const sourceClient = this.activeThread.source(aSource);
-    const deferred = promise.defer();
-
-    sourceClient[aBlackBoxFlag ? "blackBox" : "unblackBox"](aPacket => {
-      const { error, message } = aPacket;
-      if (error) {
-        let msg = "Couldn't toggle black boxing for " + aSource.url + ": " + message;
-        dumpn(msg);
-        Cu.reportError(msg);
-        deferred.reject([aSource, msg]);
-      } else {
-        deferred.resolve([aSource, sourceClient.isBlackBoxed]);
-      }
-    });
-
-    return deferred.promise;
-  },
-
-  /**
-   * Toggle the pretty printing of a source's text. All subsequent calls to
-   * |getText| will return the pretty-toggled text. Nothing will happen for
-   * non-javascript files.
-   *
-   * @param Object aSource
-   *        The source form from the RDP.
-   * @returns Promise
-   *          A promise that resolves to [aSource, prettyText] or rejects to
-   *          [aSource, error].
-   */
-  togglePrettyPrint: function(aSource) {
-    // Only attempt to pretty print JavaScript sources.
-    if (!SourceUtils.isJavaScript(aSource.url, aSource.contentType)) {
-      return promise.reject([aSource, "Can't prettify non-javascript files."]);
-    }
-
-    const sourceClient = this.activeThread.source(aSource);
-    const wantPretty = !sourceClient.isPrettyPrinted;
-
-    // Only use the existing promise if it is pretty printed.
-    let textPromise = this._cache.get(aSource.url);
-    if (textPromise && textPromise.pretty === wantPretty) {
-      return textPromise;
-    }
-
-    const deferred = promise.defer();
-    deferred.promise.pretty = wantPretty;
-    this._cache.set(aSource.actor, deferred.promise);
-
-    const afterToggle = ({ error, message, source: text, contentType }) => {
-      if (error) {
-        // Revert the rejected promise from the cache, so that the original
-        // source's text may be shown when the source is selected.
-        this._cache.set(aSource.actor, textPromise);
-        deferred.reject([aSource, message || error]);
-        return;
-      }
-      deferred.resolve([aSource, text, contentType]);
-    };
-
-    if (wantPretty) {
-      sourceClient.prettyPrint(Prefs.editorTabSize, afterToggle);
-    } else {
-      sourceClient.disablePrettyPrint(afterToggle);
-    }
-
-    return deferred.promise;
-  },
-
-  /**
-   * Handler for the debugger's prettyprintchange notification.
-   */
-  _onPrettyPrintChange: function(aEvent, { url }) {
-    // Remove the cached source AST from the Parser, to avoid getting
-    // wrong locations when searching for functions.
-    DebuggerController.Parser.clearSource(url);
-  },
-
-  /**
-   * Gets a specified source's text.
-   *
-   * @param object aSource
-   *        The source object coming from the active thread.
-   * @param function aOnTimeout [optional]
-   *        Function called when the source text takes a long time to fetch,
-   *        but not necessarily failing. Long fetch times don't cause the
-   *        rejection of the returned promise.
-   * @param number aDelay [optional]
-   *        The amount of time it takes to consider a source slow to fetch.
-   *        If unspecified, it defaults to a predefined value.
-   * @return object
-   *         A promise that is resolved after the source text has been fetched.
-   */
-  getText: function(aSource, aOnTimeout, aDelay = FETCH_SOURCE_RESPONSE_DELAY) {
-    // Fetch the source text only once.
-    let textPromise = this._cache.get(aSource.actor);
-    if (textPromise) {
-      return textPromise;
-    }
-
-    let deferred = promise.defer();
-    this._cache.set(aSource.actor, deferred.promise);
-
-    // If the source text takes a long time to fetch, invoke a callback.
-    if (aOnTimeout) {
-      var fetchTimeout = window.setTimeout(() => aOnTimeout(aSource), aDelay);
-    }
-
-    // Get the source text from the active thread.
-    this.activeThread.source(aSource).source(({ error, source: text, contentType }) => {
-      if (aOnTimeout) {
-        window.clearTimeout(fetchTimeout);
-      }
-      if (error) {
-        deferred.reject([aSource, error]);
-      } else {
-        deferred.resolve([aSource, text, contentType]);
-      }
-    });
-
-    return deferred.promise;
-  },
-
-  /**
-   * Starts fetching all the sources, silently.
-   *
-   * @param array aUrls
-   *        The urls for the sources to fetch. If fetching a source's text
-   *        takes too long, it will be discarded.
-   * @return object
-   *         A promise that is resolved after source texts have been fetched.
-   */
-  getTextForSources: function(aActors) {
-    let deferred = promise.defer();
-    let pending = new Set(aActors);
-    let fetched = [];
-
-    // Can't use promise.all, because if one fetch operation is rejected, then
-    // everything is considered rejected, thus no other subsequent source will
-    // be getting fetched. We don't want that. Something like Q's allSettled
-    // would work like a charm here.
-
-    // Try to fetch as many sources as possible.
-    for (let actor of aActors) {
-      let sourceItem = DebuggerView.Sources.getItemByValue(actor);
-      let sourceForm = sourceItem.attachment.source;
-      this.getText(sourceForm, onTimeout).then(onFetch, onError);
-    }
-
-    /* Called if fetching a source takes too long. */
-    function onTimeout(aSource) {
-      onError([aSource]);
-    }
-
-    /* Called if fetching a source finishes successfully. */
-    function onFetch([aSource, aText, aContentType]) {
-      // If fetching the source has previously timed out, discard it this time.
-      if (!pending.has(aSource.actor)) {
-        return;
-      }
-      pending.delete(aSource.actor);
-      fetched.push([aSource.actor, aText, aContentType]);
-      maybeFinish();
-    }
-
-    /* Called if fetching a source failed because of an error. */
-    function onError([aSource, aError]) {
-      pending.delete(aSource.actor);
-      maybeFinish();
-    }
-
-    /* Called every time something interesting happens while fetching sources. */
-    function maybeFinish() {
-      if (pending.size == 0) {
-        // Sort the fetched sources alphabetically by their url.
-        deferred.resolve(fetched.sort(([aFirst], [aSecond]) => aFirst > aSecond));
-      }
-    }
-
-    return deferred.promise;
-  }
-};
-
-/**
- * Handles all the breakpoints in the current debugger.
- */
-function Breakpoints() {
-  this._onEditorBreakpointAdd = this._onEditorBreakpointAdd.bind(this);
-  this._onEditorBreakpointRemove = this._onEditorBreakpointRemove.bind(this);
-  this.addBreakpoint = this.addBreakpoint.bind(this);
-  this.removeBreakpoint = this.removeBreakpoint.bind(this);
-}
-
-Breakpoints.prototype = {
-  /**
-   * A map of breakpoint promises as tracked by the debugger frontend.
-   * The keys consist of a string representation of the breakpoint location.
-   */
-  _added: new Map(),
-  _removing: new Map(),
-  _disabled: new Map(),
-
-  /**
-   * Adds the source editor breakpoint handlers.
-   *
-   * @return object
-   *         A promise that is resolved when the breakpoints finishes initializing.
-   */
-  initialize: function() {
-    DebuggerView.editor.on("breakpointAdded", this._onEditorBreakpointAdd);
-    DebuggerView.editor.on("breakpointRemoved", this._onEditorBreakpointRemove);
-
-    // Initialization is synchronous, for now.
-    return promise.resolve(null);
-  },
-
-  /**
-   * Removes the source editor breakpoint handlers & all the added breakpoints.
-   *
-   * @return object
-   *         A promise that is resolved when the breakpoints finishes destroying.
-   */
-  destroy: function() {
-    DebuggerView.editor.off("breakpointAdded", this._onEditorBreakpointAdd);
-    DebuggerView.editor.off("breakpointRemoved", this._onEditorBreakpointRemove);
-
-    return this.removeAllBreakpoints();
-  },
-
-  /**
-   * Event handler for new breakpoints that come from the editor.
-   *
-   * @param number aLine
-   *        Line number where breakpoint was set.
-   */
-  _onEditorBreakpointAdd: Task.async(function*(_, aLine) {
-    let actor = DebuggerView.Sources.selectedValue;
-    let location = { actor: actor, line: aLine + 1 };
-
-    // Initialize the breakpoint, but don't update the editor, since this
-    // callback is invoked because a breakpoint was added in the
-    // editor itself.
-    let breakpointClient = yield this.addBreakpoint(location, { noEditorUpdate: true });
-
-    // Notify that we've shown a breakpoint in the source editor.
-    window.emit(EVENTS.BREAKPOINT_SHOWN_IN_EDITOR);
-  }),
-
-  /**
-   * Event handler for breakpoints that are removed from the editor.
-   *
-   * @param number aLine
-   *        Line number where breakpoint was removed.
-   */
-  _onEditorBreakpointRemove: Task.async(function*(_, aLine) {
-    let actor = DebuggerView.Sources.selectedValue;
-    let location = { actor: actor, line: aLine + 1 };
-    yield this.removeBreakpoint(location, { noEditorUpdate: true });
-
-    // Notify that we've hidden a breakpoint in the source editor.
-    window.emit(EVENTS.BREAKPOINT_HIDDEN_IN_EDITOR);
-  }),
-
-  /**
-   * Update the breakpoints in the editor view. This function takes the list of
-   * breakpoints in the debugger and adds them back into the editor view.
-   * This is invoked when the selected script is changed, or when new sources
-   * are received via the _onNewSource and _onSourcesAdded event listeners.
-   */
-  updateEditorBreakpoints: Task.async(function*() {
-    for (let breakpointPromise of this._addedOrDisabled) {
-      let breakpointClient = yield breakpointPromise;
-      let location = breakpointClient.location;
-      let currentSourceActor = DebuggerView.Sources.selectedValue;
-      let sourceActor = DebuggerView.Sources.getActorForLocation(location);
-
-      // Update the view only if the breakpoint is in the currently
-      // shown source.
-      if (currentSourceActor === sourceActor) {
-        yield this._showBreakpoint(breakpointClient, { noPaneUpdate: true });
-      }
-    }
-  }),
-
-  /**
-   * Update the breakpoints in the pane view. This function takes the list of
-   * breakpoints in the debugger and adds them back into the breakpoints pane.
-   * This is invoked when new sources are received via the _onNewSource and
-   * _onSourcesAdded event listeners.
-   */
-  updatePaneBreakpoints: Task.async(function*() {
-    for (let breakpointPromise of this._addedOrDisabled) {
-      let breakpointClient = yield breakpointPromise;
-      let container = DebuggerView.Sources;
-      let sourceActor = breakpointClient.location.actor;
-
-      // Update the view only if the breakpoint exists in a known source.
-      if (container.containsValue(sourceActor)) {
-        yield this._showBreakpoint(breakpointClient, { noEditorUpdate: true });
-      }
-    }
-  }),
-
-  /**
-   * Add a breakpoint.
-   *
-   * @param object aLocation
-   *        The location where you want the breakpoint.
-   *        This object must have two properties:
-   *          - url: the breakpoint's source location.
-   *          - line: the breakpoint's line number.
-   *        It can also have the following optional properties:
-   *          - condition: only pause if this condition evaluates truthy
-   * @param object aOptions [optional]
-   *        Additional options or flags supported by this operation:
-   *          - openPopup: tells if the expression popup should be shown.
-   *          - noEditorUpdate: tells if you want to skip editor updates.
-   *          - noPaneUpdate: tells if you want to skip breakpoint pane updates.
-   * @return object
-   *         A promise that is resolved after the breakpoint is added, or
-   *         rejected if there was an error.
-   */
-  addBreakpoint: Task.async(function*(aLocation, aOptions = {}) {
-    // Make sure a proper location is available.
-    if (!aLocation) {
-      throw new Error("Invalid breakpoint location.");
-    }
-    let addedPromise, removingPromise;
-
-    // If the breakpoint was already added, or is currently being added at the
-    // specified location, then return that promise immediately.
-    if ((addedPromise = this._getAdded(aLocation))) {
-      return addedPromise;
-    }
-
-    // If the breakpoint is currently being removed from the specified location,
-    // then wait for that to finish.
-    if ((removingPromise = this._getRemoving(aLocation))) {
-      yield removingPromise;
-    }
-
-    let deferred = promise.defer();
-
-    // Remember the breakpoint initialization promise in the store.
-    let identifier = this.getIdentifier(aLocation);
-    this._added.set(identifier, deferred.promise);
-
-    let source = gThreadClient.source(
-      DebuggerView.Sources.getItemByValue(aLocation.actor).attachment.source
-    );
-
-    source.setBreakpoint(aLocation, Task.async(function*(aResponse, aBreakpointClient) {
-      // If the breakpoint response has an "actualLocation" attached, then
-      // the original requested placement for the breakpoint wasn't accepted.
-      let actualLocation = aResponse.actualLocation;
-      if (actualLocation) {
-        // Update the editor to reflect the new location of the breakpoint. We
-        // always need to do this, even when we already have a breakpoint for
-        // the actual location, because the editor already as already shown the
-        // breakpoint at the original location at this point. Calling
-        // moveBreakpoint will hide the breakpoint at the original location, and
-        // show it at the actual location, if necessary.
-        //
-        // FIXME: The call to moveBreakpoint triggers another call to remove-
-        // and addBreakpoint, respectively. These calls do not have any effect,
-        // because there is no breakpoint to remove at the old location, and
-        // the breakpoint is already being added at the new location, but they
-        // are redundant and confusing.
-        DebuggerView.editor.moveBreakpoint(
-          aBreakpointClient.location.line - 1,
-          actualLocation.line - 1
-        );
-
-        aBreakpointClient.location = actualLocation;
-        aBreakpointClient.location.actor = actualLocation.source
-                                         ? actualLocation.source.actor
-                                         : null;
-
-        let oldIdentifier = identifier;
-        this._added.delete(oldIdentifier);
-
-        if ((addedPromise = this._getAdded(actualLocation))) {
-          deferred.resolve(addedPromise);
-          return;
-        }
-
-        // Remember the initialization promise for the new location instead.
-        let newIdentifier = identifier = this.getIdentifier(actualLocation);
-        this._added.set(newIdentifier, deferred.promise);
-      }
-
-      // By default, new breakpoints are always enabled. Disabled breakpoints
-      // are, in fact, removed from the server but preserved in the frontend,
-      // so that they may not be forgotten across target navigations.
-      let disabledPromise = this._disabled.get(identifier);
-      if (disabledPromise) {
-        let aPrevBreakpointClient = yield disabledPromise;
-        let condition = aPrevBreakpointClient.getCondition();
-        this._disabled.delete(identifier);
-
-        if (condition) {
-          aBreakpointClient = yield aBreakpointClient.setCondition(
-            gThreadClient,
-            condition
-          );
-        }
-      }
-
-      // Preserve information about the breakpoint's line text, to display it in
-      // the sources pane without requiring fetching the source (for example,
-      // after the target navigated). Note that this will get out of sync if the
-      // source text contents change.
-      let line = aBreakpointClient.location.line - 1;
-      aBreakpointClient.text = DebuggerView.editor.getText(line).trim();
-
-      // Show the breakpoint in the breakpoints pane, and resolve.
-      yield this._showBreakpoint(aBreakpointClient, aOptions);
-
-      // Notify that we've added a breakpoint.
-      window.emit(EVENTS.BREAKPOINT_ADDED, aBreakpointClient);
-      deferred.resolve(aBreakpointClient);
-    }.bind(this)));
-
-    return deferred.promise;
-  }),
-
-  /**
-   * Remove a breakpoint.
-   *
-   * @param object aLocation
-   *        @see DebuggerController.Breakpoints.addBreakpoint
-   * @param object aOptions [optional]
-   *        @see DebuggerController.Breakpoints.addBreakpoint
-   * @return object
-   *         A promise that is resolved after the breakpoint is removed, or
-   *         rejected if there was an error.
-   */
-  removeBreakpoint: function(aLocation, aOptions = {}) {
-    // Make sure a proper location is available.
-    if (!aLocation) {
-      return promise.reject(new Error("Invalid breakpoint location."));
-    }
-
-    // If the breakpoint was already removed, or has never even been added,
-    // then return a resolved promise immediately.
-    let addedPromise = this._getAdded(aLocation);
-    if (!addedPromise) {
-      return promise.resolve(aLocation);
-    }
-
-    // If the breakpoint is currently being removed from the specified location,
-    // then return that promise immediately.
-    let removingPromise = this._getRemoving(aLocation);
-    if (removingPromise) {
-      return removingPromise;
-    }
-
-    let deferred = promise.defer();
-
-    // Remember the breakpoint removal promise in the store.
-    let identifier = this.getIdentifier(aLocation);
-    this._removing.set(identifier, deferred.promise);
-
-    // Retrieve the corresponding breakpoint client first.
-    addedPromise.then(aBreakpointClient => {
-      // Try removing the breakpoint.
-      aBreakpointClient.remove(aResponse => {
-        // If there was an error removing the breakpoint, reject the promise
-        // and forget about it that the breakpoint may be re-removed later.
-        if (aResponse.error) {
-          deferred.reject(aResponse);
-          return void this._removing.delete(identifier);
-        }
-
-        // When a breakpoint is removed, the frontend may wish to preserve some
-        // details about it, so that it can be easily re-added later. In such
-        // cases, breakpoints are marked and stored as disabled, so that they
-        // may not be forgotten across target navigations.
-        if (aOptions.rememberDisabled) {
-          aBreakpointClient.disabled = true;
-          this._disabled.set(identifier, promise.resolve(aBreakpointClient));
-        }
-
-        // Forget both the initialization and removal promises from the store.
-        this._added.delete(identifier);
-        this._removing.delete(identifier);
-
-        // Hide the breakpoint from the editor and breakpoints pane, and resolve.
-        this._hideBreakpoint(aLocation, aOptions);
-
-        // Notify that we've removed a breakpoint.
-        window.emit(EVENTS.BREAKPOINT_REMOVED, aLocation);
-        deferred.resolve(aLocation);
-      });
-    });
-
-    return deferred.promise;
-  },
-
-  /**
-   * Removes all the currently enabled breakpoints.
-   *
-   * @return object
-   *         A promise that is resolved after all breakpoints are removed, or
-   *         rejected if there was an error.
-   */
-  removeAllBreakpoints: function() {
-    /* Gets an array of all the existing breakpoints promises. */
-    let getActiveBreakpoints = (aPromises, aStore = []) => {
-      for (let [, breakpointPromise] of aPromises) {
-        aStore.push(breakpointPromise);
-      }
-      return aStore;
-    }
-
-    /* Gets an array of all the removed breakpoints promises. */
-    let getRemovedBreakpoints = (aClients, aStore = []) => {
-      for (let breakpointClient of aClients) {
-        aStore.push(this.removeBreakpoint(breakpointClient.location));
-      }
-      return aStore;
-    }
-
-    // First, populate an array of all the currently added breakpoints promises.
-    // Then, once all the breakpoints clients are retrieved, populate an array
-    // of all the removed breakpoints promises and wait for their fulfillment.
-    return promise.all(getActiveBreakpoints(this._added)).then(aBreakpointClients => {
-      return promise.all(getRemovedBreakpoints(aBreakpointClients));
-    });
-  },
-
-  /**
-   * Update the condition of a breakpoint.
-   *
-   * @param object aLocation
-   *        @see DebuggerController.Breakpoints.addBreakpoint
-   * @param string aClients
-   *        The condition to set on the breakpoint
-   * @return object
-   *         A promise that will be resolved with the breakpoint client
-   */
-  updateCondition: Task.async(function*(aLocation, aCondition) {
-    let addedPromise = this._getAdded(aLocation);
-    if (!addedPromise) {
-      throw new Error("Breakpoint does not exist at the specified location");
-    }
-    let breakpointClient = yield addedPromise;
-    let promise = breakpointClient.setCondition(gThreadClient, aCondition);
-
-    // `setCondition` returns a new breakpoint that has the condition,
-    // so we need to update the store
-    this._added.set(this.getIdentifier(aLocation), promise);
-    return promise;
-  }),
-
-  /**
-   * Update the editor and breakpoints pane to show a specified breakpoint.
-   *
-   * @param object aBreakpointClient
-   *        A BreakpointClient instance.
-   *        This object has additional properties dynamically added by
-   *        our code:
-   *          - disabled: the breakpoint's disabled state, boolean
-   *          - text: the breakpoint's line text to be displayed
-   * @param object aOptions [optional]
-   *        @see DebuggerController.Breakpoints.addBreakpoint
-   */
-  _showBreakpoint: function(aBreakpointClient, aOptions = {}) {
-    let tasks = [];
-    let currentSourceActor = DebuggerView.Sources.selectedValue;
-    let location = aBreakpointClient.location;
-    let actor = DebuggerView.Sources.getActorForLocation(location);
-
-    // Update the editor if required.
-    if (!aOptions.noEditorUpdate && !aBreakpointClient.disabled) {
-      if (currentSourceActor === actor) {
-        tasks.push(DebuggerView.editor.addBreakpoint(location.line - 1));
-      }
-    }
-
-    // Update the breakpoints pane if required.
-    if (!aOptions.noPaneUpdate) {
-      DebuggerView.Sources.addBreakpoint(aBreakpointClient, aOptions);
-    }
-
-    return promise.all(tasks);
-  },
-
-  /**
-   * Update the editor and breakpoints pane to hide a specified breakpoint.
-   *
-   * @param object aLocation
-   *        @see DebuggerController.Breakpoints.addBreakpoint
-   * @param object aOptions [optional]
-   *        @see DebuggerController.Breakpoints.addBreakpoint
-   */
-  _hideBreakpoint: function(aLocation, aOptions = {}) {
-    let currentSourceActor = DebuggerView.Sources.selectedValue;
-    let actor = DebuggerView.Sources.getActorForLocation(aLocation);
-
-    // Update the editor if required.
-    if (!aOptions.noEditorUpdate) {
-      if (currentSourceActor === actor) {
-        DebuggerView.editor.removeBreakpoint(aLocation.line - 1);
-      }
-    }
-
-    // Update the breakpoints pane if required.
-    if (!aOptions.noPaneUpdate) {
-      DebuggerView.Sources.removeBreakpoint(aLocation);
-    }
-  },
-
-  /**
-   * Get a Promise for the BreakpointActor client object which is already added
-   * or currently being added at the given location.
-   *
-   * @param object aLocation
-   *        @see DebuggerController.Breakpoints.addBreakpoint
-   * @return object | null
-   *         A promise that is resolved after the breakpoint is added, or
-   *         null if no breakpoint was found.
-   */
-  _getAdded: function(aLocation) {
-    return this._added.get(this.getIdentifier(aLocation));
-  },
-
-  /**
-   * Get a Promise for the BreakpointActor client object which is currently
-   * being removed from the given location.
-   *
-   * @param object aLocation
-   *        @see DebuggerController.Breakpoints.addBreakpoint
-   * @return object | null
-   *         A promise that is resolved after the breakpoint is removed, or
-   *         null if no breakpoint was found.
-   */
-  _getRemoving: function(aLocation) {
-    return this._removing.get(this.getIdentifier(aLocation));
-  },
-
-  /**
-   * Get an identifier string for a given location. Breakpoint promises are
-   * identified in the store by a string representation of their location.
-   *
-   * @param object aLocation
-   *        The location to serialize to a string.
-   * @return string
-   *         The identifier string.
-   */
-  getIdentifier: function(aLocation) {
-    return (aLocation.source ? aLocation.source.actor : aLocation.actor) +
-      ":" + aLocation.line;
-  }
-};
-
-/**
- * Gets all Promises for the BreakpointActor client objects that are
- * either enabled (added to the server) or disabled (removed from the server,
- * but for which some details are preserved).
- */
-Object.defineProperty(Breakpoints.prototype, "_addedOrDisabled", {
-  get: function* () {
-    yield* this._added.values();
-    yield* this._disabled.values();
-  }
-});
-
-/**
- * Localization convenience methods.
- */
-var L10N = new ViewHelpers.L10N(DBG_STRINGS_URI);
-
-/**
  * Shortcuts for accessing various debugger preferences.
  */
 var Prefs = new ViewHelpers.Prefs("devtools", {
   workersAndSourcesWidth: ["Int", "debugger.ui.panes-workers-and-sources-width"],
   instrumentsWidth: ["Int", "debugger.ui.panes-instruments-width"],
   panesVisibleOnStartup: ["Bool", "debugger.ui.panes-visible-on-startup"],
   variablesSortingEnabled: ["Bool", "debugger.ui.variables-sorting-enabled"],
   variablesOnlyEnumVisible: ["Bool", "debugger.ui.variables-only-enum-visible"],
@@ -2043,28 +1300,25 @@ var Prefs = new ViewHelpers.Prefs("devto
   autoBlackBox: ["Bool", "debugger.auto-black-box"],
   promiseDebuggerEnabled: ["Bool", "debugger.promise"]
 });
 
 /**
  * Convenient way of emitting events from the panel window.
  */
 EventEmitter.decorate(this);
-EventEmitter.decorate(DebuggerController);
 
 /**
  * Preliminary setup for the DebuggerController object.
  */
 DebuggerController.initialize();
 DebuggerController.Parser = new Parser();
 DebuggerController.Workers = new Workers();
 DebuggerController.ThreadState = new ThreadState();
 DebuggerController.StackFrames = new StackFrames();
-DebuggerController.SourceScripts = new SourceScripts();
-DebuggerController.Breakpoints = new Breakpoints();
 
 /**
  * Export some properties to the global scope for easier access.
  */
 Object.defineProperties(window, {
   "gTarget": {
     get: function() {
       return DebuggerController._target;
--- a/devtools/client/debugger/debugger-view.js
+++ b/devtools/client/debugger/debugger-view.js
@@ -5,20 +5,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE = 1048576; // 1 MB in bytes
 const SOURCE_URL_DEFAULT_MAX_LENGTH = 64; // chars
 const STACK_FRAMES_SOURCE_URL_MAX_LENGTH = 15; // chars
 const STACK_FRAMES_SOURCE_URL_TRIM_SECTION = "center";
 const STACK_FRAMES_SCROLL_DELAY = 100; // ms
-const BREAKPOINT_LINE_TOOLTIP_MAX_LENGTH = 1000; // chars
-const BREAKPOINT_CONDITIONAL_POPUP_POSITION = "before_start";
-const BREAKPOINT_CONDITIONAL_POPUP_OFFSET_X = 7; // px
-const BREAKPOINT_CONDITIONAL_POPUP_OFFSET_Y = -3; // px
 const BREAKPOINT_SMALL_WINDOW_WIDTH = 850; // px
 const RESULTS_PANEL_POPUP_POSITION = "before_end";
 const RESULTS_PANEL_MAX_RESULTS = 10;
 const FILE_SEARCH_ACTION_MAX_DELAY = 300; // ms
 const GLOBAL_SEARCH_EXPAND_MAX_RESULTS = 50;
 const GLOBAL_SEARCH_LINE_MAX_LENGTH = 300; // chars
 const GLOBAL_SEARCH_ACTION_MAX_DELAY = 1500; // ms
 const FUNCTION_SEARCH_ACTION_MAX_DELAY = 400; // ms
@@ -26,117 +22,125 @@ const SEARCH_GLOBAL_FLAG = "!";
 const SEARCH_FUNCTION_FLAG = "@";
 const SEARCH_TOKEN_FLAG = "#";
 const SEARCH_LINE_FLAG = ":";
 const SEARCH_VARIABLE_FLAG = "*";
 const SEARCH_AUTOFILL = [SEARCH_GLOBAL_FLAG, SEARCH_FUNCTION_FLAG, SEARCH_TOKEN_FLAG];
 const EDITOR_VARIABLE_HOVER_DELAY = 750; // ms
 const EDITOR_VARIABLE_POPUP_POSITION = "topcenter bottomleft";
 const TOOLBAR_ORDER_POPUP_POSITION = "topcenter bottomleft";
-const FUNCTION_SEARCH_POPUP_POSITION = "topcenter bottomleft";
 const RESIZE_REFRESH_RATE = 50; // ms
 const PROMISE_DEBUGGER_URL =
   "chrome://devtools/content/promisedebugger/promise-debugger.xhtml";
 
-const debuggerControllerEmit = DebuggerController.emit.bind(DebuggerController);
-const createStore = require("devtools/client/shared/redux/create-store")();
-const { combineEmittingReducers } = require("devtools/client/shared/redux/reducers");
-const reducers = require("./content/reducers/index");
-const store = createStore(combineEmittingReducers(reducers, debuggerControllerEmit));
-const { NAME: WAIT_UNTIL_NAME } = require("devtools/client/shared/redux/middleware/wait-service");
-
-const services = {
-  WAIT_UNTIL: WAIT_UNTIL_NAME
-};
-
 const EventListenersView = require('./content/views/event-listeners-view');
-const actions = require('./content/actions/event-listeners');
-
-Object.defineProperties(this, {
-  "store": {
-    value: store,
-    enumerable: true,
-    writable: false
-  },
-  "services": {
-    value: services,
-    enumerable: true,
-    writable: false
-  }
-});
+const SourcesView = require('./content/views/sources-view');
+var actions = Object.assign(
+  {},
+  require('./content/globalActions'),
+  require('./content/actions/breakpoints'),
+  require('./content/actions/sources'),
+  require('./content/actions/event-listeners')
+);
+var queries = require('./content/queries');
+var constants = require('./content/constants');
 
 /**
  * Object defining the debugger view components.
  */
 var DebuggerView = {
   /**
    * Initializes the debugger view.
    *
    * @return object
    *         A promise that is resolved when the view finishes initializing.
    */
   initialize: function() {
-    if (this._startup) {
-      return this._startup;
+    if (this._hasStartup) {
+      return;
     }
-
-    let deferred = promise.defer();
-    this._startup = deferred.promise;
+    this._hasStartup = true;
 
     this._initializePanes();
     this.Toolbar.initialize();
     this.Options.initialize();
     this.Filtering.initialize();
     this.StackFrames.initialize();
     this.StackFramesClassicList.initialize();
     this.Workers.initialize();
     this.Sources.initialize();
     this.VariableBubble.initialize();
     this.WatchExpressions.initialize();
     this.EventListeners.initialize();
     this.GlobalSearch.initialize();
     this._initializeVariablesView();
-    this._initializeEditor(deferred.resolve);
+    this._initializeEditor();
+    this._editorSource = {};
 
     document.title = L10N.getStr("DebuggerWindowTitle");
 
-    return deferred.promise;
+    this.editor.on("cursorActivity", this.Sources._onEditorCursorActivity);
+
+    this.controller = DebuggerController;
+    const getState = this.controller.getState;
+
+    onReducerEvents(this.controller, {
+      "source-text-loaded": this.renderSourceText,
+      "source-selected": this.renderSourceText,
+      "blackboxed": this.renderBlackBoxed,
+      "prettyprinted": this.renderPrettyPrinted,
+      "breakpoint-added": this.addEditorBreakpoint,
+      "breakpoint-enabled": this.addEditorBreakpoint,
+      "breakpoint-disabled": this.removeEditorBreakpoint,
+      "breakpoint-removed": this.removeEditorBreakpoint,
+      "breakpoint-moved": ({ breakpoint, prevLocation }) => {
+        const selectedSource = queries.getSelectedSource(getState());
+        const { location } = breakpoint;
+
+        if (selectedSource &&
+           selectedSource.actor === location.actor) {
+          this.editor.moveBreakpoint(prevLocation.line - 1,
+                                     location.line - 1);
+        }
+      }
+    }, this);
   },
 
   /**
    * Destroys the debugger view.
    *
    * @return object
    *         A promise that is resolved when the view finishes destroying.
    */
   destroy: function() {
-    if (this._shutdown) {
-      return this._shutdown;
+    if (this._hasShutdown) {
+      return;
     }
+    this._hasShutdown = true;
 
     window.removeEventListener("resize", this._onResize, false);
-
-    let deferred = promise.defer();
-    this._shutdown = deferred.promise;
+    this.editor.off("cursorActivity", this.Sources._onEditorCursorActivity);
 
     this.Toolbar.destroy();
     this.Options.destroy();
     this.Filtering.destroy();
     this.StackFrames.destroy();
     this.StackFramesClassicList.destroy();
     this.Sources.destroy();
     this.VariableBubble.destroy();
     this.WatchExpressions.destroy();
     this.EventListeners.destroy();
     this.GlobalSearch.destroy();
     this._destroyPromiseDebugger();
     this._destroyPanes();
-    this._destroyEditor(deferred.resolve);
 
-    return deferred.promise;
+    this.editor.destroy();
+    this.editor = null;
+
+    this.controller.dispatch(actions.removeAllBreakpoints());
   },
 
   /**
    * Initializes the UI for all the displayed panes.
    */
   _initializePanes: function() {
     dumpn("Initializing the DebuggerView panes");
 
@@ -145,17 +149,16 @@ var DebuggerView = {
     this._workersAndSourcesPane = document.getElementById("workers-and-sources-pane");
     this._instrumentsPane = document.getElementById("instruments-pane");
     this._instrumentsPaneToggleButton = document.getElementById("instruments-pane-toggle");
     this._promisePane = document.getElementById("promise-debugger-pane");
 
     this.showEditor = this.showEditor.bind(this);
     this.showBlackBoxMessage = this.showBlackBoxMessage.bind(this);
     this.showProgressBar = this.showProgressBar.bind(this);
-    this.maybeShowBlackBoxMessage = this.maybeShowBlackBoxMessage.bind(this);
 
     this._onTabSelect = this._onInstrumentsPaneTabSelect.bind(this);
     this._instrumentsPane.tabpanels.addEventListener("select", this._onTabSelect);
 
     this._collapsePaneString = L10N.getStr("collapsePanes");
     this._expandPaneString = L10N.getStr("expandPanes");
 
     this._workersAndSourcesPane.setAttribute("width", Prefs.workersAndSourcesWidth);
@@ -265,17 +268,17 @@ var DebuggerView = {
   },
 
   /**
    * Initializes the Editor instance.
    *
    * @param function aCallback
    *        Called after the editor finishes initializing.
    */
-  _initializeEditor: function(aCallback) {
+  _initializeEditor: function() {
     dumpn("Initializing the DebuggerView editor");
 
     let extraKeys = {};
     bindKey("_doTokenSearch", "tokenSearchKey");
     bindKey("_doGlobalSearch", "globalSearchKey", { alt: true });
     bindKey("_doFunctionSearch", "functionSearchKey");
     extraKeys[Editor.keyFor("jumpToLine")] = false;
     extraKeys["Esc"] = false;
@@ -297,70 +300,70 @@ var DebuggerView = {
       extraKeys: extraKeys,
       contextMenu: "sourceEditorContextMenu",
       enableCodeFolding: false
     });
 
     this.editor.appendTo(document.getElementById("editor")).then(() => {
       this.editor.extend(DebuggerEditor);
       this._loadingText = L10N.getStr("loadingText");
-      this._onEditorLoad(aCallback);
     });
 
     this.editor.on("gutterClick", (ev, line, button) => {
       // A right-click shouldn't do anything but keep track of where
       // it was clicked.
       if (button == 2) {
         this.clickedLine = line;
       }
-      // Bug 1201008: Only add the breakpoint to the editor if we're currently
-      // looking at a source. Even if no source is loaded, you can
-      // interact with line 1 of the editor.
-      else if (DebuggerView.Sources.selectedValue) {
-        if (this.editor.hasBreakpoint(line)) {
-          this.editor.removeBreakpoint(line);
-        } else {
-          this.editor.addBreakpoint(line);
+      else {
+        const source = queries.getSelectedSource(this.controller.getState());
+        if (source) {
+          const location = { actor: source.actor, line: line + 1 };
+          if (this.editor.hasBreakpoint(line)) {
+            this.controller.dispatch(actions.removeBreakpoint(location));
+          } else {
+            this.controller.dispatch(actions.addBreakpoint(location));
+          }
         }
       }
     });
   },
 
-  /**
-   * The load event handler for the source editor, also executing any necessary
-   * post-load operations.
-   *
-   * @param function aCallback
-   *        Called after the editor finishes loading.
-   */
-  _onEditorLoad: function(aCallback) {
-    dumpn("Finished loading the DebuggerView editor");
+  updateEditorBreakpoints: function(source) {
+    const breakpoints = queries.getBreakpoints(this.controller.getState());
+    const sources = queries.getSources(this.controller.getState());
 
-    DebuggerController.Breakpoints.initialize().then(() => {
-      window.emit(EVENTS.EDITOR_LOADED, this.editor);
-      aCallback();
-    });
+    for (let bp of breakpoints) {
+      if (sources[bp.location.actor] && !bp.disabled) {
+        this.addEditorBreakpoint(bp);
+      }
+      else {
+        this.removeEditorBreakpoint(bp);
+      }
+    }
   },
 
-  /**
-   * Destroys the Editor instance and also executes any necessary
-   * post-unload operations.
-   *
-   * @param function aCallback
-   *        Called after the editor finishes destroying.
-   */
-  _destroyEditor: function(aCallback) {
-    dumpn("Destroying the DebuggerView editor");
+  addEditorBreakpoint: function(breakpoint) {
+    const { location } = breakpoint;
+    const source = queries.getSelectedSource(this.controller.getState());
 
-    DebuggerController.Breakpoints.destroy().then(() => {
-      window.emit(EVENTS.EDITOR_UNLOADED, this.editor);
-      this.editor.destroy();
-      this.editor = null;
-      aCallback();
-    });
+    if (source &&
+       source.actor === location.actor &&
+       !breakpoint.disabled) {
+      this.editor.addBreakpoint(location.line - 1);
+    }
+  },
+
+  removeEditorBreakpoint: function (breakpoint) {
+    const { location } = breakpoint;
+    const source = queries.getSelectedSource(this.controller.getState());
+
+    if (source && source.actor === location.actor) {
+      this.editor.removeBreakpoint(location.line - 1);
+    }
   },
 
   /**
    * Display the source editor.
    */
   showEditor: function() {
     this._editorDeck.selectedIndex = 0;
   },
@@ -375,29 +378,16 @@ var DebuggerView = {
   /**
    * Display the progress bar.
    */
   showProgressBar: function() {
     this._editorDeck.selectedIndex = 2;
   },
 
   /**
-   * Show or hide the black box message vs. source editor depending on if the
-   * selected source is black boxed or not.
-   */
-  maybeShowBlackBoxMessage: function() {
-    let { source } = DebuggerView.Sources.selectedItem.attachment;
-    if (gThreadClient.source(source).isBlackBoxed) {
-      this.showBlackBoxMessage();
-    } else {
-      this.showEditor();
-    }
-  },
-
-  /**
    * Sets the currently displayed text contents in the source editor.
    * This resets the mode and undo stack.
    *
    * @param string aTextContent
    *        The source text content.
    */
   _setEditorText: function(aTextContent = "") {
     this.editor.setMode(Editor.modes.text);
@@ -434,79 +424,132 @@ var DebuggerView = {
     if (aTextContent.match(/^\s*</)) {
       return void this.editor.setMode(Editor.modes.html);
     }
 
     // Unknown language, use text.
     this.editor.setMode(Editor.modes.text);
   },
 
-  /**
-   * Sets the currently displayed source text in the editor.
-   *
-   * You should use DebuggerView.updateEditor instead. It updates the current
-   * caret and debug location based on a requested url and line.
-   *
-   * @param object aSource
-   *        The source object coming from the active thread.
-   * @param object aFlags
-   *        Additional options for setting the source. Supported options:
-   *          - force: boolean forcing all text to be reshown in the editor
-   * @return object
-   *         A promise that is resolved after the source text has been set.
-   */
-  _setEditorSource: function(aSource, aFlags={}) {
-    // Avoid setting the same source text in the editor again.
-    if (this._editorSource.actor == aSource.actor && !aFlags.force) {
-      return this._editorSource.promise;
-    }
-    let transportType = gClient.localTransport ? "_LOCAL" : "_REMOTE";
-    let histogramId = "DEVTOOLS_DEBUGGER_DISPLAY_SOURCE" + transportType + "_MS";
-    let histogram = Services.telemetry.getHistogramById(histogramId);
-    let startTime = Date.now();
+  renderBlackBoxed: function(source) {
+    this._renderSourceText(
+      source,
+      queries.getSourceText(this.controller.getState(), source.actor)
+    );
+  },
 
-    let deferred = promise.defer();
+  renderPrettyPrinted: function(source) {
+    this._renderSourceText(
+      source,
+      queries.getSourceText(this.controller.getState(), source.actor)
+    );
+  },
+
+  renderSourceText: function(source) {
+    this._renderSourceText(
+      source,
+      queries.getSourceText(this.controller.getState(), source.actor),
+      queries.getSelectedSourceOpts(this.controller.getState())
+    );
+  },
+
+  _renderSourceText: function(source, textInfo, opts = {}) {
+    const selectedSource = queries.getSelectedSource(this.controller.getState());
 
-    this._setEditorText(L10N.getStr("loadingText"));
-    this._editorSource = { actor: aSource.actor, promise: deferred.promise };
-
-    DebuggerController.SourceScripts.getText(aSource).then(([, aText, aContentType]) => {
-      // Avoid setting an unexpected source. This may happen when switching
-      // very fast between sources that haven't been fetched yet.
-      if (this._editorSource.actor != aSource.actor) {
-        return;
-      }
-
-      this._setEditorText(aText);
-      this._setEditorMode(aSource.url, aContentType, aText);
+    if (!selectedSource || selectedSource.actor !== source.actor) {
+      return;
+    }
 
-      // Synchronize any other components with the currently displayed
-      // source.
-      DebuggerView.Sources.selectedValue = aSource.actor;
-      DebuggerController.Breakpoints.updateEditorBreakpoints();
-
-      histogram.add(Date.now() - startTime);
+    if (source.isBlackBoxed) {
+      this.showBlackBoxMessage();
+      setTimeout(() => {
+        window.emit(EVENTS.SOURCE_SHOWN, source);
+      }, 0);
+      return;
+    }
+    else {
+      this.showEditor();
+    }
 
-      // Resolve and notify that a source file was shown.
-      window.emit(EVENTS.SOURCE_SHOWN, aSource);
-      deferred.resolve([aSource, aText, aContentType]);
-    },
-    ([, aError]) => {
-      let url = aError;
+    if (textInfo.loading) {
+      // TODO: bug 1228866, we need to update `_editorSource` here but
+      // still make the editor be updated when the full text comes
+      // through somehow.
+      this._setEditorText(L10N.getStr("loadingText"));
+      return;
+    }
+    else if (textInfo.error) {
+      let url = textInfo.error;
       let msg = L10N.getFormatStr("errorLoadingText2", url);
       this._setEditorText(msg);
       Cu.reportError(msg);
       dumpn(msg);
 
-      // Reject and notify that there was an error showing the source file.
-      window.emit(EVENTS.SOURCE_ERROR_SHOWN, aSource);
-      deferred.reject([aSource, aError]);
-    });
+      this.showEditor();
+      window.emit(EVENTS.SOURCE_ERROR_SHOWN, source);
+      return;
+    }
+
+    // If the line is not specified, default to the current frame's position,
+    // if available and the frame's url corresponds to the requested url.
+    if (!('line' in opts)) {
+      let cachedFrames = DebuggerController.activeThread.cachedFrames;
+      let currentDepth = DebuggerController.StackFrames.currentFrameDepth;
+      let frame = cachedFrames[currentDepth];
+      if (frame && frame.source.actor == source.actor) {
+        opts.line = frame.where.line;
+      }
+    }
+
+    if (this._editorSource.actor === source.actor &&
+        this._editorSource.prettyPrinted === source.isPrettyPrinted &&
+        this._editorSource.blackboxed === source.isBlackBoxed) {
+      this.updateEditorPosition(opts);
+      return;
+    }
+
+    this._editorSource.actor = source.actor;
+    this._editorSource.prettyPrinted = source.isPrettyPrinted;
+    this._editorSource.blackboxed = source.isBlackBoxed;
 
-    return deferred.promise;
+    let { text, contentType } = textInfo;
+    this._setEditorText(text);
+    this._setEditorMode(source.url, contentType, text);
+    this.updateEditorBreakpoints(source);
+    setTimeout(() => {
+      window.emit(EVENTS.SOURCE_SHOWN, source);
+    }, 0);
+
+    this.updateEditorPosition(opts);
+  },
+
+  updateEditorPosition: function(opts) {
+    let line = opts.line || 0;
+
+    // Line numbers in the source editor should start from 1. If
+    // invalid or not specified, then don't do anything.
+    if (line < 1) {
+      window.emit(EVENTS.EDITOR_LOCATION_SET);
+      return;
+    }
+
+    if (opts.charOffset) {
+      line += this.editor.getPosition(opts.charOffset).line;
+    }
+    if (opts.lineOffset) {
+      line += opts.lineOffset;
+    }
+    if (opts.moveCursor) {
+      let location = { line: line - 1, ch: opts.columnOffset || 0 };
+      this.editor.setCursor(location);
+    }
+    if (!opts.noDebug) {
+      this.editor.setDebugLocation(line - 1);
+    }
+    window.emit(EVENTS.EDITOR_LOCATION_SET);
   },
 
   /**
    * Update the source editor's current caret and debug location based on
    * a requested url and line.
    *
    * @param string aActor
    *        The target actor id.
@@ -520,68 +563,37 @@ var DebuggerView = {
    *          - noCaret: don't set the caret location at the specified line
    *          - noDebug: don't set the debug location at the specified line
    *          - align: string specifying whether to align the specified line
    *                   at the "top", "center" or "bottom" of the editor
    *          - force: boolean forcing all text to be reshown in the editor
    * @return object
    *         A promise that is resolved after the source text has been set.
    */
-  setEditorLocation: function(aActor, aLine = 0, aFlags = {}) {
+  setEditorLocation: function(aActor, aLine, aFlags = {}) {
     // Avoid trying to set a source for a url that isn't known yet.
     if (!this.Sources.containsValue(aActor)) {
-      return promise.reject(new Error("Unknown source for the specified URL."));
-    }
-
-    // If the line is not specified, default to the current frame's position,
-    // if available and the frame's url corresponds to the requested url.
-    if (!aLine) {
-      let cachedFrames = DebuggerController.activeThread.cachedFrames;
-      let currentDepth = DebuggerController.StackFrames.currentFrameDepth;
-      let frame = cachedFrames[currentDepth];
-      if (frame && frame.source.actor == aActor) {
-        aLine = frame.where.line;
-      }
+      throw new Error("Unknown source for the specified URL.");
     }
 
     let sourceItem = this.Sources.getItemByValue(aActor);
-    let sourceForm = sourceItem.attachment.source;
-
-    this._editorLoc = { actor: sourceForm.actor };
-
-    // Make sure the requested source client is shown in the editor, then
-    // update the source editor's caret position and debug location.
-    return this._setEditorSource(sourceForm, aFlags).then(([,, aContentType]) => {
-      if (this._editorLoc.actor !== sourceForm.actor) {
-        return;
-      }
+    let source = sourceItem.attachment.source;
 
-      // Record the contentType learned from fetching
-      sourceForm.contentType = aContentType;
-      // Line numbers in the source editor should start from 1. If invalid
-      // or not specified, then don't do anything.
-      if (aLine < 1) {
-        window.emit(EVENTS.EDITOR_LOCATION_SET);
-        return;
-      }
-      if (aFlags.charOffset) {
-        aLine += this.editor.getPosition(aFlags.charOffset).line;
-      }
-      if (aFlags.lineOffset) {
-        aLine += aFlags.lineOffset;
-      }
-      if (!aFlags.noCaret) {
-        let location = { line: aLine -1, ch: aFlags.columnOffset || 0 };
-        this.editor.setCursor(location, aFlags.align);
-      }
-      if (!aFlags.noDebug) {
-        this.editor.setDebugLocation(aLine - 1);
-      }
-      window.emit(EVENTS.EDITOR_LOCATION_SET);
-    }).then(null, console.error);
+    // Make sure the requested source client is shown in the editor,
+    // then update the source editor's caret position and debug
+    // location.
+    this.controller.dispatch(actions.selectSource(source, {
+      line: aLine,
+      charOffset: aFlags.charOffset,
+      lineOffset: aFlags.lineOffset,
+      columnOffset: aFlags.columnOffset,
+      moveCursor: !aFlags.noCaret,
+      noDebug: aFlags.noDebug,
+      forceUpdate: aFlags.force
+    }));
   },
 
   /**
    * Gets the visibility state of the instruments pane.
    * @return boolean
    */
   get instrumentsPaneHidden() {
     return this._instrumentsPane.hasAttribute("pane-collapsed");
@@ -641,17 +653,17 @@ var DebuggerView = {
     }, 0);
   },
 
   /**
    * Handles a tab selection event on the instruments pane.
    */
   _onInstrumentsPaneTabSelect: function() {
     if (this._instrumentsPane.selectedTab.id == "events-tab") {
-      store.dispatch(actions.fetchEventListeners());
+      this.controller.dispatch(actions.fetchEventListeners());
     }
   },
 
   /**
    * Handles a host change event issued by the parent toolbox.
    *
    * @param string aType
    *        The host type, either "bottom", "side" or "window".
@@ -766,30 +778,27 @@ var DebuggerView = {
       this.editor.setText("");
       this.editor.clearHistory();
       this._editorSource = {};
     }
 
     this.Sources.emptyText = L10N.getStr("loadingSourcesText");
   },
 
-  _startup: null,
-  _shutdown: null,
   Toolbar: null,
   Options: null,
   Filtering: null,
   GlobalSearch: null,
   StackFrames: null,
   Sources: null,
   Variables: null,
   VariableBubble: null,
   WatchExpressions: null,
   EventListeners: null,
   editor: null,
-  _editorSource: {},
   _loadingText: "",
   _body: null,
   _editorDeck: null,
   _workersAndSourcesPane: null,
   _instrumentsPane: null,
   _instrumentsPaneToggleButton: null,
   _collapsePaneString: "",
   _expandPaneString: ""
@@ -943,9 +952,10 @@ ResultsPanelContainer.prototype = Herita
 
   _anchor: null,
   _panel: null,
   position: RESULTS_PANEL_POPUP_POSITION,
   left: 0,
   top: 0
 });
 
-DebuggerView.EventListeners = new EventListenersView(store, DebuggerController);
+DebuggerView.EventListeners = new EventListenersView(DebuggerController);
+DebuggerView.Sources = new SourcesView(DebuggerController, DebuggerView);
--- a/devtools/client/debugger/debugger.css
+++ b/devtools/client/debugger/debugger.css
@@ -54,8 +54,16 @@
   display: flex;
   flex-flow: column;
   justify-content: center;
 }
 
 #source-progress {
   flex: none;
 }
+
+#redux-devtools * {
+  display: block;
+}
+
+#redux-devtools span {
+  display: inline
+}
--- a/devtools/client/debugger/debugger.xul
+++ b/devtools/client/debugger/debugger.xul
@@ -491,9 +491,11 @@
          consumeoutsideclicks="false">
     <vbox>
       <label id="conditional-breakpoint-panel-description"
              value="&debuggerUI.condBreakPanelTitle;"/>
       <textbox id="conditional-breakpoint-panel-textbox"/>
     </vbox>
   </panel>
 
+  <script type="text/javascript" src="resource://gre/browser/modules/devtools/debugger/mocks.js"/>
+  <script type="text/javascript" src="resource://gre/browser/modules/devtools/debugger/mock-init.js"/>
 </window>
--- a/devtools/client/debugger/panel.js
+++ b/devtools/client/debugger/panel.js
@@ -96,22 +96,34 @@ DebuggerPanel.prototype = {
 
     return this._destroyer = this._controller.shutdownDebugger().then(() => {
       this.emit("destroyed");
     });
   },
 
   // DebuggerPanel API
 
-  addBreakpoint: function(aLocation, aOptions) {
-    return this._controller.Breakpoints.addBreakpoint(aLocation, aOptions);
+  addBreakpoint: function(location) {
+    const { actions } = this.panelWin;
+    const { dispatch } =  this._controller;
+
+    return dispatch(actions.addBreakpoint(location));
   },
 
-  removeBreakpoint: function(aLocation) {
-    return this._controller.Breakpoints.removeBreakpoint(aLocation);
+  removeBreakpoint: function(location) {
+    const { actions } = this.panelWin;
+    const { dispatch } =  this._controller;
+
+    return dispatch(actions.removeBreakpoint(location));
+  },
+
+  blackbox: function(source, flag) {
+    const { actions } = this.panelWin;
+    const { dispatch } =  this._controller;
+    return dispatch(actions.blackbox(source, flag))
   },
 
   handleHostChanged: function() {
     this._view.handleHostChanged(this._toolbox.hostType);
   },
 
   highlightWhenPaused: function() {
     this._toolbox.highlightTool("jsdebugger");
--- a/devtools/client/debugger/test/mochitest/browser.ini
+++ b/devtools/client/debugger/test/mochitest/browser.ini
@@ -1,11 +1,12 @@
 [DEFAULT]
 tags = devtools
 subsuite = devtools
+skip-if = (os == 'linux' && debug && bits == 32)
 support-files =
   addon1.xpi
   addon2.xpi
   addon3.xpi
   addon4.xpi
   addon5.xpi
   code_binary_search.coffee
   code_binary_search.js
--- a/devtools/client/debugger/test/mochitest/browser_dbg_auto-pretty-print-02.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_auto-pretty-print-02.js
@@ -35,17 +35,17 @@ function test(){
 
     waitForSourceShown(gPanel, gFirstSourceLabel)
       .then(testSourceIsUgly)
       .then(() => waitForSourceShown(gPanel, gFirstSourceLabel))
       .then(testSourceIsPretty)
       .then(testPrettyPrintButtonOn)
       .then(() => {
         // Switch to the second source.
-        let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN);
+        let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN, 2);
         gSources.selectedIndex = 1;
         return finished;
       })
       .then(testSecondSourceLabel)
       .then(() => {
         // Switch back to first source.
         let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN);
         gSources.selectedIndex = 0;
@@ -91,18 +91,16 @@ function testPrettyPrintButtonOn(){
     "The button should be checked when the source is selected.");
 }
 
 function disableAutoPrettyPrint(){
   gOptions._autoPrettyPrint.setAttribute("checked", "false");
   gOptions._toggleAutoPrettyPrint();
   gOptions._onPopupHidden();
   info("Disabled auto pretty printing.");
-  // Wait for the pref update to be communicated to the server.
-  return waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN);
 }
 
 function testSourceIsPretty() {
   ok(gEditor.getText().includes("\n  "),
     "The source should be pretty printed.")
 }
 
 registerCleanupFunction(function() {
--- a/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-01.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-01.js
@@ -24,18 +24,18 @@ function test() {
       });
   });
 }
 
 function testBlackBoxSource() {
   const bbButton = getBlackBoxButton(gPanel);
   ok(!bbButton.checked, "Should not be black boxed by default");
 
-  return toggleBlackBoxing(gPanel).then(aSource => {
-    ok(aSource.isBlackBoxed, "The source should be black boxed now.");
+  return toggleBlackBoxing(gPanel).then(source => {
+    ok(source.isBlackBoxed, "The source should be black boxed now.");
     ok(bbButton.checked, "The checkbox should no longer be checked.");
   });
 }
 
 function testBlackBoxReload() {
   return reloadActiveTab(gPanel, gDebugger.EVENTS.SOURCE_SHOWN).then(() => {
     const bbButton = getBlackBoxButton(gPanel);
     const selectedSource = getSelectedSourceElement(gPanel);
--- a/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-02.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-02.js
@@ -25,18 +25,18 @@ function test() {
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function testBlackBoxSource() {
-  return toggleBlackBoxing(gPanel).then(aSource => {
-    ok(aSource.isBlackBoxed, "The source should be black boxed now.");
+  return toggleBlackBoxing(gPanel).then(source => {
+    ok(source.isBlackBoxed, "The source should be black boxed now.");
   });
 }
 
 function testBlackBoxStack() {
   let finished = waitForSourceAndCaretAndScopes(gPanel, ".html", 21).then(() => {
     is(gFrames.itemCount, 3,
       "Should only get 3 frames.");
     is(gDebugger.document.querySelectorAll(".dbg-stackframe-black-boxed").length, 1,
--- a/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-05.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-05.js
@@ -39,17 +39,17 @@ function testSourceEditorShown() {
 function testBlackBoxMessageShown() {
   is(gDeck.selectedIndex, "1",
     "The second item in the deck should be selected (the black box message).");
 }
 
 function clickStopBlackBoxingButton() {
   // Give the test a chance to finish before triggering the click event.
   executeSoon(() => getEditorBlackboxMessageButton().click());
-  return waitForThreadEvents(gPanel, "blackboxchange");
+  return waitForDispatch(gPanel, gDebugger.constants.BLACKBOX);
 }
 
 function testSourceEditorShownAgain() {
   // Wait a tick for the final check to make sure the frontend's click handlers
   // have finished.
   return new Promise(resolve => {
     is(gDeck.selectedIndex, "0",
       "The first item in the deck should be selected again (the source editor).");
--- a/devtools/client/debugger/test/mochitest/browser_dbg_breadcrumbs-access.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_breadcrumbs-access.js
@@ -28,59 +28,58 @@ function test() {
       });
 
     callInTab(gTab, "firstCall");
   });
 
   function checkNavigationWhileNotFocused() {
     checkState({ frame: 1, source: 1, line: 6 });
 
-    EventUtils.sendKey("DOWN", gDebugger);
-    checkState({ frame: 1, source: 1, line: 7 });
+    return Task.spawn(function*() {
+      EventUtils.sendKey("DOWN", gDebugger);
+      checkState({ frame: 1, source: 1, line: 7 });
 
-    EventUtils.sendKey("UP", gDebugger);
-    checkState({ frame: 1, source: 1, line: 6 });
+      EventUtils.sendKey("UP", gDebugger);
+      checkState({ frame: 1, source: 1, line: 6 });
+    });
   }
 
   function focusCurrentStackFrame() {
     EventUtils.sendMouseEvent({ type: "mousedown" },
       gFrames.selectedItem.target,
       gDebugger);
   }
 
   function checkNavigationWhileFocused() {
     return Task.spawn(function*() {
       yield promise.all([
         waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
         waitForSourceAndCaret(gPanel, "-01.js", 5),
-        waitForEditorLocationSet(gPanel),
         EventUtils.sendKey("UP", gDebugger)
       ]);
       checkState({ frame: 0, source: 0, line: 5 });
 
       // Need to refocus the stack frame due to a focus bug in e10s
       // (See Bug 1205482)
       focusCurrentStackFrame();
 
       yield promise.all([
         waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
         waitForSourceAndCaret(gPanel, "-02.js", 6),
-        waitForEditorLocationSet(gPanel),
         EventUtils.sendKey("END", gDebugger)
       ]);
       checkState({ frame: 1, source: 1, line: 6 });
 
       // Need to refocus the stack frame due to a focus bug in e10s
       // (See Bug 1205482)
       focusCurrentStackFrame();
 
       yield promise.all([
         waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
         waitForSourceAndCaret(gPanel, "-01.js", 5),
-        waitForEditorLocationSet(gPanel),
         EventUtils.sendKey("HOME", gDebugger)
       ]);
       checkState({ frame: 0, source: 0, line: 5 });
     });
   }
 
   function checkState({ frame, source, line, column }) {
     is(gFrames.selectedIndex, frame,
--- a/devtools/client/debugger/test/mochitest/browser_dbg_break-in-anon.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-in-anon.js
@@ -4,33 +4,28 @@
 /**
  * Make sure anonymous eval scripts can still break with a `debugger`
  * statement
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_script-eval.html";
 
 function test() {
-  let gTab, gPanel, gDebugger;
-  let gSources, gBreakpoints;
-
   initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
-    gTab = aTab;
-    gPanel = aPanel;
-    gDebugger = gPanel.panelWin;
-    gSources = gDebugger.DebuggerView.Sources;
-    gBreakpoints = gDebugger.DebuggerController.Breakpoints;
+    const gTab = aTab;
+    const gPanel = aPanel;
+    const gDebugger = gPanel.panelWin;
+    const gSources = gDebugger.DebuggerView.Sources;
 
     return Task.spawn(function*() {
       yield waitForSourceShown(gPanel, "-eval.js");
       is(gSources.values.length, 1, "Should have 1 source");
 
-      let hasFrames = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_REFILLED);
       callInTab(gTab, "evalSourceWithDebugger");
-      yield hasFrames;
+      yield waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN);
 
       is(gSources.values.length, 2, "Should have 2 sources");
 
       let item = gSources.getItemForAttachment(e => e.label.indexOf("SCRIPT") === 0);
       ok(item, "Source label is incorrect.");
       is(item.attachment.group, gDebugger.L10N.getStr('anonymousSourcesLabel'),
          'Source group is incorrect');
 
--- a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-01.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-01.js
@@ -7,16 +7,18 @@
 
 const TAB_URL = EXAMPLE_URL + "doc_event-listeners-02.html";
 
 function test() {
   initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     let gDebugger = aPanel.panelWin;
     let gView = gDebugger.DebuggerView;
     let gEvents = gView.EventListeners;
+    let gController = gDebugger.DebuggerController;
+    let constants = gDebugger.require('./content/constants');
 
     gDebugger.on(gDebugger.EVENTS.EVENT_LISTENERS_FETCHED, () => {
       ok(false, "Shouldn't have fetched any event listeners.");
     });
     gDebugger.on(gDebugger.EVENTS.EVENT_BREAKPOINTS_UPDATED, () => {
       ok(false, "Shouldn't have updated any event breakpoints.");
     });
 
@@ -39,14 +41,14 @@ function test() {
       is(gEvents.itemCount, 0, "There should be no events after reloading.");
 
       yield closeDebuggerAndFinish(aPanel);
     });
 
     function waitForSourcesAfterReload() {
       return promise.all([
         waitForDebuggerEvents(aPanel, gDebugger.EVENTS.NEW_SOURCE),
-        waitForDebuggerEvents(aPanel, gDebugger.EVENTS.SOURCES_ADDED),
+        waitForDispatch(aPanel, gDebugger.constants.LOAD_SOURCES),
         waitForDebuggerEvents(aPanel, gDebugger.EVENTS.SOURCE_SHOWN)
       ]);
     }
   });
 }
--- a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-02.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-02.js
@@ -8,30 +8,30 @@
 
 const TAB_URL = EXAMPLE_URL + "doc_event-listeners-02.html";
 
 function test() {
   initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     let gDebugger = aPanel.panelWin;
     let gView = gDebugger.DebuggerView;
     let gEvents = gView.EventListeners;
-    let gStore = gDebugger.store;
+    let gController = gDebugger.DebuggerController;
     let constants = gDebugger.require('./content/constants');
 
     Task.spawn(function*() {
       yield waitForSourceShown(aPanel, ".html");
       yield testFetchOnFocus();
       yield testFetchOnReloadWhenFocused();
       yield testFetchOnReloadWhenNotFocused();
       yield closeDebuggerAndFinish(aPanel);
     });
 
     function testFetchOnFocus() {
       return Task.spawn(function*() {
-        let fetched = afterDispatch(gStore, constants.FETCH_EVENT_LISTENERS);
+        let fetched = waitForDispatch(aPanel, constants.FETCH_EVENT_LISTENERS);
 
         gView.toggleInstrumentsPane({ visible: true, animated: false }, 1);
         is(gView.instrumentsPaneHidden, false,
           "The instruments pane should be visible now.");
         is(gView.instrumentsPaneTab, "events-tab",
           "The events tab should be selected.");
 
         yield fetched;
@@ -40,17 +40,17 @@ function test() {
           "Event listeners were fetched when the events tab was selected");
         is(gEvents.itemCount, 4,
           "There should be 4 events displayed in the view.");
       });
     }
 
     function testFetchOnReloadWhenFocused() {
       return Task.spawn(function*() {
-        let fetched = afterDispatch(gStore, constants.FETCH_EVENT_LISTENERS);
+        let fetched = waitForDispatch(aPanel, constants.FETCH_EVENT_LISTENERS);
 
         let reloading = once(gDebugger.gTarget, "will-navigate");
         let reloaded = waitForSourcesAfterReload();
         gDebugger.DebuggerController._target.activeTab.reload();
 
         yield reloading;
 
         is(gEvents.itemCount, 0,
@@ -71,17 +71,17 @@ function test() {
           "There should be 4 events displayed in the view after reloading.");
         ok(true,
           "Event listeners were added back after the target finished navigating.");
       });
     }
 
     function testFetchOnReloadWhenNotFocused() {
       return Task.spawn(function*() {
-        gStore.dispatch({
+        gController.dispatch({
           type: gDebugger.services.WAIT_UNTIL,
           predicate: action => {
             return (action.type === constants.FETCH_EVENT_LISTENERS ||
                     action.type === constants.UPDATE_EVENT_BREAKPOINTS);
           },
           run: (dispatch, getState, action) => {
             if(action.type === constants.FETCH_EVENT_LISTENERS) {
               ok(false, "Shouldn't have fetched any event listeners.");
@@ -124,14 +124,14 @@ function test() {
         ok(true,
           "Event listeners were not added after the target finished navigating.");
       });
     }
 
     function waitForSourcesAfterReload() {
       return promise.all([
         waitForDebuggerEvents(aPanel, gDebugger.EVENTS.NEW_SOURCE),
-        waitForDebuggerEvents(aPanel, gDebugger.EVENTS.SOURCES_ADDED),
+        waitForDispatch(aPanel, constants.LOAD_SOURCES),
         waitForDebuggerEvents(aPanel, gDebugger.EVENTS.SOURCE_SHOWN)
       ]);
     }
   });
 }
--- a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-03.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-03.js
@@ -7,23 +7,23 @@
 
 const TAB_URL = EXAMPLE_URL + "doc_event-listeners-02.html";
 
 function test() {
   initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     let gDebugger = aPanel.panelWin;
     let gView = gDebugger.DebuggerView;
     let gEvents = gView.EventListeners;
-    let gStore = gDebugger.store;
+    let gController = gDebugger.DebuggerController;
     let constants = gDebugger.require('./content/constants');
 
     Task.spawn(function*() {
       yield waitForSourceShown(aPanel, ".html");
 
-      let fetched = afterDispatch(gStore, constants.FETCH_EVENT_LISTENERS);
+      let fetched = waitForDispatch(aPanel, constants.FETCH_EVENT_LISTENERS);
       gView.toggleInstrumentsPane({ visible: true, animated: false }, 1);
       yield fetched;
 
       is(gEvents.widget._parent.querySelectorAll(".side-menu-widget-group").length, 3,
         "There should be 3 groups shown in the view.");
       is(gEvents.widget._parent.querySelectorAll(".side-menu-widget-group-checkbox").length, 3,
         "There should be a checkbox for each group shown in the view.");
 
--- a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-04.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-04.js
@@ -9,50 +9,48 @@
 const TAB_URL = EXAMPLE_URL + "doc_event-listeners-02.html";
 
 function test() {
   initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     let gDebugger = aPanel.panelWin;
     let gView = gDebugger.DebuggerView;
     let gController = gDebugger.DebuggerController
     let gEvents = gView.EventListeners;
-    let gStore = gDebugger.store;
-    let getState = gStore.getState;
     let constants = gDebugger.require('./content/constants');
 
     Task.spawn(function*() {
       yield waitForSourceShown(aPanel, ".html");
 
-      let fetched = afterDispatch(gStore, constants.FETCH_EVENT_LISTENERS);
+      let fetched = waitForDispatch(aPanel, constants.FETCH_EVENT_LISTENERS);
       gView.toggleInstrumentsPane({ visible: true, animated: false }, 1);
       yield fetched;
 
       testEventItem(0, false);
       testEventItem(1, false);
       testEventItem(2, false);
       testEventItem(3, false);
       testEventGroup("interactionEvents", false);
       testEventGroup("keyboardEvents", false);
       testEventGroup("mouseEvents", false);
       testEventArrays("change,click,keydown,keyup", "");
 
-      let updated = afterDispatch(gStore, constants.UPDATE_EVENT_BREAKPOINTS);
+      let updated = waitForDispatch(aPanel, constants.UPDATE_EVENT_BREAKPOINTS);
       EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(0), gDebugger);
       yield updated;
 
       testEventItem(0, true);
       testEventItem(1, false);
       testEventItem(2, false);
       testEventItem(3, false);
       testEventGroup("interactionEvents", false);
       testEventGroup("keyboardEvents", false);
       testEventGroup("mouseEvents", false);
       testEventArrays("change,click,keydown,keyup", "change");
 
-      updated = afterDispatch(gStore, constants.UPDATE_EVENT_BREAKPOINTS);
+      updated = waitForDispatch(aPanel, constants.UPDATE_EVENT_BREAKPOINTS);
       EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(0), gDebugger);
       yield updated;
 
       testEventItem(0, false);
       testEventItem(1, false);
       testEventItem(2, false);
       testEventItem(3, false);
       testEventGroup("interactionEvents", false);
@@ -87,13 +85,13 @@ function test() {
         "The correct checkbox state is shown for the group " + string + ".");
     }
 
     function testEventArrays(all, checked) {
       is(gEvents.getAllEvents().toString(), all,
         "The getAllEvents() method returns the correct stuff.");
       is(gEvents.getCheckedEvents().toString(), checked,
         "The getCheckedEvents() method returns the correct stuff.");
-      is(getState().eventListeners.activeEventNames.toString(), checked,
+      is(gController.getState().eventListeners.activeEventNames.toString(), checked,
         "The correct event names are listed as being active breakpoints.");
     }
   });
 }
--- a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-05.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-05.js
@@ -10,76 +10,74 @@
 const TAB_URL = EXAMPLE_URL + "doc_event-listeners-02.html";
 
 function test() {
   initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     let gDebugger = aPanel.panelWin;
     let gView = gDebugger.DebuggerView;
     let gController = gDebugger.DebuggerController
     let gEvents = gView.EventListeners;
-    let gStore = gDebugger.store;
-    let getState = gStore.getState;
     let constants = gDebugger.require('./content/constants');
 
     Task.spawn(function*() {
       yield waitForSourceShown(aPanel, ".html");
 
-      let fetched = afterDispatch(gStore, constants.FETCH_EVENT_LISTENERS);
+      let fetched = waitForDispatch(aPanel, constants.FETCH_EVENT_LISTENERS);
       gView.toggleInstrumentsPane({ visible: true, animated: false }, 1);
       yield fetched;
 
       testEventItem(0, false);
       testEventItem(1, false);
       testEventItem(2, false);
       testEventItem(3, false);
       testEventGroup("interactionEvents", false);
       testEventGroup("keyboardEvents", false);
       testEventGroup("mouseEvents", false);
       testEventArrays("change,click,keydown,keyup", "");
 
-      let updated = afterDispatch(gStore, constants.UPDATE_EVENT_BREAKPOINTS);
+      let updated = waitForDispatch(aPanel, constants.UPDATE_EVENT_BREAKPOINTS);
       EventUtils.sendMouseEvent({ type: "click" }, getGroupCheckboxNode("interactionEvents"), gDebugger);
       yield updated;
 
       testEventItem(0, true);
       testEventItem(1, false);
       testEventItem(2, false);
       testEventItem(3, false);
       testEventGroup("interactionEvents", true);
       testEventGroup("keyboardEvents", false);
       testEventGroup("mouseEvents", false);
       testEventArrays("change,click,keydown,keyup", "change");
 
-      updated = afterDispatch(gStore, constants.UPDATE_EVENT_BREAKPOINTS);
+      updated = waitForDispatch(aPanel, constants.UPDATE_EVENT_BREAKPOINTS);
       EventUtils.sendMouseEvent({ type: "click" }, getGroupCheckboxNode("interactionEvents"), gDebugger);
       yield updated;
 
       testEventItem(0, false);
       testEventItem(1, false);
       testEventItem(2, false);
       testEventItem(3, false);
       testEventGroup("interactionEvents", false);
       testEventGroup("keyboardEvents", false);
       testEventGroup("mouseEvents", false);
       testEventArrays("change,click,keydown,keyup", "");
 
-      updated = afterDispatch(gStore, constants.UPDATE_EVENT_BREAKPOINTS);
+      updated = waitForDispatch(aPanel, constants.UPDATE_EVENT_BREAKPOINTS);
       EventUtils.sendMouseEvent({ type: "click" }, getGroupCheckboxNode("keyboardEvents"), gDebugger);
       yield updated;
 
       testEventItem(0, false);
       testEventItem(1, false);
       testEventItem(2, true);
       testEventItem(3, true);
       testEventGroup("interactionEvents", false);
       testEventGroup("keyboardEvents", true);
       testEventGroup("mouseEvents", false);
       testEventArrays("change,click,keydown,keyup", "keydown,keyup");
 
-      updated = afterDispatch(gStore, constants.UPDATE_EVENT_BREAKPOINTS);
+      updated = waitForDispatch(aPanel, constants.UPDATE_EVENT_BREAKPOINTS);
       EventUtils.sendMouseEvent({ type: "click" }, getGroupCheckboxNode("keyboardEvents"), gDebugger);
       yield updated;
 
       testEventItem(0, false);
       testEventItem(1, false);
       testEventItem(2, false);
       testEventItem(3, false);
       testEventGroup("interactionEvents", false);
@@ -114,13 +112,13 @@ function test() {
         "The correct checkbox state is shown for the group " + string + ".");
     }
 
     function testEventArrays(all, checked) {
       is(gEvents.getAllEvents().toString(), all,
         "The getAllEvents() method returns the correct stuff.");
       is(gEvents.getCheckedEvents().toString(), checked,
         "The getCheckedEvents() method returns the correct stuff.");
-      is(getState().eventListeners.activeEventNames.toString(), checked,
+      is(gController.getState().eventListeners.activeEventNames.toString(), checked,
         "The correct event names are listed as being active breakpoints.");
     }
   });
 }
--- a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-06.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-06.js
@@ -10,80 +10,78 @@ const TAB_URL = EXAMPLE_URL + "doc_event
 
 function test() {
   initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     let gDebugger = aPanel.panelWin;
     let gView = gDebugger.DebuggerView;
     let gController = gDebugger.DebuggerController
     let gEvents = gView.EventListeners;
     let gBreakpoints = gController.Breakpoints;
-    let gStore = gDebugger.store;
-    let getState = gStore.getState;
     let constants = gDebugger.require('./content/constants');
 
     Task.spawn(function*() {
       yield waitForSourceShown(aPanel, ".html");
 
-      let fetched = afterDispatch(gStore, constants.FETCH_EVENT_LISTENERS);
+      let fetched = waitForDispatch(aPanel, constants.FETCH_EVENT_LISTENERS);
       gView.toggleInstrumentsPane({ visible: true, animated: false }, 1);
       yield fetched;
 
       testEventItem(0, false);
       testEventItem(1, false);
       testEventItem(2, false);
       testEventItem(3, false);
       testEventGroup("interactionEvents", false);
       testEventGroup("keyboardEvents", false);
       testEventGroup("mouseEvents", false);
       testEventArrays("change,click,keydown,keyup", "");
 
-      let updated = afterDispatch(gStore, constants.UPDATE_EVENT_BREAKPOINTS);
+      let updated = waitForDispatch(aPanel, constants.UPDATE_EVENT_BREAKPOINTS);
       EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(0), gDebugger);
       EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(1), gDebugger);
       EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(2), gDebugger);
       yield updated;
 
       testEventItem(0, true);
       testEventItem(1, true);
       testEventItem(2, true);
       testEventItem(3, false);
       testEventGroup("interactionEvents", false);
       testEventGroup("keyboardEvents", false);
       testEventGroup("mouseEvents", false);
       testEventArrays("change,click,keydown,keyup", "change,click,keydown");
 
       reload(aPanel);
-      yield afterDispatch(gStore, constants.FETCH_EVENT_LISTENERS);
+      yield waitForDispatch(aPanel, constants.FETCH_EVENT_LISTENERS);
 
       testEventItem(0, true);
       testEventItem(1, true);
       testEventItem(2, true);
       testEventItem(3, false);
       testEventGroup("interactionEvents", false);
       testEventGroup("keyboardEvents", false);
       testEventGroup("mouseEvents", false);
       testEventArrays("change,click,keydown,keyup", "change,click,keydown");
 
-      updated = afterDispatch(gStore, constants.UPDATE_EVENT_BREAKPOINTS);
+      updated = waitForDispatch(aPanel, constants.UPDATE_EVENT_BREAKPOINTS);
       EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(0), gDebugger);
       EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(1), gDebugger);
       EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(2), gDebugger);
       yield updated;
 
       testEventItem(0, false);
       testEventItem(1, false);
       testEventItem(2, false);
       testEventItem(3, false);
       testEventGroup("interactionEvents", false);
       testEventGroup("keyboardEvents", false);
       testEventGroup("mouseEvents", false);
       testEventArrays("change,click,keydown,keyup", "");
 
       reload(aPanel);
-      yield afterDispatch(gStore, constants.FETCH_EVENT_LISTENERS);
+      yield waitForDispatch(aPanel, constants.FETCH_EVENT_LISTENERS);
 
       testEventItem(0, false);
       testEventItem(1, false);
       testEventItem(2, false);
       testEventItem(3, false);
       testEventGroup("interactionEvents", false);
       testEventGroup("keyboardEvents", false);
       testEventGroup("mouseEvents", false);
@@ -116,13 +114,13 @@ function test() {
         "The correct checkbox state is shown for the group " + string + ".");
     }
 
     function testEventArrays(all, checked) {
       is(gEvents.getAllEvents().toString(), all,
         "The getAllEvents() method returns the correct stuff.");
       is(gEvents.getCheckedEvents().toString(), checked,
          "The getCheckedEvents() method returns the correct stuff.");
-      is(getState().eventListeners.activeEventNames.toString(), checked,
+      is(gController.getState().eventListeners.activeEventNames.toString(), checked,
          "The correct event names are listed as being active breakpoints.");
     }
   });
 }
--- a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-08.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-08.js
@@ -8,35 +8,34 @@
 const TAB_URL = EXAMPLE_URL + "doc_event-listeners-02.html";
 
 function test() {
   initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     let gTab = aTab;
     let gDebugger = aPanel.panelWin;
     let gView = gDebugger.DebuggerView;
     let gEvents = gView.EventListeners;
-    let gStore = gDebugger.store;
-    let getState = gStore.getState;
+    let gController = gDebugger.DebuggerController;
     let constants = gDebugger.require('./content/constants');
 
     Task.spawn(function*() {
       yield waitForSourceShown(aPanel, ".html");
       yield callInTab(gTab, "addBodyClickEventListener");
 
-      let fetched = afterDispatch(gStore, constants.FETCH_EVENT_LISTENERS);
+      let fetched = waitForDispatch(aPanel, constants.FETCH_EVENT_LISTENERS);
       gView.toggleInstrumentsPane({ visible: true, animated: false }, 1);
       yield fetched;
       yield ensureThreadClientState(aPanel, "attached");
 
       is(gView.instrumentsPaneHidden, false,
         "The instruments pane should be visible.");
       is(gView.instrumentsPaneTab, "events-tab",
         "The events tab should be selected.");
 
-      let updated = afterDispatch(gStore, constants.UPDATE_EVENT_BREAKPOINTS);
+      let updated = waitForDispatch(aPanel, constants.UPDATE_EVENT_BREAKPOINTS);
       EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(1), gDebugger);
       yield updated;
       yield ensureThreadClientState(aPanel, "attached");
 
       let paused = waitForCaretAndScopes(aPanel, 48);
       generateMouseClickInTab(gTab, "content.document.body");
       yield paused;
       yield ensureThreadClientState(aPanel, "paused");
--- a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-next-console.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-next-console.js
@@ -12,17 +12,16 @@ function test() {
   let gTab, gPanel, gDebugger;
   let gSources, gBreakpoints, gTarget, gResumeButton, gResumeKey, gThreadClient;
 
   initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     gTab = aTab;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gSources = gDebugger.DebuggerView.Sources;
-    gBreakpoints = gDebugger.DebuggerController.Breakpoints;
     gTarget = gDebugger.gTarget;
     gThreadClient = gDebugger.gThreadClient;
     gResumeButton = gDebugger.document.getElementById("resume");
     gResumeKey = gDebugger.document.getElementById("resumeKey");
 
     waitForSourceShown(gPanel, "-eval.js")
       .then(testConsole)
       .then(() => closeDebuggerAndFinish(gPanel));
--- a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-next.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-next.js
@@ -12,17 +12,16 @@ function test() {
   let gTab, gPanel, gDebugger;
   let gSources, gBreakpoints, gTarget, gResumeButton, gResumeKey, gThreadClient;
 
   initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     gTab = aTab;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gSources = gDebugger.DebuggerView.Sources;
-    gBreakpoints = gDebugger.DebuggerController.Breakpoints;
     gTarget = gDebugger.gTarget;
     gThreadClient = gDebugger.gThreadClient;
     gResumeButton = gDebugger.document.getElementById("resume");
     gResumeKey = gDebugger.document.getElementById("resumeKey");
 
     waitForSourceShown(gPanel, "-eval.js")
       .then(testInterval)
       .then(testEvent)
@@ -41,17 +40,17 @@ function test() {
         return 1+1;
       }, 100);
     `);
 
     let oncePaused = gTarget.once("thread-paused");
     EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger);
     yield oncePaused;
 
-    let updatedFrame = yield waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES);
+    yield waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN);
     let variables = gDebugger.DebuggerView.Variables;
 
     is(variables._store.length, 4, "Correct number of scopes available");
     is(variables.getScopeAtIndex(0).name, "Function scope [interval<]",
         "Paused with correct scope (0)");
     is(variables.getScopeAtIndex(1).name, "Block scope",
         "Paused with correct scope (1)");
     is(variables.getScopeAtIndex(2).name, "Block scope",
@@ -70,17 +69,17 @@ function test() {
 
     let oncePaused = gTarget.once("thread-paused");
     EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger);
     once(gDebugger.gClient, "willInterrupt").then(() => {
       generateMouseClickInTab(gTab, "content.document.querySelector('button')");
     });
     yield oncePaused;
 
-    let updatedFrame = yield waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES);
+    yield waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN);
     let variables = gDebugger.DebuggerView.Variables;
 
     is(variables._store.length, 6, "Correct number of scopes available");
     is(variables.getScopeAtIndex(0).name, "Function scope [onclick]",
         "Paused with correct scope (0)");
     // Non-syntactic lexical scope introduced by non-syntactic scope chain.
     is(variables.getScopeAtIndex(1).name, "Block scope",
         "Paused with correct scope (1)");
--- a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-actual-location.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-actual-location.js
@@ -4,88 +4,46 @@
 /**
  * Bug 737803: Setting a breakpoint in a line without code should move
  * the icon to the actual location.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
 
 function test() {
-  let gTab, gPanel, gDebugger;
-  let gEditor, gSources, gBreakpoints, gBreakpointsAdded, gBreakpointsRemoving;
+  initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+    const gTab = aTab;
+    const gPanel = aPanel;
+    const gDebugger = gPanel.panelWin;
+    const gEditor = gDebugger.DebuggerView.editor;
+    const gSources = gDebugger.DebuggerView.Sources;
+    const gController = gDebugger.DebuggerController;
+    const constants = gDebugger.require('./content/constants');
+    const queries = gDebugger.require('./content/queries');
+    const actions = bindActionCreators(gPanel);
+
+    Task.spawn(function*() {
+      yield waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1);
+
+      is(queries.getBreakpoints(gController.getState()).length, 0,
+         "There are no breakpoints in the editor");
 
-  initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
-    gTab = aTab;
-    gPanel = aPanel;
-    gDebugger = gPanel.panelWin;
-    gEditor = gDebugger.DebuggerView.editor;
-    gSources = gDebugger.DebuggerView.Sources;
-    gBreakpoints = gDebugger.DebuggerController.Breakpoints;
-    gBreakpointsAdded = gBreakpoints._added;
-    gBreakpointsRemoving = gBreakpoints._removing;
+      const response = yield actions.addBreakpoint({
+        actor: gSources.selectedValue, line: 4
+      });
+
+      ok(response.actualLocation, "has an actualLocation");
+      is(response.actualLocation.line, 6, "moved to line 6");
 
-    waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1).then(performTest);
+      is(queries.getBreakpoints(gController.getState()).length, 1,
+         "There is only one breakpoint in the editor");
+
+      ok(!queries.getBreakpoint(gController.getState(), { actor: gSources.selectedValue, line: 4 }),
+         "There isn't any breakpoint added on an invalid line.");
+      ok(queries.getBreakpoint(gController.getState(), { actor: gSources.selectedValue, line: 6 }),
+         "There isn't any breakpoint added on an invalid line.");
+
+      resumeDebuggerThenCloseAndFinish(gPanel);
+    })
+
     callInTab(gTab, "firstCall");
   });
-
-  function performTest() {
-    is(gBreakpointsAdded.size, 0,
-      "No breakpoints currently added.");
-    is(gBreakpointsRemoving.size, 0,
-      "No breakpoints currently being removed.");
-    is(gEditor.getBreakpoints().length, 0,
-      "No breakpoints currently shown in the editor.");
-
-    gEditor.on("breakpointAdded", onEditorBreakpointAdd);
-    gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 4 }).then(onBreakpointAdd);
-  }
-
-  let onBpDebuggerAdd = false;
-  let onBpEditorAdd = false;
-
-  function onBreakpointAdd(aBreakpointClient) {
-    ok(aBreakpointClient,
-      "Breakpoint added, client received.");
-    is(aBreakpointClient.location.actor, gSources.selectedValue,
-      "Breakpoint client url is the same.");
-    is(aBreakpointClient.location.line, 6,
-      "Breakpoint client line is new.");
-
-    onBpDebuggerAdd = true;
-    maybeFinish();
-  }
-
-  function onEditorBreakpointAdd() {
-    gEditor.off("breakpointAdded", onEditorBreakpointAdd);
-
-    is(gEditor.getBreakpoints().length, 1,
-      "There is only one breakpoint in the editor");
-
-    ok(!gBreakpoints._getAdded({ actor: gSources.selectedValue, line: 4 }),
-      "There isn't any breakpoint added on an invalid line.");
-    ok(!gBreakpoints._getRemoving({ actor: gSources.selectedValue, line: 4 }),
-      "There isn't any breakpoint removed from an invalid line.");
-
-    ok(gBreakpoints._getAdded({ actor: gSources.selectedValue, line: 6 }),
-      "There is a breakpoint added on the actual line.");
-    ok(!gBreakpoints._getRemoving({ actor: gSources.selectedValue, line: 6 }),
-      "There isn't any breakpoint removed from the actual line.");
-
-    gBreakpoints._getAdded({ actor: gSources.selectedValue, line: 6 }).then(aBreakpointClient => {
-      is(aBreakpointClient.location.actor, gSources.selectedValue,
-        "Breakpoint client location actor is correct.");
-      is(aBreakpointClient.location.line, 6,
-        "Breakpoint client location line is correct.");
-
-      onBpEditorAdd = true;
-      maybeFinish();
-    });
-  }
-
-  function maybeFinish() {
-    info("onBpDebuggerAdd: " + onBpDebuggerAdd);
-    info("onBpEditorAdd: " + onBpEditorAdd);
-
-    if (onBpDebuggerAdd && onBpEditorAdd) {
-      resumeDebuggerThenCloseAndFinish(gPanel);
-    }
-  }
 }
--- a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-actual-location2.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-actual-location2.js
@@ -5,94 +5,78 @@
  * Bug 1008372: Setting a breakpoint in a line without code should move
  * the icon to the actual location, and if a breakpoint already exists
  * on the new location don't duplicate
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_breakpoint-move.html";
 
 function test() {
-  let gTab, gPanel, gDebugger;
-  let gEditor, gSources, gBreakpoints, gBreakpointsAdded, gBreakpointsRemoving;
+  initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+    const gTab = aTab;
+    const gPanel = aPanel;
+    const gDebugger = gPanel.panelWin;
+    const gEditor = gDebugger.DebuggerView.editor;
+    const gSources = gDebugger.DebuggerView.Sources;
+    const gController = gDebugger.DebuggerController;
+    const actions = bindActionCreators(gPanel);
+    const constants = gDebugger.require('./content/constants');
+    const queries = gDebugger.require('./content/queries');
 
-  initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
-    gTab = aTab;
-    gPanel = aPanel;
-    gDebugger = gPanel.panelWin;
-    gEditor = gDebugger.DebuggerView.editor;
-    gSources = gDebugger.DebuggerView.Sources;
-    gBreakpoints = gDebugger.DebuggerController.Breakpoints;
-    gBreakpointsAdded = gBreakpoints._added;
-    gBreakpointsRemoving = gBreakpoints._removing;
+    function resumeAndTestBreakpoint(line) {
+      return Task.spawn(function*() {
+        let event = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES);
+        doResume(gPanel);
+        yield event;
+        testBreakpoint(line);
+      });
+    };
 
-    waitForSourceAndCaretAndScopes(gPanel, ".html", 1).then(performTest);
-    callInTab(gTab, "ermahgerd");
-  });
-
-  function performTest() {
-    is(gBreakpointsAdded.size, 0,
-      "No breakpoints currently added.");
-    is(gBreakpointsRemoving.size, 0,
-      "No breakpoints currently being removed.");
-    is(gEditor.getBreakpoints().length, 0,
-      "No breakpoints currently shown in the editor.");
+    function testBreakpoint(line) {
+      let bp = gSources._selectedBreakpoint;
+      ok(bp, "There should be a selected breakpoint on line " + line);
+      is(bp.location.line, line,
+         "The breakpoint on line " + line + " was not hit");
+    }
 
     Task.spawn(function*() {
-      let bpClient = yield gPanel.addBreakpoint({
+      yield waitForSourceAndCaretAndScopes(gPanel, ".html", 1);
+
+      is(queries.getBreakpoints(gController.getState()).length, 0,
+         "There are no breakpoints in the editor");
+
+      yield actions.addBreakpoint({
         actor: gSources.selectedValue,
         line: 19
       });
-      yield gPanel.addBreakpoint({
+      yield actions.addBreakpoint({
         actor: gSources.selectedValue,
         line: 20
       });
 
-      let movedBpClient = yield gPanel.addBreakpoint({
+      const response = yield actions.addBreakpoint({
         actor: gSources.selectedValue,
         line: 17
       });
 
-      testMovedLocation(movedBpClient);
+      is(response.actualLocation.line, 19,
+         "Breakpoint client line is new.");
 
       yield resumeAndTestBreakpoint(19);
 
-      yield gPanel.removeBreakpoint({
+      yield actions.removeBreakpoint({
         actor: gSources.selectedValue,
         line: 19
       });
 
       yield resumeAndTestBreakpoint(20);
       yield doResume(gPanel);
 
       callInTab(gTab, "ermahgerd");
       yield waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES);
 
       yield resumeAndTestBreakpoint(20);
       resumeDebuggerThenCloseAndFinish(gPanel);
     });
-  }
 
-  function resumeAndTestBreakpoint(line) {
-    return Task.spawn(function*() {
-      let event = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES);
-      doResume(gPanel);
-      yield event;
-      testBreakpoint(line);
-    });
-  };
-
-  function testBreakpoint(line) {
-    let selectedBreakpoint = gSources._selectedBreakpointItem;
-    ok(selectedBreakpoint,
-       "There should be a selected breakpoint on line " + line);
-    is(selectedBreakpoint.attachment.line, line,
-       "The breakpoint on line " + line + " was not hit");
-  }
-
-  function testMovedLocation(breakpointClient) {
-    ok(breakpointClient,
-      "Breakpoint added, client received.");
-    is(breakpointClient.location.actor, gSources.selectedValue,
-      "Breakpoint client url is the same.");
-    is(breakpointClient.location.line, 19,
-      "Breakpoint client line is new.");
-  }
+    callInTab(gTab, "ermahgerd");
+  });
 }
--- a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js
@@ -16,16 +16,17 @@ function test() {
   let gPanel, gDebugger, gThreadClient, gEvents, gSources;
 
   initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gThreadClient = gDebugger.gThreadClient;
     gEvents = gDebugger.EVENTS;
     gSources = gDebugger.DebuggerView.Sources;
+    const actions = bindActionCreators(gPanel);
 
     Task.spawn(function* () {
       try {
 
         // Refresh and hit the debugger statement before the location we want to
         // set our breakpoints. We have to pause before the breakpoint locations
         // so that GC doesn't get a chance to kick in and collect the IIFE's
         // script, which would causes us to receive a 'noScript' error from the
@@ -33,27 +34,28 @@ function test() {
         const [paused, ] = yield promise.all([
           waitForThreadEvents(gPanel, "paused"),
           reloadActiveTab(gPanel, gEvents.SOURCE_SHOWN),
         ]);
 
         is(paused.why.type, "debuggerStatement");
 
         // Set our breakpoints.
-        const [bp1, bp2, bp3] = yield promise.all([
-          setBreakpoint({
-            url: CODE_URL,
+        const sourceActor = getSourceActor(gSources, CODE_URL);
+        yield promise.all([
+          actions.addBreakpoint({
+            actor: sourceActor,
             line: 3
           }),
-          setBreakpoint({
-            url: CODE_URL,
+          actions.addBreakpoint({
+            actor: sourceActor,
             line: 4
           }),
-          setBreakpoint({
-            url: CODE_URL,
+          actions.addBreakpoint({
+            actor: sourceActor,
             line: 5
           })
         ]);
 
         // Refresh and hit the debugger statement again.
         yield promise.all([
           reloadActiveTab(gPanel, gEvents.SOURCE_SHOWN),
           waitForCaretAndScopes(gPanel, 1)
@@ -70,19 +72,19 @@ function test() {
         ]);
         yield promise.all([
           doResume(gPanel),
           waitForCaretAndScopes(gPanel, 5)
         ]);
 
         // Clean up the breakpoints.
         yield promise.all([
-          rdpInvoke(bp1, bp1.remove),
-          rdpInvoke(bp2, bp1.remove),
-          rdpInvoke(bp3, bp1.remove),
+          actions.removeBreakpoint({ actor: sourceActor, line: 3 }),
+          actions.removeBreakpoint({ actor: sourceActor, line: 4 }),
+          actions.removeBreakpoint({ actor: sourceActor, line: 5 })
         ]);
 
         yield resumeDebuggerThenCloseAndFinish(gPanel);
 
       } catch (e) {
         DevToolsUtils.reportException(
           "browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js",
           e
--- a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-button-01.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-button-01.js
@@ -3,58 +3,47 @@
 
 /**
  * Test if the breakpoints toggle button works as advertised.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
 
 function test() {
-  let gTab, gPanel, gDebugger;
-  let gSources, gBreakpoints;
-
   initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
-    gTab = aTab;
-    gPanel = aPanel;
-    gDebugger = gPanel.panelWin;
-    gSources = gDebugger.DebuggerView.Sources;
-    gBreakpoints = gDebugger.DebuggerController.Breakpoints;
+    const gTab = aTab;
+    const gPanel = aPanel;
+    const gDebugger = gPanel.panelWin;
+    const gSources = gDebugger.DebuggerView.Sources;
+    const actions = bindActionCreators(gPanel);
+    const getState = gDebugger.DebuggerController.getState;
 
-    waitForSourceShown(gPanel, "-01.js")
-      .then(addBreakpoints)
-      .then(testDisableBreakpoints)
-      .then(testEnableBreakpoints)
-      .then(() => ensureThreadClientState(gPanel, "resumed"))
-      .then(() => closeDebuggerAndFinish(gPanel))
-      .then(null, aError => {
-        ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
-      });
-  });
+    function checkBreakpointsDisabled(isDisabled, total = 3) {
+      let breakpoints = gDebugger.queries.getBreakpoints(getState());
+
+      is(breakpoints.length, total,
+         "Breakpoints should still be set.");
+      is(breakpoints.filter(bp => bp.disabled === isDisabled).length, total,
+         "Breakpoints should be " + (isDisabled ? "disabled" : "enabled") + ".");
+    }
+
+    Task.spawn(function*() {
+      yield waitForSourceShown(gPanel, "-01.js");
 
-  function addBreakpoints() {
-    return promise.resolve(null)
-      .then(() => gPanel.addBreakpoint({ actor: gSources.values[0], line: 5 }))
-      .then(() => gPanel.addBreakpoint({ actor: gSources.values[1], line: 6 }))
-      .then(() => gPanel.addBreakpoint({ actor: gSources.values[1], line: 7 }))
-      .then(() => ensureThreadClientState(gPanel, "resumed"));
-  }
+      yield actions.addBreakpoint({ actor: gSources.values[0], line: 5 });
+      yield actions.addBreakpoint({ actor: gSources.values[1], line: 6 });
+      yield actions.addBreakpoint({ actor: gSources.values[1], line: 7 });
+      yield ensureThreadClientState(gPanel, "resumed");
 
-  function testDisableBreakpoints() {
-    let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_REMOVED, 3);
-    gSources.toggleBreakpoints();
-    return finished.then(() => checkBreakpointsDisabled(true));
-  }
+      gSources.toggleBreakpoints();
+      yield waitForDispatch(gPanel, gDebugger.constants.REMOVE_BREAKPOINT, 3);
+      checkBreakpointsDisabled(true)
 
-  function testEnableBreakpoints() {
-    let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_ADDED, 3);
-    gSources.toggleBreakpoints();
-    return finished.then(() => checkBreakpointsDisabled(false));
-  }
+      const finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_ADDED, 3);
+      gSources.toggleBreakpoints();
+      yield waitForDispatch(gPanel, gDebugger.constants.ADD_BREAKPOINT, 3);
+      checkBreakpointsDisabled(false);
 
-  function checkBreakpointsDisabled(aState, aTotal = 3) {
-    let breakpoints = gSources.getAllBreakpoints();
-
-    is(breakpoints.length, aTotal,
-      "Breakpoints should still be set.");
-    is(breakpoints.filter(e => e.attachment.disabled == aState).length, aTotal,
-      "Breakpoints should be " + (aState ? "disabled" : "enabled") + ".");
-  }
+      yield ensureThreadClientState(gPanel, "resumed");
+      closeDebuggerAndFinish(gPanel)
+    });
+  });
 }
--- a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-button-02.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-button-02.js
@@ -4,66 +4,57 @@
 /**
  * Test if the breakpoints toggle button works as advertised when there are
  * some breakpoints already disabled.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
 
 function test() {
-  let gTab, gPanel, gDebugger;
-  let gSources, gBreakpoints;
-
   initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
-    gTab = aTab;
-    gPanel = aPanel;
-    gDebugger = gPanel.panelWin;
-    gSources = gDebugger.DebuggerView.Sources;
-    gBreakpoints = gDebugger.DebuggerController.Breakpoints;
+    const gTab = aTab;
+    const gPanel = aPanel;
+    const gDebugger = gPanel.panelWin;
+    const gSources = gDebugger.DebuggerView.Sources;
+    const actions = bindActionCreators(gPanel);
+    const getState = gDebugger.DebuggerController.getState;
 
-    waitForSourceShown(gPanel, "-01.js")
-      .then(addBreakpoints)
-      .then(disableSomeBreakpoints)
-      .then(testToggleBreakpoints)
-      .then(testEnableBreakpoints)
-      .then(() => ensureThreadClientState(gPanel, "resumed"))
-      .then(() => closeDebuggerAndFinish(gPanel))
-      .then(null, aError => {
-        ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
-      });
-  });
+    function checkBreakpointsDisabled(isDisabled, total = 3) {
+      let breakpoints = gDebugger.queries.getBreakpoints(getState());
+
+      is(breakpoints.length, total,
+         "Breakpoints should still be set.");
+      is(breakpoints.filter(bp => bp.disabled === isDisabled).length, total,
+         "Breakpoints should be " + (isDisabled ? "disabled" : "enabled") + ".");
+    }
+
+    Task.spawn(function*() {
+      yield waitForSourceShown(gPanel, "-01.js");
 
-  function addBreakpoints() {
-    return promise.resolve(null)
-      .then(() => gPanel.addBreakpoint({ actor: gSources.values[0], line: 5 }))
-      .then(() => gPanel.addBreakpoint({ actor: gSources.values[1], line: 6 }))
-      .then(() => gPanel.addBreakpoint({ actor: gSources.values[1], line: 7 }))
-      .then(() => ensureThreadClientState(gPanel, "resumed"));
-  }
+      yield promise.all([
+        actions.addBreakpoint({ actor: gSources.values[0], line: 5 }),
+        actions.addBreakpoint({ actor: gSources.values[1], line: 6 }),
+        actions.addBreakpoint({ actor: gSources.values[1], line: 7 })
+      ]);
+      if(gDebugger.gThreadClient.state !== "attached") {
+        yield waitForThreadEvents(gPanel, "resumed");
+      }
 
-  function disableSomeBreakpoints() {
-    return promise.all([
-      gSources.disableBreakpoint({ actor: gSources.values[0], line: 5 }),
-      gSources.disableBreakpoint({ actor: gSources.values[1], line: 6 })
-    ]);
-  }
+      yield promise.all([
+        actions.disableBreakpoint({ actor: gSources.values[0], line: 5 }),
+        actions.disableBreakpoint({ actor: gSources.values[1], line: 6 })
+      ]);
 
-  function testToggleBreakpoints() {
-    let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_REMOVED, 1);
-    gSources.toggleBreakpoints();
-    return finished.then(() => checkBreakpointsDisabled(true));
-  }
+      gSources.toggleBreakpoints();
+      yield waitForDispatch(gPanel, gDebugger.constants.REMOVE_BREAKPOINT, 1);
+      checkBreakpointsDisabled(true);
+
+      gSources.toggleBreakpoints();
+      yield waitForDispatch(gPanel, gDebugger.constants.ADD_BREAKPOINT, 3);
+      checkBreakpointsDisabled(false);
 
-  function testEnableBreakpoints() {
-    let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_ADDED, 3);
-    gSources.toggleBreakpoints();
-    return finished.then(() => checkBreakpointsDisabled(false));
-  }
-
-  function checkBreakpointsDisabled(aState, aTotal = 3) {
-    let breakpoints = gSources.getAllBreakpoints();
-
-    is(breakpoints.length, aTotal,
-      "Breakpoints should still be set.");
-    is(breakpoints.filter(e => e.attachment.disabled == aState).length, aTotal,
-      "Breakpoints should be " + (aState ? "disabled" : "enabled") + ".");
-  }
+      if(gDebugger.gThreadClient.state !== "attached") {
+        yield waitForThreadEvents(gPanel, "resumed");
+      }
+      closeDebuggerAndFinish(gPanel);
+    });
+  });
 }
--- a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-condition-thrown-message.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-condition-thrown-message.js
@@ -4,103 +4,98 @@
 /**
  * Make sure that the message which breakpoint condition throws
  * could be displayed on UI correctly
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_conditional-breakpoints.html";
 
 function test() {
-  let gTab, gPanel, gDebugger, gEditor;
-  let gSources, gLocation;
+  initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+    const gTab = aTab;
+    const gPanel = aPanel;
+    const gDebugger = gPanel.panelWin;
+    const gEditor = gDebugger.DebuggerView.editor;
+    const gSources = gDebugger.DebuggerView.Sources;
+    const actions = bindActionCreators(gPanel);
+    const getState = gDebugger.DebuggerController.getState;
+
+    function initialCheck(aCaretLine) {
+      let bp = gDebugger.queries.getBreakpoint(getState(),
+                                               { actor: gSources.values[0], line: aCaretLine });
+      ok(bp, "There should be a breakpoint on line " + aCaretLine);
+
+      let attachment = gSources._getBreakpoint(bp).attachment;
+      ok(attachment,
+         "There should be a breakpoint on line " + aCaretLine + " in the sources pane.");
 
-  initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
-    gTab = aTab;
-    gPanel = aPanel;
-    gDebugger = gPanel.panelWin;
-    gEditor = gDebugger.DebuggerView.editor;
-    gSources = gDebugger.DebuggerView.Sources;
+      let thrownNode = attachment.view.container.querySelector(".dbg-breakpoint-condition-thrown-message");
+      ok(thrownNode,
+         "The breakpoint item should contain a thrown message node.")
+
+      ok(!attachment.view.container.classList.contains("dbg-breakpoint-condition-thrown"),
+         "The thrown message on line " + aCaretLine + " should be hidden when condition has not been evaluated.")
+    }
+
+    function resumeAndTestThrownMessage(line) {
+      doResume(gPanel);
+
+      return waitForCaretUpdated(gPanel, line).then(() => {
+        // Test that the thrown message is correctly shown.
+        let bp = gDebugger.queries.getBreakpoint(
+          getState(),
+          { actor: gSources.values[0], line: line }
+        );
+        let attachment = gSources._getBreakpoint(bp).attachment;
+        ok(attachment.view.container.classList.contains('dbg-breakpoint-condition-thrown'),
+           "Message on line " + line + " should be shown when condition throws.");
+      });
+    }
 
-    waitForSourceAndCaretAndScopes(gPanel, ".html", 17)
-      .then(addBreakpoints)
-      .then(() => resumeAndTestThrownMessage(18))
-      .then(() => resumeAndTestNoThrownMessage(19))
-      .then(() => resumeAndTestThrownMessage(22))
-      .then(() => resumeAndFinishTest())
-      .then(() => closeDebuggerAndFinish(gPanel))
-      .then(null, aError => {
-        ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+    function resumeAndTestNoThrownMessage(line) {
+      doResume(gPanel);
+
+      return waitForCaretUpdated(gPanel, line).then(() => {
+        //test that the thrown message is correctly shown
+        let bp = gDebugger.queries.getBreakpoint(
+          getState(),
+          { actor: gSources.values[0], line: line }
+        );
+        let attachment = gSources._getBreakpoint(bp).attachment;
+        ok(!attachment.view.container.classList.contains("dbg-breakpoint-condition-thrown"),
+           "Message on line " + line + " should be hidden if condition doesn't throw.");
       });
+    }
+
+    Task.spawn(function*() {
+      yield waitForSourceAndCaretAndScopes(gPanel, ".html", 17);
+
+      yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 18 }, " 1afff");
+      // Close the popup because a SET_BREAKPOINT_CONDITION action is
+      // fired when it's closed, and it sets it on the currently
+      // selected breakpoint and we want to make sure it uses the
+      // current breakpoint. This isn't a problem outside of tests
+      // because any UI interaction will close the popup before the
+      // new breakpoint is added.
+      gSources._hideConditionalPopup();
+      initialCheck(18);
+
+      yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 19 }, "true");
+      gSources._hideConditionalPopup();
+      initialCheck(19);
+
+      yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 20 }, "false");
+      gSources._hideConditionalPopup();
+      initialCheck(20);
+
+      yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 22 }, "randomVar");
+      gSources._hideConditionalPopup();
+      initialCheck(22)
+
+      yield resumeAndTestThrownMessage(18);
+      yield resumeAndTestNoThrownMessage(19);
+      yield resumeAndTestThrownMessage(22);
+      resumeDebuggerThenCloseAndFinish(gPanel);
+    });
 
     callInTab(gTab, "ermahgerd");
   });
-
-  function resumeAndTestThrownMessage(aLine) {
-    EventUtils.sendMouseEvent({ type: "mousedown" },
-      gDebugger.document.getElementById("resume"),
-      gDebugger);
-
-    let finished = waitForCaretUpdated(gPanel, aLine).then(() => {
-      //test that the thrown message is correctly shown
-      let attachment = gSources.getBreakpoint({ actor: gSources.values[0], line: aLine}).attachment;
-      ok(attachment.view.container.classList.contains('dbg-breakpoint-condition-thrown'),
-        "Message on line " + aLine + " should be shown when condition throws.");
-    });
-
-    return finished;
-  }
-
-  function resumeAndTestNoThrownMessage(aLine) {
-    EventUtils.sendMouseEvent({ type: "mousedown" },
-      gDebugger.document.getElementById("resume"),
-      gDebugger);
-
-    let finished = waitForCaretUpdated(gPanel, aLine).then(() => {
-      //test that the thrown message is correctly shown
-      let attachment = gSources.getBreakpoint({ actor: gSources.values[0], line: aLine}).attachment;
-      ok(!attachment.view.container.classList.contains("dbg-breakpoint-condition-thrown"),
-        "Message on line " + aLine + " should be hidden if condition doesn't throw.");
-    });
-    return finished;
-  }
-
-  function resumeAndFinishTest() {
-    let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_CLEARED)
-
-    gDebugger.gThreadClient.resume();
-
-    return finished;
-  }
-
-  function addBreakpoints() {
-    return promise.resolve(null)
-      .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
-                                         line: 18,
-                                         condition: " 1a"}))
-      .then(() => initialCheck(18))
-      .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
-                                         line: 19,
-                                         condition: "true"}))
-      .then(() => initialCheck(19))
-      .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
-                                         line: 20,
-                                         condition: "false"}))
-      .then(() => initialCheck(20))
-      .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
-                                         line: 22,
-                                         condition: "randomVar"}))
-      .then(() => initialCheck(22));
-  }
-
-  function initialCheck(aCaretLine) {
-    let bp = gSources.getBreakpoint({ actor: gSources.values[0], line: aCaretLine})
-    let attachment = bp.attachment;
-    ok(attachment,
-      "There should be an item for line " + aCaretLine + " in the sources pane.");
-
-    let thrownNode = attachment.view.container.querySelector(".dbg-breakpoint-condition-thrown-message");
-    ok(thrownNode,
-      "The breakpoint item should contain a thrown message node.")
-
-    ok(!attachment.view.container.classList.contains("dbg-breakpoint-condition-thrown"),
-      "The thrown message on line " + aCaretLine + " should be hidden when condition has not been evaluated.")
-  }
 }
--- a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-contextmenu-add.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-contextmenu-add.js
@@ -3,93 +3,76 @@
 
 /**
  * Test adding breakpoints from the source editor context menu
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
 
 function test() {
-  let gTab, gPanel, gDebugger;
-  let gEditor, gSources, gContextMenu, gBreakpoints, gBreakpointsAdded;
+  initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+    const gTab = aTab;
+    const gPanel = aPanel;
+    const gDebugger = gPanel.panelWin;
+    const gEditor = gDebugger.DebuggerView.editor;
+    const gSources = gDebugger.DebuggerView.Sources;
+    const gContextMenu = gDebugger.document.getElementById("sourceEditorContextMenu");
+    const queries = gDebugger.require('./content/queries');
+    const actions = bindActionCreators(gPanel);
+    const getState = gDebugger.DebuggerController.getState;
+
+    Task.spawn(function*() {
+      yield waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1);
+
+      is(gDebugger.gThreadClient.state, "paused",
+         "Should only be getting stack frames while paused.");
+      is(queries.getSourceCount(getState()), 2,
+         "Found the expected number of sources.");
+      isnot(gEditor.getText().indexOf("debugger"), -1,
+            "The correct source was loaded initially.");
+      is(gSources.selectedValue, gSources.values[1],
+         "The correct source is selected.");
+
+      ok(gContextMenu,
+         "The source editor's context menupopup is available.");
+
+      gEditor.focus();
+      gEditor.setSelection({ line: 1, ch: 0 }, { line: 1, ch: 10 });
+
+      gContextMenu.openPopup(gEditor.container, "overlap", 0, 0, true, false);
+      gEditor.emit("gutterClick", 6, 2);
 
-  initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
-    gTab = aTab;
-    gPanel = aPanel;
-    gDebugger = gPanel.panelWin;
-    gEditor = gDebugger.DebuggerView.editor;
-    gSources = gDebugger.DebuggerView.Sources;
-    gBreakpoints = gDebugger.DebuggerController.Breakpoints;
-    gBreakpointsAdded = gBreakpoints._added;
-    gContextMenu = gDebugger.document.getElementById("sourceEditorContextMenu");
+      yield once(gContextMenu, "popupshown");
+      is(queries.getBreakpoints(getState()).length, 0, "no breakpoints added");
+
+      let cmd = gContextMenu.querySelector('menuitem[command=addBreakpointCommand]');
+      EventUtils.synthesizeMouseAtCenter(cmd, {}, gDebugger);
+      yield waitForDispatch(gPanel, gDebugger.constants.ADD_BREAKPOINT);
+
+      is(queries.getBreakpoints(getState()).length, 1,
+         "1 breakpoint correctly added");
+      ok(queries.getBreakpoint(getState(),
+                               { actor: gSources.values[1], line: 7 }),
+         "Breakpoint on line 7 exists");
+
+      gContextMenu.openPopup(gEditor.container, "overlap", 0, 0, true, false);
+      gEditor.emit("gutterClick", 7, 2);
 
-    waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1)
-      .then(performTest)
-      .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
-      .then(null, aError => {
-        ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
-      });
+      yield once(gContextMenu, "popupshown");
+      is(queries.getBreakpoints(getState()).length, 1,
+         "1 breakpoint correctly added");
+
+      cmd = gContextMenu.querySelector('menuitem[command=addConditionalBreakpointCommand]');
+      EventUtils.synthesizeMouseAtCenter(cmd, {}, gDebugger);
+      yield waitForDispatch(gPanel, gDebugger.constants.ADD_BREAKPOINT);
+
+      is(queries.getBreakpoints(getState()).length, 2,
+         "2 breakpoints correctly added");
+      ok(queries.getBreakpoint(getState(),
+                               { actor: gSources.values[1], line: 8 }),
+         "Breakpoint on line 8 exists");
+
+      resumeDebuggerThenCloseAndFinish(gPanel);
+    });
 
     callInTab(gTab, "firstCall");
-  });
-
-  function performTest() {
-    is(gDebugger.gThreadClient.state, "paused",
-       "Should only be getting stack frames while paused.");
-    is(gSources.itemCount, 2,
-       "Found the expected number of sources.");
-    isnot(gEditor.getText().indexOf("debugger"), -1,
-       "The correct source was loaded initially.");
-    is(gSources.selectedValue, gSources.values[1],
-       "The correct source is selected.");
-
-    ok(gContextMenu,
-       "The source editor's context menupopup is available.");
-
-    gEditor.focus();
-    gEditor.setSelection({ line: 1, ch: 0 }, { line: 1, ch: 10 });
-
-    return testAddBreakpoint().then(testAddConditionalBreakpoint);
-  }
-
-  function testAddBreakpoint() {
-    gContextMenu.openPopup(gEditor.container, "overlap", 0, 0, true, false);
-    gEditor.emit("gutterClick", 6, 2);
-
-    return once(gContextMenu, "popupshown").then(() => {
-      is(gBreakpointsAdded.size, 0, "no breakpoints added");
-
-      let cmd = gContextMenu.querySelector('menuitem[command=addBreakpointCommand]');
-      let bpShown = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_SHOWN_IN_EDITOR);
-      EventUtils.synthesizeMouseAtCenter(cmd, {}, gDebugger);
-      return bpShown;
-    }).then(() => {
-      is(gBreakpointsAdded.size, 1,
-         "1 breakpoint correctly added");
-      is(gEditor.getBreakpoints().length, 1,
-         "1 breakpoint currently shown in the editor.");
-      ok(gBreakpoints._getAdded({ actor: gSources.values[1], line: 7 }),
-         "Breakpoint on line 7 exists");
-    });
-  }
-
-  function testAddConditionalBreakpoint() {
-    gContextMenu.openPopup(gEditor.container, "overlap", 0, 0, true, false);
-    gEditor.emit("gutterClick", 7, 2);
-
-    return once(gContextMenu, "popupshown").then(() => {
-      is(gBreakpointsAdded.size, 1,
-         "1 breakpoint correctly added");
-
-      let cmd = gContextMenu.querySelector('menuitem[command=addConditionalBreakpointCommand]');
-      let bpShown = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.CONDITIONAL_BREAKPOINT_POPUP_SHOWING);
-      EventUtils.synthesizeMouseAtCenter(cmd, {}, gDebugger);
-      return bpShown;
-    }).then(() => {
-      is(gBreakpointsAdded.size, 2,
-         "2 breakpoints correctly added");
-      is(gEditor.getBreakpoints().length, 2,
-         "2 breakpoints currently shown in the editor.");
-      ok(gBreakpoints._getAdded({ actor: gSources.values[1], line: 8 }),
-         "Breakpoint on line 8 exists");
-    });
-  }
+  })
 }
--- a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-contextmenu.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-contextmenu.js
@@ -6,328 +6,243 @@
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
 
 function test() {
   // Debug test slaves are a bit slow at this test.
   requestLongerTimeout(2);
 
-  let gTab, gPanel, gDebugger;
-  let gSources, gBreakpoints;
-
-  initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
-    gTab = aTab;
-    gPanel = aPanel;
-    gDebugger = gPanel.panelWin;
-    gSources = gDebugger.DebuggerView.Sources;
-    gBreakpoints = gDebugger.DebuggerController.Breakpoints;
-
-    waitForSourceShown(gPanel, "-01.js")
-      .then(performTestWhileNotPaused)
-      .then(performTestWhilePaused)
-      .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
-      .then(null, aError => {
-        ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
-      });
-  });
-
-  function addBreakpoints() {
-    return promise.resolve(null)
-      .then(() => gPanel.addBreakpoint({ actor: gSources.values[0], line: 5 }))
-      .then(() => gPanel.addBreakpoint({ actor: gSources.values[1], line: 6 }))
-      .then(() => gPanel.addBreakpoint({ actor: gSources.values[1], line: 7 }))
-      .then(() => gPanel.addBreakpoint({ actor: gSources.values[1], line: 8 }))
-      .then(() => gPanel.addBreakpoint({ actor: gSources.values[1], line: 9 }))
-      .then(() => ensureThreadClientState(gPanel, "resumed"));
-  }
-
-  function performTestWhileNotPaused() {
-    info("Performing test while not paused...");
+  Task.spawn(function*() {
+    const [gTab,, gPanel ] = yield initDebugger(TAB_URL);
+    const gDebugger = gPanel.panelWin;
+    const gSources = gDebugger.DebuggerView.Sources;
+    const queries = gDebugger.require('./content/queries');
+    const actions = bindActionCreators(gPanel);
+    const getState = gDebugger.DebuggerController.getState;
 
-    return addBreakpoints()
-      .then(initialChecks)
-      .then(() => checkBreakpointToggleSelf(0))
-      .then(() => checkBreakpointToggleOthers(0))
-      .then(() => checkBreakpointToggleSelf(1))
-      .then(() => checkBreakpointToggleOthers(1))
-      .then(() => checkBreakpointToggleSelf(2))
-      .then(() => checkBreakpointToggleOthers(2))
-      .then(() => checkBreakpointToggleSelf(3))
-      .then(() => checkBreakpointToggleOthers(3))
-      .then(() => checkBreakpointToggleSelf(4))
-      .then(() => checkBreakpointToggleOthers(4))
-      .then(testDeleteAll);
-  }
-
-  function performTestWhilePaused() {
-    info("Performing test while paused...");
-
-    return addBreakpoints()
-      .then(initialChecks)
-      .then(pauseAndCheck)
-      .then(() => checkBreakpointToggleSelf(0))
-      .then(() => checkBreakpointToggleOthers(0))
-      .then(() => checkBreakpointToggleSelf(1))
-      .then(() => checkBreakpointToggleOthers(1))
-      .then(() => checkBreakpointToggleSelf(2))
-      .then(() => checkBreakpointToggleOthers(2))
-      .then(() => checkBreakpointToggleSelf(3))
-      .then(() => checkBreakpointToggleOthers(3))
-      .then(() => checkBreakpointToggleSelf(4))
-      .then(() => checkBreakpointToggleOthers(4))
-      .then(testDeleteAll);
-  }
-
-  function pauseAndCheck() {
-    let finished = waitForSourceAndCaretAndScopes(gPanel, "-01.js", 5).then(() => {
-      let source = gSources.selectedItem.attachment.source;
-      is(source.url, EXAMPLE_URL + "code_script-switching-01.js",
-        "The currently selected source is incorrect (3).");
-      is(gSources.selectedIndex, 0,
-        "The currently selected source is incorrect (4).");
-      ok(isCaretPos(gPanel, 5),
-        "The editor location is correct after pausing.");
+    const addBreakpoints = Task.async(function*() {
+      yield actions.addBreakpoint({ actor: gSources.values[0], line: 5 });
+      yield actions.addBreakpoint({ actor: gSources.values[1], line: 6 });
+      yield actions.addBreakpoint({ actor: gSources.values[1], line: 7 });
+      yield actions.addBreakpoint({ actor: gSources.values[1], line: 8 });
+      yield actions.addBreakpoint({ actor: gSources.values[1], line: 9 });
+      yield ensureThreadClientState(gPanel, "resumed");
+      gSources.highlightBreakpoint({ actor: gSources.values[1], line: 9 });
     });
 
-    let source = gSources.selectedItem.attachment.source;
-    is(source.url, EXAMPLE_URL + "code_script-switching-02.js",
-      "The currently selected source is incorrect (1).");
-    is(gSources.selectedIndex, 1,
-      "The currently selected source is incorrect (2).");
-    ok(isCaretPos(gPanel, 9),
-      "The editor location is correct before pausing.");
+    const pauseAndCheck = Task.async(function*() {
+      let source = queries.getSelectedSource(getState());
+      is(source.url, EXAMPLE_URL + "code_script-switching-02.js",
+         "The currently selected source is incorrect (1).");
+      is(gSources.selectedIndex, 1,
+         "The currently selected source is incorrect (2).");
+      ok(isCaretPos(gPanel, 9),
+         "The editor location is correct before pausing.");
 
-    generateMouseClickInTab(gTab, "content.document.querySelector('button')");
+      generateMouseClickInTab(gTab, "content.document.querySelector('button')");
 
-    return finished;
-  }
+      return waitForSourceAndCaretAndScopes(gPanel, "-01.js", 5).then(() => {
+        let source = queries.getSelectedSource(getState());
+        is(source.url, EXAMPLE_URL + "code_script-switching-01.js",
+           "The currently selected source is incorrect (3).");
+        is(gSources.selectedIndex, 0,
+           "The currently selected source is incorrect (4).");
+        ok(isCaretPos(gPanel, 5),
+           "The editor location is correct after pausing.");
+      });
+    });
 
-  let initialChecks = Task.async(function*() {
-    for (let source of gSources) {
-      for (let breakpoint of source) {
-        ok(gBreakpoints._getAdded(breakpoint.attachment),
-          "All breakpoint items should have corresponding promises (1).");
-        ok(!gBreakpoints._getRemoving(breakpoint.attachment),
-          "All breakpoint items should have corresponding promises (2).");
-        ok(breakpoint.attachment.actor,
-          "All breakpoint items should have corresponding promises (3).");
-        is(!!breakpoint.attachment.disabled, false,
-          "All breakpoints should initially be enabled.");
+    let initialChecks = Task.async(function*() {
+      for (let bp of queries.getBreakpoints(getState())) {
+        ok(bp.actor, "All breakpoint items should have an actor");
+        ok(!bp.disabled, "All breakpoints should initially be enabled.");
 
         let prefix = "bp-cMenu-"; // "breakpoints context menu"
-        let identifier = gBreakpoints.getIdentifier(breakpoint.attachment);
+        let identifier = queries.makeLocationId(bp.location);
         let enableSelfId = prefix + "enableSelf-" + identifier + "-menuitem";
         let disableSelfId = prefix + "disableSelf-" + identifier + "-menuitem";
 
         // Check to make sure that only the bp context menu is shown when right clicking
         // this node (Bug 1159276).
+        let breakpointItem = gSources._getBreakpoint(bp);
         let menu = gDebugger.document.getElementById("bp-mPop-" + identifier);
         let contextMenuShown = once(gDebugger.document, "popupshown");
-        EventUtils.synthesizeMouseAtCenter(breakpoint.prebuiltNode, {type: 'contextmenu', button: 2}, gDebugger);
+        EventUtils.synthesizeMouseAtCenter(breakpointItem.prebuiltNode, {type: 'contextmenu', button: 2}, gDebugger);
         let event = yield contextMenuShown;
         is (event.originalTarget.id, menu.id, "The correct context menu was shown");
         let contextMenuHidden = once(gDebugger.document, "popuphidden");
         menu.hidePopup();
         yield contextMenuHidden;
 
         is(gDebugger.document.getElementById(enableSelfId).getAttribute("hidden"), "true",
-          "The 'Enable breakpoint' context menu item should initially be hidden'.");
+           "The 'Enable breakpoint' context menu item should initially be hidden'.");
         ok(!gDebugger.document.getElementById(disableSelfId).hasAttribute("hidden"),
-          "The 'Disable breakpoint' context menu item should initially not be hidden'.");
-        is(breakpoint.attachment.view.checkbox.getAttribute("checked"), "true",
-          "All breakpoints should initially have a checked checkbox.");
-      }
-    }
-  });
-
-  function checkBreakpointToggleSelf(aIndex) {
-    let deferred = promise.defer();
+           "The 'Disable breakpoint' context menu item should initially not be hidden'.");
 
-    EventUtils.sendMouseEvent({ type: "click" },
-      gDebugger.document.querySelectorAll(".dbg-breakpoint")[aIndex],
-      gDebugger);
-
-    let selectedBreakpoint = gSources._selectedBreakpointItem;
-
-    ok(gBreakpoints._getAdded(selectedBreakpoint.attachment),
-      "There should be a breakpoint client available (1).");
-    ok(!gBreakpoints._getRemoving(selectedBreakpoint.attachment),
-      "There should be a breakpoint client available (2).");
-    ok(selectedBreakpoint.attachment.actor,
-      "There should be a breakpoint client available (3).");
-    is(!!selectedBreakpoint.attachment.disabled, false,
-      "The breakpoint should not be disabled yet (" + aIndex + ").");
-
-    gBreakpoints._getAdded(selectedBreakpoint.attachment).then(aBreakpointClient => {
-      ok(aBreakpointClient,
-        "There should be a breakpoint client available as a promise.");
+        is(breakpointItem.attachment.view.checkbox.getAttribute("checked"), "true",
+           "All breakpoints should initially have a checked checkbox.");
+      }
     });
 
-    let prefix = "bp-cMenu-"; // "breakpoints context menu"
-    let identifier = gBreakpoints.getIdentifier(selectedBreakpoint.attachment);
-    let enableSelfId = prefix + "enableSelf-" + identifier + "-menuitem";
-    let disableSelfId = prefix + "disableSelf-" + identifier + "-menuitem";
+    const checkBreakpointToggleSelf = Task.async(function*(index) {
+      EventUtils.sendMouseEvent({ type: "click" },
+                                gDebugger.document.querySelectorAll(".dbg-breakpoint")[index],
+                                gDebugger);
 
-    is(gDebugger.document.getElementById(enableSelfId).getAttribute("hidden"), "true",
-      "The 'Enable breakpoint' context menu item should be hidden'.");
-    ok(!gDebugger.document.getElementById(disableSelfId).hasAttribute("hidden"),
-      "The 'Disable breakpoint' context menu item should not be hidden'.");
-
-    ok(isCaretPos(gPanel, selectedBreakpoint.attachment.line),
-      "The source editor caret position was incorrect (" + aIndex + ").");
+      let selectedBreakpoint = gSources._selectedBreakpoint;
+      let selectedBreakpointItem = gSources._getBreakpoint(selectedBreakpoint);
 
-    waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_REMOVED).then(() => {
-      ok(!gBreakpoints._getAdded(selectedBreakpoint.attachment),
-        "There should be no breakpoint client available (4).");
-
-      waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_ADDED).then(() => {
-        ok(gBreakpoints._getAdded(selectedBreakpoint.attachment),
-          "There should be a breakpoint client available (5).");
+      ok(selectedBreakpoint.actor,
+         "Selected breakpoint should have an actor.");
+      ok(!selectedBreakpoint.disabled,
+         "The breakpoint should not be disabled yet (" + index + ").");
 
-        deferred.resolve();
-      });
-
-      // Test re-disabling this breakpoint.
-      gSources._onEnableSelf(selectedBreakpoint.attachment);
-      is(selectedBreakpoint.attachment.disabled, false,
-        "The current breakpoint should now be enabled.")
+      let prefix = "bp-cMenu-"; // "breakpoints context menu"
+      let identifier = queries.makeLocationId(selectedBreakpoint.location);
+      let enableSelfId = prefix + "enableSelf-" + identifier + "-menuitem";
+      let disableSelfId = prefix + "disableSelf-" + identifier + "-menuitem";
 
       is(gDebugger.document.getElementById(enableSelfId).getAttribute("hidden"), "true",
-        "The 'Enable breakpoint' context menu item should be hidden'.");
+         "The 'Enable breakpoint' context menu item should be hidden'.");
       ok(!gDebugger.document.getElementById(disableSelfId).hasAttribute("hidden"),
-        "The 'Disable breakpoint' context menu item should not be hidden'.");
-      ok(selectedBreakpoint.attachment.view.checkbox.hasAttribute("checked"),
-        "The breakpoint should now be checked.");
+         "The 'Disable breakpoint' context menu item should not be hidden'.");
+
+      ok(isCaretPos(gPanel, selectedBreakpoint.location.line),
+         "The source editor caret position was incorrect (" + index + ").");
+
+      // Test disabling this breakpoint.
+      gSources._onDisableSelf(selectedBreakpoint.location);
+      yield waitForDispatch(gPanel, gDebugger.constants.REMOVE_BREAKPOINT);
+
+      ok(!!queries.getBreakpoint(getState(), selectedBreakpoint.location).disabled,
+         "The breakpoint should be disabled.");
+
+      ok(!gDebugger.document.getElementById(enableSelfId).hasAttribute("hidden"),
+         "The 'Enable breakpoint' context menu item should not be hidden'.");
+      is(gDebugger.document.getElementById(disableSelfId).getAttribute("hidden"), "true",
+         "The 'Disable breakpoint' context menu item should be hidden'.");
+      ok(!selectedBreakpointItem.attachment.view.checkbox.hasAttribute("checked"),
+         "The breakpoint should now be unchecked.");
+
+      gSources._onEnableSelf(selectedBreakpoint.location);
+      yield waitForDispatch(gPanel, gDebugger.constants.ADD_BREAKPOINT);
+
+      ok(!queries.getBreakpoint(getState(), selectedBreakpoint.location).disabled,
+         "The breakpoint should be enabled.");
+      is(gDebugger.document.getElementById(enableSelfId).getAttribute("hidden"), "true",
+         "The 'Enable breakpoint' context menu item should be hidden'.");
+      ok(!gDebugger.document.getElementById(disableSelfId).hasAttribute("hidden"),
+         "The 'Disable breakpoint' context menu item should not be hidden'.");
+      ok(selectedBreakpointItem.attachment.view.checkbox.hasAttribute("checked"),
+         "The breakpoint should now be checked.");
     });
 
-    // Test disabling this breakpoint.
-    gSources._onDisableSelf(selectedBreakpoint.attachment);
-    is(selectedBreakpoint.attachment.disabled, true,
-      "The current breakpoint should now be disabled.")
+    const checkBreakpointToggleOthers = Task.async(function*(index) {
+      EventUtils.sendMouseEvent(
+        { type: "click" },
+        gDebugger.document.querySelectorAll(".dbg-breakpoint")[index],
+        gDebugger
+      );
 
-    ok(!gDebugger.document.getElementById(enableSelfId).hasAttribute("hidden"),
-      "The 'Enable breakpoint' context menu item should not be hidden'.");
-    is(gDebugger.document.getElementById(disableSelfId).getAttribute("hidden"), "true",
-      "The 'Disable breakpoint' context menu item should be hidden'.");
-    ok(!selectedBreakpoint.attachment.view.checkbox.hasAttribute("checked"),
-      "The breakpoint should now be unchecked.");
-
-    return deferred.promise;
-  }
-
-  function checkBreakpointToggleOthers(aIndex) {
-    let deferred = promise.defer();
+      // Test disabling other breakpoints.
+      disableOthers();
+      yield waitForDispatch(gPanel, gDebugger.constants.REMOVE_BREAKPOINT, 4);
 
-    waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_REMOVED, 4).then(() => {
-      let selectedBreakpoint = gSources._selectedBreakpointItem;
+      let selectedBreakpoint = queries.getBreakpoint(getState(), gSources._selectedBreakpoint.location);
 
-      ok(gBreakpoints._getAdded(selectedBreakpoint.attachment),
-        "There should be a breakpoint client available (6).");
-      ok(!gBreakpoints._getRemoving(selectedBreakpoint.attachment),
-        "There should be a breakpoint client available (7).");
-      ok(selectedBreakpoint.attachment.actor,
-        "There should be a breakpoint client available (8).");
-      is(!!selectedBreakpoint.attachment.disabled, false,
-        "The targetted breakpoint should not have been disabled (" + aIndex + ").");
+      ok(selectedBreakpoint.actor,
+         "There should be a breakpoint actor.");
+      ok(!selectedBreakpoint.disabled,
+         "The targetted breakpoint should not have been disabled (" + index + ").");
 
-      for (let source of gSources) {
-        for (let otherBreakpoint of source) {
-          if (otherBreakpoint != selectedBreakpoint) {
-            ok(!gBreakpoints._getAdded(otherBreakpoint.attachment),
-              "There should be no breakpoint client for a disabled breakpoint (9).");
-            is(otherBreakpoint.attachment.disabled, true,
-              "Non-targetted breakpoints should have been disabled (10).");
-          }
+      for (let bp of queries.getBreakpoints(getState())) {
+        if (bp !== selectedBreakpoint) {
+          ok(bp.disabled,
+             "Non-targetted breakpoints should have been disabled.");
         }
       }
 
-      waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_ADDED, 4).then(() => {
-        for (let source of gSources) {
-          for (let someBreakpoint of source) {
-            ok(gBreakpoints._getAdded(someBreakpoint.attachment),
-              "There should be a breakpoint client for all enabled breakpoints (11).");
-            is(someBreakpoint.attachment.disabled, false,
-              "All breakpoints should now have been enabled (12).");
-          }
-        }
-
-        waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_REMOVED, 5).then(() => {
-          for (let source of gSources) {
-            for (let someBreakpoint of source) {
-              ok(!gBreakpoints._getAdded(someBreakpoint.attachment),
-                "There should be no breakpoint client for a disabled breakpoint (13).");
-              is(someBreakpoint.attachment.disabled, true,
-                "All breakpoints should now have been disabled (14).");
-            }
-          }
-
-          waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_ADDED, 5).then(() => {
-            for (let source of gSources) {
-              for (let someBreakpoint of source) {
-                ok(gBreakpoints._getAdded(someBreakpoint.attachment),
-                  "There should be a breakpoint client for all enabled breakpoints (15).");
-                is(someBreakpoint.attachment.disabled, false,
-                  "All breakpoints should now have been enabled (16).");
-              }
-            }
-
-            // Done.
-            deferred.resolve();
-          });
-
-          // Test re-enabling all breakpoints.
-          enableAll();
-        });
-
-        // Test disabling all breakpoints.
-        disableAll();
-      });
-
       // Test re-enabling other breakpoints.
       enableOthers();
+      yield waitForDispatch(gPanel, gDebugger.constants.ADD_BREAKPOINT, 4);
+      for (let bp of queries.getBreakpoints(getState())) {
+        ok(!bp.disabled, "All breakpoints should be enabled.");
+      }
+
+      // Test disabling all breakpoints.
+      disableAll();
+      yield waitForDispatch(gPanel, gDebugger.constants.REMOVE_BREAKPOINT, 5);
+      for (let bp of queries.getBreakpoints(getState())) {
+        ok(!!bp.disabled, "All breakpoints should be disabled.");
+      }
+
+      // // Test re-enabling all breakpoints.
+      enableAll();
+      yield waitForDispatch(gPanel, gDebugger.constants.ADD_BREAKPOINT, 5);
+      for (let bp of queries.getBreakpoints(getState())) {
+        ok(!bp.disabled, "All breakpoints should be enabled.");
+      }
+    });
+
+    const testDeleteAll = Task.async(function*() {
+      // Test deleting all breakpoints.
+      deleteAll();
+      yield waitForDispatch(gPanel, gDebugger.constants.REMOVE_BREAKPOINT, 5);
+
+      ok(!gSources._selectedBreakpoint,
+         "There should be no breakpoint available after removing all breakpoints.");
+
+      for (let bp of queries.getBreakpoints(getState())) {
+        ok(false, "It's a trap!");
+      }
     });
 
-    // Test disabling other breakpoints.
-    disableOthers();
-
-    return deferred.promise;
-  }
-
-  function testDeleteAll() {
-    let deferred = promise.defer();
+    function disableOthers() {
+      gSources._onDisableOthers(gSources._selectedBreakpoint.location);
+    }
+    function enableOthers() {
+      gSources._onEnableOthers(gSources._selectedBreakpoint.location);
+    }
+    function disableAll() {
+      gSources._onDisableAll();
+    }
+    function enableAll() {
+      gSources._onEnableAll();
+    }
+    function deleteAll() {
+      gSources._onDeleteAll();
+    }
 
-    waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_REMOVED, 5).then(() => {
-      ok(!gSources._selectedBreakpointItem,
-        "There should be no breakpoint available after removing all breakpoints.");
-
-      for (let source of gSources) {
-        for (let otherBreakpoint of source) {
-          ok(false, "It's a trap!");
-        }
-      }
-
-      // Done.
-      deferred.resolve()
-    });
+    yield waitForSourceShown(gPanel, "-01.js");
 
-    // Test deleting all breakpoints.
-    deleteAll();
-
-    return deferred.promise;
-  }
+    yield addBreakpoints();
+    yield initialChecks();
+    yield checkBreakpointToggleSelf(0);
+    yield checkBreakpointToggleOthers(0);
+    yield checkBreakpointToggleSelf(1);
+    yield checkBreakpointToggleOthers(1);
+    yield checkBreakpointToggleSelf(2);
+    yield checkBreakpointToggleOthers(2);
+    yield checkBreakpointToggleSelf(3);
+    yield checkBreakpointToggleOthers(3);
+    yield checkBreakpointToggleSelf(4);
+    yield checkBreakpointToggleOthers(4);
+    yield testDeleteAll();
 
-  function disableOthers() {
-    gSources._onDisableOthers(gSources._selectedBreakpointItem.attachment);
-  }
-  function enableOthers() {
-    gSources._onEnableOthers(gSources._selectedBreakpointItem.attachment);
-  }
-  function disableAll() {
-    gSources._onDisableAll();
-  }
-  function enableAll() {
-    gSources._onEnableAll();
-  }
-  function deleteAll() {
-    gSources._onDeleteAll();
-  }
+    yield addBreakpoints()
+    yield initialChecks();
+    yield pauseAndCheck();
+    yield checkBreakpointToggleSelf(0);
+    yield checkBreakpointToggleOthers(0);
+    yield checkBreakpointToggleSelf(1);
+    yield checkBreakpointToggleOthers(1);
+    yield checkBreakpointToggleSelf(2);
+    yield checkBreakpointToggleOthers(2);
+    yield checkBreakpointToggleSelf(3);
+    yield checkBreakpointToggleOthers(3);
+    yield checkBreakpointToggleSelf(4);
+    yield checkBreakpointToggleOthers(4);
+    yield testDeleteAll();
+
+    resumeDebuggerThenCloseAndFinish(gPanel);
+  });
 }
--- a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-disabled-reload.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-disabled-reload.js
@@ -4,115 +4,116 @@
 /**
  * Test that disabled breakpoints survive target navigation.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
 
 function test() {
   initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
-    let gTab = aTab;
-    let gDebugger = aPanel.panelWin;
-    let gEvents = gDebugger.EVENTS;
-    let gEditor = gDebugger.DebuggerView.editor;
-    let gSources = gDebugger.DebuggerView.Sources;
-    let gBreakpoints = gDebugger.DebuggerController.Breakpoints;
+    const gPanel = aPanel;
+    const gTab = aTab;
+    const gDebugger = gPanel.panelWin;
+    const gEvents = gDebugger.EVENTS;
+    const gEditor = gDebugger.DebuggerView.editor;
+    const gSources = gDebugger.DebuggerView.Sources;
+    const queries = gDebugger.require('./content/queries');
+    const actions = bindActionCreators(gPanel);
+    const getState = gDebugger.DebuggerController.getState;
     let gBreakpointLocation;
+
     Task.spawn(function*() {
-      yield waitForSourceShown(aPanel, "-01.js");
+      yield waitForSourceShown(gPanel, "-01.js");
       gBreakpointLocation = { actor: getSourceActor(gSources, EXAMPLE_URL + "code_script-switching-01.js"),
                               line: 5 };
 
-      yield aPanel.addBreakpoint(gBreakpointLocation);
+      yield actions.addBreakpoint(gBreakpointLocation);
 
-      yield ensureThreadClientState(aPanel, "resumed");
+      yield ensureThreadClientState(gPanel, "resumed");
       yield testWhenBreakpointEnabledAndFirstSourceShown();
 
-      yield reloadActiveTab(aPanel, gEvents.SOURCE_SHOWN);
+      gSources._preferredSourceURL = EXAMPLE_URL + "code_script-switching-02.js";
+      yield reloadActiveTab(gPanel, gEvents.SOURCE_SHOWN);
       yield testWhenBreakpointEnabledAndSecondSourceShown();
 
-      yield gSources.disableBreakpoint(gBreakpointLocation);
-      yield reloadActiveTab(aPanel, gEvents.SOURCE_SHOWN);
+      yield actions.disableBreakpoint(gBreakpointLocation);
+      yield reloadActiveTab(gPanel, gEvents.SOURCE_SHOWN);
+
       yield testWhenBreakpointDisabledAndSecondSourceShown();
 
-      yield gSources.enableBreakpoint(gBreakpointLocation);
-      yield reloadActiveTab(aPanel, gEvents.SOURCE_SHOWN);
+      yield actions.enableBreakpoint(gBreakpointLocation);
+      yield reloadActiveTab(gPanel, gEvents.SOURCE_SHOWN);
       yield testWhenBreakpointEnabledAndSecondSourceShown();
 
-      yield resumeDebuggerThenCloseAndFinish(aPanel);
+      yield resumeDebuggerThenCloseAndFinish(gPanel);
     });
 
-    function verifyView({ disabled, visible }) {
+    function verifyView({ disabled }) {
       return Task.spawn(function*() {
         // It takes a tick for the checkbox in the SideMenuWidget and the
         // gutter in the editor to get updated.
         yield waitForTick();
 
-        let breakpointItem = gSources.getBreakpoint(gBreakpointLocation);
-        let visibleBreakpoints = gEditor.getBreakpoints();
-        is(!!breakpointItem.attachment.disabled, disabled,
-          "The selected brekapoint state was correct.");
+        let breakpoint = queries.getBreakpoint(getState(), gBreakpointLocation);
+        let breakpointItem = gSources._getBreakpoint(breakpoint);
+        is(!!breakpoint.disabled, disabled,
+           "The selected breakpoint state was correct.");
+
         is(breakpointItem.attachment.view.checkbox.hasAttribute("checked"), !disabled,
-          "The selected brekapoint's checkbox state was correct.");
-
-        // Source editor starts counting line and column numbers from 0.
-        let breakpointLine = breakpointItem.attachment.line - 1;
-        let matchedBreakpoints = visibleBreakpoints.filter(e => e.line == breakpointLine);
-        is(!!matchedBreakpoints.length, visible,
-          "The selected breakpoint's visibility in the editor was correct.");
+          "The selected breakpoint's checkbox state was correct.");
       });
     }
 
     // All the following executeSoon()'s are required to spin the event loop
     // before causing the debuggee to pause, to allow functions to yield first.
 
     function testWhenBreakpointEnabledAndFirstSourceShown() {
       return Task.spawn(function*() {
-        yield ensureSourceIs(aPanel, "-01.js");
-        yield verifyView({ disabled: false, visible: true });
+        yield ensureSourceIs(gPanel, "-01.js");
+        yield verifyView({ disabled: false });
 
         callInTab(gTab, "firstCall");
-        yield waitForDebuggerEvents(aPanel, gEvents.FETCHED_SCOPES);
-        yield ensureSourceIs(aPanel, "-01.js");
-        yield ensureCaretAt(aPanel, 5);
-        yield verifyView({ disabled: false, visible: true });
+        yield waitForDebuggerEvents(gPanel, gEvents.FETCHED_SCOPES);
+        yield ensureSourceIs(gPanel, "-01.js");
+        yield ensureCaretAt(gPanel, 5);
+        yield verifyView({ disabled: false });
 
         executeSoon(() => gDebugger.gThreadClient.resume());
-        yield waitForSourceAndCaretAndScopes(aPanel, "-02.js", 1);
-        yield verifyView({ disabled: false, visible: false });
+        yield waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1);
+        yield verifyView({ disabled: false });
       });
     }
 
     function testWhenBreakpointEnabledAndSecondSourceShown() {
       return Task.spawn(function*() {
-        yield ensureSourceIs(aPanel, "-02.js", true);
-        yield verifyView({ disabled: false, visible: false });
+        yield ensureSourceIs(gPanel, "-02.js", true);
+        yield verifyView({ disabled: false });
 
         callInTab(gTab, "firstCall");
-        yield waitForSourceAndCaretAndScopes(aPanel, "-01.js", 1);
-        yield verifyView({ disabled: false, visible: true });
+        yield waitForSourceAndCaretAndScopes(gPanel, "-01.js", 1);
+        yield verifyView({ disabled: false });
 
         executeSoon(() => gDebugger.gThreadClient.resume());
-        yield waitForSourceAndCaretAndScopes(aPanel, "-02.js", 1);
-        yield verifyView({ disabled: false, visible: false });
+        yield waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1);
+        yield verifyView({ disabled: false });
       });
     }
 
     function testWhenBreakpointDisabledAndSecondSourceShown() {
       return Task.spawn(function*() {
-        yield ensureSourceIs(aPanel, "-02.js", true);
-        yield verifyView({ disabled: true, visible: false });
+        yield ensureSourceIs(gPanel, "-02.js", true);
+        yield verifyView({ disabled: true });
 
         callInTab(gTab, "firstCall");
-        yield waitForDebuggerEvents(aPanel, gEvents.FETCHED_SCOPES);
-        yield ensureSourceIs(aPanel, "-02.js");
-        yield ensureCaretAt(aPanel, 6);
-        yield verifyView({ disabled: true, visible: false });
+        yield waitForDebuggerEvents(gPanel, gEvents.FETCHED_SCOPES);
+        yield ensureSourceIs(gPanel, "-02.js");
+        yield ensureCaretAt(gPanel, 6);
+        yield verifyView({ disabled: true });
 
         executeSoon(() => gDebugger.gThreadClient.resume());
-        yield waitForDebuggerEvents(aPanel, gEvents.AFTER_FRAMES_CLEARED);
-        yield ensureSourceIs(aPanel, "-02.js");
-        yield ensureCaretAt(aPanel, 6);
-        yield verifyView({ disabled: true, visible: false });
+        yield waitForDebuggerEvents(gPanel, gEvents.AFTER_FRAMES_CLEARED);
+        yield ensureSourceIs(gPanel, "-02.js");
+        yield ensureCaretAt(gPanel, 6);
+        yield verifyView({ disabled: true });
       });
     }
   });
 }
--- a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-editor.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-editor.js
@@ -4,298 +4,232 @@
 /**
  * Bug 723069: Test the debugger breakpoint API and connection to the
  * source editor.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
 
 function test() {
-  let gTab, gPanel, gDebugger;
-  let gEditor, gSources, gBreakpoints, gBreakpointsAdded, gBreakpointsRemoving;
-
   initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
-    gTab = aTab;
-    gPanel = aPanel;
-    gDebugger = gPanel.panelWin;
-    gEditor = gDebugger.DebuggerView.editor;
-    gSources = gDebugger.DebuggerView.Sources;
-    gBreakpoints = gDebugger.DebuggerController.Breakpoints;
-    gBreakpointsAdded = gBreakpoints._added;
-    gBreakpointsRemoving = gBreakpoints._removing;
-
-    waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1).then(performTest);
-    callInTab(gTab, "firstCall");
-  });
+    const gTab = aTab;
+    const gPanel = aPanel;
+    const gDebugger = gPanel.panelWin;
+    const gEditor = gDebugger.DebuggerView.editor;
+    const gSources = gDebugger.DebuggerView.Sources;
+    const queries = gDebugger.require('./content/queries');
+    const constants = gDebugger.require('./content/constants');
+    const actions = bindActionCreators(gPanel);
+    const getState = gDebugger.DebuggerController.getState;
 
-  function performTest() {
-    is(gDebugger.gThreadClient.state, "paused",
-      "Should only be getting stack frames while paused.");
-    is(gSources.itemCount, 2,
-      "Found the expected number of sources.");
-    is(gEditor.getText().indexOf("debugger"), 166,
-      "The correct source was loaded initially.");
-    is(gSources.selectedValue, gSources.values[1],
-      "The correct source is selected.");
-
-    is(gBreakpointsAdded.size, 0,
-      "No breakpoints currently added.");
-    is(gBreakpointsRemoving.size, 0,
-      "No breakpoints currently being removed.");
-    is(gEditor.getBreakpoints().length, 0,
-      "No breakpoints currently shown in the editor.");
-
-    ok(!gBreakpoints._getAdded({ url: "foo", line: 3 }),
-      "_getAdded('foo', 3) returns falsey.");
-    ok(!gBreakpoints._getRemoving({ url: "bar", line: 3 }),
-      "_getRemoving('bar', 3) returns falsey.");
-
-    is(gSources.values[1], gSources.selectedValue,
-      "The second source should be currently selected.");
+    Task.spawn(function*() {
+      yield waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1);
 
-    info("Add the first breakpoint.");
-    let location = { actor: gSources.selectedValue, line: 6 };
-    gEditor.once("breakpointAdded", onEditorBreakpointAddFirst);
-    gPanel.addBreakpoint(location).then(onBreakpointAddFirst);
-  }
-
-  let breakpointsAdded = 0;
-  let breakpointsRemoved = 0;
-  let editorBreakpointChanges = 0;
+      is(gDebugger.gThreadClient.state, "paused",
+         "Should only be getting stack frames while paused.");
+      is(queries.getSourceCount(getState()), 2,
+         "Found the expected number of sources.");
+      is(gEditor.getText().indexOf("debugger"), 166,
+         "The correct source was loaded initially.");
+      is(queries.getSelectedSource(getState()).actor, gSources.values[1],
+         "The correct source is selected.");
 
-  function onEditorBreakpointAddFirst(aEvent, aLine) {
-    editorBreakpointChanges++;
-
-    ok(aEvent,
-      "breakpoint1 added to the editor.");
-    is(aLine, 5,
-      "Editor breakpoint line is correct.");
-
-    is(gEditor.getBreakpoints().length, 1,
-      "editor.getBreakpoints().length is correct.");
-  }
+      is(queries.getBreakpoints(getState()).length, 0,
+         "No breakpoints currently added.");
 
-  function onBreakpointAddFirst(aBreakpointClient) {
-    breakpointsAdded++;
-
-    ok(aBreakpointClient,
-      "breakpoint1 added, client received.");
-    is(aBreakpointClient.location.actor, gSources.selectedValue,
-      "breakpoint1 client url is correct.");
-    is(aBreakpointClient.location.line, 6,
-      "breakpoint1 client line is correct.");
+      info("Add the first breakpoint.");
+      gEditor.once("breakpointAdded", onEditorBreakpointAddFirst);
+      let location = { actor: gSources.selectedValue, line: 6 };
+      yield actions.addBreakpoint(location);
+      checkFirstBreakpoint(location);
 
-    ok(gBreakpoints._getAdded(aBreakpointClient.location),
-      "breakpoint1 client found in the list of added breakpoints.");
-    ok(!gBreakpoints._getRemoving(aBreakpointClient.location),
-      "breakpoint1 client found in the list of removing breakpoints.");
+      info("Remove the first breakpoint.");
+      gEditor.once("breakpointRemoved", onEditorBreakpointRemoveFirst);
+      yield actions.removeBreakpoint(location);
+      checkFirstBreakpointRemoved(location);
+      checkBackgroundBreakpoint(yield testBreakpointAddBackground());
 
-    is(gBreakpointsAdded.size, 1,
-      "The list of added breakpoints holds only one breakpoint.");
-    is(gBreakpointsRemoving.size, 0,
-      "The list of removing breakpoints holds no breakpoint.");
-
-    gBreakpoints._getAdded(aBreakpointClient.location).then(aClient => {
-      is(aClient, aBreakpointClient,
-        "_getAdded() returns the correct breakpoint.");
+      info("Switch to the first source, which is not yet selected");
+      gEditor.once("breakpointAdded", onEditorBreakpointAddSwitch);
+      gEditor.once("change", onEditorTextChanged);
+      actions.selectSource(gSources.items[0].attachment.source);
+      yield waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN);
+      onReadyForClick();
     });
 
-    is(gSources.values[1], gSources.selectedValue,
-      "The second source should be currently selected.");
+    callInTab(gTab, "firstCall");
 
-    info("Remove the first breakpoint.");
-    gEditor.once("breakpointRemoved", onEditorBreakpointRemoveFirst);
-    gPanel.removeBreakpoint(aBreakpointClient.location).then(onBreakpointRemoveFirst);
-  }
+    let breakpointsAdded = 0;
+    let breakpointsRemoved = 0;
+    let editorBreakpointChanges = 0;
 
-  function onEditorBreakpointRemoveFirst(aEvent, aLine) {
-    editorBreakpointChanges++;
+    function onEditorBreakpointAddFirst(aEvent, aLine) {
+      editorBreakpointChanges++;
 
-    ok(aEvent,
-      "breakpoint1 removed from the editor.");
-    is(aLine, 5,
-      "Editor breakpoint line is correct.");
+      ok(aEvent,
+         "breakpoint1 added to the editor.");
+      is(aLine, 5,
+         "Editor breakpoint line is correct.");
 
-    is(gEditor.getBreakpoints().length, 0,
-      "editor.getBreakpoints().length is correct.");
-  }
+      is(gEditor.getBreakpoints().length, 1,
+         "editor.getBreakpoints().length is correct.");
+    }
 
-  function onBreakpointRemoveFirst(aLocation) {
-    breakpointsRemoved++;
+    function onEditorBreakpointRemoveFirst(aEvent, aLine) {
+      editorBreakpointChanges++;
 
-    ok(aLocation,
-      "breakpoint1 removed");
-    is(aLocation.actor, gSources.selectedValue,
-      "breakpoint1 removal url is correct.");
-    is(aLocation.line, 6,
-      "breakpoint1 removal line is correct.");
+      ok(aEvent,
+         "breakpoint1 removed from the editor.");
+      is(aLine, 5,
+         "Editor breakpoint line is correct.");
 
-    testBreakpointAddBackground();
-  }
+      is(gEditor.getBreakpoints().length, 0,
+         "editor.getBreakpoints().length is correct.");
+    }
 
-  function testBreakpointAddBackground() {
-    is(gBreakpointsAdded.size, 0,
-      "No breakpoints currently added.");
-    is(gBreakpointsRemoving.size, 0,
-      "No breakpoints currently being removed.");
-    is(gEditor.getBreakpoints().length, 0,
-      "No breakpoints currently shown in the editor.");
+    function checkFirstBreakpoint(location) {
+      breakpointsAdded++;
+      const bp = queries.getBreakpoint(getState(), location);
 
-    ok(!gBreakpoints._getAdded({ actor: gSources.selectedValue, line: 6 }),
-      "_getAdded('gSources.selectedValue', 6) returns falsey.");
-    ok(!gBreakpoints._getRemoving({ actor: gSources.selectedValue, line: 6 }),
-      "_getRemoving('gSources.selectedValue', 6) returns falsey.");
+      ok(bp,
+         "breakpoint1 exists");
+      is(bp.location.actor, queries.getSelectedSource(getState()).actor,
+         "breakpoint1 actor is correct.");
+      is(bp.location.line, 6,
+         "breakpoint1 line is correct.");
 
-    is(gSources.values[1], gSources.selectedValue,
-      "The second source should be currently selected.");
+      is(queries.getBreakpoints(getState()).length, 1,
+         "The list of added breakpoints holds only one breakpoint.");
 
-    info("Add a breakpoint to the first source, which is not selected.");
-    let location = { actor: gSources.values[0], line: 5 };
-    let options = { noEditorUpdate: true };
-    gEditor.on("breakpointAdded", onEditorBreakpointAddBackgroundTrap);
-    gPanel.addBreakpoint(location, options).then(onBreakpointAddBackground);
-  }
+      is(queries.getSelectedSource(getState()).actor, gSources.values[1],
+         "The second source should be currently selected.");
+    }
 
-  function onEditorBreakpointAddBackgroundTrap() {
-    // Trap listener: no breakpoint must be added to the editor when a
-    // breakpoint is added to a source that is not currently selected.
-    editorBreakpointChanges++;
-    ok(false, "breakpoint2 must not be added to the editor.");
-  }
+    function checkFirstBreakpointRemoved(location) {
+      breakpointsRemoved++;
+      const bp = queries.getBreakpoint(getState(), location);
+      ok(!bp, "breakpoint1 removed");
+    }
 
-  function onBreakpointAddBackground(aBreakpointClient, aResponseError) {
-    breakpointsAdded++;
+    function testBreakpointAddBackground() {
+      is(queries.getBreakpoints(getState()).length, 0,
+         "No breakpoints currently added.");
 
-    ok(aBreakpointClient,
-      "breakpoint2 added, client received");
-    is(aBreakpointClient.location.actor, gSources.values[0],
-      "breakpoint2 client url is correct.");
-    is(aBreakpointClient.location.line, 5,
-      "breakpoint2 client line is correct.");
+      is(gSources.values[1], gSources.selectedValue,
+         "The second source should be currently selected.");
 
-    ok(gBreakpoints._getAdded(aBreakpointClient.location),
-      "breakpoint2 client found in the list of added breakpoints.");
-    ok(!gBreakpoints._getRemoving(aBreakpointClient.location),
-      "breakpoint2 client found in the list of removing breakpoints.");
+      info("Add a breakpoint to the first source, which is not selected.");
+      let location = { actor: gSources.values[0], line: 5 };
+      gEditor.on("breakpointAdded", onEditorBreakpointAddBackgroundTrap);
+      return actions.addBreakpoint(location).then(() => location);
+    }
 
-    is(gBreakpointsAdded.size, 1,
-      "The list of added breakpoints holds only one breakpoint.");
-    is(gBreakpointsRemoving.size, 0,
-      "The list of removing breakpoints holds no breakpoint.");
+    function onEditorBreakpointAddBackgroundTrap() {
+      // Trap listener: no breakpoint must be added to the editor when a
+      // breakpoint is added to a source that is not currently selected.
+      editorBreakpointChanges++;
+      ok(false, "breakpoint2 must not be added to the editor.");
+    }
 
-    gBreakpoints._getAdded(aBreakpointClient.location).then(aClient => {
-      is(aClient, aBreakpointClient,
-        "_getAdded() returns the correct breakpoint.");
-    });
-
-    is(gSources.values[1], gSources.selectedValue,
-      "The second source should be currently selected.");
+    function checkBackgroundBreakpoint(location) {
+      breakpointsAdded++;
+      const bp = queries.getBreakpoint(getState(), location);
 
-    // Remove the trap listener.
-    gEditor.off("breakpointAdded", onEditorBreakpointAddBackgroundTrap);
+      ok(bp,
+        "breakpoint2 added, client received");
+      is(bp.location.actor, gSources.values[0],
+        "breakpoint2 client url is correct.");
+      is(bp.location.line, 5,
+        "breakpoint2 client line is correct.");
 
-    info("Switch to the first source, which is not yet selected");
-    gEditor.once("breakpointAdded", onEditorBreakpointAddSwitch);
-    gEditor.once("change", onEditorTextChanged);
-    gSources.selectedIndex = 0;
-  }
+      ok(queries.getBreakpoint(getState(), bp.location),
+         "breakpoint2 found in the list of added breakpoints.");
 
-  function onEditorBreakpointAddSwitch(aEvent, aLine) {
-    editorBreakpointChanges++;
+      is(queries.getBreakpoints(getState()).length, 1,
+         "The list of added breakpoints holds only one breakpoint.");
 
-    ok(aEvent,
-      "breakpoint2 added to the editor.");
-    is(aLine, 4,
-      "Editor breakpoint line is correct.");
-
-    is(gEditor.getBreakpoints().length, 1,
-      "editor.getBreakpoints().length is correct");
-  }
+      is(queries.getSelectedSource(getState()).actor, gSources.values[1],
+         "The second source should be currently selected.");
 
-  function onEditorTextChanged() {
-    // Wait for the actual text to be shown.
-    if (gEditor.getText() == gDebugger.L10N.getStr("loadingText"))
-      return void gEditor.once("change", onEditorTextChanged);
+      // Remove the trap listener.
+      gEditor.off("breakpointAdded", onEditorBreakpointAddBackgroundTrap);
+    }
 
-    is(gEditor.getText().indexOf("debugger"), -1,
-      "The second source is no longer displayed.");
-    is(gEditor.getText().indexOf("firstCall"), 118,
-      "The first source is displayed.");
+    function onEditorBreakpointAddSwitch(aEvent, aLine) {
+      editorBreakpointChanges++;
 
-    is(gSources.values[0], gSources.selectedValue,
-      "The first source should be currently selected.");
+      ok(aEvent,
+        "breakpoint2 added to the editor.");
+      is(aLine, 4,
+        "Editor breakpoint line is correct.");
 
-    let window = gEditor.container.contentWindow;
-    executeSoon(() => window.requestAnimationFrame(onReadyForClick));
-  }
+      is(gEditor.getBreakpoints().length, 1,
+        "editor.getBreakpoints().length is correct");
+    }
 
-  function onReadyForClick() {
-    info("Remove the second breakpoint using the mouse.");
-    gEditor.once("breakpointRemoved", onEditorBreakpointRemoveSecond);
-
-    let iframe = gEditor.container;
-    let testWin = iframe.ownerDocument.defaultView;
+    function onEditorTextChanged() {
+      // Wait for the actual text to be shown.
+      if (gEditor.getText() == gDebugger.L10N.getStr("loadingText"))
+        return void gEditor.once("change", onEditorTextChanged);
 
-    // Flush the layout for the iframe.
-    info("rect " + iframe.contentDocument.documentElement.getBoundingClientRect());
-
-    let utils = testWin
-      .QueryInterface(Ci.nsIInterfaceRequestor)
-      .getInterface(Ci.nsIDOMWindowUtils);
+      is(gEditor.getText().indexOf("debugger"), -1,
+        "The second source is no longer displayed.");
+      is(gEditor.getText().indexOf("firstCall"), 118,
+        "The first source is displayed.");
 
-    let coords = gEditor.getCoordsFromPosition({ line: 4, ch: 0 });
-    let rect = iframe.getBoundingClientRect();
-    let left = rect.left + 10;
-    let top = rect.top + coords.top + 4;
-    utils.sendMouseEventToWindow("mousedown", left, top, 0, 1, 0, false, 0, 0);
-    utils.sendMouseEventToWindow("mouseup", left, top, 0, 1, 0, false, 0, 0);
-  }
+      is(gSources.values[0], gSources.selectedValue,
+        "The first source should be currently selected.");
+    }
+
+    function onReadyForClick() {
+      info("Remove the second breakpoint using the mouse.");
+      gEditor.once("breakpointRemoved", onEditorBreakpointRemoveSecond);
+
+      let iframe = gEditor.container;
+      let testWin = iframe.ownerDocument.defaultView;
 
-  function onEditorBreakpointRemoveSecond(aEvent, aLine) {
-    editorBreakpointChanges++;
+      // Flush the layout for the iframe.
+      info("rect " + iframe.contentDocument.documentElement.getBoundingClientRect());
 
-    ok(aEvent,
-      "breakpoint2 removed from the editor.");
-    is(aLine, 4,
-      "Editor breakpoint line is correct.");
+      let utils = testWin
+        .QueryInterface(Ci.nsIInterfaceRequestor)
+        .getInterface(Ci.nsIDOMWindowUtils);
 
-    is(gEditor.getBreakpoints().length, 0,
-      "editor.getBreakpoints().length is correct.");
+      let coords = gEditor.getCoordsFromPosition({ line: 4, ch: 0 });
+      let rect = iframe.getBoundingClientRect();
+      let left = rect.left + 10;
+      let top = rect.top + coords.top + 4;
+      utils.sendMouseEventToWindow("mousedown", left, top, 0, 1, 0, false, 0, 0);
+      utils.sendMouseEventToWindow("mouseup", left, top, 0, 1, 0, false, 0, 0);
+    }
 
-    waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_CLEARED).then(() => {
-      finalCheck();
-      closeDebuggerAndFinish(gPanel);
-    });
+    function onEditorBreakpointRemoveSecond(aEvent, aLine) {
+      editorBreakpointChanges++;
 
-    gDebugger.gThreadClient.resume();
-  }
+      ok(aEvent,
+        "breakpoint2 removed from the editor.");
+      is(aLine, 4,
+        "Editor breakpoint line is correct.");
 
-  function finalCheck() {
-    is(gBreakpointsAdded.size, 0,
-      "No breakpoints currently added.");
-    is(gBreakpointsRemoving.size, 0,
-      "No breakpoints currently being removed.");
-    is(gEditor.getBreakpoints().length, 0,
-      "No breakpoints currently shown in the editor.");
+      is(gEditor.getBreakpoints().length, 0,
+        "editor.getBreakpoints().length is correct.");
 
-    ok(!gBreakpoints._getAdded({ actor: gSources.values[0], line: 5 }),
-      "_getAdded('gSources.values[0]', 5) returns falsey.");
-    ok(!gBreakpoints._getRemoving({ actor: gSources.values[0], line: 5 }),
-      "_getRemoving('gSources.values[0]', 5) returns falsey.");
+      waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_CLEARED).then(() => {
+        finalCheck();
+        closeDebuggerAndFinish(gPanel);
+      });
+
+      gDebugger.gThreadClient.resume();
+    }
 
-    ok(!gBreakpoints._getAdded({ actor: gSources.values[1], line: 6 }),
-      "_getAdded('gSources.values[1]', 6) returns falsey.");
-    ok(!gBreakpoints._getRemoving({ actor: gSources.values[1], line: 6 }),
-      "_getRemoving('gSources.values[1]', 6) returns falsey.");
+    function finalCheck() {
+      is(queries.getBreakpoints(getState()).length, 0,
+         "No breakpoints currently added.");
 
-    ok(!gBreakpoints._getAdded({ actor: "foo", line: 3 }),
-      "_getAdded('foo', 3) returns falsey.");
-    ok(!gBreakpoints._getRemoving({ actor: "bar", line: 3 }),
-      "_getRemoving('bar', 3) returns falsey.");
-
-    is(breakpointsAdded, 2,
-      "Correct number of breakpoints have been added.");
-    is(breakpointsRemoved, 1,
-      "Correct number of breakpoints have been removed.");
-    is(editorBreakpointChanges, 4,
-      "Correct number of editor breakpoint changes.");
-  }
+      is(breakpointsAdded, 2,
+         "Correct number of breakpoints have been added.");
+      is(breakpointsRemoved, 1,
+         "Correct number of breakpoints have been removed.");
+      is(editorBreakpointChanges, 4,
+         "Correct number of editor breakpoint changes.");
+    }
+  });
 }
--- a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-eval.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-eval.js
@@ -3,44 +3,41 @@
 
 /**
  * Test setting breakpoints on an eval script
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_script-eval.html";
 
 function test() {
-  let gTab, gPanel, gDebugger;
-  let gSources, gBreakpoints;
-
   initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
-    gTab = aTab;
-    gPanel = aPanel;
-    gDebugger = gPanel.panelWin;
-    gSources = gDebugger.DebuggerView.Sources;
-    gBreakpoints = gDebugger.DebuggerController.Breakpoints;
+    const gTab = aTab;
+    const gPanel = aPanel;
+    const gDebugger = gPanel.panelWin;
+    const gSources = gDebugger.DebuggerView.Sources;
+    const actions = bindActionCreators(gPanel);
 
-    waitForSourceShown(gPanel, "-eval.js")
-      .then(run)
-      .then(null, aError => {
-        ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
-      });
-  });
+    Task.spawn(function*() {
+      yield waitForSourceShown(gPanel, "-eval.js");
 
-  function run() {
-    return Task.spawn(function*() {
       let newSource = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.NEW_SOURCE);
       callInTab(gTab, "evalSourceWithSourceURL");
       yield newSource;
+      // Wait for it to be added to the UI
+      yield waitForTick();
 
-      yield gPanel.addBreakpoint({ actor: gSources.values[0], line: 2 });
+      const newSourceActor = getSourceActor(gSources, EXAMPLE_URL + 'bar.js');
+      yield actions.addBreakpoint({
+        actor: newSourceActor,
+        line: 2
+      });
       yield ensureThreadClientState(gPanel, "resumed");
 
       const paused = waitForThreadEvents(gPanel, "paused");
       callInTab(gTab, "bar");
       let frame = (yield paused).frame;
-      is(frame.where.source.actor, gSources.values[0], "Should have broken on the eval'ed source");
+      is(frame.where.source.actor, newSourceActor, "Should have broken on the eval'ed source");
       is(frame.where.line, 2, "Should break on line 2");
 
       yield resumeDebuggerThenCloseAndFinish(gPanel);
     });
-  }
+  });
 }
--- a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-highlight.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-highlight.js
@@ -3,101 +3,84 @@
 
 /**
  * Test if breakpoints are highlighted when they should.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
 
 function test() {
-  let gTab, gPanel, gDebugger;
-  let gEditor, gSources;
-
   initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
-    gTab = aTab;
-    gPanel = aPanel;
-    gDebugger = gPanel.panelWin;
-    gEditor = gDebugger.DebuggerView.editor;
-    gSources = gDebugger.DebuggerView.Sources;
-
-    waitForSourceShown(gPanel, "-01.js")
-      .then(addBreakpoints)
-      .then(() => clickBreakpointAndCheck(0, 0, 5))
-      .then(() => clickBreakpointAndCheck(1, 1, 6))
-      .then(() => clickBreakpointAndCheck(2, 1, 7))
-      .then(() => clickBreakpointAndCheck(3, 1, 8))
-      .then(() => clickBreakpointAndCheck(4, 1, 9))
-      .then(() => ensureThreadClientState(gPanel, "resumed"))
-      .then(() => closeDebuggerAndFinish(gPanel))
-      .then(null, aError => {
-        ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
-      });
-  });
+    const gTab = aTab;
+    const gPanel = aPanel;
+    const gDebugger = gPanel.panelWin;
+    const gEditor = gDebugger.DebuggerView.editor;
+    const gSources = gDebugger.DebuggerView.Sources;
+    const queries = gDebugger.require('./content/queries');
+    const actions = bindActionCreators(gPanel);
+    const getState = gDebugger.DebuggerController.getState;
 
-  function addBreakpoints() {
-    return promise.resolve(null)
-      .then(() => initialChecks(0, 1))
-      .then(() => gPanel.addBreakpoint({ actor: gSources.values[0], line: 5 }))
-      .then(() => initialChecks(0, 5))
-      .then(() => gPanel.addBreakpoint({ actor: gSources.values[1], line: 6 }))
-      .then(() => waitForSourceShown(gPanel, "-02.js"))
-      .then(() => waitForCaretUpdated(gPanel, 6))
-      .then(() => initialChecks(1, 6))
-      .then(() => gPanel.addBreakpoint({ actor: gSources.values[1], line: 7 }))
-      .then(() => initialChecks(1, 7))
-      .then(() => gPanel.addBreakpoint({ actor: gSources.values[1], line: 8 }))
-      .then(() => initialChecks(1, 8))
-      .then(() => gPanel.addBreakpoint({ actor: gSources.values[1], line: 9 }))
-      .then(() => initialChecks(1, 9));
-  }
-
-  function initialChecks(aSourceIndex, aCaretLine) {
-    checkEditorContents(aSourceIndex);
-
-    is(gSources.selectedLabel, gSources.items[aSourceIndex].label,
-      "The currently selected source label is incorrect (0).");
-    is(gSources.selectedValue, gSources.items[aSourceIndex].value,
-      "The currently selected source value is incorrect (0).");
-    ok(isCaretPos(gPanel, aCaretLine),
-      "The editor caret line and column were incorrect (0).");
-  }
-
-  function clickBreakpointAndCheck(aBreakpointIndex, aSourceIndex, aCaretLine) {
-    let finished = waitForCaretUpdated(gPanel, aCaretLine).then(() => {
-      checkHighlight(gSources.values[aSourceIndex], aCaretLine);
-      checkEditorContents(aSourceIndex);
-
-      is(gSources.selectedLabel, gSources.items[aSourceIndex].label,
-        "The currently selected source label is incorrect (1).");
-      is(gSources.selectedValue, gSources.items[aSourceIndex].value,
-        "The currently selected source value is incorrect (1).");
-      ok(isCaretPos(gPanel, aCaretLine),
-        "The editor caret line and column were incorrect (1).");
+    const addBreakpoints = Task.async(function*() {
+      yield actions.addBreakpoint({ actor: gSources.values[0], line: 5 });
+      yield actions.addBreakpoint({ actor: gSources.values[1], line: 6 });
+      yield actions.addBreakpoint({ actor: gSources.values[1], line: 7 });
+      yield actions.addBreakpoint({ actor: gSources.values[1], line: 8 });
+      yield actions.addBreakpoint({ actor: gSources.values[1], line: 9 })
     });
 
-    EventUtils.sendMouseEvent({ type: "click" },
-      gDebugger.document.querySelectorAll(".dbg-breakpoint")[aBreakpointIndex],
-      gDebugger);
+    function clickBreakpointAndCheck(aBreakpointIndex, aSourceIndex, aCaretLine) {
+      let finished = waitForCaretUpdated(gPanel, aCaretLine).then(() => {
+        checkHighlight(gSources.values[aSourceIndex], aCaretLine);
+        checkEditorContents(aSourceIndex);
+
+        is(queries.getSelectedSource(getState()).actor,
+           gSources.items[aSourceIndex].value,
+           "The currently selected source value is incorrect (1).");
+        ok(isCaretPos(gPanel, aCaretLine),
+           "The editor caret line and column were incorrect (1).");
+      });
 
-    return finished;
-  }
+      EventUtils.sendMouseEvent(
+        { type: "click" },
+        gDebugger.document.querySelectorAll(".dbg-breakpoint")[aBreakpointIndex],
+        gDebugger
+      );
+
+      return finished;
+    }
+
+    function checkHighlight(actor, line) {
+      let breakpoint = gSources._selectedBreakpoint;
+      let breakpointItem = gSources._getBreakpoint(breakpoint);
 
-  function checkHighlight(aActor, aLine) {
-    is(gSources._selectedBreakpointItem, gSources.getBreakpoint({ actor: aActor, line: aLine }),
-      "The currently selected breakpoint item is incorrect.");
-    is(gSources._selectedBreakpointItem.attachment.actor, aActor,
-      "The selected breakpoint item's source location attachment is incorrect.");
-    is(gSources._selectedBreakpointItem.attachment.line, aLine,
-      "The selected breakpoint item's source line number is incorrect.");
-    ok(gSources._selectedBreakpointItem.target.classList.contains("selected"),
-      "The selected breakpoint item's target should have a selected class.");
-  }
+      is(breakpoint.location.actor, actor,
+         "The currently selected breakpoint actor is incorrect.");
+      is(breakpoint.location.line, line,
+         "The currently selected breakpoint line is incorrect.");
+      is(breakpointItem.attachment.actor, actor,
+         "The selected breakpoint item's source location attachment is incorrect.");
+      ok(breakpointItem.target.classList.contains("selected"),
+         "The selected breakpoint item's target should have a selected class.");
+    }
 
-  function checkEditorContents(aSourceIndex) {
-    if (aSourceIndex == 0) {
-      is(gEditor.getText().indexOf("firstCall"), 118,
-        "The first source is correctly displayed.");
-    } else {
-      is(gEditor.getText().indexOf("debugger"), 166,
-        "The second source is correctly displayed.");
+    function checkEditorContents(aSourceIndex) {
+      if (aSourceIndex == 0) {
+        is(gEditor.getText().indexOf("firstCall"), 118,
+           "The first source is correctly displayed.");
+      } else {
+        is(gEditor.getText().indexOf("debugger"), 166,
+           "The second source is correctly displayed.");
+      }
     }
-  }
+
+    Task.spawn(function*() {
+      yield waitForSourceShown(gPanel, "-01.js");
+
+      yield addBreakpoints();
+      yield clickBreakpointAndCheck(0, 0, 5);
+      yield clickBreakpointAndCheck(1, 1, 6);
+      yield clickBreakpointAndCheck(2, 1, 7);
+      yield clickBreakpointAndCheck(3, 1, 8);
+      yield clickBreakpointAndCheck(4, 1, 9);
+      closeDebuggerAndFinish(gPanel);
+    });
+  });
 }
--- a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-new-script.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-new-script.js
@@ -3,86 +3,83 @@
 
 /**
  * Bug 771452: Make sure that setting a breakpoint in an inline source doesn't
  * add it twice.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_inline-script.html";
 
-var gTab, gPanel, gDebugger, gSources;
-
 function test() {
   initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
-    gTab = aTab;
-    gPanel = aPanel;
-    gDebugger = gPanel.panelWin;
-    gSources = gDebugger.DebuggerView.Sources;
+    const gTab = aTab;
+    const gPanel = aPanel;
+    const gDebugger = gPanel.panelWin;
+    const gSources = gDebugger.DebuggerView.Sources;
+    const queries = gDebugger.require('./content/queries');
+    const actions = bindActionCreators(gPanel);
+    const getState = gDebugger.DebuggerController.getState;
+
+    function testResume() {
+      const deferred = promise.defer();
+      is(gDebugger.gThreadClient.state, "paused",
+         "The breakpoint wasn't hit yet.");
+
+      gDebugger.gThreadClient.resume(() => {
+        gDebugger.gThreadClient.addOneTimeListener("paused", (aEvent, aPacket) => {
+          waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES).then(() => {
+            is(aPacket.why.type, "breakpoint",
+               "Execution has advanced to the next breakpoint.");
+            isnot(aPacket.why.type, "debuggerStatement",
+                  "The breakpoint was hit before the debugger statement.");
+            ok(isCaretPos(gPanel, 20),
+               "The source editor caret position is incorrect (2).");
+
+            deferred.resolve();
+          });
+        });
+
+        generateMouseClickInTab(gTab, "content.document.querySelector('button')");
+      });
+
+      return deferred.promise;
+    }
 
-    addBreakpoint();
+    function testBreakpointHit() {
+      const deferred = promise.defer();
+      is(gDebugger.gThreadClient.state, "paused",
+         "The breakpoint was hit.");
+
+      gDebugger.gThreadClient.addOneTimeListener("paused", (aEvent, aPacket) => {
+        waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES).then(() => {
+          is(aPacket.why.type, "debuggerStatement",
+             "Execution has advanced to the next line.");
+          isnot(aPacket.why.type, "breakpoint",
+                "No ghost breakpoint was hit.");
+          ok(isCaretPos(gPanel, 20),
+             "The source editor caret position is incorrect (3).");
+
+          deferred.resolve();
+        });
+      });
+
+      EventUtils.sendMouseEvent({ type: "mousedown" },
+                                gDebugger.document.getElementById("resume"),
+                                gDebugger);
+      return deferred.promise;
+    }
+
+    Task.spawn(function(){
+      yield waitForSourceAndCaretAndScopes(gPanel, ".html", 16);
+      is(gDebugger.gThreadClient.state, "paused",
+         "The debugger statement was reached.");
+      ok(isCaretPos(gPanel, 16),
+         "The source editor caret position is incorrect (1).");
+
+      yield actions.addBreakpoint({ actor: getSourceActor(gSources, TAB_URL), line: 20 });
+      yield testResume();
+      yield testBreakpointHit();
+      resumeDebuggerThenCloseAndFinish(gPanel);
+    });
+
+    callInTab(gTab, "runDebuggerStatement");
   });
 }
-
-function addBreakpoint() {
-  waitForSourceAndCaretAndScopes(gPanel, ".html", 16).then(() => {
-    is(gDebugger.gThreadClient.state, "paused",
-      "The debugger statement was reached.");
-    ok(isCaretPos(gPanel, 16),
-      "The source editor caret position is incorrect (1).");
-
-    gPanel.addBreakpoint({ actor: getSourceActor(gSources, TAB_URL), line: 20 }).then(() => {
-      testResume();
-    });
-  });
-
-  callInTab(gTab, "runDebuggerStatement");
-}
-
-function testResume() {
-  is(gDebugger.gThreadClient.state, "paused",
-    "The breakpoint wasn't hit yet.");
-
-  gDebugger.gThreadClient.resume(() => {
-    gDebugger.gThreadClient.addOneTimeListener("paused", (aEvent, aPacket) => {
-      waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES).then(() => {
-        is(aPacket.why.type, "breakpoint",
-          "Execution has advanced to the next breakpoint.");
-        isnot(aPacket.why.type, "debuggerStatement",
-          "The breakpoint was hit before the debugger statement.");
-        ok(isCaretPos(gPanel, 20),
-          "The source editor caret position is incorrect (2).");
-
-        testBreakpointHit();
-      });
-    });
-
-    generateMouseClickInTab(gTab, "content.document.querySelector('button')");
-  });
-}
-
-function testBreakpointHit() {
-  is(gDebugger.gThreadClient.state, "paused",
-    "The breakpoint was hit.");
-
-  gDebugger.gThreadClient.addOneTimeListener("paused", (aEvent, aPacket) => {
-    waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES).then(() => {
-      is(aPacket.why.type, "debuggerStatement",
-        "Execution has advanced to the next line.");
-      isnot(aPacket.why.type, "breakpoint",
-        "No ghost breakpoint was hit.");
-      ok(isCaretPos(gPanel, 20),
-        "The source editor caret position is incorrect (3).");
-
-      resumeDebuggerThenCloseAndFinish(gPanel);
-    });
-  });
-
-  EventUtils.sendMouseEvent({ type: "mousedown" },
-    gDebugger.document.getElementById("resume"),
-    gDebugger);
-}
-
-registerCleanupFunction(function() {
-  gTab = null;
-  gPanel = null;
-  gDebugger = null;
-  gSources = null;
-});
--- a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-other-tabs.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-other-tabs.js
@@ -6,23 +6,26 @@
  * the same source to pause at that location.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_breakpoints-other-tabs.html";
 
 var test = Task.async(function* () {
   const [tab1,, panel1] = yield initDebugger(TAB_URL);
   const [tab2,, panel2] = yield initDebugger(TAB_URL);
+  const queries = panel1.panelWin.require('./content/queries');
+  const actions = bindActionCreators(panel1);
+  const getState = panel1.panelWin.DebuggerController.getState;
 
   yield ensureSourceIs(panel1, "code_breakpoints-other-tabs.js", true);
 
   const sources = panel1.panelWin.DebuggerView.Sources;
 
-  yield panel1.addBreakpoint({
-    actor: sources.selectedValue,
+  yield actions.addBreakpoint({
+    actor: queries.getSelectedSource(getState()).actor,
     line: 2
   });
 
   const paused = waitForThreadEvents(panel2, "paused");
   callInTab(tab2, "testCase");
   const packet = yield paused;
 
   is(packet.why.type, "debuggerStatement",
--- a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-pane.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-pane.js
@@ -4,251 +4,229 @@
 /**
  * Bug 723071: Test adding a pane to display the list of breakpoints across
  * all sources in the debuggee.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
 
 function test() {
-  let gTab, gPanel, gDebugger;
-  let gEditor, gSources, gBreakpoints, gBreakpointsAdded, gBreakpointsRemoving;
-
   initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
-    gTab = aTab;
-    gPanel = aPanel;
-    gDebugger = gPanel.panelWin;
-    gEditor = gDebugger.DebuggerView.editor;
-    gSources = gDebugger.DebuggerView.Sources;
-    gBreakpoints = gDebugger.DebuggerController.Breakpoints;
-    gBreakpointsAdded = gBreakpoints._added;
-    gBreakpointsRemoving = gBreakpoints._removing;
-
-    waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1).then(performTest);
-    callInTab(gTab, "firstCall");
-  });
-
-  let breakpointsAdded = 0;
-  let breakpointsDisabled = 0;
-  let breakpointsRemoved = 0;
+    const gTab = aTab;
+    const gPanel = aPanel;
+    const gDebugger = gPanel.panelWin;
+    const gEditor = gDebugger.DebuggerView.editor;
+    const gSources = gDebugger.DebuggerView.Sources;
+    const queries = gDebugger.require('./content/queries');
+    const actions = bindActionCreators(gPanel);
+    const getState = gDebugger.DebuggerController.getState;
+    const { getBreakpoint } = queries;
 
-  function performTest() {
-    is(gDebugger.gThreadClient.state, "paused",
-      "Should only be getting stack frames while paused.");
-    is(gSources.itemCount, 2,
-      "Found the expected number of sources.");
-    is(gEditor.getText().indexOf("debugger"), 166,
-      "The correct source was loaded initially.");
-    is(gSources.selectedValue, gSources.values[1],
-      "The correct source is selected.");
-
-    is(gBreakpointsAdded.size, 0,
-      "No breakpoints currently added.");
-    is(gBreakpointsRemoving.size, 0,
-      "No breakpoints currently being removed.");
-    is(gEditor.getBreakpoints().length, 0,
-      "No breakpoints currently shown in the editor.");
-
-    ok(!gBreakpoints._getAdded({ url: "foo", line: 3 }),
-      "_getAdded('foo', 3) returns falsey.");
-    ok(!gBreakpoints._getRemoving({ url: "bar", line: 3 }),
-      "_getRemoving('bar', 3) returns falsey.");
-
-    let breakpointsParent = gSources.widget._parent;
-    let breakpointsList = gSources.widget._list;
-
-    is(breakpointsParent.childNodes.length, 1, // one sources list
-      "Found junk in the breakpoints container.");
-    is(breakpointsList.childNodes.length, 1, // one sources group
-      "Found junk in the breakpoints container.");
-    is(breakpointsList.querySelectorAll(".dbg-breakpoint").length, 0,
-      "No breakpoints should be visible at this point.");
+    let breakpointsAdded = 0;
+    let breakpointsDisabled = 0;
+    let breakpointsRemoved = 0;
+    let breakpointsList;
 
-    addBreakpoints(true).then(() => {
-      is(breakpointsAdded, 3,
-        "Should have added 3 breakpoints so far.");
-      is(breakpointsDisabled, 0,
-        "Shouldn't have disabled anything so far.");
-      is(breakpointsRemoved, 0,
-        "Shouldn't have removed anything so far.");
-
-      is(breakpointsParent.childNodes.length, 1, // one sources list
-        "Found junk in the breakpoints container.");
-      is(breakpointsList.childNodes.length, 1, // one sources group
-        "Found junk in the breakpoints container.");
-      is(breakpointsList.querySelectorAll(".dbg-breakpoint").length, 3,
-        "3 breakpoints should be visible at this point.");
-
-      disableBreakpoints().then(() => {
-        is(breakpointsAdded, 3,
-          "Should still have 3 breakpoints added so far.");
-        is(breakpointsDisabled, 3,
-          "Should have 3 disabled breakpoints.");
-        is(breakpointsRemoved, 0,
-          "Shouldn't have removed anything so far.");
-
-        is(breakpointsParent.childNodes.length, 1, // one sources list
-          "Found junk in the breakpoints container.");
-        is(breakpointsList.childNodes.length, 1, // one sources group
-          "Found junk in the breakpoints container.");
-        is(breakpointsList.querySelectorAll(".dbg-breakpoint").length, breakpointsAdded,
-          "Should have the same number of breakpoints in the pane.");
-        is(breakpointsList.querySelectorAll(".dbg-breakpoint").length, breakpointsDisabled,
-          "Should have the same number of disabled breakpoints.");
+    const addBreakpoints = Task.async(function*(aIncrementFlag) {
+      const loc1 = { actor: gSources.selectedValue, line: 6 };
+      yield actions.addBreakpoint(loc1);
+      onBreakpointAdd(getBreakpoint(getState(), loc1), {
+        increment: aIncrementFlag,
+        line: 6,
+        text: "debugger;"
+      });
 
-        addBreakpoints().then(() => {
-          is(breakpointsAdded, 3,
-            "Should still have only 3 breakpoints added so far.");
-          is(breakpointsDisabled, 3,
-            "Should still have 3 disabled breakpoints.");
-          is(breakpointsRemoved, 0,
-            "Shouldn't have removed anything so far.");
-
-          is(breakpointsParent.childNodes.length, 1, // one sources list
-            "Found junk in the breakpoints container.");
-          is(breakpointsList.childNodes.length, 1, // one sources group
-            "Found junk in the breakpoints container.");
-          is(breakpointsList.querySelectorAll(".dbg-breakpoint").length, breakpointsAdded,
-            "Since half of the breakpoints already existed, but disabled, " +
-            "only half of the added breakpoints are actually in the pane.");
+      const loc2 = { actor: gSources.selectedValue, line: 7 };
+      yield actions.addBreakpoint(loc2);
+      onBreakpointAdd(getBreakpoint(getState(), loc2), {
+        increment: aIncrementFlag,
+        line: 7,
+        text: "function foo() {}"
+      });
 
-          removeBreakpoints().then(() => {
-            is(breakpointsRemoved, 3,
-              "Should have 3 removed breakpoints.");
-
-            is(breakpointsParent.childNodes.length, 1, // one sources list
-               "Found junk in the breakpoints container.");
-            is(breakpointsList.childNodes.length, 1, // one sources group
-               "Found junk in the breakpoints container.");
-            is(breakpointsList.querySelectorAll(".dbg-breakpoint").length, 0,
-               "No breakpoints should be visible at this point.");
-
-            waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_CLEARED).then(() => {
-              finalCheck();
-              closeDebuggerAndFinish(gPanel);
-            });
-
-            gDebugger.gThreadClient.resume();
-          });
-        });
+      const loc3 = {actor: gSources.selectedValue, line: 9 }
+      yield actions.addBreakpoint(loc3);
+      onBreakpointAdd(getBreakpoint(getState(), loc3), {
+        increment: aIncrementFlag,
+        line: 9,
+        text: "foo();"
       });
     });
 
-    function addBreakpoints(aIncrementFlag) {
-      let deferred = promise.defer();
-
-      gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 6 }).then(aClient => {
-        onBreakpointAdd(aClient, {
-          increment: aIncrementFlag,
-          line: 6,
-          text: "debugger;"
-        });
-
-        gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 7 }).then(aClient => {
-          onBreakpointAdd(aClient, {
-            increment: aIncrementFlag,
-            line: 7,
-            text: "function foo() {}"
-          });
-
-          gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 9 }).then(aClient => {
-            onBreakpointAdd(aClient, {
-              increment: aIncrementFlag,
-              line: 9,
-              text: "foo();"
-            });
-
-            deferred.resolve();
-          });
-        });
-      });
-
-      return deferred.promise;
-    }
-
     function disableBreakpoints() {
       let deferred = promise.defer();
 
       let nodes = breakpointsList.querySelectorAll(".dbg-breakpoint");
       info("Nodes to disable: " + breakpointsAdded.length);
 
       is(nodes.length, breakpointsAdded,
-        "The number of nodes to disable is incorrect.");
+         "The number of nodes to disable is incorrect.");
 
       for (let node of nodes) {
         info("Disabling breakpoint: " + node.id);
 
         let sourceItem = gSources.getItemForElement(node);
         let breakpointItem = gSources.getItemForElement.call(sourceItem, node);
         info("Found data: " + breakpointItem.attachment.toSource());
 
-        gSources.disableBreakpoint(breakpointItem.attachment).then(() => {
+        actions.disableBreakpoint(breakpointItem.attachment).then(() => {
           if (++breakpointsDisabled == breakpointsAdded) {
             deferred.resolve();
           }
         });
       }
 
       return deferred.promise;
     }
 
     function removeBreakpoints() {
       let deferred = promise.defer();
 
       let nodes = breakpointsList.querySelectorAll(".dbg-breakpoint");
       info("Nodes to remove: " + breakpointsAdded.length);
 
       is(nodes.length, breakpointsAdded,
-        "The number of nodes to remove is incorrect.");
+         "The number of nodes to remove is incorrect.");
 
       for (let node of nodes) {
         info("Removing breakpoint: " + node.id);
 
         let sourceItem = gSources.getItemForElement(node);
         let breakpointItem = gSources.getItemForElement.call(sourceItem, node);
         info("Found data: " + breakpointItem.attachment.toSource());
 
-        gPanel.removeBreakpoint(breakpointItem.attachment).then(() => {
+        actions.removeBreakpoint(breakpointItem.attachment).then(() => {
           if (++breakpointsRemoved == breakpointsAdded) {
             deferred.resolve();
           }
         });
       }
 
       return deferred.promise;
     }
 
-    function onBreakpointAdd(aBreakpointClient, aTestData) {
-      if (aTestData.increment) {
+    function onBreakpointAdd(bp, testData) {
+      if (testData.increment) {
         breakpointsAdded++;
       }
 
       is(breakpointsList.querySelectorAll(".dbg-breakpoint").length, breakpointsAdded,
-        aTestData.increment
-          ? "Should have added a breakpoint in the pane."
-          : "Should have the same number of breakpoints in the pane.");
+         testData.increment
+         ? "Should have added a breakpoint in the pane."
+         : "Should have the same number of breakpoints in the pane.");
 
-      let identifier = gBreakpoints.getIdentifier(aBreakpointClient.location);
+      let identifier = queries.makeLocationId(bp.location);
       let node = gDebugger.document.getElementById("breakpoint-" + identifier);
       let line = node.getElementsByClassName("dbg-breakpoint-line")[0];
       let text = node.getElementsByClassName("dbg-breakpoint-text")[0];
       let check = node.querySelector("checkbox");
 
       ok(node,
-        "Breakpoint element found successfully.");
-      is(line.getAttribute("value"), aTestData.line,
-        "The expected information wasn't found in the breakpoint element.");
-      is(text.getAttribute("value"), aTestData.text,
-        "The expected line text wasn't found in the breakpoint element.");
+         "Breakpoint element found successfully.");
+      is(line.getAttribute("value"), testData.line,
+         "The expected information wasn't found in the breakpoint element.");
+      is(text.getAttribute("value"), testData.text,
+         "The expected line text wasn't found in the breakpoint element.");
       is(check.getAttribute("checked"), "true",
-        "The breakpoint enable checkbox is checked as expected.");
+         "The breakpoint enable checkbox is checked as expected.");
     }
-  }
+
+    Task.spawn(function*() {
+      yield waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1);
+
+      is(gDebugger.gThreadClient.state, "paused",
+         "Should only be getting stack frames while paused.");
+      is(queries.getSourceCount(getState()), 2,
+         "Found the expected number of sources.");
+      is(gEditor.getText().indexOf("debugger"), 166,
+         "The correct source was loaded initially.");
+      is(gSources.selectedValue, gSources.values[1],
+         "The correct source is selected.");
+
+      is(queries.getBreakpoints(getState()).length, 0,
+         "No breakpoints currently added.");
+
+      let breakpointsParent = gSources.widget._parent;
+      breakpointsList = gSources.widget._list;
+
+      is(breakpointsParent.childNodes.length, 1, // one sources list
+         "Found junk in the breakpoints container.");
+      is(breakpointsList.childNodes.length, 1, // one sources group
+         "Found junk in the breakpoints container.");
+      is(breakpointsList.querySelectorAll(".dbg-breakpoint").length, 0,
+         "No breakpoints should be visible at this point.");
+
+      yield addBreakpoints(true);
+
+      is(breakpointsAdded, 3,
+        "Should have added 3 breakpoints so far.");
+      is(breakpointsDisabled, 0,
+        "Shouldn't have disabled anything so far.");
+      is(breakpointsRemoved, 0,
+        "Shouldn't have removed anything so far.");
+
+      is(breakpointsParent.childNodes.length, 1, // one sources list
+        "Found junk in the breakpoints container.");
+      is(breakpointsList.childNodes.length, 1, // one sources group
+        "Found junk in the breakpoints container.");
+      is(breakpointsList.querySelectorAll(".dbg-breakpoint").length, 3,
+         "3 breakpoints should be visible at this point.");
+
+      yield disableBreakpoints();
 
-  function finalCheck() {
-    is(gBreakpointsAdded.size, 0,
-      "No breakpoints currently added.");
-    is(gBreakpointsRemoving.size, 0,
-      "No breakpoints currently being removed.");
-    is(gEditor.getBreakpoints().length, 0,
-      "No breakpoints currently shown in the editor.");
-  }
+      is(breakpointsAdded, 3,
+         "Should still have 3 breakpoints added so far.");
+      is(breakpointsDisabled, 3,
+         "Should have 3 disabled breakpoints.");
+      is(breakpointsRemoved, 0,
+         "Shouldn't have removed anything so far.");
+
+      is(breakpointsParent.childNodes.length, 1, // one sources list
+         "Found junk in the breakpoints container.");
+      is(breakpointsList.childNodes.length, 1, // one sources group
+         "Found junk in the breakpoints container.");
+      is(breakpointsList.querySelectorAll(".dbg-breakpoint").length, breakpointsAdded,
+         "Should have the same number of breakpoints in the pane.");
+      is(breakpointsList.querySelectorAll(".dbg-breakpoint").length, breakpointsDisabled,
+         "Should have the same number of disabled breakpoints.");
+
+      yield addBreakpoints();
+
+      is(breakpointsAdded, 3,
+         "Should still have only 3 breakpoints added so far.");
+      is(breakpointsDisabled, 3,
+         "Should still have 3 disabled breakpoints.");
+      is(breakpointsRemoved, 0,
+         "Shouldn't have removed anything so far.");
+
+      is(breakpointsParent.childNodes.length, 1, // one sources list
+         "Found junk in the breakpoints container.");
+      is(breakpointsList.childNodes.length, 1, // one sources group
+         "Found junk in the breakpoints container.");
+      is(breakpointsList.querySelectorAll(".dbg-breakpoint").length, breakpointsAdded,
+         "Since half of the breakpoints already existed, but disabled, " +
+         "only half of the added breakpoints are actually in the pane.");
+
+      yield removeBreakpoints();
+
+      is(breakpointsRemoved, 3,
+         "Should have 3 removed breakpoints.");
+
+      is(breakpointsParent.childNodes.length, 1, // one sources list
+         "Found junk in the breakpoints container.");
+      is(breakpointsList.childNodes.length, 1, // one sources group
+         "Found junk in the breakpoints container.");
+      is(breakpointsList.querySelectorAll(".dbg-breakpoint").length, 0,
+         "No breakpoints should be visible at this point.");
+
+      const cleared = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_CLEARED);
+      gDebugger.gThreadClient.resume();
+      yield cleared;
+
+      is(queries.getBreakpoints(getState()).length, 0,
+         "No breakpoints currently added.");
+
+      closeDebuggerAndFinish(gPanel);
+    });
+
+    callInTab(gTab, "firstCall");
+  });
 }
--- a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-reload.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-reload.js
@@ -7,21 +7,22 @@
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_breakpoints-reload.html";
 
 var test = Task.async(function* () {
   requestLongerTimeout(4);
 
   const [tab,, panel] = yield initDebugger(TAB_URL);
+  const actions = bindActionCreators(panel);
 
   yield ensureSourceIs(panel, "doc_breakpoints-reload.html", true);
 
   const sources = panel.panelWin.DebuggerView.Sources;
-  yield panel.addBreakpoint({
+  yield actions.addBreakpoint({
     actor: sources.selectedValue,
     line: 10 // "break on me" string
   });
 
   const paused = waitForThreadEvents(panel, "paused");
   reloadActiveTab(panel);
   const packet = yield paused;
 
--- a/devtools/client/debugger/test/mochitest/browser_dbg_cmd-blackbox.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_cmd-blackbox.js
@@ -17,22 +17,23 @@ function test() {
 }
 
 function* spawnTest() {
   let options = yield helpers.openTab(TEST_URL);
   yield helpers.openToolbar(options);
 
   let toolbox = yield gDevTools.showToolbox(options.target, "jsdebugger");
   let panel = toolbox.getCurrentPanel();
+  let constants = panel.panelWin.require('./content/constants');
 
   yield waitForDebuggerEvents(panel, panel.panelWin.EVENTS.SOURCE_SHOWN);
 
   function cmd(aTyped, aEventRepeat = 1, aOutput = "") {
     return promise.all([
-      waitForThreadEvents(panel, "blackboxchange", aEventRepeat),
+      waitForDispatch(panel, constants.BLACKBOX, aEventRepeat),
       helpers.audit(options, [{ setup: aTyped, output: aOutput, exec: {} }])
     ]);
   }
 
   // test Black-Box Source
   yield cmd("dbg blackbox " + BLACKBOXME_URL);
 
   let bbButton = yield selectSourceAndGetBlackBoxButton(panel, BLACKBOXME_URL);
--- a/devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-01.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-01.js
@@ -6,238 +6,206 @@
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_conditional-breakpoints.html";
 
 function test() {
   // Linux debug test slaves are a bit slow at this test sometimes.
   requestLongerTimeout(2);
 
-  let gTab, gPanel, gDebugger;
-  let gEditor, gSources, gBreakpoints, gBreakpointsAdded, gBreakpointsRemoving;
-
   initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
-    gTab = aTab;
-    gPanel = aPanel;
-    gDebugger = gPanel.panelWin;
-    gEditor = gDebugger.DebuggerView.editor;
-    gSources = gDebugger.DebuggerView.Sources;
-    gBreakpoints = gDebugger.DebuggerController.Breakpoints;
-    gBreakpointsAdded = gBreakpoints._added;
-    gBreakpointsRemoving = gBreakpoints._removing;
+    const gTab = aTab;
+    const gPanel = aPanel;
+    const gDebugger = gPanel.panelWin;
+    const gEditor = gDebugger.DebuggerView.editor;
+    const gSources = gDebugger.DebuggerView.Sources;
+    const queries = gDebugger.require('./content/queries');
+    const constants = gDebugger.require('./content/constants');
+    const actions = bindActionCreators(gPanel);
+    const getState = gDebugger.DebuggerController.getState;
 
     // This test forces conditional breakpoints to be evaluated on the
     // client-side
     var client = gPanel.target.client;
     client.mainRoot.traits.conditionalBreakpoints = false;
 
-    waitForSourceAndCaretAndScopes(gPanel, ".html", 17)
-      .then(() => addBreakpoints())
-      .then(() => initialChecks())
-      .then(() => resumeAndTestBreakpoint(20))
-      .then(() => resumeAndTestBreakpoint(21))
-      .then(() => resumeAndTestBreakpoint(22))
-      .then(() => resumeAndTestBreakpoint(23))
-      .then(() => resumeAndTestBreakpoint(24))
-      .then(() => resumeAndTestBreakpoint(25))
-      .then(() => resumeAndTestBreakpoint(27))
-      .then(() => resumeAndTestBreakpoint(28))
-      .then(() => {
-        // Note: the breakpoint on line 29 should not be hit since the
-        // conditional expression evaluates to undefined. It used to
-        // be on line 30, but it can't be the last breakpoint because
-        // there is a race condition (the "frames cleared" event might
-        // fire from the conditional expression evaluation if it's too
-        // slow, which is what we wait for to reload the page)
-        return resumeAndTestBreakpoint(30);
-      })
-      .then(() => resumeAndTestNoBreakpoint())
-      .then(() => {
-        return promise.all([
-          reloadActiveTab(gPanel, gDebugger.EVENTS.BREAKPOINT_SHOWN_IN_EDITOR, 13),
-          waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_SHOWN_IN_PANE, 13)
-        ]);
-      })
-      .then(() => testAfterReload())
-      .then(() => {
-        // Reset traits back to default value
-        client.mainRoot.traits.conditionalBreakpoints = true;
-      })
-      .then(() => closeDebuggerAndFinish(gPanel))
-      .then(null, aError => {
-        ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+    const addBreakpoints = Task.async(function*() {
+      yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 18 },
+                                  "undefined");
+      yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 19 },
+                                  "null");
+      yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 20 },
+                                  "42");
+      yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 21 },
+                                  "true");
+      yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 22 },
+                                  "'nasu'");
+      yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 23 },
+                                  "/regexp/");
+      yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 24 },
+                                  "({})");
+      yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 25 },
+                                  "(function() {})");
+      yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 26 },
+                                  "(function() { return false; })()");
+      yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 27 },
+                                  "a");
+      yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 28 },
+                                  "a !== undefined");
+      yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 29 },
+                                  "b");
+      yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 30 },
+                                  "a !== null");
+    });
+
+    function resumeAndTestBreakpoint(line) {
+      let finished = waitForCaretUpdated(gPanel, line).then(() => testBreakpoint(line));
+
+      EventUtils.sendMouseEvent({ type: "mousedown" },
+                                gDebugger.document.getElementById("resume"),
+                                gDebugger);
+
+      return finished;
+    }
+
+    function resumeAndTestNoBreakpoint() {
+      let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_CLEARED).then(() => {
+        is(gSources.itemCount, 1,
+           "Found the expected number of sources.");
+        is(gEditor.getText().indexOf("ermahgerd"), 253,
+           "The correct source was loaded initially.");
+        is(gSources.selectedValue, gSources.values[0],
+           "The correct source is selected.");
+
+        ok(gSources.selectedItem,
+           "There should be a selected source in the sources pane.")
+        ok(!gSources._selectedBreakpoint,
+           "There should be no selected breakpoint in the sources pane.")
+        is(gSources._conditionalPopupVisible, false,
+           "The breakpoint conditional expression popup should not be shown.");
+
+        is(gDebugger.document.querySelectorAll(".dbg-stackframe").length, 0,
+           "There should be no visible stackframes.");
+        is(gDebugger.document.querySelectorAll(".dbg-breakpoint").length, 13,
+           "There should be thirteen visible breakpoints.");
       });
 
+      gDebugger.gThreadClient.resume();
+
+      return finished;
+    }
+
+    function testBreakpoint(line, highlightBreakpoint) {
+      // Highlight the breakpoint only if required.
+      if (highlightBreakpoint) {
+        let finished = waitForCaretUpdated(gPanel, line).then(() => testBreakpoint(line));
+        gSources.highlightBreakpoint({ actor: gSources.selectedValue, line: line });
+        return finished;
+      }
+
+      let selectedActor = gSources.selectedValue;
+      let selectedBreakpoint = gSources._selectedBreakpoint;
+      let selectedBreakpointItem = gSources._getBreakpoint(selectedBreakpoint);
+
+      ok(selectedActor,
+         "There should be a selected item in the sources pane.");
+      ok(selectedBreakpoint,
+         "There should be a selected breakpoint in the sources pane.");
+
+      let source = gSources.selectedItem.attachment.source;
+      let bp = queries.getBreakpoint(getState(), selectedBreakpoint.location);
+
+      ok(bp, "The selected breakpoint exists");
+      is(bp.location.actor, source.actor,
+         "The breakpoint on line " + line + " wasn't added on the correct source.");
+      is(bp.location.line, line,
+         "The breakpoint on line " + line + " wasn't found.");
+      is(!!bp.disabled, false,
+         "The breakpoint on line " + line + " should be enabled.");
+      is(!!selectedBreakpointItem.attachment.openPopup, false,
+         "The breakpoint on line " + line + " should not have opened a popup.");
+      is(gSources._conditionalPopupVisible, false,
+         "The breakpoint conditional expression popup should not have been shown.");
+      isnot(bp.condition, undefined,
+            "The breakpoint on line " + line + " should have a conditional expression.");
+      ok(isCaretPos(gPanel, line),
+         "The editor caret position is not properly set.");
+    }
+
+    const testAfterReload = Task.async(function*() {
+      let selectedActor = gSources.selectedValue;
+      let selectedBreakpoint = gSources._selectedBreakpoint;
+
+      ok(selectedActor,
+         "There should be a selected item in the sources pane after reload.");
+      ok(!selectedBreakpoint,
+         "There should be no selected breakpoint in the sources pane after reload.");
+
+      yield testBreakpoint(18, true);
+      yield testBreakpoint(19, true);
+      yield testBreakpoint(20, true);
+      yield testBreakpoint(21, true);
+      yield testBreakpoint(22, true);
+      yield testBreakpoint(23, true);
+      yield testBreakpoint(24, true);