Merge m-c to b2g-inbound.
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 13 Sep 2013 16:11:28 -0400
changeset 160045 93c39030cdb32f1abcc3eb09478b9d4d0d237ad2
parent 160044 4932106f16e57e74983ee341f5025ee2fa9e9472 (current diff)
parent 160006 67e73bb25c75c604708461da210ce418a8d0ab2f (diff)
child 160059 cdc22fbf554ea6d69ba5e17991d910d968a380bf
child 160074 76a80defebfa3bcabbaf3b9d720ea48f0d1e4ad7
push id2961
push userlsblakk@mozilla.com
push dateMon, 28 Oct 2013 21:59:28 +0000
treeherdermozilla-beta@73ef4f13486f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone26.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
Merge m-c to b2g-inbound.
browser/devtools/debugger/test/binary_search.coffee
browser/devtools/debugger/test/binary_search.html
browser/devtools/debugger/test/binary_search.js
browser/devtools/debugger/test/binary_search.map
browser/devtools/debugger/test/blackboxing_blackboxme.js
browser/devtools/debugger/test/blackboxing_one.js
browser/devtools/debugger/test/blackboxing_three.js
browser/devtools/debugger/test/blackboxing_two.js
browser/devtools/debugger/test/browser_dbg_addon1.xpi
browser/devtools/debugger/test/browser_dbg_addon2.xpi
browser/devtools/debugger/test/browser_dbg_big-data.html
browser/devtools/debugger/test/browser_dbg_blackboxing.html
browser/devtools/debugger/test/browser_dbg_breakpoint-new-script.html
browser/devtools/debugger/test/browser_dbg_breakpoint-new-script.js
browser/devtools/debugger/test/browser_dbg_bug723069_editor-breakpoints.js
browser/devtools/debugger/test/browser_dbg_bug723071_editor-breakpoints-contextmenu.js
browser/devtools/debugger/test/browser_dbg_bug723071_editor-breakpoints-highlight.js
browser/devtools/debugger/test/browser_dbg_bug723071_editor-breakpoints-pane.js
browser/devtools/debugger/test/browser_dbg_bug727429_watch-expressions-01.js
browser/devtools/debugger/test/browser_dbg_bug727429_watch-expressions-02.js
browser/devtools/debugger/test/browser_dbg_bug731394_editor-contextmenu.js
browser/devtools/debugger/test/browser_dbg_bug737803_editor_actual_location.js
browser/devtools/debugger/test/browser_dbg_bug740825_conditional-breakpoints-01.js
browser/devtools/debugger/test/browser_dbg_bug740825_conditional-breakpoints-02.js
browser/devtools/debugger/test/browser_dbg_bug786070_hide_nonenums.js
browser/devtools/debugger/test/browser_dbg_bug868163_highight_on_pause.js
browser/devtools/debugger/test/browser_dbg_bug883220_raise_on_pause.js
browser/devtools/debugger/test/browser_dbg_cmd.html
browser/devtools/debugger/test/browser_dbg_cmd.js
browser/devtools/debugger/test/browser_dbg_cmd_blackbox.js
browser/devtools/debugger/test/browser_dbg_cmd_break.html
browser/devtools/debugger/test/browser_dbg_cmd_break.js
browser/devtools/debugger/test/browser_dbg_conditional-breakpoints.html
browser/devtools/debugger/test/browser_dbg_createChrome.js
browser/devtools/debugger/test/browser_dbg_debuggerstatement.html
browser/devtools/debugger/test/browser_dbg_debuggerstatement.js
browser/devtools/debugger/test/browser_dbg_displayName.html
browser/devtools/debugger/test/browser_dbg_displayName.js
browser/devtools/debugger/test/browser_dbg_frame-parameters.html
browser/devtools/debugger/test/browser_dbg_function-search-01.html
browser/devtools/debugger/test/browser_dbg_function-search-02.html
browser/devtools/debugger/test/browser_dbg_function-search.js
browser/devtools/debugger/test/browser_dbg_globalactor-01.js
browser/devtools/debugger/test/browser_dbg_iframes.html
browser/devtools/debugger/test/browser_dbg_location-changes-blank.js
browser/devtools/debugger/test/browser_dbg_location-changes-bp.js
browser/devtools/debugger/test/browser_dbg_location-changes-new.js
browser/devtools/debugger/test/browser_dbg_location-changes.js
browser/devtools/debugger/test/browser_dbg_menustatus.js
browser/devtools/debugger/test/browser_dbg_nav-01.js
browser/devtools/debugger/test/browser_dbg_pane-collapse.js
browser/devtools/debugger/test/browser_dbg_panesize-inner.js
browser/devtools/debugger/test/browser_dbg_pause-exceptions-reload.js
browser/devtools/debugger/test/browser_dbg_pause-exceptions.html
browser/devtools/debugger/test/browser_dbg_pause-exceptions.js
browser/devtools/debugger/test/browser_dbg_propertyview-01.js
browser/devtools/debugger/test/browser_dbg_propertyview-02.js
browser/devtools/debugger/test/browser_dbg_propertyview-03.js
browser/devtools/debugger/test/browser_dbg_propertyview-04.js
browser/devtools/debugger/test/browser_dbg_propertyview-05.js
browser/devtools/debugger/test/browser_dbg_propertyview-06.js
browser/devtools/debugger/test/browser_dbg_propertyview-07.js
browser/devtools/debugger/test/browser_dbg_propertyview-08.js
browser/devtools/debugger/test/browser_dbg_propertyview-09.js
browser/devtools/debugger/test/browser_dbg_propertyview-10.js
browser/devtools/debugger/test/browser_dbg_propertyview-11.js
browser/devtools/debugger/test/browser_dbg_propertyview-12.js
browser/devtools/debugger/test/browser_dbg_propertyview-data-big.js
browser/devtools/debugger/test/browser_dbg_propertyview-data-getset-01.js
browser/devtools/debugger/test/browser_dbg_propertyview-data-getset-02.js
browser/devtools/debugger/test/browser_dbg_propertyview-data.js
browser/devtools/debugger/test/browser_dbg_propertyview-edit-value.js
browser/devtools/debugger/test/browser_dbg_propertyview-edit-watch.js
browser/devtools/debugger/test/browser_dbg_propertyview-filter-01.js
browser/devtools/debugger/test/browser_dbg_propertyview-filter-02.js
browser/devtools/debugger/test/browser_dbg_propertyview-filter-03.js
browser/devtools/debugger/test/browser_dbg_propertyview-filter-04.js
browser/devtools/debugger/test/browser_dbg_propertyview-filter-05.js
browser/devtools/debugger/test/browser_dbg_propertyview-filter-06.js
browser/devtools/debugger/test/browser_dbg_propertyview-filter-07.js
browser/devtools/debugger/test/browser_dbg_propertyview-filter-08.js
browser/devtools/debugger/test/browser_dbg_propertyview-reexpand.js
browser/devtools/debugger/test/browser_dbg_reload-preferred-script.js
browser/devtools/debugger/test/browser_dbg_script-switching-02.html
browser/devtools/debugger/test/browser_dbg_script-switching.html
browser/devtools/debugger/test/browser_dbg_scripts-searching-01.js
browser/devtools/debugger/test/browser_dbg_scripts-searching-02.js
browser/devtools/debugger/test/browser_dbg_scripts-searching-03.js
browser/devtools/debugger/test/browser_dbg_scripts-searching-04.js
browser/devtools/debugger/test/browser_dbg_scripts-searching-05.js
browser/devtools/debugger/test/browser_dbg_scripts-searching-06.js
browser/devtools/debugger/test/browser_dbg_scripts-searching-07.js
browser/devtools/debugger/test/browser_dbg_scripts-searching-08.js
browser/devtools/debugger/test/browser_dbg_scripts-searching-files_ui.js
browser/devtools/debugger/test/browser_dbg_scripts-searching-popup.js
browser/devtools/debugger/test/browser_dbg_scripts-sorting.js
browser/devtools/debugger/test/browser_dbg_scripts-switching.js
browser/devtools/debugger/test/browser_dbg_select-line.js
browser/devtools/debugger/test/browser_dbg_source_maps-01.js
browser/devtools/debugger/test/browser_dbg_source_maps-02.js
browser/devtools/debugger/test/browser_dbg_source_maps-03.js
browser/devtools/debugger/test/browser_dbg_stack.html
browser/devtools/debugger/test/browser_dbg_tab1.html
browser/devtools/debugger/test/browser_dbg_tab2.html
browser/devtools/debugger/test/browser_dbg_update-editor-mode.html
browser/devtools/debugger/test/browser_dbg_update-editor-mode.js
browser/devtools/debugger/test/browser_dbg_watch-expressions.html
browser/devtools/debugger/test/browser_dbg_with-frame.html
browser/devtools/debugger/test/math.js
browser/devtools/debugger/test/math.map
browser/devtools/debugger/test/math.min.js
browser/devtools/debugger/test/minified.html
browser/devtools/debugger/test/test-editor-mode
browser/devtools/debugger/test/test-event-listeners.html
browser/devtools/debugger/test/test-function-search-01.js
browser/devtools/debugger/test/test-function-search-02.js
browser/devtools/debugger/test/test-function-search-03.js
browser/devtools/debugger/test/test-location-changes-bp.html
browser/devtools/debugger/test/test-location-changes-bp.js
browser/devtools/debugger/test/test-pause-exceptions-reload.html
browser/devtools/debugger/test/test-script-switching-01.js
browser/devtools/debugger/test/test-script-switching-02.js
browser/devtools/debugger/test/test-step-out.html
browser/devtools/webconsole/test/browser_webconsole_bug_626484_output_copy_order.js
browser/devtools/webconsole/test/browser_webconsole_copying_multiple_messages_inserts_newlines_in_between.js
browser/devtools/webconsole/test/browser_webconsole_js_input_and_output_styling.js
browser/devtools/webconsole/test/browser_webconsole_log_node_classes.js
browser/devtools/webconsole/test/browser_webconsole_null_and_undefined_output.js
content/canvas/src/ImageEncoder.cpp
content/canvas/src/ImageEncoder.h
layout/base/nsDisplayList.cpp
new file mode 100644
--- /dev/null
+++ b/b2g/config/mako/config.json
@@ -0,0 +1,36 @@
+{
+    "config_version": 2,
+    "tooltool_manifest": "releng-mako.tt",
+    "mock_target": "mozilla-centos6-x86_64",
+    "mock_packages": ["ccache", "make", "bison", "flex", "gcc", "g++", "mpfr", "zlib-devel", "ncurses-devel", "zip", "autoconf213", "glibc-static", "perl-Digest-SHA", "wget", "alsa-lib", "atk", "cairo", "dbus-glib", "fontconfig", "freetype", "glib2", "gtk2", "libXRender", "libXt", "pango", "mozilla-python27-mercurial", "openssh-clients", "nss-devel", "glibc-devel.i686", "libstdc++.i686", "zlib-devel.i686", "ncurses-devel.i686", "libX11-devel.i686", "mesa-libGL-devel.i686", "mesa-libGL-devel", "libX11-devel", "git", "libxml2"],
+    "mock_files": [["/home/cltbld/.ssh", "/home/mock_mozilla/.ssh"]],
+    "build_targets": [],
+    "upload_files": [
+        "{objdir}/dist/b2g-*.crashreporter-symbols.zip",
+        "{objdir}/dist/b2g-*.tar.gz",
+        "{workdir}/sources.xml"
+    ],
+    "zip_files": [
+        ["{workdir}/out/target/product/mako/*.img", "out/target/product/mako/"],
+        ["{workdir}/boot.img", "out/target/product/mako/"],
+        "{workdir}/flash.sh",
+        "{workdir}/load-config.sh",
+        "{workdir}/.config",
+        "{workdir}/sources.xml"
+    ],
+    "env": {
+        "VARIANT": "user",
+        "MOZILLA_OFFICIAL": "1",
+        "B2GUPDATER": "1"
+    },
+    "b2g_manifest": "nexus-4.xml",
+    "b2g_manifest_branch": "master",
+    "additional_source_tarballs": [],
+    "gecko_l10n_root": "http://hg.mozilla.org/l10n-central",
+    "gaia": {
+        "l10n": {
+            "vcs": "hgtool",
+            "root": "http://hg.mozilla.org/gaia-l10n"
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/b2g/config/mako/releng-mako.tt
@@ -0,0 +1,21 @@
+[
+{
+"size": 13111,
+"digest": "09373556ddb4325897b9e008184228f9088b4c8c22bacf4fa2d39793ecfd264316ad69c2bc8082229ad7fdb80f89154c7b995a60f9b18beb1847e7111e7e69b2",
+"algorithm": "sha512",
+"filename": "broadcom-mako-jwr66v-cbde0d61.tgz"
+},
+{
+"size": 12658359,
+"digest": "2483df1a949df53d02ca33a87731cedd8f7cd07114d723bde1addf63fd71154c23b6f11f64f390b9849121725fb53a402db8df2f96a3673ec52416f45260f79d",
+"algorithm": "sha512",
+"filename": "qcom-mako-jwr66v-30ef957c.tgz"
+},
+{
+"size": 378532,
+"digest": "27aced8feb0e757d61df37839e62410ff30a059cfa8f04897d29ab74b787c765313acf904b1f9cf311c3e682883514df7da54197665251ef9b8bdad6bd0f62c5",
+"algorithm": "sha512",
+"filename": "lge-mako-jwr66v-985845e4.tgz"
+}
+]
+
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1084,20 +1084,16 @@ pref("devtools.toolbox.sideEnabled", tru
 
 // Enable the Inspector
 pref("devtools.inspector.enabled", true);
 pref("devtools.inspector.activeSidebar", "ruleview");
 pref("devtools.inspector.markupPreview", false);
 pref("devtools.inspector.remote", false);
 pref("devtools.inspector.show_pseudo_elements", true);
 
-// Enable the Layout View
-pref("devtools.layoutview.enabled", true);
-pref("devtools.layoutview.open", false);
-
 // Enable the Responsive UI tool
 pref("devtools.responsiveUI.enabled", true);
 pref("devtools.responsiveUI.no-reload-notification", false);
 
 // Enable the Debugger
 pref("devtools.debugger.enabled", true);
 pref("devtools.debugger.chrome-enabled", true);
 pref("devtools.debugger.chrome-debugging-host", "localhost");
--- a/browser/base/content/abouthome/aboutHome.js
+++ b/browser/base/content/abouthome/aboutHome.js
@@ -337,24 +337,37 @@ function setupSearchEngine()
   else {
     logoElt.parentNode.hidden = true;
     searchText.placeholder = searchEngineName;
   }
 
 }
 
 /**
+ * Inform the test harness that we're done loading the page.
+ */
+function loadSucceeded()
+{
+  var event = new CustomEvent("AboutHomeLoadSnippetsSucceeded", {bubbles:true});
+  document.dispatchEvent(event);
+}
+
+/**
  * Update the local snippets from the remote storage, then show them through
  * showSnippets.
  */
 function loadSnippets()
 {
   if (!gSnippetsMap)
     throw new Error("Snippets map has not properly been initialized");
 
+  // Allow tests to modify the snippets map before using it.
+  var event = new CustomEvent("AboutHomeLoadSnippets", {bubbles:true});
+  document.dispatchEvent(event);
+
   // Check cached snippets version.
   let cachedVersion = gSnippetsMap.get("snippets-cached-version") || 0;
   let currentVersion = document.documentElement.getAttribute("snippetsVersion");
   if (cachedVersion < currentVersion) {
     // The cached snippets are old and unsupported, restart from scratch.
     gSnippetsMap.clear();
   }
 
@@ -365,35 +378,38 @@ function loadSnippets()
                      Date.now() - lastUpdate > SNIPPETS_UPDATE_INTERVAL_MS;
   if (updateURL && shouldUpdate) {
     // Try to update from network.
     let xhr = new XMLHttpRequest();
     try {
       xhr.open("GET", updateURL, true);
     } catch (ex) {
       showSnippets();
+      loadSucceeded();
       return;
     }
     // Even if fetching should fail we don't want to spam the server, thus
     // set the last update time regardless its results.  Will retry tomorrow.
     gSnippetsMap.set("snippets-last-update", Date.now());
     xhr.onerror = function (event) {
       showSnippets();
     };
     xhr.onload = function (event)
     {
       if (xhr.status == 200) {
         gSnippetsMap.set("snippets", xhr.responseText);
         gSnippetsMap.set("snippets-cached-version", currentVersion);
       }
       showSnippets();
+      loadSucceeded();
     };
     xhr.send(null);
   } else {
     showSnippets();
+    loadSucceeded();
   }
 }
 
 /**
  * Shows locally cached remote snippets, or default ones when not available.
  *
  * @note: snippets should never invoke showSnippets(), or they may cause
  *        a "too much recursion" exception.
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -2748,16 +2748,20 @@
       <property name="pageReport"
                 onget="return this.mCurrentBrowser.pageReport;"
                 readonly="true"/>
 
       <property name="currentURI"
                 onget="return this.mCurrentBrowser.currentURI;"
                 readonly="true"/>
 
+      <property name="finder"
+                onget="return this.mCurrentBrowser.finder"
+                readonly="true"/>
+
       <property name="docShell"
                 onget="return this.mCurrentBrowser.docShell"
                 readonly="true"/>
 
       <property name="webNavigation"
                 onget="return this.mCurrentBrowser.webNavigation"
                 readonly="true"/>
 
--- a/browser/base/content/test/browser_aboutHome.js
+++ b/browser/base/content/test/browser_aboutHome.js
@@ -12,16 +12,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 let gRightsVersion = Services.prefs.getIntPref("browser.rights.version");
 
 registerCleanupFunction(function() {
   // Ensure we don't pollute prefs for next tests.
   Services.prefs.clearUserPref("network.cookies.cookieBehavior");
   Services.prefs.clearUserPref("network.cookie.lifetimePolicy");
   Services.prefs.clearUserPref("browser.rights.override");
   Services.prefs.clearUserPref("browser.rights." + gRightsVersion + ".shown");
+  Services.prefs.clearUserPref("browser.aboutHomeSnippets.updateUrl");
 });
 
 let gTests = [
 
 {
   desc: "Check that clearing cookies does not clear storage",
   setup: function ()
   {
@@ -333,123 +334,128 @@ function test()
   waitForExplicitFinish();
   requestLongerTimeout(2);
   ignoreAllUncaughtExceptions();
 
   Task.spawn(function () {
     for (let test of gTests) {
       info(test.desc);
 
+      // Make sure we don't try to load snippets from the network.
+      Services.prefs.setCharPref("browser.aboutHomeSnippets.updateUrl", "nonexistent://test");
+
       if (test.beforeRun)
         yield test.beforeRun();
 
-      let tab = yield promiseNewTabLoadEvent("about:home", "DOMContentLoaded");
+      // Create a tab to run the test.
+      let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank");
+
+      // Add an event handler to modify the snippets map once it's ready.
+      let snippetsPromise = promiseSetupSnippetsMap(tab, test.setup);
 
-      // Must wait for both the snippets map and the browser attributes, since
-      // can't guess the order they will happen.
-      // So, start listening now, but verify the promise is fulfilled only
-      // after the snippets map setup.
-      let promise = promiseBrowserAttributes(tab);
-      // Prepare the snippets map with default values, then run the test setup.
-      let snippetsMap = yield promiseSetupSnippetsMap(tab, test.setup);
-      // Ensure browser has set attributes already, or wait for them.
-      yield promise;
+      // Start loading about:home and wait for it to complete.
+      yield promiseTabLoadEvent(tab, "about:home", "AboutHomeLoadSnippetsSucceeded");
+
+      // This promise should already be resolved since the page is done,
+      // but we still want to get the snippets map out of it.
+      let snippetsMap = yield snippetsPromise;
+
       info("Running test");
       yield test.run(snippetsMap);
       info("Cleanup");
       gBrowser.removeCurrentTab();
     }
   }).then(finish, ex => {
     ok(false, "Unexpected Exception: " + ex);
     finish();
   });
 }
 
 /**
- * Creates a new tab and waits for a load event.
+ * Starts a load in an existing tab and waits for it to finish (via some event).
  *
+ * @param aTab
+ *        The tab to load into.
  * @param aUrl
- *        The url to load in a new tab.
+ *        The url to load.
  * @param aEvent
  *        The load event type to wait for.  Defaults to "load".
- * @return {Promise} resolved when the event is handled.  Gets the new tab.
+ * @return {Promise} resolved when the event is handled.
  */
-function promiseNewTabLoadEvent(aUrl, aEventType="load")
+function promiseTabLoadEvent(aTab, aURL, aEventType="load")
 {
   let deferred = Promise.defer();
-  let tab = gBrowser.selectedTab = gBrowser.addTab(aUrl);
   info("Wait tab event: " + aEventType);
-  tab.linkedBrowser.addEventListener(aEventType, function load(event) {
-    if (event.originalTarget != tab.linkedBrowser.contentDocument ||
+  aTab.linkedBrowser.addEventListener(aEventType, function load(event) {
+    if (event.originalTarget != aTab.linkedBrowser.contentDocument ||
         event.target.location.href == "about:blank") {
       info("skipping spurious load event");
       return;
     }
-    tab.linkedBrowser.removeEventListener(aEventType, load, true);
+    aTab.linkedBrowser.removeEventListener(aEventType, load, true);
     info("Tab event received: " + aEventType);
-    deferred.resolve(tab);
-  }, true);
+    deferred.resolve();
+  }, true, true);
+  aTab.linkedBrowser.loadURI(aURL);
   return deferred.promise;
 }
 
 /**
  * Cleans up snippets and ensures that by default we don't try to check for
  * remote snippets since that may cause network bustage or slowness.
  *
  * @param aTab
  *        The tab containing about:home.
  * @param aSetupFn
  *        The setup function to be run.
  * @return {Promise} resolved when the snippets are ready.  Gets the snippets map.
  */
 function promiseSetupSnippetsMap(aTab, aSetupFn)
 {
   let deferred = Promise.defer();
-  let cw = aTab.linkedBrowser.contentWindow.wrappedJSObject;
   info("Waiting for snippets map");
-  cw.ensureSnippetsMapThen(function (aSnippetsMap) {
-    info("Got snippets map: " +
-         "{ last-update: " + aSnippetsMap.get("snippets-last-update") +
-         ", cached-version: " + aSnippetsMap.get("snippets-cached-version") +
-         " }");
-    // Don't try to update.
-    aSnippetsMap.set("snippets-last-update", Date.now());
-    aSnippetsMap.set("snippets-cached-version", AboutHomeUtils.snippetsVersion);
-    // Clear snippets.
-    aSnippetsMap.delete("snippets");
-    aSetupFn(aSnippetsMap);
-    // Must be sure to continue after the page snippets map setup.
-    executeSoon(function() deferred.resolve(aSnippetsMap));
-  });
+  aTab.linkedBrowser.addEventListener("AboutHomeLoadSnippets", function load(event) {
+    aTab.linkedBrowser.removeEventListener("AboutHomeLoadSnippets", load, true);
+
+    let cw = aTab.linkedBrowser.contentWindow.wrappedJSObject;
+    // The snippets should already be ready by this point. Here we're
+    // just obtaining a reference to the snippets map.
+    cw.ensureSnippetsMapThen(function (aSnippetsMap) {
+      info("Got snippets map: " +
+           "{ last-update: " + aSnippetsMap.get("snippets-last-update") +
+           ", cached-version: " + aSnippetsMap.get("snippets-cached-version") +
+           " }");
+      // Don't try to update.
+      aSnippetsMap.set("snippets-last-update", Date.now());
+      aSnippetsMap.set("snippets-cached-version", AboutHomeUtils.snippetsVersion);
+      // Clear snippets.
+      aSnippetsMap.delete("snippets");
+      aSetupFn(aSnippetsMap);
+      deferred.resolve(aSnippetsMap);
+    });
+  }, true, true);
   return deferred.promise;
 }
 
 /**
- * Waits for the attributes being set by browser.js and overwrites snippetsURL
- * to ensure we won't try to hit the network and we can force xhr to throw.
+ * Waits for the attributes being set by browser.js.
  *
  * @param aTab
  *        The tab containing about:home.
  * @return {Promise} resolved when the attributes are ready.
  */
 function promiseBrowserAttributes(aTab)
 {
   let deferred = Promise.defer();
 
   let docElt = aTab.linkedBrowser.contentDocument.documentElement;
-  //docElt.setAttribute("snippetsURL", "nonexistent://test");
   let observer = new MutationObserver(function (mutations) {
     for (let mutation of mutations) {
       info("Got attribute mutation: " + mutation.attributeName +
                                     " from " + mutation.oldValue); 
-      if (mutation.attributeName == "snippetsURL" &&
-          docElt.getAttribute("snippetsURL") != "nonexistent://test") {
-        docElt.setAttribute("snippetsURL", "nonexistent://test");
-      }
-
       // Now we just have to wait for the last attribute.
       if (mutation.attributeName == "searchEngineName") {
         info("Remove attributes observer");
         observer.disconnect();
         // Must be sure to continue after the page mutation observer.
         executeSoon(function() deferred.resolve());
         break;
       }
--- a/browser/base/content/test/browser_zbug569342.js
+++ b/browser/base/content/test/browser_zbug569342.js
@@ -36,17 +36,21 @@ let urls = [
 ];
 
 function nextTest() {
   let url = urls.shift();
   if (url) {
     testFindDisabled(url, nextTest);
   } else {
     // Make sure the find bar is re-enabled after disabled page is closed.
-    testFindEnabled("about:blank", finish);
+    testFindEnabled("about:blank", function () {
+      EventUtils.synthesizeKey("VK_ESCAPE", { });
+      ok(gFindBar.hidden, "Find bar should now be hidden");
+      finish();
+    });
   }
 }
 
 function testFindDisabled(url, cb) {
   load(url, function() {
     ok(gFindBar.hidden, "Find bar should not be visible");
     EventUtils.synthesizeKey("/", {}, gTab.linkedBrowser.contentWindow);
     ok(gFindBar.hidden, "Find bar should not be visible");
--- a/browser/components/shell/src/nsWindowsShellService.cpp
+++ b/browser/components/shell/src/nsWindowsShellService.cpp
@@ -155,18 +155,22 @@ typedef struct {
 
 // The DefaultIcon registry key value should never be used when checking if
 // Firefox is the default browser for file handlers since other applications
 // (e.g. MS Office) may modify the DefaultIcon registry key value to add Icon
 // Handlers. see http://msdn2.microsoft.com/en-us/library/aa969357.aspx for
 // more info. The FTP protocol is not checked so advanced users can set the FTP
 // handler to another application and still have Firefox check if it is the
 // default HTTP and HTTPS handler.
+// *** Do not add additional checks here unless you skip them when aForAllTypes
+// is false below***.
 static SETTING gSettings[] = {
   // File Handler Class
+  // ***keep this as the first entry because when aForAllTypes is not set below
+  // it will skip over this check.***
   { MAKE_KEY_NAME1("FirefoxHTML", SOC), VAL_OPEN, OLD_VAL_OPEN },
 
   // Protocol Handler Class - for Vista and above
   { MAKE_KEY_NAME1("FirefoxURL", SOC), VAL_OPEN, OLD_VAL_OPEN },
 
   // Protocol Handlers
   { MAKE_KEY_NAME1("HTTP", DI), VAL_FILE_ICON },
   { MAKE_KEY_NAME1("HTTP", SOC), VAL_OPEN, OLD_VAL_OPEN },
@@ -350,16 +354,22 @@ IsAARDefaultHTML(IApplicationAssociation
     *aIsDefaultBrowser = !wcsicmp(registeredApp, firefoxHTMLProgID);
     CoTaskMemFree(registeredApp);
   } else {
     *aIsDefaultBrowser = false;
   }
   return SUCCEEDED(hr);
 }
 
+/*
+ * Query's the AAR for the default status.
+ * This only checks for FirefoxURL and if aCheckAllTypes is set, then
+ * it also checks for FirefoxHTML.  Note that those ProgIDs are shared
+ * by all Firefox browsers.
+*/
 bool
 nsWindowsShellService::IsDefaultBrowserVista(bool aCheckAllTypes,
                                              bool* aIsDefaultBrowser)
 {
   IApplicationAssociationRegistration* pAAR;
   HRESULT hr = CoCreateInstance(CLSID_ApplicationAssociationRegistration,
                                 nullptr,
                                 CLSCTX_INPROC,
@@ -398,23 +408,16 @@ nsWindowsShellService::IsDefaultBrowser(
                                         bool* aIsDefaultBrowser)
 {
   // If this is the first browser window, maintain internal state that we've
   // checked this session (so that subsequent window opens don't show the
   // default browser dialog).
   if (aStartupCheck)
     mCheckedThisSession = true;
 
-  // Check if we only care about a lightweight check, and make sure this
-  // only has an effect on Win8 and later.
-  if (!aForAllTypes && IsWin8OrLater()) {
-    return IsDefaultBrowserVista(false,
-                                 aIsDefaultBrowser) ? NS_OK : NS_ERROR_FAILURE;
-  }
-
   // Assume we're the default unless one of the several checks below tell us
   // otherwise.
   *aIsDefaultBrowser = true;
 
   PRUnichar exePath[MAX_BUF];
   if (!::GetModuleFileNameW(0, exePath, MAX_BUF))
     return NS_ERROR_FAILURE;
 
@@ -425,20 +428,25 @@ nsWindowsShellService::IsDefaultBrowser(
 
   nsAutoString appLongPath(exePath);
 
   HKEY theKey;
   DWORD res;
   nsresult rv;
   PRUnichar currValue[MAX_BUF];
 
-  SETTING* settings;
+  SETTING* settings = gSettings;
+  if (!aForAllTypes && IsWin8OrLater()) {
+    // Skip over the file handler check
+    settings++;
+  }
+
   SETTING* end = gSettings + sizeof(gSettings) / sizeof(SETTING);
 
-  for (settings = gSettings; settings < end; ++settings) {
+  for (; settings < end; ++settings) {
     NS_ConvertUTF8toUTF16 keyName(settings->keyName);
     NS_ConvertUTF8toUTF16 valueData(settings->valueData);
     int32_t offset = valueData.Find("%APPPATH%");
     valueData.Replace(offset, 9, appLongPath);
 
     rv = OpenKeyForReading(HKEY_CLASSES_ROOT, keyName, &theKey);
     if (NS_FAILED(rv)) {
       *aIsDefaultBrowser = false;
@@ -485,25 +493,25 @@ nsWindowsShellService::IsDefaultBrowser(
         return NS_OK;
       }
     }
   }
 
   // Only check if Firefox is the default browser on Vista and above if the
   // previous checks show that Firefox is the default browser.
   if (*aIsDefaultBrowser) {
-    IsDefaultBrowserVista(true, aIsDefaultBrowser);
+    IsDefaultBrowserVista(aForAllTypes, aIsDefaultBrowser);
   }
 
   // To handle the case where DDE isn't disabled due for a user because there
   // account didn't perform a Firefox update this will check if Firefox is the
   // default browser and if dde is disabled for each handler
   // and if it isn't disable it. When Firefox is not the default browser the
   // helper application will disable dde for each handler.
-  if (*aIsDefaultBrowser) {
+  if (*aIsDefaultBrowser && aForAllTypes) {
     // Check ftp settings
 
     end = gDDESettings + sizeof(gDDESettings) / sizeof(SETTING);
 
     for (settings = gDDESettings; settings < end; ++settings) {
       NS_ConvertUTF8toUTF16 keyName(settings->keyName);
 
       rv = OpenKeyForReading(HKEY_CURRENT_USER, keyName, &theKey);
@@ -602,37 +610,37 @@ nsWindowsShellService::GetCanSetDesktopB
   return NS_OK;
 }
 
 static nsresult
 DynSHOpenWithDialog(HWND hwndParent, const OPENASINFO *poainfo)
 {
   typedef HRESULT (WINAPI * SHOpenWithDialogPtr)(HWND hwndParent,
                                                  const OPENASINFO *poainfo);
-  static SHOpenWithDialogPtr SHOpenWithDialogFn = nullptr;
-  if (!SHOpenWithDialogFn) {
-    // shell32.dll is in the knownDLLs list so will always be loaded from the
-    // system32 directory.
-    static const PRUnichar kSehllLibraryName[] =  L"shell32.dll";
-    HMODULE shellDLL = ::LoadLibraryW(kSehllLibraryName);
-    if (!shellDLL) {
-      return NS_ERROR_FAILURE;
-    }
-
-    SHOpenWithDialogFn =
-      (SHOpenWithDialogPtr)GetProcAddress(shellDLL, "SHOpenWithDialog");
-    FreeLibrary(shellDLL);
-
-    if (!SHOpenWithDialogFn) {
-      return NS_ERROR_FAILURE;
-    }
+  
+  // shell32.dll is in the knownDLLs list so will always be loaded from the
+  // system32 directory.
+  static const PRUnichar kSehllLibraryName[] =  L"shell32.dll";
+  HMODULE shellDLL = ::LoadLibraryW(kSehllLibraryName);
+  if (!shellDLL) {
+    return NS_ERROR_FAILURE;
   }
 
-  return SUCCEEDED(SHOpenWithDialogFn(hwndParent, poainfo)) ? NS_OK :
-                                                              NS_ERROR_FAILURE;
+  SHOpenWithDialogPtr SHOpenWithDialogFn =
+    (SHOpenWithDialogPtr)GetProcAddress(shellDLL, "SHOpenWithDialog");
+
+  if (!SHOpenWithDialogFn) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsresult rv = 
+    SUCCEEDED(SHOpenWithDialogFn(hwndParent, poainfo)) ? NS_OK :
+                                                         NS_ERROR_FAILURE;
+  FreeLibrary(shellDLL);
+  return rv;
 }
 
 nsresult
 nsWindowsShellService::LaunchControlPanelDefaultPrograms()
 {
   // Build the path control.exe path safely
   WCHAR controlEXEPath[MAX_PATH + 1] = { '\0' };
   if (!GetSystemDirectoryW(controlEXEPath, MAX_PATH)) {
--- a/browser/devtools/commandline/test/browser_cmd_commands.js
+++ b/browser/devtools/commandline/test/browser_cmd_commands.js
@@ -23,34 +23,33 @@ tests.testConsole = function(options) {
     subject.QueryInterface(Ci.nsISupportsString);
     hud = HUDService.getHudReferenceById(subject.data);
     ok(hud, "console open");
 
     hud.jsterm.execute("pprint(window)", onExecute);
   }
   Services.obs.addObserver(onWebConsoleOpen, "web-console-created", false);
 
-  function onExecute () {
-    let labels = hud.outputNode.querySelectorAll(".webconsole-msg-output");
-    ok(labels.length > 0, "output for pprint(window)");
+  function onExecute (msg) {
+    ok(msg, "output for pprint(window)");
 
     hud.jsterm.once("messages-cleared", onClear);
 
     helpers.audit(options, [
       {
         setup: "console clear",
         exec: {
           output: ""
         },
       }
     ]);
   }
 
   function onClear() {
-    let labels = hud.outputNode.querySelectorAll(".webconsole-msg-output");
+    let labels = hud.outputNode.querySelectorAll(".message");
     is(labels.length, 0, "no output in console");
 
     helpers.audit(options, [
       {
         setup: "console close",
         exec: {
           output: ""
         },
--- a/browser/devtools/debugger/CmdDebugger.jsm
+++ b/browser/devtools/debugger/CmdDebugger.jsm
@@ -14,43 +14,42 @@ Cu.import('resource://gre/modules/XPCOMU
 
 XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
   "resource:///modules/devtools/gDevTools.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "console",
   "resource://gre/modules/devtools/Console.jsm");
 
 /**
- * Utility to get access to the current breakpoint list
- * @param dbg The debugger panel
- * @returns an array of object, one for each breakpoint, where each breakpoint
- * object has the following properties:
- * - id: A unique identifier for the breakpoint. This is not designed to be
- *       shown to the user.
- * - label: A unique string identifier designed to be user visible. In theory
- *          the label of a breakpoint could change
- * - url: The URL of the source file
- * - lineNumber: The line number of the breakpoint in the source file
- * - lineText: The text of the line at the breakpoint
- * - truncatedLineText: lineText truncated to MAX_LINE_TEXT_LENGTH
+ * Utility to get access to the current breakpoint list.
+ *
+ * @param DebuggerPanel dbg
+ *        The debugger panel.
+ * @return array
+ *         An array of objects, one for each breakpoint, where each breakpoint
+ *         object has the following properties:
+ *           - url: the URL of the source file.
+ *           - label: a unique string identifier designed to be user visible.
+ *           - lineNumber: the line number of the breakpoint in the source file.
+ *           - lineText: the text of the line at the breakpoint.
+ *           - truncatedLineText: lineText truncated to MAX_LINE_TEXT_LENGTH.
  */
 function getAllBreakpoints(dbg) {
   let breakpoints = [];
-  let sources = dbg.panelWin.DebuggerView.Sources;
-  let { trimUrlLength: tr } = dbg.panelWin.SourceUtils;
+  let sources = dbg._view.Sources;
+  let { trimUrlLength: trim } = dbg.panelWin.SourceUtils;
 
   for (let source in sources) {
     for (let { attachment: breakpoint } in source) {
       breakpoints.push({
-        id: source.value + ":" + breakpoint.lineNumber,
-        label: source.label + ":" + breakpoint.lineNumber,
         url: source.value,
-        lineNumber: breakpoint.lineNumber,
-        lineText: breakpoint.lineText,
-        truncatedLineText: tr(breakpoint.lineText, MAX_LINE_TEXT_LENGTH, "end")
+        label: source.label + ":" + breakpoint.line,
+        lineNumber: breakpoint.line,
+        lineText: breakpoint.text,
+        truncatedLineText: trim(breakpoint.text, MAX_LINE_TEXT_LENGTH, "end")
       });
     }
   }
 
   return breakpoints;
 }
 
 /**
@@ -65,20 +64,18 @@ gcli.addCommand({
 /**
  * 'break list' command
  */
 gcli.addCommand({
   name: "break list",
   description: gcli.lookup("breaklistDesc"),
   returnType: "breakpoints",
   exec: function(args, context) {
-    let dbg = getPanel(context, "jsdebugger", { ensure_opened: true });
-    return dbg.then(function(dbg) {
-      return getAllBreakpoints(dbg);
-    });
+    let dbg = getPanel(context, "jsdebugger", { ensureOpened: true });
+    return dbg.then(getAllBreakpoints);
   }
 });
 
 gcli.addConverter({
   from: "breakpoints",
   to: "view",
   exec: function(breakpoints, context) {
     let dbg = getPanel(context, "jsdebugger");
@@ -146,47 +143,45 @@ gcli.addCommand({
   params: [
     {
       name: "file",
       type: {
         name: "selection",
         data: function(context) {
           let dbg = getPanel(context, "jsdebugger");
           if (dbg) {
-            return dbg.panelWin.DebuggerView.Sources.values;
+            return dbg._view.Sources.values;
           }
           return [];
         }
       },
       description: gcli.lookup("breakaddlineFileDesc")
     },
     {
       name: "line",
       type: { name: "number", min: 1, step: 10 },
       description: gcli.lookup("breakaddlineLineDesc")
     }
   ],
   returnType: "string",
   exec: function(args, context) {
-    args.type = "line";
-
     let dbg = getPanel(context, "jsdebugger");
     if (!dbg) {
       return gcli.lookup("debuggerStopped");
     }
 
     let deferred = context.defer();
     let position = { url: args.file, line: args.line };
-    dbg.addBreakpoint(position, function(aBreakpoint, aError) {
-      if (aError) {
-        deferred.resolve(gcli.lookupFormat("breakaddFailed", [aError]));
-        return;
-      }
+
+    dbg.addBreakpoint(position).then(() => {
       deferred.resolve(gcli.lookup("breakaddAdded"));
+    }, aError => {
+      deferred.resolve(gcli.lookupFormat("breakaddFailed", [aError]));
     });
+
     return deferred.promise;
   }
 });
 
 /**
  * 'break del' command
  */
 gcli.addCommand({
@@ -194,55 +189,45 @@ gcli.addCommand({
   description: gcli.lookup("breakdelDesc"),
   params: [
     {
       name: "breakpoint",
       type: {
         name: "selection",
         lookup: function(context) {
           let dbg = getPanel(context, "jsdebugger");
-          if (dbg == null) {
+          if (!dbg) {
             return [];
           }
-          return getAllBreakpoints(dbg).map(breakpoint => {
-            return {
-              name: breakpoint.label,
-              value: breakpoint,
-              description: breakpoint.truncatedLineText
-            };
-          });
+          return getAllBreakpoints(dbg).map(breakpoint => ({
+            name: breakpoint.label,
+            value: breakpoint,
+            description: breakpoint.truncatedLineText
+          }));
         }
       },
       description: gcli.lookup("breakdelBreakidDesc")
     }
   ],
   returnType: "string",
   exec: function(args, context) {
     let dbg = getPanel(context, "jsdebugger");
     if (!dbg) {
       return gcli.lookup("debuggerStopped");
     }
 
-    let breakpoint = dbg.getBreakpoint(
-      args.breakpoint.url, args.breakpoint.lineNumber);
-
-    if (breakpoint == null) {
-      return gcli.lookup("breakNotFound");
-    }
+    let deferred = context.defer();
+    let position = { url: args.breakpoint.url, line: args.breakpoint.lineNumber };
 
-    let deferred = context.defer();
-    try {
-      dbg.removeBreakpoint(breakpoint, function() {
-        deferred.resolve(gcli.lookup("breakdelRemoved"));
-      });
-    } catch (ex) {
-      console.error('Error removing breakpoint, already removed?', ex);
-      // If the debugger has been closed already, don't scare the user.
+    dbg.removeBreakpoint(position).then(() => {
       deferred.resolve(gcli.lookup("breakdelRemoved"));
-    }
+    }, () => {
+      deferred.resolve(gcli.lookup("breakNotFound"));
+    });
+
     return deferred.promise;
   }
 });
 
 /**
  * 'dbg' command
  */
 gcli.addCommand({
@@ -254,34 +239,34 @@ gcli.addCommand({
 /**
  * 'dbg open' command
  */
 gcli.addCommand({
   name: "dbg open",
   description: gcli.lookup("dbgOpen"),
   params: [],
   exec: function(args, context) {
-    return gDevTools.showToolbox(context.environment.target, "jsdebugger")
-                    .then(() => null);
+    let target = context.environment.target;
+    return gDevTools.showToolbox(target, "jsdebugger").then(() => null);
   }
 });
 
 /**
  * 'dbg close' command
  */
 gcli.addCommand({
   name: "dbg close",
   description: gcli.lookup("dbgClose"),
   params: [],
   exec: function(args, context) {
-    if (!getPanel(context, "jsdebugger"))
+    if (!getPanel(context, "jsdebugger")) {
       return;
-
-    return gDevTools.closeToolbox(context.environment.target)
-                    .then(() => null);
+    }
+    let target = context.environment.target;
+    return gDevTools.closeToolbox(target).then(() => null);
   }
 });
 
 /**
  * 'dbg interrupt' command
  */
 gcli.addCommand({
   name: "dbg interrupt",
@@ -399,27 +384,28 @@ gcli.addCommand({
  */
 gcli.addCommand({
   name: "dbg list",
   description: gcli.lookup("dbgListSourcesDesc"),
   params: [],
   returnType: "dom",
   exec: function(args, context) {
     let dbg = getPanel(context, "jsdebugger");
-    let doc = context.environment.chromeDocument;
     if (!dbg) {
       return gcli.lookup("debuggerClosed");
     }
 
     let sources = dbg._view.Sources.values;
+    let doc = context.environment.chromeDocument;
     let div = createXHTMLElement(doc, "div");
     let ol = createXHTMLElement(doc, "ol");
-    sources.forEach(function(src) {
+
+    sources.forEach(source => {
       let li = createXHTMLElement(doc, "li");
-      li.textContent = src;
+      li.textContent = source;
       ol.appendChild(li);
     });
     div.appendChild(ol);
 
     return div;
   }
 });
 
@@ -432,34 +418,35 @@ gcli.addCommand({
     clientMethod: "blackBox",
     l10nPrefix: "dbgBlackBox"
   },
   {
     name: "unblackbox",
     clientMethod: "unblackBox",
     l10nPrefix: "dbgUnBlackBox"
   }
-].forEach(function (cmd) {
-  const lookup = function (id) {
+].forEach(function(cmd) {
+  const lookup = function(id) {
     return gcli.lookup(cmd.l10nPrefix + id);
   };
 
   gcli.addCommand({
     name: "dbg " + cmd.name,
     description: lookup("Desc"),
     params: [
       {
         name: "source",
         type: {
           name: "selection",
-          data: function (context) {
+          data: function(context) {
             let dbg = getPanel(context, "jsdebugger");
-            return dbg
-              ? [s for (s of dbg._view.Sources.values)]
-              : [];
+            if (dbg) {
+              return dbg._view.Sources.values;
+            }
+            return [];
           }
         },
         description: lookup("SourceDesc"),
         defaultValue: null
       },
       {
         name: "glob",
         type: "string",
@@ -468,28 +455,26 @@ gcli.addCommand({
       },
       {
         name: "invert",
         type: "boolean",
         description: lookup("InvertDesc")
       }
     ],
     returnType: "dom",
-    exec: function (args, context) {
+    exec: function(args, context) {
       const dbg = getPanel(context, "jsdebugger");
       const doc = context.environment.chromeDocument;
       if (!dbg) {
         throw new Error(gcli.lookup("debuggerClosed"));
       }
 
       const { promise, resolve, reject } = context.defer();
       const { activeThread } = dbg._controller;
-      const globRegExp = args.glob
-        ? globToRegExp(args.glob)
-        : null;
+      const globRegExp = args.glob ? globToRegExp(args.glob) : null;
 
       // Filter the sources down to those that we will need to black box.
 
       function shouldBlackBox(source) {
         var value = globRegExp && globRegExp.test(source.url)
           || args.source && source.url == args.source;
         return args.invert ? !value : value;
       }
@@ -507,86 +492,88 @@ gcli.addCommand({
       }
 
       // 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) {
-        let { url } = source;
-        activeThread.source(source)[cmd.clientMethod](function ({ error }) {
+        activeThread.source(source)[cmd.clientMethod](function({ error }) {
           if (error) {
-            blackBoxed.push(lookup("ErrorDesc") + " " + url);
+            blackBoxed.push(lookup("ErrorDesc") + " " + source.url);
           } else {
-            blackBoxed.push(url);
+            blackBoxed.push(source.url);
           }
 
           if (toBlackBox.length === blackBoxed.length) {
             displayResults();
           }
         });
       }
 
       // List the results for the user.
 
       function displayResults() {
         const results = doc.createElement("div");
         results.textContent = lookup("NonEmptyDesc");
+
         const list = createXHTMLElement(doc, "ul");
         results.appendChild(list);
+
         for (let result of blackBoxed) {
           const item = createXHTMLElement(doc, "li");
           item.textContent = result;
           list.appendChild(item);
         }
         resolve(results);
       }
 
       return promise;
     }
   });
 });
 
 /**
- * A helper to create xhtml namespaced elements
+ * A helper to create xhtml namespaced elements.
  */
 function createXHTMLElement(document, tagname) {
   return document.createElementNS("http://www.w3.org/1999/xhtml", tagname);
 }
 
 /**
- * A helper to go from a command context to a debugger panel
+ * A helper to go from a command context to a debugger panel.
  */
 function getPanel(context, id, options = {}) {
-  if (context == null) {
+  if (!context) {
     return undefined;
   }
 
   let target = context.environment.target;
-  if (options.ensure_opened) {
-    return gDevTools.showToolbox(target, id).then(function(toolbox) {
+
+  if (options.ensureOpened) {
+    return gDevTools.showToolbox(target, id).then(toolbox => {
       return toolbox.getPanel(id);
     });
   } else {
     let toolbox = gDevTools.getToolbox(target);
     if (toolbox) {
       return toolbox.getPanel(id);
     } else {
       return undefined;
     }
   }
 }
 
 /**
- * Converts a glob to a regular expression
+ * Converts a glob to a regular expression.
  */
 function globToRegExp(glob) {
   const reStr = glob
-  // Escape existing regular expression syntax
+  // Escape existing regular expression syntax.
     .replace(/\\/g, "\\\\")
     .replace(/\//g, "\\/")
     .replace(/\^/g, "\\^")
     .replace(/\$/g, "\\$")
     .replace(/\+/g, "\\+")
     .replace(/\?/g, "\\?")
     .replace(/\./g, "\\.")
     .replace(/\(/g, "\\(")
@@ -595,12 +582,12 @@ function globToRegExp(glob) {
     .replace(/\!/g, "\\!")
     .replace(/\|/g, "\\|")
     .replace(/\{/g, "\\{")
     .replace(/\}/g, "\\}")
     .replace(/\,/g, "\\,")
     .replace(/\[/g, "\\[")
     .replace(/\]/g, "\\]")
     .replace(/\-/g, "\\-")
-  // Turn * into the match everything wildcard
+  // Turn * into the match everything wildcard.
     .replace(/\*/g, ".*")
   return new RegExp("^" + reStr + "$");
 }
--- a/browser/devtools/debugger/DebuggerProcess.jsm
+++ b/browser/devtools/debugger/DebuggerProcess.jsm
@@ -30,17 +30,17 @@ this.EXPORTED_SYMBOLS = ["BrowserDebugge
 this.BrowserDebuggerProcess = function BrowserDebuggerProcess(aOnClose, aOnRun) {
   this._closeCallback = aOnClose;
   this._runCallback = aOnRun;
   this._telemetry = new Telemetry();
 
   this._initServer();
   this._initProfile();
   this._create();
-}
+};
 
 /**
  * Initializes and starts a chrome debugger process.
  * @return object
  */
 BrowserDebuggerProcess.init = function(aOnClose, aOnRun) {
   return new BrowserDebuggerProcess(aOnClose, aOnRun);
 };
--- a/browser/devtools/debugger/debugger-controller.js
+++ b/browser/devtools/debugger/debugger-controller.js
@@ -5,48 +5,105 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 const DBG_STRINGS_URI = "chrome://browser/locale/devtools/debugger.properties";
 const NEW_SOURCE_IGNORED_URLS = ["debugger eval code", "self-hosted", "XStringBundle"];
 const NEW_SOURCE_DISPLAY_DELAY = 200; // ms
-const FETCH_SOURCE_RESPONSE_DELAY = 50; // ms
+const FETCH_SOURCE_RESPONSE_DELAY = 200; // ms
 const FRAME_STEP_CLEAR_DELAY = 100; // ms
 const CALL_STACK_PAGE_SIZE = 25; // frames
 
+// The panel's window global is an EventEmitter firing the following events:
+const EVENTS = {
+  // When the debugger's source editor instance finishes loading or unloading.
+  EDITOR_LOADED: "Debugger:EditorLoaded",
+  EDITOR_UNLOADED: "Debugger:EditorUnoaded",
+
+  // When new sources are received from the debugger server.
+  NEW_SOURCE: "Debugger:NewSource",
+  SOURCES_ADDED: "Debugger:SourcesAdded",
+
+  // When a source is shown in the source editor.
+  SOURCE_SHOWN: "Debugger:EditorSourceShown",
+  SOURCE_ERROR_SHOWN: "Debugger:EditorSourceErrorShown",
+
+  // When scopes, variables, properties and watch expressions are fetched and
+  // displayed in the variables view.
+  FETCHED_SCOPES: "Debugger:FetchedScopes",
+  FETCHED_VARIABLES: "Debugger:FetchedVariables",
+  FETCHED_PROPERTIES: "Debugger:FetchedProperties",
+  FETCHED_WATCH_EXPRESSIONS: "Debugger:FetchedWatchExpressions",
+
+  // When a breakpoint has been added or removed on the debugger server.
+  BREAKPOINT_ADDED: "Debugger:BreakpointAdded",
+  BREAKPOINT_REMOVED: "Debugger:BreakpointRemoved",
+
+  // When a breakpoint has been shown or hidden in the source editor.
+  BREAKPOINT_SHOWN: "Debugger:BreakpointShown",
+  BREAKPOINT_HIDDEN: "Debugger:BreakpointHidden",
+
+  // When a conditional breakpoint's popup is showing or hiding.
+  CONDITIONAL_BREAKPOINT_POPUP_SHOWING: "Debugger:ConditionalBreakpointPopupShowing",
+  CONDITIONAL_BREAKPOINT_POPUP_HIDING: "Debugger:ConditionalBreakpointPopupHiding",
+
+  // When a file search was performed.
+  FILE_SEARCH_MATCH_FOUND: "Debugger:FileSearch:MatchFound",
+  FILE_SEARCH_MATCH_NOT_FOUND: "Debugger:FileSearch:MatchNotFound",
+
+  // When a function search was performed.
+  FUNCTION_SEARCH_MATCH_FOUND: "Debugger:FunctionSearch:MatchFound",
+  FUNCTION_SEARCH_MATCH_NOT_FOUND: "Debugger:FunctionSearch:MatchNotFound",
+
+  // When a global text search was performed.
+  GLOBAL_SEARCH_MATCH_FOUND: "Debugger:GlobalSearch:MatchFound",
+  GLOBAL_SEARCH_MATCH_NOT_FOUND: "Debugger:GlobalSearch:MatchNotFound",
+
+  // After the stackframes are cleared and debugger won't pause anymore.
+  AFTER_FRAMES_CLEARED: "Debugger:AfterFramesCleared",
+
+  // When the options popup is showing or hiding.
+  OPTIONS_POPUP_SHOWING: "Debugger:OptionsPopupShowing",
+  OPTIONS_POPUP_HIDDEN: "Debugger:OptionsPopupHidden",
+};
+
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
 let promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js").Promise;
+Cu.import("resource:///modules/devtools/shared/event-emitter.js");
 Cu.import("resource:///modules/source-editor.jsm");
 Cu.import("resource:///modules/devtools/BreadcrumbsWidget.jsm");
 Cu.import("resource:///modules/devtools/SideMenuWidget.jsm");
 Cu.import("resource:///modules/devtools/VariablesView.jsm");
 Cu.import("resource:///modules/devtools/VariablesViewController.jsm");
 Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Parser",
   "resource:///modules/devtools/Parser.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "devtools",
   "resource://gre/modules/devtools/Loader.jsm");
 
-Object.defineProperty(this, "NetworkHelper", {
+XPCOMUtils.defineLazyModuleGetter(this, "DevToolsUtils",
+  "resource://gre/modules/devtools/DevToolsUtils.jsm");
+
+Object.defineProperty(this, "DevtoolsHelpers", {
   get: function() {
-    return devtools.require("devtools/toolkit/webconsole/network-helper");
+    return devtools.require("devtools/shared/helpers");
   },
   configurable: true,
   enumerable: true
 });
 
-Object.defineProperty(this, "DevtoolsHelpers", {
+Object.defineProperty(this, "NetworkHelper", {
   get: function() {
-    return devtools.require("devtools/shared/helpers");
+    return devtools.require("devtools/toolkit/webconsole/network-helper");
   },
   configurable: true,
   enumerable: true
 });
 
 /**
  * Object defining the debugger controller components.
  */
@@ -72,124 +129,112 @@ let DebuggerController = {
 
   /**
    * Initializes the view.
    *
    * @return object
    *         A promise that is resolved when the debugger finishes startup.
    */
   startupDebugger: function() {
-    if (this._isInitialized) {
-      return this._startup.promise;
+    if (this._startup) {
+      return this._startup;
     }
-    this._isInitialized = true;
 
     // Chrome debugging lives in a different process and needs to handle
     // debugger startup by itself.
     if (window._isChromeDebugger) {
       window.removeEventListener("DOMContentLoaded", this.startupDebugger, true);
     }
 
-    let deferred = this._startup = promise.defer();
-
-    DebuggerView.initialize(() => {
-      DebuggerView._isInitialized = true;
-
+    return this._startup = DebuggerView.initialize().then(() => {
       // Chrome debugging needs to initiate the connection by itself.
       if (window._isChromeDebugger) {
-        this.connect().then(deferred.resolve);
+        return this.connect();
       } else {
-        deferred.resolve();
+        return promise.resolve(null); // Done.
       }
     });
-
-    return deferred.promise;
   },
 
   /**
    * Destroys the view and disconnects the debugger client from the server.
    *
    * @return object
    *         A promise that is resolved when the debugger finishes shutdown.
    */
   shutdownDebugger: function() {
-    if (this._isDestroyed) {
-      return this._shutdown.promise;
+    if (this._shutdown) {
+      return this._shutdown;
     }
-    this._isDestroyed = true;
-    this._startup = null;
 
     // Chrome debugging lives in a different process and needs to handle
     // debugger shutdown by itself.
     if (window._isChromeDebugger) {
       window.removeEventListener("unload", this.shutdownDebugger, true);
     }
 
-    let deferred = this._shutdown = promise.defer();
-
-    DebuggerView.destroy(() => {
-      DebuggerView._isDestroyed = true;
-
+    return this._shutdown = DebuggerView.destroy().then(() => {
+      DebuggerView.destroy();
       this.SourceScripts.disconnect();
       this.StackFrames.disconnect();
       this.ThreadState.disconnect();
-
       this.disconnect();
-      deferred.resolve();
 
       // Chrome debugging needs to close its parent process on shutdown.
-      window._isChromeDebugger && this._quitApp();
+      if (window._isChromeDebugger) {
+        return this._quitApp();
+      } else {
+        return promise.resolve(null); // Done.
+      }
     });
-
-    return deferred.promise;
   },
 
   /**
    * Initiates remote or chrome debugging based on the current target,
    * wiring event handlers as necessary.
    *
    * In case of a chrome debugger living in a different process, a socket
    * connection pipe is opened as well.
    *
    * @return object
    *         A promise that is resolved when the debugger finishes connecting.
    */
   connect: function() {
     if (this._connection) {
-      return this._connection.promise;
+      return this._connection;
     }
 
-    let deferred = this._connection = promise.defer();
+    let deferred = promise.defer();
+    this._connection = deferred.promise;
 
     if (!window._isChromeDebugger) {
       let target = this._target;
-      let { client, form, threadActor } = target;
+      let { client, form: { chromeDebugger }, threadActor } = target;
       target.on("close", this._onTabDetached);
       target.on("navigate", this._onTabNavigated);
       target.on("will-navigate", this._onTabNavigated);
 
       if (target.chrome) {
-        this._startChromeDebugging(client, form.chromeDebugger, deferred.resolve);
+        this._startChromeDebugging(client, chromeDebugger, deferred.resolve);
       } else {
         this._startDebuggingTab(client, threadActor, deferred.resolve);
       }
 
       return deferred.promise;
     }
 
     // Chrome debugging needs to make its own connection to the debuggee.
     let transport = debuggerSocketConnect(
       Prefs.chromeDebuggingHost, Prefs.chromeDebuggingPort);
 
     let client = new DebuggerClient(transport);
     client.addListener("tabNavigated", this._onTabNavigated);
     client.addListener("tabDetached", this._onTabDetached);
-
-    client.connect((aType, aTraits) => {
-      client.listTabs((aResponse) => {
+    client.connect(() => {
+      client.listTabs(aResponse => {
         this._startChromeDebugging(client, aResponse.chromeDebugger, deferred.resolve);
       });
     });
 
     return deferred.promise;
   },
 
   /**
@@ -219,28 +264,33 @@ let DebuggerController = {
    * 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) {
-    if (aType == "will-navigate") {
-      DebuggerView._handleTabNavigation();
+    switch (aType) {
+      case "will-navigate": {
+        // Reset UI.
+        DebuggerView._handleTabNavigation();
 
-      // Discard all the old sources.
-      DebuggerController.Parser.clearCache();
-      SourceUtils.clearCache();
-      return;
+        // Discard all the old sources.
+        DebuggerController.Parser.clearCache();
+        SourceUtils.clearCache();
+        break;
+      }
+      case "navigate": {
+        this.ThreadState._handleTabNavigation();
+        this.StackFrames._handleTabNavigation();
+        this.SourceScripts._handleTabNavigation();
+        break;
+      }
     }
-
-    this.ThreadState._handleTabNavigation();
-    this.StackFrames._handleTabNavigation();
-    this.SourceScripts._handleTabNavigation();
   },
 
   /**
    * Called when the debugged tab is closed.
    */
   _onTabDetached: function() {
     this.shutdownDebugger();
   },
@@ -321,55 +371,64 @@ let DebuggerController = {
       if (aCallback) {
         aCallback();
       }
     }, { useSourceMaps: Prefs.sourceMapsEnabled });
   },
 
   /**
    * Detach and reattach to the thread actor with useSourceMaps true, blow
-   * away old scripts and get sources again.
+   * away old sources and get them again.
    */
   reconfigureThread: function(aUseSourceMaps) {
-    this.client.reconfigureThread({ useSourceMaps: aUseSourceMaps },
-                                  (aResponse) => {
+    this.client.reconfigureThread({ useSourceMaps: aUseSourceMaps }, aResponse => {
       if (aResponse.error) {
         let msg = "Couldn't reconfigure thread: " + aResponse.message;
         Cu.reportError(msg);
         dumpn(msg);
         return;
       }
 
+      // Reset UI, discard all the old sources and get them again.
       DebuggerView._handleTabNavigation();
       this.SourceScripts._handleTabNavigation();
 
       // Update the stack frame list.
       this.activeThread._clearFrames();
       this.activeThread.fillFrames(CALL_STACK_PAGE_SIZE);
     });
   },
 
   /**
    * Attempts to quit the current process if allowed.
+   *
+   * @return object
+   *         A promise that is resolved if the app will quit successfully.
    */
   _quitApp: function() {
-    let canceled = Cc["@mozilla.org/supports-PRBool;1"]
-      .createInstance(Ci.nsISupportsPRBool);
+    let deferred = promise.defer();
 
-    Services.obs.notifyObservers(canceled, "quit-application-requested", null);
+    // Quitting the app is synchronous. Give the returned promise consumers
+    // a chance to do their thing before killing the process.
+    Services.tm.currentThread.dispatch({ run: () => {
+      let quit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
+      Services.obs.notifyObservers(quit, "quit-application-requested", null);
 
-    // Somebody canceled our quit request.
-    if (canceled.data) {
-      return;
-    }
-    Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit);
+      // Somebody canceled our quit request.
+      if (quit.data) {
+        deferred.reject(quit.data);
+      } else {
+        deferred.resolve(quit.data);
+        Services.startup.quit(Ci.nsIAppStartup.eForceQuit);
+      }
+    }}, 0);
+
+    return deferred.promise;
   },
 
-  _isInitialized: false,
-  _isDestroyed: false,
   _startup: null,
   _shutdown: null,
   _connection: null,
   client: null,
   activeThread: null
 };
 
 /**
@@ -441,25 +500,25 @@ function StackFrames() {
   this._onFramesCleared = this._onFramesCleared.bind(this);
   this._onBlackBoxChange = this._onBlackBoxChange.bind(this);
   this._afterFramesCleared = this._afterFramesCleared.bind(this);
   this.evaluate = this.evaluate.bind(this);
 }
 
 StackFrames.prototype = {
   get activeThread() DebuggerController.activeThread,
-  autoScopeExpand: false,
-  currentFrame: null,
-  syncedWatchExpressions: null,
-  currentWatchExpressions: null,
-  currentBreakpointLocation: null,
-  currentEvaluation: null,
-  currentException: null,
-  currentReturnedValue: null,
-  _dontSwitchSources: false,
+  currentFrameDepth: -1,
+  _isWatchExpressionsEvaluation: false,
+  _isConditionalBreakpointEvaluation: false,
+  _syncedWatchExpressions: null,
+  _currentWatchExpressions: null,
+  _currentBreakpointLocation: null,
+  _currentEvaluation: null,
+  _currentException: null,
+  _currentReturnedValue: null,
 
   /**
    * Connect to the current thread client.
    */
   connect: function() {
     dumpn("StackFrames is connecting...");
     this.activeThread.addListener("paused", this._onPaused);
     this.activeThread.addListener("resumed", this._onResumed);
@@ -499,188 +558,180 @@ StackFrames.prototype = {
    *        The name of the notification ("paused" in this case).
    * @param object aPacket
    *        The response packet.
    */
   _onPaused: function(aEvent, aPacket) {
     switch (aPacket.why.type) {
       // If paused by a breakpoint, store the breakpoint location.
       case "breakpoint":
-        this.currentBreakpointLocation = aPacket.frame.where;
+        this._currentBreakpointLocation = aPacket.frame.where;
         break;
       // If paused by a client evaluation, store the evaluated value.
       case "clientEvaluated":
-        this.currentEvaluation = aPacket.why.frameFinished;
+        this._currentEvaluation = aPacket.why.frameFinished;
         break;
       // If paused by an exception, store the exception value.
       case "exception":
-        this.currentException = aPacket.why.exception;
+        this._currentException = aPacket.why.exception;
         break;
       // If paused while stepping out of a frame, store the returned value or
       // thrown exception.
       case "resumeLimit":
         if (!aPacket.why.frameFinished) {
           break;
         } else if (aPacket.why.frameFinished.throw) {
-          this.currentException = aPacket.why.frameFinished.throw;
+          this._currentException = aPacket.why.frameFinished.throw;
         } else if (aPacket.why.frameFinished.return) {
-          this.currentReturnedValue = aPacket.why.frameFinished.return;
+          this._currentReturnedValue = aPacket.why.frameFinished.return;
         }
         break;
     }
 
     this.activeThread.fillFrames(CALL_STACK_PAGE_SIZE);
     DebuggerView.editor.focus();
   },
 
   /**
    * Handler for the thread client's resumed notification.
    */
   _onResumed: function() {
     DebuggerView.editor.setDebugLocation(-1);
 
     // Prepare the watch expression evaluation string for the next pause.
     if (!this._isWatchExpressionsEvaluation) {
-      this.currentWatchExpressions = this.syncedWatchExpressions;
+      this._currentWatchExpressions = this._syncedWatchExpressions;
     }
   },
 
   /**
    * Handler for the thread client's framesadded notification.
    */
   _onFrames: function() {
     // Ignore useless notifications.
-    if (!this.activeThread.cachedFrames.length) {
+    if (!this.activeThread || !this.activeThread.cachedFrames.length) {
       return;
     }
 
+    let waitForNextPause = false;
+    let breakLocation = this._currentBreakpointLocation;
+    let watchExpressions = this._currentWatchExpressions;
+
     // Conditional breakpoints are { breakpoint, expression } tuples. The
     // boolean evaluation of the expression decides if the active thread
     // automatically resumes execution or not.
     // TODO: handle all of this server-side: Bug 812172.
-    if (this.currentBreakpointLocation) {
-      let { url, line } = this.currentBreakpointLocation;
-      let breakpointClient = DebuggerController.Breakpoints.getBreakpoint(url, line);
-      if (breakpointClient) {
-        // Make sure a breakpoint actually exists at the specified url and line.
-        let conditionalExpression = breakpointClient.conditionalExpression;
-        if (conditionalExpression) {
-          // 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.
-          this.evaluate(conditionalExpression, 0);
-          this._isConditionalBreakpointEvaluation = true;
-          return;
-        }
+    if (breakLocation) {
+      // Make sure a breakpoint actually exists at the specified url and line.
+      let breakpointPromise = DebuggerController.Breakpoints._getAdded(breakLocation);
+      if (breakpointPromise) {
+        breakpointPromise.then(aBreakpointClient => {
+          if ("conditionalExpression" in aBreakpointClient) {
+            // 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.
+            this.evaluate(aBreakpointClient.conditionalExpression, 0);
+            this._isConditionalBreakpointEvaluation = true;
+            waitForNextPause = true;
+          }
+        });
       }
     }
-    // Got our evaluation of the current breakpoint's conditional expression.
+    // We'll get our evaluation of the current breakpoint's conditional
+    // expression the next time the thread client pauses...
+    if (waitForNextPause) {
+      return;
+    }
     if (this._isConditionalBreakpointEvaluation) {
       this._isConditionalBreakpointEvaluation = false;
       // If the breakpoint's conditional expression evaluation is falsy,
       // automatically resume execution.
-      if (VariablesView.isFalsy({ value: this.currentEvaluation.return })) {
+      if (VariablesView.isFalsy({ value: this._currentEvaluation.return })) {
         this.activeThread.resume(DebuggerController._ensureResumptionOrder);
         return;
       }
     }
 
-
     // Watch expressions are evaluated in the context of the topmost frame,
     // and the results are displayed in the variables view.
     // TODO: handle all of this server-side: Bug 832470, comment 14.
-    if (this.currentWatchExpressions) {
+    if (watchExpressions) {
       // Evaluation causes the stack frames to be cleared and active thread to
       // pause, sending a 'clientEvaluated' packet and adding the frames again.
-      this.evaluate(this.currentWatchExpressions, 0);
+      this.evaluate(watchExpressions, 0);
       this._isWatchExpressionsEvaluation = true;
+      waitForNextPause = true;
+    }
+    // We'll get our evaluation of the current watch expressions the next time
+    // the thread client pauses...
+    if (waitForNextPause) {
       return;
     }
-    // Got our evaluation of the current watch expressions.
     if (this._isWatchExpressionsEvaluation) {
       this._isWatchExpressionsEvaluation = false;
       // If an error was thrown during the evaluation of the watch expressions,
       // then at least one expression evaluation could not be performed. So
       // remove the most recent watch expression and try again.
-      if (this.currentEvaluation.throw) {
+      if (this._currentEvaluation.throw) {
         DebuggerView.WatchExpressions.removeAt(0);
         DebuggerController.StackFrames.syncWatchExpressions();
         return;
       }
     }
 
-
-    // Make sure the debugger view panes are visible.
+    // Make sure the debugger view panes are visible, then refill the frames.
     DebuggerView.showInstrumentsPane();
-
     this._refillFrames();
   },
 
   /**
    * Fill the StackFrames view with the frames we have in the cache, compressing
    * frames which have black boxed sources into single frames.
    */
   _refillFrames: function() {
     // Make sure all the previous stackframes are removed before re-adding them.
     DebuggerView.StackFrames.empty();
 
-    let previousBlackBoxed = null;
     for (let frame of this.activeThread.cachedFrames) {
       let { depth, where: { url, line }, source } = frame;
-
-      let isBlackBoxed = source
-        ? this.activeThread.source(source).isBlackBoxed
-        : false;
-      let frameLocation = NetworkHelper.convertToUnicode(unescape(url));
-      let frameTitle = StackFrameUtils.getFrameTitle(frame);
-
-      if (isBlackBoxed) {
-        if (previousBlackBoxed == url) {
-          continue;
-        }
-        previousBlackBoxed = url;
-      } else {
-        previousBlackBoxed = null;
-      }
-
-      DebuggerView.StackFrames.addFrame(
-        frameTitle, frameLocation, line, depth, isBlackBoxed);
+      let isBlackBoxed = source ? this.activeThread.source(source).isBlackBoxed : false;
+      let location = NetworkHelper.convertToUnicode(unescape(url));
+      let title = StackFrameUtils.getFrameTitle(frame);
+      DebuggerView.StackFrames.addFrame(title, location, line, depth, isBlackBoxed);
     }
-
-    if (this.currentFrame == null) {
+    if (this.currentFrameDepth == -1) {
       DebuggerView.StackFrames.selectedDepth = 0;
     }
     if (this.activeThread.moreFrames) {
       DebuggerView.StackFrames.dirty = true;
     }
   },
 
   /**
    * Handler for the thread client's framescleared notification.
    */
   _onFramesCleared: function() {
-    this.currentFrame = null;
-    this.currentWatchExpressions = null;
-    this.currentBreakpointLocation = null;
-    this.currentEvaluation = null;
-    this.currentException = null;
-    this.currentReturnedValue = null;
+    this.currentFrameDepth = -1;
+    this._currentWatchExpressions = null;
+    this._currentBreakpointLocation = null;
+    this._currentEvaluation = null;
+    this._currentException = null;
+    this._currentReturnedValue = null;
     // After each frame step (in, over, out), framescleared is fired, which
     // forces the UI to be emptied and rebuilt on framesadded. Most of the times
     // this is not necessary, and will result in a brief redraw flicker.
     // To avoid it, invalidate the UI only after a short time if necessary.
     window.setTimeout(this._afterFramesCleared, FRAME_STEP_CLEAR_DELAY);
   },
 
   /**
    * Handler for the debugger's blackboxchange notification.
    */
   _onBlackBoxChange: function() {
     if (this.activeThread.state == "paused") {
-      this._dontSwitchSources = true;
       this.currentFrame = null;
       this._refillFrames();
     }
   },
 
   /**
    * Called soon after the thread client's framescleared notification.
    */
@@ -688,71 +739,68 @@ StackFrames.prototype = {
     // Ignore useless notifications.
     if (this.activeThread.cachedFrames.length) {
       return;
     }
     DebuggerView.StackFrames.empty();
     DebuggerView.Sources.unhighlightBreakpoint();
     DebuggerView.WatchExpressions.toggleContents(true);
     DebuggerView.Variables.empty(0);
-    window.dispatchEvent(document, "Debugger:AfterFramesCleared");
+
+    window.emit(EVENTS.AFTER_FRAMES_CLEARED);
   },
 
   /**
    * Marks the stack frame at the specified depth as selected and updates the
    * properties view with the stack frame's data.
    *
    * @param number aDepth
    *        The depth of the frame in the stack.
    * @param boolean aDontSwitchSources
    *        Flag on whether or not we want to switch the selected source.
    */
   selectFrame: function(aDepth, aDontSwitchSources) {
     // Make sure the frame at the specified depth exists first.
-    let frame = this.activeThread.cachedFrames[this.currentFrame = aDepth];
+    let frame = this.activeThread.cachedFrames[this.currentFrameDepth = aDepth];
     if (!frame) {
       return;
     }
 
     // Check if the frame does not represent the evaluation of debuggee code.
-    let { environment, where: { url, line } } = frame;
+    let { environment, where } = frame;
     if (!environment) {
       return;
     }
 
-    let noSwitch = this._dontSwitchSources;
-    this._dontSwitchSources = false;
-
     // Move the editor's caret to the proper url and line.
-    DebuggerView.updateEditor(url, line, { noSwitch: noSwitch });
+    DebuggerView.setEditorLocation(where.url, where.line);
     // Highlight the breakpoint at the specified url and line if it exists.
-    DebuggerView.Sources.highlightBreakpoint(url, line);
+    DebuggerView.Sources.highlightBreakpoint(where, { noEditorUpdate: true });
     // Don't display the watch expressions textbox inputs in the pane.
     DebuggerView.WatchExpressions.toggleContents(false);
     // Start recording any added variables or properties in any scope.
     DebuggerView.Variables.createHierarchy();
     // Clear existing scopes and create each one dynamically.
     DebuggerView.Variables.empty();
 
-
     // If watch expressions evaluation results are available, create a scope
     // to contain all the values.
-    if (this.syncedWatchExpressions && aDepth == 0) {
+    if (this._syncedWatchExpressions && aDepth == 0) {
       let label = L10N.getStr("watchExpressionsScopeLabel");
       let scope = DebuggerView.Variables.addScope(label);
 
       // Customize the scope for holding watch expressions evaluations.
       scope.descriptorTooltip = false;
       scope.contextMenuId = "debuggerWatchExpressionsContextMenu";
       scope.separatorStr = L10N.getStr("watchExpressionsSeparatorLabel");
       scope.switch = DebuggerView.WatchExpressions.switchExpression;
       scope.delete = DebuggerView.WatchExpressions.deleteExpression;
 
       // The evaluation hasn't thrown, so fetch and add the returned results.
-      this._fetchWatchExpressions(scope, this.currentEvaluation.return);
+      this._fetchWatchExpressions(scope, this._currentEvaluation.return);
 
       // The watch expressions scope is always automatically expanded.
       scope.expand();
     }
 
     do {
       // Create a scope to contain all the inspected variables in the
       // current environment.
@@ -767,66 +815,69 @@ StackFrames.prototype = {
 
       // Handle the expansion of the scope, lazily populating it with the
       // variables in the current environment.
       DebuggerView.Variables.controller.addExpander(scope, environment);
 
       // The innermost scope is always automatically expanded, because it
       // contains the variables in the current stack frame which are likely to
       // be inspected.
-      if (innermost || this.autoScopeExpand) {
+      if (innermost) {
         scope.expand();
       }
     } while ((environment = environment.parent));
 
-    // Signal that variables have been fetched.
-    window.dispatchEvent(document, "Debugger:FetchedVariables");
+    // Signal that scope environments have been shown and commit the current
+    // variables view hierarchy to briefly flash items that changed between the
+    // previous and current scope/variables/properties.
+    window.emit(EVENTS.FETCHED_SCOPES);
     DebuggerView.Variables.commitHierarchy();
   },
 
   /**
    * Loads more stack frames from the debugger server cache.
    */
   addMoreFrames: function() {
     this.activeThread.fillFrames(
       this.activeThread.cachedFrames.length + CALL_STACK_PAGE_SIZE);
   },
 
   /**
-   * Evaluate an expression in the context of the selected frame. This is used
-   * for modifying the value of variables or properties in scope.
+   * Evaluate an expression in the context of the selected frame.
    *
    * @param string aExpression
    *        The expression to evaluate.
    * @param number aFrame [optional]
    *        The frame depth used for evaluation.
    */
-  evaluate: function(aExpression, aFrame = this.currentFrame || 0) {
+  evaluate: function(aExpression, aFrame = this.currentFrameDepth) {
     let frame = this.activeThread.cachedFrames[aFrame];
-    this.activeThread.eval(frame.actor, aExpression);
+    if (frame) {
+      this.activeThread.eval(frame.actor, aExpression);
+    }
   },
 
   /**
    * Add nodes for special frame references in the innermost scope.
    *
    * @param Scope aScope
    *        The scope where the references will be placed into.
    * @param object aFrame
    *        The frame to get some references from.
    */
   _insertScopeFrameReferences: function(aScope, aFrame) {
     // Add any thrown exception.
-    if (this.currentException) {
-      let excRef = aScope.addItem("<exception>", { value: this.currentException });
-      DebuggerView.Variables.controller.addExpander(excRef, this.currentException);
+    if (this._currentException) {
+      let excRef = aScope.addItem("<exception>", { value: this._currentException });
+      DebuggerView.Variables.controller.addExpander(excRef, this._currentException);
     }
     // Add any returned value.
-    if (this.currentReturnedValue) {
-      let retRef = aScope.addItem("<return>", { value: this.currentReturnedValue });
-      DebuggerView.Variables.controller.addExpander(retRef, this.currentReturnedValue);
+    if (this._currentReturnedValue) {
+      let retRef = aScope.addItem("<return>", { value: this._currentReturnedValue });
+      DebuggerView.Variables.controller.addExpander(retRef, this._currentReturnedValue);
     }
     // Add "this".
     if (aFrame.this) {
       let thisRef = aScope.addItem("this", { value: aFrame.this });
       DebuggerView.Variables.controller.addExpander(thisRef, aFrame.this);
     }
   },
 
@@ -841,17 +892,17 @@ StackFrames.prototype = {
   _fetchWatchExpressions: function(aScope, aExp) {
     // Fetch the expressions only once.
     if (aScope._fetched) {
       return;
     }
     aScope._fetched = true;
 
     // Add nodes for every watch expression in scope.
-    this.activeThread.pauseGrip(aExp).getPrototypeAndProperties((aResponse) => {
+    this.activeThread.pauseGrip(aExp).getPrototypeAndProperties(aResponse => {
       let ownProperties = aResponse.ownProperties;
       let totalExpressions = DebuggerView.WatchExpressions.itemCount;
 
       for (let i = 0; i < totalExpressions; i++) {
         let name = DebuggerView.WatchExpressions.getString(i);
         let expVal = ownProperties[i].value;
         let expRef = aScope.addItem(name, ownProperties[i]);
         DebuggerView.Variables.controller.addExpander(expRef, expVal);
@@ -859,65 +910,67 @@ StackFrames.prototype = {
         // Revert some of the custom watch expressions scope presentation flags,
         // so that they don't propagate to child items.
         expRef.switch = null;
         expRef.delete = null;
         expRef.descriptorTooltip = true;
         expRef.separatorStr = L10N.getStr("variablesSeparatorLabel");
       }
 
-      // Signal that watch expressions have been fetched.
-      window.dispatchEvent(document, "Debugger:FetchedWatchExpressions");
+      // Signal that watch expressions have been fetched and commit the
+      // current variables view hierarchy to briefly flash items that changed
+      // between the previous and current scope/variables/properties.
+      window.emit(EVENTS.FETCHED_WATCH_EXPRESSIONS);
       DebuggerView.Variables.commitHierarchy();
     });
   },
 
   /**
    * Updates a list of watch expressions to evaluate on each pause.
    * TODO: handle all of this server-side: Bug 832470, comment 14.
    */
   syncWatchExpressions: function() {
     let list = DebuggerView.WatchExpressions.getAllStrings();
 
     // Sanity check all watch expressions before syncing them. To avoid
     // having the whole watch expressions array throw because of a single
     // faulty expression, simply convert it to a string describing the error.
     // There's no other information necessary to be offered in such cases.
-    let sanitizedExpressions = list.map((aString) => {
+    let sanitizedExpressions = list.map(aString => {
       // Reflect.parse throws when it encounters a syntax error.
       try {
         Parser.reflectionAPI.parse(aString);
         return aString; // Watch expression can be executed safely.
       } catch (e) {
         return "\"" + e.name + ": " + e.message + "\""; // Syntax error.
       }
     });
 
     if (sanitizedExpressions.length) {
-      this.syncedWatchExpressions =
-        this.currentWatchExpressions =
+      this._syncedWatchExpressions =
+        this._currentWatchExpressions =
           "[" +
-            sanitizedExpressions.map((aString) =>
+            sanitizedExpressions.map(aString =>
               "eval(\"" +
                 "try {" +
                   // Make sure all quotes are escaped in the expression's syntax,
                   // and add a newline after the statement to avoid comments
                   // breaking the code integrity inside the eval block.
                   aString.replace(/"/g, "\\$&") + "\" + " + "'\\n'" + " + \"" +
                 "} catch (e) {" +
                   "e.name + ': ' + e.message;" + // TODO: Bug 812765, 812764.
                 "}" +
               "\")"
             ).join(",") +
           "]";
     } else {
-      this.syncedWatchExpressions =
-        this.currentWatchExpressions = null;
+      this._syncedWatchExpressions =
+        this._currentWatchExpressions = null;
     }
-    this.currentFrame = null;
+    this.currentFrameDepth = -1;
     this._onFrames();
   }
 };
 
 /**
  * Keeps the source script list up-to-date, using the thread client's
  * source script cache.
  */
@@ -926,17 +979,17 @@ function SourceScripts() {
   this._onNewSource = this._onNewSource.bind(this);
   this._onSourcesAdded = this._onSourcesAdded.bind(this);
   this._onBlackBoxChange = this._onBlackBoxChange.bind(this);
 }
 
 SourceScripts.prototype = {
   get activeThread() DebuggerController.activeThread,
   get debuggerClient() DebuggerController.client,
-  _newSourceTimeout: null,
+  _cache: new Map(),
 
   /**
    * Connect to the current thread client.
    */
   connect: function() {
     dumpn("SourceScripts is connecting...");
     this.debuggerClient.addListener("newGlobal", this._onNewGlobal);
     this.debuggerClient.addListener("newSource", this._onNewSource);
@@ -947,34 +1000,36 @@ SourceScripts.prototype = {
   /**
    * Disconnect from the client.
    */
   disconnect: function() {
     if (!this.activeThread) {
       return;
     }
     dumpn("SourceScripts is disconnecting...");
-    window.clearTimeout(this._newSourceTimeout);
     this.debuggerClient.removeListener("newGlobal", this._onNewGlobal);
     this.debuggerClient.removeListener("newSource", this._onNewSource);
     this.activeThread.removeListener("blackboxchange", this._onBlackBoxChange);
   },
 
   /**
    * 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");
-    window.clearTimeout(this._newSourceTimeout);
+
+    // Don't expect the old sources to matter after the tab navigated.
+    clearNamedTimeout("new-source");
 
     // Retrieve the list of script sources known to the server from before
     // the client was ready to handle "newSource" notifications.
+    this._cache.clear();
     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
@@ -988,84 +1043,80 @@ SourceScripts.prototype = {
     // 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 });
 
-    let container = DebuggerView.Sources;
-    let preferredValue = container.preferredValue;
-
     // Select this source if it's the preferred one.
+    let preferredValue = DebuggerView.Sources.preferredValue;
     if (aPacket.source.url == preferredValue) {
-      container.selectedValue = preferredValue;
+      DebuggerView.Sources.selectedValue = preferredValue;
     }
     // ..or the first entry if there's none selected yet after a while
     else {
-      window.clearTimeout(this._newSourceTimeout);
-      this._newSourceTimeout = window.setTimeout(() => {
+      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 (!container.selectedValue) {
-          container.selectedIndex = 0;
+        if (!DebuggerView.Sources.selectedValue) {
+          DebuggerView.Sources.selectedIndex = 0;
         }
-      }, NEW_SOURCE_DISPLAY_DELAY);
+      });
     }
 
     // If there are any stored breakpoints for this source, display them again,
     // both in the editor and the breakpoints pane.
     DebuggerController.Breakpoints.updateEditorBreakpoints();
     DebuggerController.Breakpoints.updatePaneBreakpoints();
 
-    // Signal that a new script has been added.
-    window.dispatchEvent(document, "Debugger:AfterNewSource");
+    // 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) {
-      Cu.reportError("Error getting sources: " + aResponse.message);
+      let msg = "Error getting sources: " + aResponse.message;
+      Cu.reportError(msg);
+      dumpn(msg);
       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) {
-        continue;
+      if (NEW_SOURCE_IGNORED_URLS.indexOf(source.url) == -1) {
+        DebuggerView.Sources.addSource(source, { staged: true });
       }
-      DebuggerView.Sources.addSource(source, { staged: true });
     }
 
-    let container = DebuggerView.Sources;
-    let preferredValue = container.preferredValue;
-
     // Flushes all the prepared sources into the sources container.
-    container.commit({ sorted: true });
+    DebuggerView.Sources.commit({ sorted: true });
 
     // Select the preferred source if it exists and was part of the response.
-    if (container.containsValue(preferredValue)) {
-      container.selectedValue = preferredValue;
+    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 (!container.selectedValue) {
-      container.selectedIndex = 0;
+    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.updateEditorBreakpoints();
     DebuggerController.Breakpoints.updatePaneBreakpoints();
 
-    // Signal that scripts have been added.
-    window.dispatchEvent(document, "Debugger:AfterSourcesAdded");
+    // Signal that sources have been added.
+    window.emit(EVENTS.SOURCES_ADDED);
   },
 
   /**
    * Handler for the debugger client's 'blackboxchange' notification.
    */
   _onBlackBoxChange: function (aEvent, { url, isBlackBoxed }) {
     const item = DebuggerView.Sources.getItemByValue(url);
     if (item) {
@@ -1079,22 +1130,22 @@ SourceScripts.prototype = {
    *
    * @param Object aSource
    *        The source form.
    * @param bool aBlackBoxFlag
    *        True to black box the source, false to un-black box it.
    */
   blackBox: function(aSource, aBlackBoxFlag) {
     const sourceClient = this.activeThread.source(aSource);
-    sourceClient[aBlackBoxFlag ? "blackBox" : "unblackBox"](function({ error, message }) {
+    sourceClient[aBlackBoxFlag ? "blackBox" : "unblackBox"](({ error, message }) => {
       if (error) {
-        let msg = "Could not toggle black boxing for "
-          + aSource.url + ": " + message;
+        let msg = "Couldn't toggle black boxing for " + aSource.url + ": " + message;
         dumpn(msg);
-        return void Cu.reportError(msg);
+        Cu.reportError(msg);
+        return;
       }
     });
   },
 
   /**
    * Gets a specified source's text.
    *
    * @param object aSource
@@ -1104,32 +1155,33 @@ SourceScripts.prototype = {
    *        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.
    */
-  getTextForSource: function(aSource, aOnTimeout, aDelay = FETCH_SOURCE_RESPONSE_DELAY) {
+  getText: function(aSource, aOnTimeout, aDelay = FETCH_SOURCE_RESPONSE_DELAY) {
     // Fetch the source text only once.
-    if (aSource._fetched) {
-      return aSource._fetched;
+    let textPromise = this._cache.get(aSource.url);
+    if (textPromise) {
+      return textPromise;
     }
 
     let deferred = promise.defer();
-    aSource._fetched = deferred.promise;
+    this._cache.set(aSource.url, 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((aResponse) => {
+    this.activeThread.source(aSource).source(aResponse => {
       if (aOnTimeout) {
         window.clearTimeout(fetchTimeout);
       }
       if (aResponse.error) {
         deferred.reject([aSource, aResponse.message || aResponse.error]);
       } else {
         deferred.resolve([aSource, aResponse.source]);
       }
@@ -1147,26 +1199,26 @@ SourceScripts.prototype = {
    * @return object
    *         A promise that is resolved after source texts have been fetched.
    */
   getTextForSources: function(aUrls) {
     let deferred = promise.defer();
     let pending = new Set(aUrls);
     let fetched = [];
 
-    // Can't use Promise.all, because if one fetch operation is rejected, then
+    // 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 url of aUrls) {
       let sourceItem = DebuggerView.Sources.getItemByValue(url);
-      let sourceClient = sourceItem.attachment.source;
-      this.getTextForSource(sourceClient, onTimeout).then(onFetch, onError);
+      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. */
@@ -1184,16 +1236,17 @@ SourceScripts.prototype = {
     function onError([aSource, aError]) {
       pending.delete(aSource.url);
       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;
   }
 };
 
@@ -1201,353 +1254,418 @@ SourceScripts.prototype = {
  * Handles all the breakpoints in the current debugger.
  */
 function Breakpoints() {
   this._onEditorBreakpointChange = this._onEditorBreakpointChange.bind(this);
   this._onEditorBreakpointAdd = this._onEditorBreakpointAdd.bind(this);
   this._onEditorBreakpointRemove = this._onEditorBreakpointRemove.bind(this);
   this.addBreakpoint = this.addBreakpoint.bind(this);
   this.removeBreakpoint = this.removeBreakpoint.bind(this);
-  this.getBreakpoint = this.getBreakpoint.bind(this);
 }
 
 Breakpoints.prototype = {
-  get activeThread() DebuggerController.ThreadState.activeThread,
-  get editor() DebuggerView.editor,
-
-  /**
-   * The list of breakpoints in the debugger as tracked by the current
-   * debugger instance. This is an object where the values are BreakpointActor
-   * objects received from the client, while the keys are actor names, for
-   * example "conn0.breakpoint3".
-   */
-  store: {},
+  get activeThread() DebuggerController.activeThread,
 
   /**
-   * Skip editor breakpoint change events.
-   *
-   * This property tells the source editor event handler to skip handling of
-   * the BREAKPOINT_CHANGE events. This is used when the debugger adds/removes
-   * breakpoints from the editor. Typically, the BREAKPOINT_CHANGE event handler
-   * adds/removes events from the debugger, but when breakpoints are added from
-   * the public debugger API, we need to do things in reverse.
-   *
-   * This implementation relies on the fact that the source editor fires the
-   * BREAKPOINT_CHANGE events synchronously.
+   * A map of breakpoint promises as tracked by the debugger frontend.
+   * The keys consist of a string representation of the breakpoint location.
    */
-  _skipEditorBreakpointCallbacks: false,
+  _added: new Map(),
+  _removing: new Map(),
 
   /**
    * Adds the source editor breakpoint handlers.
+   *
+   * @return object
+   *         A promise that is resolved when the breakpoints finishes initializing.
    */
   initialize: function() {
-    this.editor.addEventListener(
+    DebuggerView.editor.addEventListener(
       SourceEditor.EVENTS.BREAKPOINT_CHANGE, this._onEditorBreakpointChange);
+
+    // 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() {
-    this.editor.removeEventListener(
+    DebuggerView.editor.removeEventListener(
       SourceEditor.EVENTS.BREAKPOINT_CHANGE, this._onEditorBreakpointChange);
 
-    for each (let breakpointClient in this.store) {
-      this.removeBreakpoint(breakpointClient);
-    }
+    return this.removeAllBreakpoints();
   },
 
   /**
    * Event handler for breakpoint changes that happen in the editor. This
    * function syncs the breakpoints in the editor to those in the debugger.
    *
    * @param object aEvent
    *        The SourceEditor.EVENTS.BREAKPOINT_CHANGE event object.
    */
   _onEditorBreakpointChange: function(aEvent) {
-    if (this._skipEditorBreakpointCallbacks) {
-      return;
-    }
-    this._skipEditorBreakpointCallbacks = true;
     aEvent.added.forEach(this._onEditorBreakpointAdd, this);
     aEvent.removed.forEach(this._onEditorBreakpointRemove, this);
-    this._skipEditorBreakpointCallbacks = false;
   },
 
   /**
    * Event handler for new breakpoints that come from the editor.
    *
    * @param object aEditorBreakpoint
    *        The breakpoint object coming from the editor.
    */
   _onEditorBreakpointAdd: function(aEditorBreakpoint) {
     let url = DebuggerView.Sources.selectedValue;
     let line = aEditorBreakpoint.line + 1;
+    let location = { url: url, line: line };
 
-    this.addBreakpoint({ url: url, line: line }, (aBreakpointClient) => {
-      // If the breakpoint client has an "actualLocation" attached, then
+    // Initialize the breakpoint, but don't update the editor, since this
+    // callback is invoked because a breakpoint was added in the editor itself.
+    this.addBreakpoint(location, { noEditorUpdate: true }).then(aBreakpointClient => {
+      // If the breakpoint client has an "requestedLocation" attached, then
       // the original requested placement for the breakpoint wasn't accepted.
       // In this case, we need to update the editor with the new location.
-      if (aBreakpointClient.actualLocation) {
-        this.editor.removeBreakpoint(line - 1);
-        this.editor.addBreakpoint(aBreakpointClient.actualLocation.line - 1);
+      if (aBreakpointClient.requestedLocation) {
+        DebuggerView.editor.removeBreakpoint(aBreakpointClient.requestedLocation.line - 1);
+        DebuggerView.editor.addBreakpoint(aBreakpointClient.location.line - 1);
       }
+      // Notify that we've shown a breakpoint in the source editor.
+      window.emit(EVENTS.BREAKPOINT_SHOWN, aEditorBreakpoint);
     });
   },
 
   /**
    * Event handler for breakpoints that are removed from the editor.
    *
    * @param object aEditorBreakpoint
    *        The breakpoint object that was removed from the editor.
    */
   _onEditorBreakpointRemove: function(aEditorBreakpoint) {
     let url = DebuggerView.Sources.selectedValue;
     let line = aEditorBreakpoint.line + 1;
+    let location = { url: url, line: line };
 
-    this.removeBreakpoint(this.getBreakpoint(url, line));
+    // Destroy the breakpoint, but don't update the editor, since this callback
+    // is invoked because a breakpoint was removed from the editor itself.
+    this.removeBreakpoint(location, { noEditorUpdate: true }).then(() => {
+      // Notify that we've hidden a breakpoint in the source editor.
+      window.emit(EVENTS.BREAKPOINT_HIDDEN, aEditorBreakpoint);
+    });
   },
 
   /**
    * 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: function() {
-    for each (let breakpointClient in this.store) {
-      if (DebuggerView.Sources.selectedValue == breakpointClient.location.url) {
-        this._showBreakpoint(breakpointClient, {
-          noPaneUpdate: true,
-          noPaneHighlight: true
-        });
-      }
+    for (let [, breakpointPromise] of this._added) {
+      breakpointPromise.then(aBreakpointClient => {
+        let currentSourceUrl = DebuggerView.Sources.selectedValue;
+        let breakpointUrl = aBreakpointClient.location.url;
+
+        // Update the view only if the breakpoint is in the currently shown source.
+        if (currentSourceUrl == breakpointUrl) {
+          this._showBreakpoint(aBreakpointClient, { 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: function() {
-    for each (let breakpointClient in this.store) {
-      if (DebuggerView.Sources.containsValue(breakpointClient.location.url)) {
-        this._showBreakpoint(breakpointClient, {
-          noEditorUpdate: true,
-          noPaneHighlight: true
-        });
-      }
+    for (let [, breakpointPromise] of this._added) {
+      breakpointPromise.then(aBreakpointClient => {
+        let container = DebuggerView.Sources;
+        let breakpointUrl = aBreakpointClient.location.url;
+
+        // Update the view only if the breakpoint exists in a known source.
+        if (container.containsValue(breakpointUrl)) {
+          this._showBreakpoint(aBreakpointClient, { noEditorUpdate: true });
+        }
+      });
     }
   },
 
   /**
    * Add a breakpoint.
    *
    * @param object aLocation
-   *        The location where you want the breakpoint. This object must have
-   *        two properties:
-   *          - url: the url of the source.
-   *          - line: the line number (starting from 1).
-   * @param function aCallback [optional]
-   *        Optional function to invoke once the breakpoint is added. The
-   *        callback is invoked with two arguments:
-   *          - aBreakpointClient: the BreakpointActor client object
-   *          - aResponseError: if there was any error
-   * @param object aFlags [optional]
-   *        An object containing some of the following boolean properties:
-   *          - conditionalExpression: tells this breakpoint's conditional expression
-   *          - 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
-   *          - noPaneHighlight: tells if you don't want to highlight the breakpoint
+   *        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.
+   * @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: function(aLocation, aCallback, aFlags = {}) {
+  addBreakpoint: function(aLocation, aOptions = {}) {
     // Make sure a proper location is available.
     if (!aLocation) {
-      aCallback && aCallback(null, new Error("Invalid breakpoint location."));
-      return;
+      return promise.reject(new Error("Invalid breakpoint location."));
     }
-    let breakpointClient = this.getBreakpoint(aLocation.url, aLocation.line);
 
-    // If the breakpoint was already added, callback immediately.
-    if (breakpointClient) {
-      aCallback && aCallback(breakpointClient);
-      return;
+    // If the breakpoint was already added, or is currently being added at the
+    // specified location, then return that promise immediately.
+    let addedPromise = this._getAdded(aLocation);
+    if (addedPromise) {
+      return addedPromise;
     }
 
-    this.activeThread.setBreakpoint(aLocation, (aResponse, aBreakpointClient) => {
-      let { url, line } = aResponse.actualLocation || aLocation;
+    // If the breakpoint is currently being removed from the specified location,
+    // then wait for that to finish and retry afterwards.
+    let removingPromise = this._getRemoving(aLocation);
+    if (removingPromise) {
+      return removingPromise.then(() => this.addBreakpoint(aLocation, aOptions));
+    }
 
-      // If the response contains a breakpoint that exists in the cache, prevent
-      // it from being shown in the source editor at an incorrect position.
-      if (this.getBreakpoint(url, line)) {
-        this._hideBreakpoint(aBreakpointClient);
-        return;
-      }
+    let deferred = promise.defer();
 
+    // Remember the breakpoint initialization promise in the store.
+    let identifier = this._getIdentifier(aLocation);
+    this._added.set(identifier, deferred.promise);
+
+    // Try adding the breakpoint.
+    this.activeThread.setBreakpoint(aLocation, (aResponse, aBreakpointClient) => {
       // If the breakpoint response has an "actualLocation" attached, then
       // the original requested placement for the breakpoint wasn't accepted.
       if (aResponse.actualLocation) {
-        // Store the originally requested location in case it's ever needed.
-        aBreakpointClient.requestedLocation = {
-          url: aBreakpointClient.location.url,
-          line: aBreakpointClient.location.line
-        };
-        // Store the response actual location to be used.
-        aBreakpointClient.actualLocation = aResponse.actualLocation;
-        // Update the breakpoint client with the actual location.
-        aBreakpointClient.location.url = aResponse.actualLocation.url;
-        aBreakpointClient.location.line = aResponse.actualLocation.line;
+        // Remember the initialization promise for the new location instead.
+        let oldIdentifier = identifier;
+        let newIdentifier = this._getIdentifier(aResponse.actualLocation);
+        this._added.delete(oldIdentifier);
+        this._added.set(newIdentifier, deferred.promise);
+
+        // Store the originally requested location in case it's ever needed
+        // and update the breakpoint client with the actual location.
+        aBreakpointClient.requestedLocation = aLocation;
+        aBreakpointClient.location = aResponse.actualLocation;
       }
 
-      // Remember the breakpoint client in the store.
-      this.store[aBreakpointClient.actor] = aBreakpointClient;
-
-      // Attach any specified conditional expression to the breakpoint client.
-      aBreakpointClient.conditionalExpression = aFlags.conditionalExpression;
-
       // 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).
-      aBreakpointClient.lineText = DebuggerView.getEditorLineText(line - 1).trim();
+      // 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.getEditorLineText(line).trim();
 
-      // Show the breakpoint in the editor and breakpoints pane.
-      this._showBreakpoint(aBreakpointClient, aFlags);
+      // Show the breakpoint in the editor and breakpoints pane, and resolve.
+      this._showBreakpoint(aBreakpointClient, aOptions);
 
-      // We're done here.
-      aCallback && aCallback(aBreakpointClient, aResponse.error);
+      // Notify that we've added a breakpoint.
+      window.emit(EVENTS.BREAKPOINT_ADDED, aBreakpointClient);
+      deferred.resolve(aBreakpointClient);
     });
+
+    return deferred.promise;
   },
 
   /**
    * Remove a breakpoint.
    *
-   * @param object aBreakpointClient
-   *        The BreakpointActor client object to remove.
-   * @param function aCallback [optional]
-   *        Optional function to invoke once the breakpoint is removed. The
-   *        callback is invoked with one argument
-   *          - aBreakpointClient: the breakpoint location (url and line)
-   * @param object aFlags [optional]
+   * @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(aBreakpointClient, aCallback, aFlags = {}) {
-    // Make sure a proper breakpoint client is available.
-    if (!aBreakpointClient) {
-      aCallback && aCallback(null, new Error("Invalid breakpoint client."));
-      return;
+  removeBreakpoint: function(aLocation, aOptions = {}) {
+    // Make sure a proper location is available.
+    if (!aLocation) {
+      return promise.reject(new Error("Invalid breakpoint location."));
     }
-    let breakpointActor = aBreakpointClient.actor;
 
-    // If the breakpoint was already removed, callback immediately.
-    if (!this.store[breakpointActor]) {
-      aCallback && aCallback(aBreakpointClient.location);
-      return;
+    // 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;
     }
 
-    aBreakpointClient.remove(() => {
-      // Delete the breakpoint client from the store.
-      delete this.store[breakpointActor];
+    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);
+        }
+
+        // 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);
+      });
+    });
 
-      // Hide the breakpoint from the editor and breakpoints pane.
-      this._hideBreakpoint(aBreakpointClient, aFlags);
+    return deferred.promise;
+  },
 
-      // We're done here.
-      aCallback && aCallback(aBreakpointClient.location);
+  /**
+   * Removes all 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 editor and breakpoints pane to show a specified breakpoint.
    *
-   * @param object aBreakpointClient
-   *        The BreakpointActor client object to show.
-   * @param object aFlags [optional]
+   * @param object aBreakpointData
+   *        Information about the breakpoint to be shown.
+   *        This object must have the following properties:
+   *          - location: the breakpoint's source location and line number
+   *          - text: the breakpoint's line text to be displayed
+   *          - actor: the breakpoint's corresponding actor id
+   * @param object aOptions [optional]
    *        @see DebuggerController.Breakpoints.addBreakpoint
    */
-  _showBreakpoint: function(aBreakpointClient, aFlags = {}) {
+  _showBreakpoint: function(aBreakpointData, aOptions = {}) {
     let currentSourceUrl = DebuggerView.Sources.selectedValue;
-    let { url, line } = aBreakpointClient.location;
+    let location = aBreakpointData.location;
 
     // Update the editor if required.
-    if (!aFlags.noEditorUpdate) {
-      if (url == currentSourceUrl) {
-        this._skipEditorBreakpointCallbacks = true;
-        this.editor.addBreakpoint(line - 1);
-        this._skipEditorBreakpointCallbacks = false;
+    if (!aOptions.noEditorUpdate) {
+      if (location.url == currentSourceUrl) {
+        DebuggerView.editor.addBreakpoint(location.line - 1);
       }
     }
+
     // Update the breakpoints pane if required.
-    if (!aFlags.noPaneUpdate) {
-      DebuggerView.Sources.addBreakpoint({
-        sourceLocation: url,
-        lineNumber: line,
-        lineText: aBreakpointClient.lineText,
-        actor: aBreakpointClient.actor,
-        openPopupFlag: aFlags.openPopup
-      });
+    if (!aOptions.noPaneUpdate) {
+      DebuggerView.Sources.addBreakpoint(aBreakpointData, aOptions);
     }
-    // Highlight the breakpoint in the pane if required.
-    if (!aFlags.noPaneHighlight) {
-      DebuggerView.Sources.highlightBreakpoint(url, line, aFlags);
-    }
-
-    // Notify that we've shown a breakpoint.
-    window.dispatchEvent(document, "Debugger:BreakpointShown", aBreakpointClient);
   },
 
   /**
    * Update the editor and breakpoints pane to hide a specified breakpoint.
    *
-   * @param object aBreakpointClient
-   *        The BreakpointActor client object to hide.
-   * @param object aFlags [optional]
+   * @param object aLocation
+   *        @see DebuggerController.Breakpoints.addBreakpoint
+   * @param object aOptions [optional]
    *        @see DebuggerController.Breakpoints.addBreakpoint
    */
-  _hideBreakpoint: function(aBreakpointClient, aFlags = {}) {
+  _hideBreakpoint: function(aLocation, aOptions = {}) {
     let currentSourceUrl = DebuggerView.Sources.selectedValue;
-    let { url, line } = aBreakpointClient.location;
 
     // Update the editor if required.
-    if (!aFlags.noEditorUpdate) {
-      if (url == currentSourceUrl) {
-        this._skipEditorBreakpointCallbacks = true;
-        this.editor.removeBreakpoint(line - 1);
-        this._skipEditorBreakpointCallbacks = false;
+    if (!aOptions.noEditorUpdate) {
+      if (aLocation.url == currentSourceUrl) {
+        DebuggerView.editor.removeBreakpoint(aLocation.line - 1);
       }
     }
+
     // Update the breakpoints pane if required.
-    if (!aFlags.noPaneUpdate) {
-      DebuggerView.Sources.removeBreakpoint(url, line);
+    if (!aOptions.noPaneUpdate) {
+      DebuggerView.Sources.removeBreakpoint(aLocation);
     }
-
-    // Notify that we've hidden a breakpoint.
-    window.dispatchEvent(document, "Debugger:BreakpointHidden", aBreakpointClient);
   },
 
   /**
-   * Get the BreakpointActor client object at the given location.
+   * Get a Promise for the BreakpointActor client object which is already added
+   * or currently being added at the given location.
    *
-   * @param string aUrl
-   *        The URL of where the breakpoint is.
-   * @param number aLine
-   *        The line number where the breakpoint is.
-   * @return object
-   *         The BreakpointActor client object.
+   * @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.
    */
-  getBreakpoint: function(aUrl, aLine) {
-    for each (let breakpointClient in this.store) {
-      if (breakpointClient.location.url == aUrl &&
-          breakpointClient.location.line == aLine) {
-        return breakpointClient;
-      }
-    }
-    return null;
+  _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.url + ":" + aLocation.line;
   }
 };
 
 /**
  * Localization convenience methods.
  */
 let L10N = new ViewHelpers.L10N(DBG_STRINGS_URI);
 
@@ -1573,58 +1691,45 @@ let Prefs = new ViewHelpers.Prefs("devto
  * @return boolean
  */
 XPCOMUtils.defineLazyGetter(window, "_isChromeDebugger", function() {
   // We're inside a single top level XUL window in a different process.
   return !(window.frameElement instanceof XULElement);
 });
 
 /**
+ * Convenient way of emitting events from the panel window.
+ */
+EventEmitter.decorate(this);
+
+/**
  * Preliminary setup for the DebuggerController object.
  */
 DebuggerController.initialize();
 DebuggerController.Parser = new Parser();
 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, {
-  "dispatchEvent": {
-    get: function() ViewHelpers.dispatchEvent,
-  },
-  "editor": {
-    get: function() DebuggerView.editor
-  },
   "gTarget": {
     get: function() DebuggerController._target
   },
   "gClient": {
     get: function() DebuggerController.client
   },
   "gThreadClient": {
     get: function() DebuggerController.activeThread
   },
-  "gThreadState": {
-    get: function() DebuggerController.ThreadState
-  },
-  "gStackFrames": {
-    get: function() DebuggerController.StackFrames
-  },
-  "gSourceScripts": {
-    get: function() DebuggerController.SourceScripts
-  },
-  "gBreakpoints": {
-    get: function() DebuggerController.Breakpoints
-  },
   "gCallStackPageSize": {
-    get: function() CALL_STACK_PAGE_SIZE,
+    get: function() CALL_STACK_PAGE_SIZE
   }
 });
 
 /**
  * Helper method for debugging.
  * @param string
  */
 function dumpn(str) {
--- a/browser/devtools/debugger/debugger-panel.js
+++ b/browser/devtools/debugger/debugger-panel.js
@@ -1,43 +1,44 @@
 /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* 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 { Cc, Ci, Cu, Cr } = require("chrome");
+const promise = require("sdk/core/promise");
 const EventEmitter = require("devtools/shared/event-emitter");
-const promise = require("sdk/core/promise");
 
 function DebuggerPanel(iframeWindow, toolbox) {
   this.panelWin = iframeWindow;
   this._toolbox = toolbox;
+  this._destroyer = null;
 
   this._view = this.panelWin.DebuggerView;
   this._controller = this.panelWin.DebuggerController;
   this._controller._target = this.target;
-  this._bkp = this._controller.Breakpoints;
 
   this.highlightWhenPaused = this.highlightWhenPaused.bind(this);
   this.unhighlightWhenResumed = this.unhighlightWhenResumed.bind(this);
 
   EventEmitter.decorate(this);
-}
+};
+
 exports.DebuggerPanel = DebuggerPanel;
 
 DebuggerPanel.prototype = {
   /**
    * Open is effectively an asynchronous constructor.
    *
    * @return object
    *         A promise that is resolved when the Debugger completes opening.
    */
-  open: function DebuggerPanel_open() {
+  open: function() {
     let targetPromise;
 
     // Local debugging needs to make the target remote.
     if (!this.target.isRemote) {
       targetPromise = this.target.makeRemote();
     } else {
       targetPromise = promise.resolve(this.target);
     }
@@ -49,50 +50,51 @@ DebuggerPanel.prototype = {
         this.target.on("thread-paused", this.highlightWhenPaused);
         this.target.on("thread-resumed", this.unhighlightWhenResumed);
         this.isReady = true;
         this.emit("ready");
         return this;
       })
       .then(null, function onError(aReason) {
         Cu.reportError("DebuggerPanel open failed. " +
-                       reason.error + ": " + reason.message);
+                       aReason.error + ": " + aReason.message);
       });
   },
 
   // DevToolPanel API
+
   get target() this._toolbox.target,
 
   destroy: function() {
+    // Make sure this panel is not already destroyed.
+    if (this._destroyer) {
+      return this._destroyer;
+    }
+
     this.target.off("thread-paused", this.highlightWhenPaused);
     this.target.off("thread-resumed", this.unhighlightWhenResumed);
-    this.emit("destroyed");
-    return promise.resolve(null);
+
+    return this._destroyer = this._controller.shutdownDebugger().then(() => {
+      this.emit("destroyed");
+    });
   },
 
   // DebuggerPanel API
 
-  addBreakpoint: function() {
-    this._bkp.addBreakpoint.apply(this._bkp, arguments);
+  addBreakpoint: function(aLocation, aOptions) {
+    return this._controller.Breakpoints.addBreakpoint(aLocation, aOptions);
   },
 
-  removeBreakpoint: function() {
-    this._bkp.removeBreakpoint.apply(this._bkp, arguments);
-  },
-
-  getBreakpoint: function() {
-    return this._bkp.getBreakpoint.apply(this._bkp, arguments);
-  },
-
-  getAllBreakpoints: function() {
-    return this._bkp.store;
+  removeBreakpoint: function(aLocation) {
+    return this._controller.Breakpoints.removeBreakpoint(aLocation);
   },
 
   highlightWhenPaused: function() {
     this._toolbox.highlightTool("jsdebugger");
+
     // Also raise the toolbox window if it is undocked or select the
     // corresponding tab when toolbox is docked.
     this._toolbox.raise();
   },
 
   unhighlightWhenResumed: function() {
     this._toolbox.unhighlightTool("jsdebugger");
   }
--- a/browser/devtools/debugger/debugger-panes.js
+++ b/browser/devtools/debugger/debugger-panes.js
@@ -35,30 +35,30 @@ SourcesView.prototype = Heritage.extend(
    */
   initialize: function() {
     dumpn("Initializing the SourcesView");
 
     this.widget = new SideMenuWidget(document.getElementById("sources"), {
       showCheckboxes: true,
       showArrows: true
     });
+
     this.emptyText = L10N.getStr("noSourcesText");
-    this.unavailableText = L10N.getStr("noMatchingSourcesText");
     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._editorDeck = document.getElementById("editor-deck");
     this._stopBlackBoxButton = document.getElementById("black-boxed-message-button");
 
-    window.addEventListener("Debugger:EditorLoaded", this._onEditorLoad, false);
-    window.addEventListener("Debugger:EditorUnloaded", this._onEditorUnload, false);
+    window.on(EVENTS.EDITOR_LOADED, this._onEditorLoad, false);
+    window.on(EVENTS.EDITOR_UNLOADED, this._onEditorUnload, false);
     this.widget.addEventListener("select", this._onSourceSelect, false);
     this.widget.addEventListener("click", this._onSourceClick, false);
     this.widget.addEventListener("check", this._onSourceCheck, 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("input", this._onConditionalTextboxInput, false);
@@ -71,51 +71,51 @@ SourcesView.prototype = Heritage.extend(
   },
 
   /**
    * Destruction function, called when the debugger is closed.
    */
   destroy: function() {
     dumpn("Destroying the SourcesView");
 
-    window.removeEventListener("Debugger:EditorLoaded", this._onEditorLoad, false);
-    window.removeEventListener("Debugger:EditorUnloaded", this._onEditorUnload, false);
+    window.off(EVENTS.EDITOR_LOADED, this._onEditorLoad, false);
+    window.off(EVENTS.EDITOR_UNLOADED, this._onEditorUnload, false);
     this.widget.removeEventListener("select", this._onSourceSelect, false);
     this.widget.removeEventListener("click", this._onSourceClick, false);
     this.widget.removeEventListener("check", this._onSourceCheck, 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("input", this._onConditionalTextboxInput, false);
     this._cbTextbox.removeEventListener("keypress", this._onConditionalTextboxKeyPress, false);
   },
 
   /**
    * Sets the preferred location to be selected in this sources container.
-   * @param string aSourceLocation
+   * @param string aUrl
    */
-  set preferredSource(aSourceLocation) {
-    this._preferredValue = aSourceLocation;
+  set preferredSource(aUrl) {
+    this._preferredValue = aUrl;
 
     // Selects the element with the specified value in this sources container,
     // if already inserted.
-    if (this.containsValue(aSourceLocation)) {
-      this.selectedValue = aSourceLocation;
+    if (this.containsValue(aUrl)) {
+      this.selectedValue = aUrl;
     }
   },
 
   /**
    * 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:
-   *        - forced: force the source to be immediately added
+   *        - staged: true to stage the item to be appended later
    */
   addSource: function(aSource, aOptions = {}) {
     let url = aSource.url;
     let label = SourceUtils.getSourceLabel(url.split(" -> ").pop());
     let group = SourceUtils.getSourceGroup(url.split(" -> ").pop());
 
     // Append a source item to this container.
     this.push([label, url, group], {
@@ -126,354 +126,361 @@ SourcesView.prototype = Heritage.extend(
         source: aSource
       }
     });
   },
 
   /**
    * Adds a breakpoint to this sources container.
    *
-   * @param object aOptions
-   *        Several options or flags supported by this operation:
-   *          - string sourceLocation
-   *            The breakpoint's source location.
-   *          - number lineNumber
-   *            The breakpoint's line number to be displayed.
-   *          - string lineText
-   *            The breakpoint's line text to be displayed.
-   *          - string actor
-   *            A breakpoint identifier specified by the debugger controller.
-   *          - boolean openPopupFlag [optional]
-   *            A flag specifying if the expression popup should be shown.
+   * @param object aBreakpointData
+   *        Information about the breakpoint to be shown.
+   *        This object must have the following properties:
+   *          - location: the breakpoint's source location and line number
+   *          - text: the breakpoint's line text to be displayed
+   *          - actor: the breakpoint's corresponding actor id
+   * @param object aOptions [optional]
+   *        @see DebuggerController.Breakpoints.addBreakpoint
    */
-  addBreakpoint: function(aOptions) {
-    let { sourceLocation: url, lineNumber: line } = aOptions;
+  addBreakpoint: function(aBreakpointData, aOptions = {}) {
+    let { location, actor } = aBreakpointData;
 
     // Make sure we're not duplicating anything. If a breakpoint at the
-    // specified source location and line number already exists, just enable it.
-    if (this.getBreakpoint(url, line)) {
-      this.enableBreakpoint(url, line, { id: aOptions.actor });
+    // specified source url and line already exists, just enable it.
+    if (this.getBreakpoint(location)) {
+      this.enableBreakpoint(location, { id: actor });
       return;
     }
 
     // Get the source item to which the breakpoint should be attached.
-    let sourceItem = this.getItemByValue(url);
+    let sourceItem = this.getItemByValue(location.url);
 
     // Create the element node and menu popup for the breakpoint item.
-    let breakpointView = this._createBreakpointView.call(this, aOptions);
-    let contextMenu = this._createContextMenu.call(this, aOptions);
+    let breakpointArgs = Heritage.extend(aBreakpointData, aOptions);
+    let breakpointView = this._createBreakpointView.call(this, breakpointArgs);
+    let contextMenu = this._createContextMenu.call(this, breakpointArgs);
 
     // Append a breakpoint child item to the corresponding source item.
-    let breakpointItem = sourceItem.append(breakpointView.container, {
-      attachment: Heritage.extend(aOptions, {
+    sourceItem.append(breakpointView.container, {
+      attachment: Heritage.extend(breakpointArgs, {
+        url: location.url,
+        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 this is a conditional breakpoint, display a panel to input the
-    // corresponding conditional expression.
-    if (aOptions.openPopupFlag) {
-      this.highlightBreakpoint(url, line, { openPopup: true });
+    // Highlight the newly appended breakpoint child item if necessary.
+    if (aOptions.openPopup || !aOptions.noEditorUpdate) {
+      this.highlightBreakpoint(location, aOptions);
     }
   },
 
   /**
    * Removes a breakpoint from this sources container.
+   * It does not also remove the breakpoint from the controller. Be careful.
    *
-   * @param string aSourceLocation
-   *        The breakpoint source location.
-   * @param number aLineNumber
-   *        The breakpoint line number.
+   * @param object aLocation
+   *        @see DebuggerController.Breakpoints.addBreakpoint
    */
-  removeBreakpoint: function(aSourceLocation, aLineNumber) {
+  removeBreakpoint: function(aLocation) {
     // When a parent source item is removed, all the child breakpoint items are
     // also automagically removed.
-    let sourceItem = this.getItemByValue(aSourceLocation);
+    let sourceItem = this.getItemByValue(aLocation.url);
     if (!sourceItem) {
       return;
     }
-    let breakpointItem = this.getBreakpoint(aSourceLocation, aLineNumber);
+    let breakpointItem = this.getBreakpoint(aLocation);
     if (!breakpointItem) {
       return;
     }
 
+    // Clear the breakpoint view.
     sourceItem.remove(breakpointItem);
   },
 
   /**
-   * Returns the breakpoint at the specified source location and line number.
+   * Returns the breakpoint at the specified source url and line.
    *
-   * @param string aSourceLocation
-   *        The breakpoint source location.
-   * @param number aLineNumber
-   *        The breakpoint line number.
+   * @param object aLocation
+   *        @see DebuggerController.Breakpoints.addBreakpoint
    * @return object
    *         The corresponding breakpoint item if found, null otherwise.
    */
-  getBreakpoint: function(aSourceLocation, aLineNumber) {
-    return this.getItemForPredicate((aItem) =>
-      aItem.attachment.sourceLocation == aSourceLocation &&
-      aItem.attachment.lineNumber == aLineNumber);
+  getBreakpoint: function(aLocation) {
+    return this.getItemForPredicate(aItem =>
+      aItem.attachment.url == aLocation.url &&
+      aItem.attachment.line == aLocation.line);
+  },
+
+  /**
+   * Returns all breakpoints which are not at the specified source url and line.
+   *
+   * @param string aId
+   *        The original breakpoint client actor.
+   * @param array aStore [optional]
+   *        A list in which to store the corresponding breakpoints.
+   * @return array
+   *         The corresponding breakpoints if found, an empty array otherwise.
+   */
+  getOtherBreakpoints: function(aId, aStore = []) {
+    for (let source in this) {
+      for (let breakpointItem in source) {
+        if (breakpointItem.attachment.actor != aId) {
+          aStore.push(breakpointItem);
+        }
+      }
+    }
+    return aStore;
   },
 
   /**
    * Enables a breakpoint.
    *
-   * @param string aSourceLocation
-   *        The breakpoint source location.
-   * @param number aLineNumber
-   *        The breakpoint line number.
+   * @param object aLocation
+   *        @see DebuggerController.Breakpoints.addBreakpoint
+   * @param object aOptions [optional]
+   *        Additional options or flags supported by this operation:
+   *          - id: a new id to be applied to the corresponding element node
+   *          - silent: pass true to not update the checkbox checked state;
+   *                    this is usually necessary when the checked state will
+   *                    be updated automatically (e.g: on a checkbox click).
+   * @return object
+   *         A promise that is resolved after the breakpoint is enabled, or
+   *         rejected if no breakpoint was found at the specified location.
+   */
+  enableBreakpoint: function(aLocation, aOptions = {}) {
+    let breakpointItem = this.getBreakpoint(aLocation);
+    if (!breakpointItem) {
+      return promise.reject(new Error("No breakpoint found."));
+    }
+
+    // Breakpoint will now be enabled.
+    let attachment = breakpointItem.attachment;
+    attachment.disabled = false;
+
+    // Update the corresponding menu items to reflect the enabled state.
+    let prefix = "bp-cMenu-"; // "breakpoints context menu"
+    let enableSelfId = prefix + "enableSelf-" + attachment.actor + "-menuitem";
+    let disableSelfId = prefix + "disableSelf-" + attachment.actor + "-menuitem";
+    document.getElementById(enableSelfId).setAttribute("hidden", "true");
+    document.getElementById(disableSelfId).removeAttribute("hidden");
+
+    // Set a new id to the corresponding breakpoint element if required.
+    if (aOptions.id) {
+      attachment.view.container.id = "breakpoint-" + aOptions.id;
+    }
+    // Update the checkbox state if necessary.
+    if (!aOptions.silent) {
+      attachment.view.checkbox.setAttribute("checked", "true");
+    }
+
+    return DebuggerController.Breakpoints.addBreakpoint(aLocation, {
+      // No need to update the pane, since this method is invoked because
+      // a breakpoint's view was interacted with.
+      noPaneUpdate: true
+    });
+  },
+
+  /**
+   * Disables a breakpoint.
+   *
+   * @param object aLocation
+   *        @see DebuggerController.Breakpoints.addBreakpoint
    * @param object aOptions [optional]
    *        Additional options or flags supported by this operation:
    *          - silent: pass true to not update the checkbox checked state;
    *                    this is usually necessary when the checked state will
    *                    be updated automatically (e.g: on a checkbox click).
-   *          - callback: function to invoke once the breakpoint is enabled
-   *          - id: a new id to be applied to the corresponding element node
-   * @return boolean
-   *         True if breakpoint existed and was enabled, false otherwise.
+   * @return object
+   *         A promise that is resolved after the breakpoint is disabled, or
+   *         rejected if no breakpoint was found at the specified location.
    */
-  enableBreakpoint: function(aSourceLocation, aLineNumber, aOptions = {}) {
-    let breakpointItem = this.getBreakpoint(aSourceLocation, aLineNumber);
+  disableBreakpoint: function(aLocation, aOptions = {}) {
+    let breakpointItem = this.getBreakpoint(aLocation);
     if (!breakpointItem) {
-      return false;
-    }
-
-    // Set a new id to the corresponding breakpoint element if required.
-    if (aOptions.id) {
-      breakpointItem.attachment.view.container.id = "breakpoint-" + aOptions.id;
-    }
-    // Update the checkbox state if necessary.
-    if (!aOptions.silent) {
-      breakpointItem.attachment.view.checkbox.setAttribute("checked", "true");
+      return promise.reject(new Error("No breakpoint found."));
     }
 
-    let { sourceLocation: url, lineNumber: line } = breakpointItem.attachment;
-    let breakpointLocation = { url: url, line: line };
-
-    // Only create a new breakpoint if it doesn't exist yet.
-    if (!DebuggerController.Breakpoints.getBreakpoint(url, line)) {
-      DebuggerController.Breakpoints.addBreakpoint(breakpointLocation, aOptions.callback, {
-        noPaneUpdate: true,
-        noPaneHighlight: true,
-        conditionalExpression: breakpointItem.attachment.conditionalExpression
-      });
-    }
-
-    // Breakpoint is now enabled.
-    breakpointItem.attachment.disabled = false;
-    return true;
-  },
+    // Breakpoint will now be disabled.
+    let attachment = breakpointItem.attachment;
+    attachment.disabled = true;
 
-  /**
-   * Disables a breakpoint.
-   *
-   * @param string aSourceLocation
-   *        The breakpoint source location.
-   * @param number aLineNumber
-   *        The breakpoint line number.
-   * @param object aOptions [optional]
-   *        Additional options or flags supported by this operation:
-   *          - silent: pass true to not update the checkbox checked state;
-   *                    this is usually necessary when the checked state will
-   *                    be updated automatically (e.g: on a checkbox click).
-   *          - callback: function to invoke once the breakpoint is disabled
-   * @return boolean
-   *         True if breakpoint existed and was disabled, false otherwise.
-   */
-  disableBreakpoint: function(aSourceLocation, aLineNumber, aOptions = {}) {
-    let breakpointItem = this.getBreakpoint(aSourceLocation, aLineNumber);
-    if (!breakpointItem) {
-      return false;
-    }
+    // Update the corresponding menu items to reflect the disabled state.
+    let prefix = "bp-cMenu-"; // "breakpoints context menu"
+    let enableSelfId = prefix + "enableSelf-" + attachment.actor + "-menuitem";
+    let disableSelfId = prefix + "disableSelf-" + attachment.actor + "-menuitem";
+    document.getElementById(enableSelfId).removeAttribute("hidden");
+    document.getElementById(disableSelfId).setAttribute("hidden", "true");
 
     // Update the checkbox state if necessary.
     if (!aOptions.silent) {
-      breakpointItem.attachment.view.checkbox.removeAttribute("checked");
+      attachment.view.checkbox.removeAttribute("checked");
     }
 
-    let { sourceLocation: url, lineNumber: line } = breakpointItem.attachment;
-    let breakpointClient = DebuggerController.Breakpoints.getBreakpoint(url, line);
-
-    // Only remove the breakpoint if it exists.
-    if (breakpointClient) {
-      DebuggerController.Breakpoints.removeBreakpoint(breakpointClient, aOptions.callback, {
-        noPaneUpdate: true
-      });
-      // Remember the current conditional expression, to be reapplied when the
-      // breakpoint is re-enabled via enableBreakpoint().
-      breakpointItem.attachment.conditionalExpression = breakpointClient.conditionalExpression;
-    }
-
-    // Breakpoint is now disabled.
-    breakpointItem.attachment.disabled = true;
-    return true;
+    return DebuggerController.Breakpoints.removeBreakpoint(aLocation, {
+      // No need to update this pane, since this method is invoked because
+      // a breakpoint's view was interacted with.
+      noPaneUpdate: true
+    });
   },
 
   /**
    * Highlights a breakpoint in this sources container.
    *
-   * @param string aSourceLocation
-   *        The breakpoint source location.
-   * @param number aLineNumber
-   *        The breakpoint line number.
-   * @param object aFlags [optional]
+   * @param object aLocation
+   *        @see DebuggerController.Breakpoints.addBreakpoint
+   * @param object aOptions [optional]
    *        An object containing some of the following boolean properties:
-   *          - updateEditor: true if editor updates should be allowed
-   *          - openPopup: true if the expression popup should be shown
+   *          - openPopup: tells if the expression popup should be shown.
+   *          - noEditorUpdate: tells if you want to skip editor updates.
    */
-  highlightBreakpoint: function(aSourceLocation, aLineNumber, aFlags = {}) {
-    let breakpointItem = this.getBreakpoint(aSourceLocation, aLineNumber);
+  highlightBreakpoint: function(aLocation, aOptions = {}) {
+    let breakpointItem = this.getBreakpoint(aLocation);
     if (!breakpointItem) {
       return;
     }
 
-    // Breakpoint is now selected.
+    // Breakpoint will now be selected.
     this._selectBreakpoint(breakpointItem);
 
-    // Update the editor source location and line number if necessary.
-    if (aFlags.updateEditor) {
-      DebuggerView.updateEditor(aSourceLocation, aLineNumber, { noDebug: true });
+    // Update the editor location if necessary.
+    if (!aOptions.noEditorUpdate) {
+      DebuggerView.setEditorLocation(aLocation.url, aLocation.line, { noDebug: true });
     }
 
     // If the breakpoint requires a new conditional expression, display
     // the panel to input the corresponding expression.
-    if (aFlags.openPopup) {
+    if (aOptions.openPopup) {
       this._openConditionalPopup();
     } else {
       this._hideConditionalPopup();
     }
   },
 
   /**
    * Unhighlights the current breakpoint in this sources container.
    */
   unhighlightBreakpoint: function() {
     this._unselectBreakpoint();
     this._hideConditionalPopup();
   },
 
   /**
-   * Gets the currently selected breakpoint item.
-   * @return object
-   */
-  get selectedBreakpointItem() this._selectedBreakpoint,
-
-  /**
-   * Gets the currently selected breakpoint client.
-   * @return object
-   */
-  get selectedBreakpointClient() {
-    let breakpointItem = this._selectedBreakpoint;
-    if (breakpointItem) {
-      let { sourceLocation: url, lineNumber: line } = breakpointItem.attachment;
-      return DebuggerController.Breakpoints.getBreakpoint(url, line);
-    }
-    return null;
-  },
-
-  /**
    * Marks a breakpoint as selected in this sources container.
    *
    * @param object aItem
    *        The breakpoint item to select.
    */
   _selectBreakpoint: function(aItem) {
-    if (this._selectedBreakpoint == aItem) {
+    if (this._selectedBreakpointItem == aItem) {
       return;
     }
     this._unselectBreakpoint();
-    this._selectedBreakpoint = aItem;
-    this._selectedBreakpoint.target.classList.add("selected");
+    this._selectedBreakpointItem = aItem;
+    this._selectedBreakpointItem.target.classList.add("selected");
 
     // Ensure the currently selected breakpoint is visible.
     this.widget.ensureElementIsVisible(aItem.target);
   },
 
   /**
    * Marks the current breakpoint as unselected in this sources container.
    */
   _unselectBreakpoint: function() {
-    if (this._selectedBreakpoint) {
-      this._selectedBreakpoint.target.classList.remove("selected");
-      this._selectedBreakpoint = null;
+    if (!this._selectedBreakpointItem) {
+      return;
     }
+    this._selectedBreakpointItem.target.classList.remove("selected");
+    this._selectedBreakpointItem = null;
   },
 
   /**
    * Opens a conditional breakpoint's expression input popup.
    */
   _openConditionalPopup: function() {
-    let selectedBreakpointItem = this.selectedBreakpointItem;
-    let selectedBreakpointClient = this.selectedBreakpointClient;
+    let breakpointItem = this._selectedBreakpointItem;
+    let attachment = breakpointItem.attachment;
 
-    if (selectedBreakpointClient.conditionalExpression === undefined) {
-      this._cbTextbox.value = selectedBreakpointClient.conditionalExpression = "";
+    // Check if this is an enabled conditional breakpoint, and if so,
+    // retrieve the current conditional epression.
+    let breakpointPromise = DebuggerController.Breakpoints._getAdded(attachment);
+    if (breakpointPromise) {
+      breakpointPromise.then(aBreakpointClient => {
+        let isConditionalBreakpoint = "conditionalExpression" in aBreakpointClient;
+        let conditionalExpression = aBreakpointClient.conditionalExpression;
+        doOpen.call(this, isConditionalBreakpoint ? conditionalExpression : "")
+      });
     } else {
-      this._cbTextbox.value = selectedBreakpointClient.conditionalExpression;
+      doOpen.call(this, "")
     }
 
-    this._cbPanel.hidden = false;
-    this._cbPanel.openPopup(selectedBreakpointItem.attachment.view.lineNumber,
-      BREAKPOINT_CONDITIONAL_POPUP_POSITION,
-      BREAKPOINT_CONDITIONAL_POPUP_OFFSET_X,
-      BREAKPOINT_CONDITIONAL_POPUP_OFFSET_Y);
+    function doOpen(aConditionalExpression) {
+      // Update the conditional expression textbox. If no expression was
+      // previously set, revert to using an empty string by default.
+      this._cbTextbox.value = aConditionalExpression;
+
+      // 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;
     this._cbPanel.hidePopup();
   },
 
   /**
    * Customization function for creating a breakpoint item's UI.
    *
    * @param object aOptions
-   *        Additional options or flags supported by this operation:
-   *          - number lineNumber
-   *            The line number specified by the debugger controller.
-   *          - string lineText
-   *            The line text to be displayed.
+   *        A couple of options or flags supported by this operation:
+   *          - location: the breakpoint's source location and line number
+   *          - text: the breakpoint's line text to be displayed.
+   *          - actor: a breakpoint identifier specified by the controller.
    * @return object
    *         An object containing the breakpoint container, checkbox,
    *         line number and line text nodes.
    */
   _createBreakpointView: function(aOptions) {
-    let { lineNumber, lineText } = aOptions;
-
     let checkbox = document.createElement("checkbox");
     checkbox.setAttribute("checked", "true");
     checkbox.className = "dbg-breakpoint-checkbox";
 
     let lineNumberNode = document.createElement("label");
     lineNumberNode.className = "plain dbg-breakpoint-line";
-    lineNumberNode.setAttribute("value", lineNumber);
+    lineNumberNode.setAttribute("value", aOptions.location.line);
 
     let lineTextNode = document.createElement("label");
     lineTextNode.className = "plain dbg-breakpoint-text";
-    lineTextNode.setAttribute("value", lineText);
+    lineTextNode.setAttribute("value", aOptions.text);
     lineTextNode.setAttribute("crop", "end");
     lineTextNode.setAttribute("flex", "1");
-    lineTextNode.setAttribute("tooltiptext",
-      lineText.substr(0, BREAKPOINT_LINE_TOOLTIP_MAX_LENGTH));
+
+    let tooltip = aOptions.text.substr(0, BREAKPOINT_LINE_TOOLTIP_MAX_LENGTH);
+    lineTextNode.setAttribute("tooltiptext", tooltip);
 
     let container = document.createElement("hbox");
     container.id = "breakpoint-" + aOptions.actor;
-    container.className = "dbg-breakpoint devtools-monospace" +
-                          " side-menu-widget-item-other";
+    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(lineNumberNode);
@@ -486,19 +493,18 @@ SourcesView.prototype = Heritage.extend(
       lineText: lineTextNode
     };
   },
 
   /**
    * Creates a context menu for a breakpoint element.
    *
    * @param aOptions
-   *        Additional options or flags supported by this operation:
-   *          - string actor
-   *            A breakpoint identifier specified by the debugger controller.
+   *        A couple of options or flags supported by this operation:
+   *          - actor: a breakpoint identifier specified by the controller.
    * @return object
    *         An object containing the breakpoint commandset and menu popup ids.
    */
   _createContextMenu: function(aOptions) {
     let commandsetId = "bp-cSet-" + aOptions.actor;
     let menupopupId = "bp-mPop-" + aOptions.actor;
 
     let commandset = document.createElement("commandset");
@@ -580,49 +586,51 @@ SourcesView.prototype = Heritage.extend(
   _onBreakpointRemoved: function(aItem) {
     dumpn("Finalizing breakpoint item: " + aItem);
 
     // Destroy the context menu for the breakpoint.
     let contextMenu = aItem.attachment.popup;
     document.getElementById(contextMenu.commandsetId).remove();
     document.getElementById(contextMenu.menupopupId).remove();
 
-    if (this._selectedBreakpoint == aItem) {
-      this._selectedBreakpoint = null;
+    // Clear the breakpoint selection.
+    if (this._selectedBreakpointItem == aItem) {
+      this._selectedBreakpointItem = null;
     }
   },
 
   /**
    * The load listener for the source editor.
    */
-  _onEditorLoad: function({ detail: editor }) {
-    editor.addEventListener("Selection", this._onEditorSelection, false);
-    editor.addEventListener("ContextMenu", this._onEditorContextMenu, false);
+  _onEditorLoad: function(aName, aEditor) {
+    aEditor.addEventListener(SourceEditor.EVENTS.SELECTION, this._onEditorSelection, false);
+    aEditor.addEventListener(SourceEditor.EVENTS.CONTEXT_MENU, this._onEditorContextMenu, false);
   },
 
   /**
    * The unload listener for the source editor.
    */
-  _onEditorUnload: function({ detail: editor }) {
-    editor.removeEventListener("Selection", this._onEditorSelection, false);
-    editor.removeEventListener("ContextMenu", this._onEditorContextMenu, false);
+  _onEditorUnload: function(aName, aEditor) {
+    aEditor.removeEventListener(SourceEditor.EVENTS.SELECTION, this._onEditorSelection, false);
+    aEditor.removeEventListener(SourceEditor.EVENTS.CONTEXT_MENU, this._onEditorContextMenu, false);
   },
 
   /**
    * The selection listener for the source editor.
    */
   _onEditorSelection: function(e) {
     let { start, end } = e.newValue;
 
-    let sourceLocation = this.selectedValue;
+    let url = this.selectedValue;
     let lineStart = DebuggerView.editor.getLineAtOffset(start) + 1;
     let lineEnd = DebuggerView.editor.getLineAtOffset(end) + 1;
+    let location = { url: url, line: lineStart };
 
-    if (this.getBreakpoint(sourceLocation, lineStart) && lineStart == lineEnd) {
-      this.highlightBreakpoint(sourceLocation, lineStart);
+    if (this.getBreakpoint(location) && lineStart == lineEnd) {
+      this.highlightBreakpoint(location, { noEditorUpdate: true });
     } else {
       this.unhighlightBreakpoint();
     }
   },
 
   /**
    * The context menu listener for the source editor.
    */
@@ -635,33 +643,28 @@ SourcesView.prototype = Heritage.extend(
   /**
    * The select listener for the sources container.
    */
   _onSourceSelect: function({ detail: sourceItem }) {
     if (!sourceItem) {
       return;
     }
     // The container is not empty and an actual item was selected.
-    let selectedSource = sourceItem.attachment.source;
-
-    if (DebuggerView.editorSource != selectedSource) {
-      DebuggerView.editorSource = selectedSource;
-    }
-
+    DebuggerView.setEditorLocation(sourceItem.value);
     this.maybeShowBlackBoxMessage();
   },
 
   /**
    * Show or hide the black box message vs. source editor depending on if the
    * selected source is black boxed or not.
    */
-  maybeShowBlackBoxMessage: function () {
-    const source = DebuggerController.activeThread.source(
-      DebuggerView.editorSource);
-    this._editorDeck.selectedIndex = source.isBlackBoxed ? 1 : 0;
+  maybeShowBlackBoxMessage: function() {
+    let sourceForm = this.selectedItem.attachment.source;
+    let sourceClient = DebuggerController.activeThread.source(sourceForm);
+    this._editorDeck.selectedIndex = sourceClient.isBlackBoxed ? 1 : 0;
   },
 
   /**
    * The click listener for the sources container.
    */
   _onSourceClick: function() {
     // Use this container as a filtering target.
     DebuggerView.Filtering.target = this;
@@ -671,84 +674,111 @@ SourcesView.prototype = Heritage.extend(
    * The check listener for the sources container.
    */
   _onSourceCheck: function({ detail: { checked }, target }) {
     let item = this.getItemForElement(target);
     DebuggerController.SourceScripts.blackBox(item.attachment.source, !checked);
   },
 
   /**
-   * The click listener for the stop black boxing button.
+   * The click listener for the "stop black boxing" button.
    */
   _onStopBlackBoxing: function() {
-    DebuggerController.SourceScripts.blackBox(DebuggerView.editorSource,
-                                              false);
+    let sourceForm = this.selectedItem.attachment.source;
+    DebuggerController.SourceScripts.blackBox(sourceForm, 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 { sourceLocation: url, lineNumber: line } = breakpointItem.attachment;
-
-    let breakpointClient = DebuggerController.Breakpoints.getBreakpoint(url, line);
-    let conditionalExpression = (breakpointClient || {}).conditionalExpression;
+    let attachment = breakpointItem.attachment;
 
-    this.highlightBreakpoint(url, line, {
-      updateEditor: true,
-      openPopup: conditionalExpression !== undefined && e.button == 0
-    });
+    // Check if this is an enabled conditional breakpoint.
+    let breakpointPromise = DebuggerController.Breakpoints._getAdded(attachment);
+    if (breakpointPromise) {
+      breakpointPromise.then(aBreakpointClient => {
+        doHighlight.call(this, "conditionalExpression" in aBreakpointClient);
+      });
+    } else {
+      doHighlight.call(this, false);
+    }
+
+    function doHighlight(aConditionalBreakpointFlag) {
+      // Highlight the breakpoint in this pane and in the editor.
+      this.highlightBreakpoint(attachment, {
+        // Don't show the conditional expression popup if this is not a
+        // conditional breakpoint, or the right mouse button was pressed (to
+        // avoid clashing the popup with the context menu).
+        openPopup: aConditionalBreakpointFlag && e.button == 0
+      });
+    }
   },
 
   /**
    * 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 { sourceLocation: url, lineNumber: line, disabled } = breakpointItem.attachment;
+    let attachment = breakpointItem.attachment;
 
-    this[disabled ? "enableBreakpoint" : "disableBreakpoint"](url, line, {
+    // Toggle the breakpoint enabled or disabled.
+    this[attachment.disabled ? "enableBreakpoint" : "disableBreakpoint"](attachment, {
+      // Do this silently (don't update the checkbox checked state), since
+      // this listener is triggered because a checkbox was already clicked.
       silent: true
     });
 
     // 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;
+    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;
+    this._conditionalPopupVisible = false; // Used in tests.
+    window.emit(EVENTS.CONDITIONAL_BREAKPOINT_POPUP_HIDING);
   },
 
   /**
    * The input listener for the breakpoints conditional expression textbox.
    */
   _onConditionalTextboxInput: function() {
-    this.selectedBreakpointClient.conditionalExpression = this._cbTextbox.value;
+    let breakpointItem = this._selectedBreakpointItem;
+    let attachment = breakpointItem.attachment;
+
+    // Check if this is an enabled conditional breakpoint, and if so,
+    // save the current conditional epression.
+    let breakpointPromise = DebuggerController.Breakpoints._getAdded(attachment);
+    if (breakpointPromise) {
+      breakpointPromise.then(aBreakpointClient => {
+        aBreakpointClient.conditionalExpression = this._cbTextbox.value;
+      });
+    }
   },
 
   /**
    * The keypress listener for the breakpoints conditional expression textbox.
    */
   _onConditionalTextboxKeyPress: function(e) {
     if (e.keyCode == e.DOM_VK_RETURN || e.keyCode == e.DOM_VK_ENTER) {
       this._hideConditionalPopup();
@@ -764,27 +794,26 @@ SourcesView.prototype = Heritage.extend(
     if (this._editorContextMenuLineNumber >= 0) {
       DebuggerView.editor.setCaretPosition(this._editorContextMenuLineNumber);
     }
     // Avoid placing breakpoints incorrectly when using key shortcuts.
     this._editorContextMenuLineNumber = -1;
 
     let url = DebuggerView.Sources.selectedValue;
     let line = DebuggerView.editor.getCaretPosition().line + 1;
-    let breakpointItem = this.getBreakpoint(url, line);
+    let location = { url: url, line: line };
+    let breakpointItem = this.getBreakpoint(location);
 
     // If a breakpoint already existed, remove it now.
     if (breakpointItem) {
-      let breakpointClient = DebuggerController.Breakpoints.getBreakpoint(url, line);
-      DebuggerController.Breakpoints.removeBreakpoint(breakpointClient);
+      DebuggerController.Breakpoints.removeBreakpoint(location);
     }
     // No breakpoint existed at the required location, add one now.
     else {
-      let breakpointLocation = { url: url, line: line };
-      DebuggerController.Breakpoints.addBreakpoint(breakpointLocation);
+      DebuggerController.Breakpoints.addBreakpoint(location);
     }
   },
 
   /**
    * Called when the add conditional breakpoint key sequence was pressed.
    */
   _onCmdAddConditionalBreakpoint: function() {
     // If this command was executed via the context menu, add the breakpoint
@@ -792,266 +821,158 @@ SourcesView.prototype = Heritage.extend(
     if (this._editorContextMenuLineNumber >= 0) {
       DebuggerView.editor.setCaretPosition(this._editorContextMenuLineNumber);
     }
     // Avoid placing breakpoints incorrectly when using key shortcuts.
     this._editorContextMenuLineNumber = -1;
 
     let url =  DebuggerView.Sources.selectedValue;
     let line = DebuggerView.editor.getCaretPosition().line + 1;
-    let breakpointItem = this.getBreakpoint(url, line);
+    let location = { url: url, line: line };
+    let breakpointItem = this.getBreakpoint(location);
 
     // If a breakpoint already existed or wasn't a conditional, morph it now.
     if (breakpointItem) {
-      let breakpointClient = DebuggerController.Breakpoints.getBreakpoint(url, line);
-      this.highlightBreakpoint(url, line, { openPopup: true });
+      this.highlightBreakpoint(location, { openPopup: true });
     }
     // No breakpoint existed at the required location, add one now.
     else {
-      DebuggerController.Breakpoints.addBreakpoint({ url: url, line: line }, null, {
-        conditionalExpression: "",
-        openPopup: true
-      });
+      DebuggerController.Breakpoints.addBreakpoint(location, { openPopup: true });
     }
   },
 
   /**
    * Function invoked on the "setConditional" menuitem command.
    *
    * @param string aId
-   *        The original breakpoint client actor. If a breakpoint was disabled
-   *        and then re-enabled, then this will not correspond to the entry in
-   *        the controller's breakpoints store.
-   * @param function aCallback [optional]
-   *        A function to invoke once this operation finishes.
+   *        The original breakpoint client actor.
    */
-  _onSetConditional: function(aId, aCallback = () => {}) {
+  _onSetConditional: function(aId) {
     let targetBreakpoint = this.getItemForPredicate(aItem => aItem.attachment.actor == aId);
-    let { sourceLocation: url, lineNumber: line } = targetBreakpoint.attachment;
+    let attachment = targetBreakpoint.attachment;
 
     // Highlight the breakpoint and show a conditional expression popup.
-    this.highlightBreakpoint(url, line, { openPopup: true });
-
-    // Breakpoint is now highlighted.
-    aCallback();
+    this.highlightBreakpoint(attachment, { openPopup: true });
   },
 
   /**
    * Function invoked on the "enableSelf" menuitem command.
    *
    * @param string aId
-   *        The original breakpoint client actor. If a breakpoint was disabled
-   *        and then re-enabled, then this will not correspond to the entry in
-   *        the controller's breakpoints store.
-   * @param function aCallback [optional]
-   *        A function to invoke once this operation finishes.
+   *        The original breakpoint client actor.
    */
-  _onEnableSelf: function(aId, aCallback = () => {}) {
+  _onEnableSelf: function(aId) {
     let targetBreakpoint = this.getItemForPredicate(aItem => aItem.attachment.actor == aId);
-    let { sourceLocation: url, lineNumber: line, actor } = targetBreakpoint.attachment;
+    let attachment = targetBreakpoint.attachment;
 
     // Enable the breakpoint, in this container and the controller store.
-    if (this.enableBreakpoint(url, line)) {
-      let prefix = "bp-cMenu-"; // "breakpoints context menu"
-      let enableSelfId = prefix + "enableSelf-" + actor + "-menuitem";
-      let disableSelfId = prefix + "disableSelf-" + actor + "-menuitem";
-      document.getElementById(enableSelfId).setAttribute("hidden", "true");
-      document.getElementById(disableSelfId).removeAttribute("hidden");
-
-      // Breakpoint is now enabled.
-      // Breakpoints can only be set while the debuggee is paused, so if the
-      // active thread wasn't paused, wait for a resume before continuing.
-      if (gThreadClient.state != "paused") {
-        gThreadClient.addOneTimeListener("resumed", aCallback);
-      } else {
-        aCallback();
-      }
-    }
+    this.enableBreakpoint(attachment);
   },
 
   /**
    * Function invoked on the "disableSelf" menuitem command.
    *
    * @param string aId
-   *        The original breakpoint client actor. If a breakpoint was disabled
-   *        and then re-enabled, then this will not correspond to the entry in
-   *        the controller's breakpoints store.
-   * @param function aCallback [optional]
-   *        A function to invoke once this operation finishes.
+   *        The original breakpoint client actor.
    */
-  _onDisableSelf: function(aId, aCallback = () => {}) {
+  _onDisableSelf: function(aId) {
     let targetBreakpoint = this.getItemForPredicate(aItem => aItem.attachment.actor == aId);
-    let { sourceLocation: url, lineNumber: line, actor } = targetBreakpoint.attachment;
+    let attachment = targetBreakpoint.attachment;
 
     // Disable the breakpoint, in this container and the controller store.
-    if (this.disableBreakpoint(url, line)) {
-      let prefix = "bp-cMenu-"; // "breakpoints context menu"
-      let enableSelfId = prefix + "enableSelf-" + actor + "-menuitem";
-      let disableSelfId = prefix + "disableSelf-" + actor + "-menuitem";
-      document.getElementById(enableSelfId).removeAttribute("hidden");
-      document.getElementById(disableSelfId).setAttribute("hidden", "true");
-
-      // Breakpoint is now disabled.
-      aCallback();
-    }
+    this.disableBreakpoint(attachment);
   },
 
   /**
    * Function invoked on the "deleteSelf" menuitem command.
    *
    * @param string aId
-   *        The original breakpoint client actor. If a breakpoint was disabled
-   *        and then re-enabled, then this will not correspond to the entry in
-   *        the controller's breakpoints store.
-   * @param function aCallback [optional]
-   *        A function to invoke once this operation finishes.
+   *        The original breakpoint client actor.
    */
-  _onDeleteSelf: function(aId, aCallback = () => {}) {
+  _onDeleteSelf: function(aId) {
     let targetBreakpoint = this.getItemForPredicate(aItem => aItem.attachment.actor == aId);
-    let { sourceLocation: url, lineNumber: line } = targetBreakpoint.attachment;
+    let attachment = targetBreakpoint.attachment;
 
     // Remove the breakpoint, from this container and the controller store.
-    this.removeBreakpoint(url, line);
-    gBreakpoints.removeBreakpoint(gBreakpoints.getBreakpoint(url, line), aCallback);
+    this.removeBreakpoint(attachment);
+    DebuggerController.Breakpoints.removeBreakpoint(attachment);
   },
 
   /**
    * Function invoked on the "enableOthers" menuitem command.
    *
    * @param string aId
-   *        The original breakpoint client actor. If a breakpoint was disabled
-   *        and then re-enabled, then this will not correspond to the entry in
-   *        the controller's breakpoints store.
-   * @param function aCallback [optional]
-   *        A function to invoke once this operation finishes.
+   *        The original breakpoint client actor.
    */
-  _onEnableOthers: function(aId, aCallback = () => {}) {
-    let targetBreakpoint = this.getItemForPredicate(aItem => aItem.attachment.actor == aId);
+  _onEnableOthers: function(aId) {
+    let enableOthers = (aCallback) => {
+      let other = this.getOtherBreakpoints(aId);
+      let outstanding = other.map(e => this.enableBreakpoint(e.attachment));
+      promise.all(outstanding).then(aCallback);
+    }
 
-    // Find a disabled breakpoint and re-enable it. Do this recursively until
-    // all required breakpoints are enabled, because each operation is async.
-    for (let source in this) {
-      for (let otherBreakpoint in source) {
-        if (otherBreakpoint != targetBreakpoint &&
-            otherBreakpoint.attachment.disabled) {
-          this._onEnableSelf(otherBreakpoint.attachment.actor, () =>
-            this._onEnableOthers(aId, aCallback));
-          return;
-        }
-      }
+    // Breakpoints can only be set while the debuggee is paused. To avoid
+    // an avalanche of pause/resume interrupts of the main thread, simply
+    // pause it beforehand if it's not already.
+    if (gThreadClient.state == "paused") {
+      enableOthers();
+    } else {
+      gThreadClient.interrupt(() => enableOthers(() => gThreadClient.resume()));
     }
-    // All required breakpoints are now enabled.
-    aCallback();
   },
 
   /**
    * Function invoked on the "disableOthers" menuitem command.
    *
    * @param string aId
-   *        The original breakpoint client actor. If a breakpoint was disabled
-   *        and then re-enabled, then this will not correspond to the entry in
-   *        the controller's breakpoints store.
-   * @param function aCallback [optional]
-   *        A function to invoke once this operation finishes.
+   *        The original breakpoint client actor.
    */
-  _onDisableOthers: function(aId, aCallback = () => {}) {
-    let targetBreakpoint = this.getItemForPredicate(aItem => aItem.attachment.actor == aId);
-
-    // Find an enabled breakpoint and disable it. Do this recursively until
-    // all required breakpoints are disabled, because each operation is async.
-    for (let source in this) {
-      for (let otherBreakpoint in source) {
-        if (otherBreakpoint != targetBreakpoint &&
-           !otherBreakpoint.attachment.disabled) {
-          this._onDisableSelf(otherBreakpoint.attachment.actor, () =>
-            this._onDisableOthers(aId, aCallback));
-          return;
-        }
-      }
-    }
-    // All required breakpoints are now disabled.
-    aCallback();
+  _onDisableOthers: function(aId) {
+    let other = this.getOtherBreakpoints(aId);
+    other.forEach(e => this._onDisableSelf(e.attachment.actor));
   },
 
   /**
    * Function invoked on the "deleteOthers" menuitem command.
    *
    * @param string aId
-   *        The original breakpoint client actor. If a breakpoint was disabled
-   *        and then re-enabled, then this will not correspond to the entry in
-   *        the controller's breakpoints store.
-   * @param function aCallback [optional]
-   *        A function to invoke once this operation finishes.
+   *        The original breakpoint client actor.
    */
-  _onDeleteOthers: function(aId, aCallback = () => {}) {
-    let targetBreakpoint = this.getItemForPredicate(aItem => aItem.attachment.actor == aId);
-
-    // Find a breakpoint and delete it. Do this recursively until all required
-    // breakpoints are deleted, because each operation is async.
-    for (let source in this) {
-      for (let otherBreakpoint in source) {
-        if (otherBreakpoint != targetBreakpoint) {
-          this._onDeleteSelf(otherBreakpoint.attachment.actor, () =>
-            this._onDeleteOthers(aId, aCallback));
-          return;
-        }
-      }
-    }
-    // All required breakpoints are now deleted.
-    aCallback();
+  _onDeleteOthers: function(aId) {
+    let other = this.getOtherBreakpoints(aId);
+    other.forEach(e => this._onDeleteSelf(e.attachment.actor));
   },
 
   /**
    * Function invoked on the "enableAll" menuitem command.
-   *
-   * @param string aId
-   *        The original breakpoint client actor. If a breakpoint was disabled
-   *        and then re-enabled, then this will not correspond to the entry in
-   *        the controller's breakpoints store.
-   * @param function aCallback [optional]
-   *        A function to invoke once this operation finishes.
    */
-  _onEnableAll: function(aId) {
-    this._onEnableOthers(aId, () => this._onEnableSelf(aId));
+  _onEnableAll: function() {
+    this._onEnableOthers(null);
   },
 
   /**
    * Function invoked on the "disableAll" menuitem command.
-   *
-   * @param string aId
-   *        The original breakpoint client actor. If a breakpoint was disabled
-   *        and then re-enabled, then this will not correspond to the entry in
-   *        the controller's breakpoints store.
-   * @param function aCallback [optional]
-   *        A function to invoke once this operation finishes.
    */
-  _onDisableAll: function(aId) {
-    this._onDisableOthers(aId, () => this._onDisableSelf(aId));
+  _onDisableAll: function() {
+    this._onDisableOthers(null);
   },
 
   /**
    * Function invoked on the "deleteAll" menuitem command.
-   *
-   * @param string aId
-   *        The original breakpoint client actor. If a breakpoint was disabled
-   *        and then re-enabled, then this will not correspond to the entry in
-   *        the controller's breakpoints store.
-   * @param function aCallback [optional]
-   *        A function to invoke once this operation finishes.
    */
-  _onDeleteAll: function(aId) {
-    this._onDeleteOthers(aId, () => this._onDeleteSelf(aId));
+  _onDeleteAll: function() {
+    this._onDeleteOthers(null);
   },
 
   _commandset: null,
   _popupset: null,
   _cmPopup: null,
   _cbPanel: null,
   _cbTextbox: null,
-  _selectedBreakpoint: null,
+  _selectedBreakpointItem: null,
   _editorContextMenuLineNumber: -1,
   _conditionalPopupVisible: false
 });
 
 /**
  * Utility functions for handling sources.
  */
 let SourceUtils = {
@@ -1410,17 +1331,17 @@ WatchExpressionsView.prototype = Heritag
 
   /**
    * Gets the watch expressions code strings for all items in this container.
    *
    * @return array
    *         The watch expressions code strings.
    */
   getAllStrings: function() {
-    return this.orderedItems.map((e) => e.attachment.currentExpression);
+    return this.items.map(e => e.attachment.currentExpression);
   },
 
   /**
    * Customization function for creating an item's UI.
    *
    * @param nsIDOMNode aElementNode
    *        The element associated with the displayed item.
    * @param any aAttachment
@@ -1543,20 +1464,17 @@ WatchExpressionsView.prototype = Heritag
 });
 
 /**
  * Functions handling the global search UI.
  */
 function GlobalSearchView() {
   dumpn("GlobalSearchView was instantiated");
 
-  this._startSearch = this._startSearch.bind(this);
-  this._performGlobalSearch = this._performGlobalSearch.bind(this);
   this._createItemView = this._createItemView.bind(this);
-  this._onScroll = this._onScroll.bind(this);
   this._onHeaderClick = this._onHeaderClick.bind(this);
   this._onLineClick = this._onLineClick.bind(this);
   this._onMatchClick = this._onMatchClick.bind(this);
 }
 
 GlobalSearchView.prototype = Heritage.extend(WidgetMethods, {
   /**
    * Initialization function, called when the debugger is started.
@@ -1564,52 +1482,48 @@ GlobalSearchView.prototype = Heritage.ex
   initialize: function() {
     dumpn("Initializing the GlobalSearchView");
 
     this.widget = new ListWidget(document.getElementById("globalsearch"));
     this._splitter = document.querySelector("#globalsearch + .devtools-horizontal-splitter");
 
     this.widget.emptyText = L10N.getStr("noMatchingStringsText");
     this.widget.itemFactory = this._createItemView;
-    this.widget.addEventListener("scroll", this._onScroll, false);
   },
 
   /**
    * Destruction function, called when the debugger is closed.
    */
   destroy: function() {
     dumpn("Destroying the GlobalSearchView");
+  },
 
-    this.widget.removeEventListener("scroll", this._onScroll, false);
+  /**
+   * Sets the results container hidden or visible. It's hidden by default.
+   * @param boolean aFlag
+   */
+  set hidden(aFlag) {
+    this.widget.setAttribute("hidden", aFlag);
+    this._splitter.setAttribute("hidden", aFlag);
   },
 
   /**
    * Gets the visibility state of the global search container.
    * @return boolean
    */
   get hidden()
     this.widget.getAttribute("hidden") == "true" ||
     this._splitter.getAttribute("hidden") == "true",
 
   /**
-   * Sets the results container hidden or visible. It's hidden by default.
-   * @param boolean aFlag
-   */
-  set hidden(aFlag) {
-    this.widget.setAttribute("hidden", aFlag);
-    this._splitter.setAttribute("hidden", aFlag);
-  },
-
-  /**
    * Hides and removes all items from this search container.
    */
   clearView: function() {
     this.hidden = true;
     this.empty();
-    window.dispatchEvent(document, "Debugger:GlobalSearch:ViewCleared");
   },
 
   /**
    * Selects the next found item in this container.
    * Does not change the currently focused node.
    */
   selectNext: function() {
     let totalLineResults = LineResults.size();
@@ -1637,208 +1551,173 @@ GlobalSearchView.prototype = Heritage.ex
       this._currentlyFocusedMatch = totalLineResults - 1;
     }
     this._onMatchClick({
       target: LineResults.getElementAtIndex(this._currentlyFocusedMatch)
     });
   },
 
   /**
-   * Allows searches to be scheduled and delayed to avoid redundant calls.
-   */
-  delayedSearch: true,
-
-  /**
    * Schedules searching for a string in all of the sources.
    *
-   * @param string aQuery
-   *        The string to search for.
-   */
-  scheduleSearch: function(aQuery) {
-    if (!this.delayedSearch) {
-      this.performSearch(aQuery);
-      return;
-    }
-    let delay = Math.max(GLOBAL_SEARCH_ACTION_MAX_DELAY / aQuery.length, 0);
-
-    window.clearTimeout(this._searchTimeout);
-    this._searchFunction = this._startSearch.bind(this, aQuery);
-    this._searchTimeout = window.setTimeout(this._searchFunction, delay);
-  },
-
-  /**
-   * Immediately searches for a string in all of the sources.
-   *
-   * @param string aQuery
+   * @param string aToken
    *        The string to search for.
+   * @param number aWait
+   *        The amount of milliseconds to wait until draining.
    */
-  performSearch: function(aQuery) {
-    window.clearTimeout(this._searchTimeout);
-    this._searchFunction = null;
-    this._startSearch(aQuery);
-  },
+  scheduleSearch: function(aToken, aWait) {
+    // The amount of time to wait for the requests to settle.
+    let maxDelay = GLOBAL_SEARCH_ACTION_MAX_DELAY;
+    let delay = aWait === undefined ? maxDelay / aToken.length : aWait;
 
-  /**
-   * Starts searching for a string in all of the sources.
-   *
-   * @param string aQuery
-   *        The string to search for.
-   */
-  _startSearch: function(aQuery) {
-    this._searchedToken = aQuery;
-
-    // Start fetching as many sources as possible, then perform the search.
-    DebuggerController.SourceScripts
-      .getTextForSources(DebuggerView.Sources.values)
-      .then(this._performGlobalSearch);
+    // Allow requests to settle down first.
+    setNamedTimeout("global-search", delay, () => {
+      // Start fetching as many sources as possible, then perform the search.
+      let urls = DebuggerView.Sources.values;
+      let sourcesFetched = DebuggerController.SourceScripts.getTextForSources(urls);
+      sourcesFetched.then(aSources => this._doSearch(aToken, aSources));
+    });
   },
 
   /**
    * Finds string matches in all the sources stored in the controller's cache,
-   * and groups them by location and line number.
+   * and groups them by url and line number.
+   *
+   * @param string aToken
+   *        The string to search for.
+   * @param array aSources
+   *        An array of [url, text] tuples for each source.
    */
-  _performGlobalSearch: function(aSources) {
-    // Get the currently searched token from the filtering input.
-    let token = this._searchedToken;
-
-    // Make sure we're actually searching for something.
-    if (!token) {
+  _doSearch: function(aToken, aSources) {
+    // Don't continue filtering if the searched token is an empty string.
+    if (!aToken) {
       this.clearView();
-      window.dispatchEvent(document, "Debugger:GlobalSearch:TokenEmpty");
       return;
     }
 
     // Search is not case sensitive, prepare the actual searched token.
-    let lowerCaseToken = token.toLowerCase();
-    let tokenLength = token.length;
+    let lowerCaseToken = aToken.toLowerCase();
+    let tokenLength = aToken.length;
 
-    // Prepare the results map, containing search details for each line.
+    // Create a Map containing search details for each source.
     let globalResults = new GlobalResults();
 
-    for (let [location, contents] of aSources) {
+    // Search for the specified token in each source's text.
+    for (let [url, text] of aSources) {
       // Verify that the search token is found anywhere in the source.
-      if (!contents.toLowerCase().contains(lowerCaseToken)) {
+      if (!text.toLowerCase().contains(lowerCaseToken)) {
         continue;
       }
-      let lines = contents.split("\n");
-      let sourceResults = new SourceResults();
+      // ...and if so, create a Map containing search details for each line.
+      let sourceResults = new SourceResults(url, globalResults);
 
-      for (let i = 0, len = lines.length; i < len; i++) {
-        let line = lines[i];
-        let lowerCaseLine = line.toLowerCase();
+      // Search for the specified token in each line's text.
+      text.split("\n").forEach((aString, aLine) => {
+        // Search is not case sensitive, prepare the actual searched line.
+        let lowerCaseLine = aString.toLowerCase();
 
-        // Search is not case sensitive, and is tied to each line in the source.
+        // Verify that the search token is found anywhere in this line.
         if (!lowerCaseLine.contains(lowerCaseToken)) {
-          continue;
+          return;
         }
+        // ...and if so, create a Map containing search details for each word.
+        let lineResults = new LineResults(aLine, sourceResults);
 
-        let lineNumber = i;
-        let lineResults = new LineResults();
+        // Search for the specified token this line's text.
+        lowerCaseLine.split(lowerCaseToken).reduce((aPrev, aCurr, aIndex, aArray) => {
+          let prevLength = aPrev.length;
+          let currLength = aCurr.length;
 
-        lowerCaseLine.split(lowerCaseToken).reduce((prev, curr, index, { length }) => {
-          let prevLength = prev.length;
-          let currLength = curr.length;
-          let unmatched = line.substr(prevLength, currLength);
+          // Everything before the token is unmatched.
+          let unmatched = aString.substr(prevLength, currLength);
           lineResults.add(unmatched);
 
-          if (index != length - 1) {
-            let matched = line.substr(prevLength + currLength, tokenLength);
-            let range = {
-              start: prevLength + currLength,
-              length: matched.length
-            };
+          // The lowered-case line was split by the lowered-case token. So,
+          // get the actual matched text from the original line's text.
+          if (aIndex != aArray.length - 1) {
+            let matched = aString.substr(prevLength + currLength, tokenLength);
+            let range = { start: prevLength + currLength, length: matched.length };
             lineResults.add(matched, range, true);
-            sourceResults.matchCount++;
           }
-          return prev + token + curr;
+
+          // Continue with the next sub-region in this line's text.
+          return aPrev + aToken + aCurr;
         }, "");
 
-        if (sourceResults.matchCount) {
-          sourceResults.add(lineNumber, lineResults);
+        if (lineResults.matchCount) {
+          sourceResults.add(lineResults);
         }
-      }
+      });
+
       if (sourceResults.matchCount) {
-        globalResults.add(location, sourceResults);
+        globalResults.add(sourceResults);
       }
     }
 
-    // Empty this container to rebuild the search results.
-    this.empty();
-
-    // Signal if there are any matches, and the rebuild the results.
-    if (globalResults.itemCount) {
+    // Rebuild the results, then signal if there are any matches.
+    if (globalResults.matchCount) {
       this.hidden = false;
       this._currentlyFocusedMatch = -1;
       this._createGlobalResultsUI(globalResults);
-      window.dispatchEvent(document, "Debugger:GlobalSearch:MatchFound");
+      window.emit(EVENTS.GLOBAL_SEARCH_MATCH_FOUND);
     } else {
-      window.dispatchEvent(document, "Debugger:GlobalSearch:MatchNotFound");
+      window.emit(EVENTS.GLOBAL_SEARCH_MATCH_NOT_FOUND);
     }
   },
 
   /**
    * Creates global search results entries and adds them to this container.
    *
    * @param GlobalResults aGlobalResults
    *        An object containing all source results, grouped by source location.
    */
   _createGlobalResultsUI: function(aGlobalResults) {
     let i = 0;
 
-    for (let [location, sourceResults] in aGlobalResults) {
+    for (let sourceResults in aGlobalResults) {
       if (i++ == 0) {
-        this._createSourceResultsUI(location, sourceResults, true);
+        this._createSourceResultsUI(sourceResults);
       } else {
         // Dispatch subsequent document manipulation operations, to avoid
         // blocking the main thread when a large number of search results
         // is found, thus giving the impression of faster searching.
         Services.tm.currentThread.dispatch({ run:
-          this._createSourceResultsUI.bind(this, location, sourceResults) }, 0);
+          this._createSourceResultsUI.bind(this, sourceResults)
+        }, 0);
       }
     }
   },
 
   /**
    * Creates source search results entries and adds them to this container.
    *
-   * @param string aLocation
-   *        The location of the source.
    * @param SourceResults aSourceResults
    *        An object containing all the matched lines for a specific source.
-   * @param boolean aExpandFlag
-   *        True to expand the source results.
    */
-  _createSourceResultsUI: function(aLocation, aSourceResults, aExpandFlag) {
+  _createSourceResultsUI: function(aSourceResults) {
     // Append a source results item to this container.
-    let sourceResultsItem = this.push([aLocation, aSourceResults.matchCount], {
+    this.push([], {
       index: -1, /* specifies on which position should the item be appended */
       relaxed: true, /* this container should allow dupes & degenerates */
       attachment: {
-        sourceResults: aSourceResults,
-        expandFlag: aExpandFlag
+        sourceResults: aSourceResults
       }
     });
   },
 
   /**
    * Customization function for creating an item's UI.
    *
    * @param nsIDOMNode aElementNode
    *        The element associated with the displayed item.
    * @param any aAttachment
    *        Some attached primitive/object.
-   * @param string aLocation
-   *        The source result's location.
-   * @param string aMatchCount
-   *        The source result's match count.
    */
-  _createItemView: function(aElementNode, aAttachment, aLocation, aMatchCount) {
-    let { sourceResults, expandFlag } = aAttachment;
-
-    sourceResults.createView(aElementNode, aLocation, aMatchCount, expandFlag, {
+  _createItemView: function(aElementNode, aAttachment) {
+    aAttachment.sourceResults.createView(aElementNode, {
       onHeaderClick: this._onHeaderClick,
       onLineClick: this._onLineClick,
       onMatchClick: this._onMatchClick
     });
   },
 
   /**
    * The click listener for a results header.
@@ -1859,65 +1738,37 @@ GlobalSearchView.prototype = Heritage.ex
   /**
    * The click listener for a result match.
    */
   _onMatchClick: function(e) {
     if (e instanceof Event) {
       e.preventDefault();
       e.stopPropagation();
     }
+
     let target = e.target;
     let sourceResultsItem = SourceResults.getItemForElement(target);
     let lineResultsItem = LineResults.getItemForElement(target);
 
     sourceResultsItem.instance.expand();
     this._currentlyFocusedMatch = LineResults.indexOfElement(target);
     this._scrollMatchIntoViewIfNeeded(target);
     this._bounceMatch(target);
 
-    let location = sourceResultsItem.location;
-    let lineNumber = lineResultsItem.lineNumber;
-    DebuggerView.updateEditor(location, lineNumber + 1, { noDebug: true });
+    let url = sourceResultsItem.instance.url;
+    let line = lineResultsItem.instance.line;
+    DebuggerView.setEditorLocation(url, line + 1, { noDebug: true });
 
     let editor = DebuggerView.editor;
     let offset = editor.getCaretOffset();
     let { start, length } = lineResultsItem.lineData.range;
     editor.setSelection(offset + start, offset + start + length);
   },
 
   /**
-   * The scroll listener for the global search container.
-   */
-  _onScroll: function(e) {
-    for (let item in this) {
-      this._expandResultsIfNeeded(item.target);
-    }
-  },
-
-  /**
-   * Expands the source results it they are currently visible.
-   *
-   * @param nsIDOMNode aTarget
-   *        The element associated with the displayed item.
-   */
-  _expandResultsIfNeeded: function(aTarget) {
-    let sourceResultsItem = SourceResults.getItemForElement(aTarget);
-    if (sourceResultsItem.instance.toggled ||
-        sourceResultsItem.instance.expanded) {
-      return;
-    }
-    let { top, height } = aTarget.getBoundingClientRect();
-    let { clientHeight } = this.widget._parent;
-
-    if (top - height <= clientHeight || this._forceExpandResults) {
-      sourceResultsItem.instance.expand();
-    }
-  },
-
-  /**
    * Scrolls a match into view if not already visible.
    *
    * @param nsIDOMNode aMatch
    *        The match to scroll into view.
    */
   _scrollMatchIntoViewIfNeeded: function(aMatch) {
     // TODO: Accessing private widget properties. Figure out what's the best
     // way to expose such things. Bug 876271.
@@ -1927,353 +1778,339 @@ GlobalSearchView.prototype = Heritage.ex
 
   /**
    * Starts a bounce animation for a match.
    *
    * @param nsIDOMNode aMatch
    *        The match to start a bounce animation for.
    */
   _bounceMatch: function(aMatch) {
-    Services.tm.currentThread.dispatch({ run: function() {
+    Services.tm.currentThread.dispatch({ run: () => {
       aMatch.addEventListener("transitionend", function onEvent() {
         aMatch.removeEventListener("transitionend", onEvent);
         aMatch.removeAttribute("focused");
       });
       aMatch.setAttribute("focused", "");
     }}, 0);
     aMatch.setAttribute("focusing", "");
   },
 
   _splitter: null,
   _currentlyFocusedMatch: -1,
-  _forceExpandResults: false,
-  _searchTimeout: null,
-  _searchFunction: null,
-  _searchedToken: ""
+  _forceExpandResults: false
 });
 
 /**
  * An object containing all source results, grouped by source location.
  * Iterable via "for (let [location, sourceResults] in globalResults) { }".
  */
 function GlobalResults() {
-  this._store = new Map();
+  this._store = [];
   SourceResults._itemsByElement = new Map();
   LineResults._itemsByElement = new Map();
 }
 
 GlobalResults.prototype = {
   /**
    * Adds source results to this store.
    *
-   * @param string aLocation
-   *        The location of the source.
    * @param SourceResults aSourceResults
-   *        An object containing all the matched lines for a specific source.
+   *        An object containing search results for a specific source.
    */
-  add: function(aLocation, aSourceResults) {
-    this._store.set(aLocation, aSourceResults);
+  add: function(aSourceResults) {
+    this._store.push(aSourceResults);
   },
 
   /**
    * Gets the number of source results in this store.
    */
-  get itemCount() this._store.size,
-
-  _store: null
+  get matchCount() this._store.length
 };
 
 /**
  * An object containing all the matched lines for a specific source.
  * Iterable via "for (let [lineNumber, lineResults] in sourceResults) { }".
+ *
+ * @param string aUrl
+ *        The target source url.
+ * @param GlobalResults aGlobalResults
+ *        An object containing all source results, grouped by source location.
  */
-function SourceResults() {
-  this._store = new Map();
-  this.matchCount = 0;
+function SourceResults(aUrl, aGlobalResults) {
+  this.url = aUrl;
+  this._globalResults = aGlobalResults;
+  this._store = [];
 }
 
 SourceResults.prototype = {
   /**
    * Adds line results to this store.
    *
-   * @param number aLineNumber
-   *        The line location in the source.
    * @param LineResults aLineResults
-   *        An object containing all the matches for a specific line.
+   *        An object containing search results for a specific line.
    */
-  add: function(aLineNumber, aLineResults) {
-    this._store.set(aLineNumber, aLineResults);
+  add: function(aLineResults) {
+    this._store.push(aLineResults);
   },
 
   /**
-   * The number of matches in this store. One line may have multiple matches.
+   * Gets the number of line results in this store.
    */
-  matchCount: -1,
+  get matchCount() this._store.length,
 
   /**
    * Expands the element, showing all the added details.
    */
   expand: function() {
-    this._target.resultsContainer.removeAttribute("hidden")
-    this._target.arrow.setAttribute("open", "");
+    this._resultsContainer.removeAttribute("hidden");
+    this._arrow.setAttribute("open", "");
   },
 
   /**
    * Collapses the element, hiding all the added details.
    */
   collapse: function() {
-    this._target.resultsContainer.setAttribute("hidden", "true");
-    this._target.arrow.removeAttribute("open");
+    this._resultsContainer.setAttribute("hidden", "true");
+    this._arrow.removeAttribute("open");
   },
 
   /**
    * Toggles between the element collapse/expand state.
    */
   toggle: function(e) {
-    if (e instanceof Event) {
-      this._userToggled = true;
-    }
     this.expanded ^= 1;
   },
 
   /**
-   * Relaxes the auto-expand rules to always show as many results as possible.
-   */
-  alwaysExpand: true,
-
-  /**
    * Gets this element's expanded state.
    * @return boolean
    */
   get expanded()
-    this._target.resultsContainer.getAttribute("hidden") != "true" &&
-    this._target.arrow.hasAttribute("open"),
+    this._resultsContainer.getAttribute("hidden") != "true" &&
+    this._arrow.hasAttribute("open"),
 
   /**
    * Sets this element's expanded state.
    * @param boolean aFlag
    */
   set expanded(aFlag) this[aFlag ? "expand" : "collapse"](),
 
   /**
-   * Returns if this element was ever toggled via user interaction.
-   * @return boolean
-   */
-  get toggled() this._userToggled,
-
-  /**
    * Gets the element associated with this item.
    * @return nsIDOMNode
    */
   get target() this._target,
 
   /**
    * Customization function for creating this item's UI.
    *
    * @param nsIDOMNode aElementNode
    *        The element associated with the displayed item.
-   * @param string aLocation
-   *        The source result's location.
-   * @param string aMatchCount
-   *        The source result's match count.
-   * @param boolean aExpandFlag
-   *        True to expand the source results.
    * @param object aCallbacks
    *        An object containing all the necessary callback functions:
    *          - onHeaderClick
    *          - onMatchClick
    */
-  createView: function(aElementNode, aLocation, aMatchCount, aExpandFlag, aCallbacks) {
+  createView: function(aElementNode, aCallbacks) {
     this._target = aElementNode;
 
-    let arrow = document.createElement("box");
+    let arrow = this._arrow = document.createElement("box");
     arrow.className = "arrow";
 
     let locationNode = document.createElement("label");
     locationNode.className = "plain dbg-results-header-location";
-    locationNode.setAttribute("value", SourceUtils.trimUrlLength(aLocation));
+    locationNode.setAttribute("value", this.url);
 
     let matchCountNode = document.createElement("label");
     matchCountNode.className = "plain dbg-results-header-match-count";
-    matchCountNode.setAttribute("value", "(" + aMatchCount + ")");
+    matchCountNode.setAttribute("value", "(" + this.matchCount + ")");
 
-    let resultsHeader = document.createElement("hbox");
+    let resultsHeader = this._resultsHeader = document.createElement("hbox");
     resultsHeader.className = "dbg-results-header";
     resultsHeader.setAttribute("align", "center")
     resultsHeader.appendChild(arrow);
     resultsHeader.appendChild(locationNode);
     resultsHeader.appendChild(matchCountNode);
     resultsHeader.addEventListener("click", aCallbacks.onHeaderClick, false);
 
-    let resultsContainer = document.createElement("vbox");
+    let resultsContainer = this._resultsContainer = document.createElement("vbox");
     resultsContainer.className = "dbg-results-container";
     resultsContainer.setAttribute("hidden", "true");
 
-    for (let [lineNumber, lineResults] of this._store) {
-      lineResults.createView(resultsContainer, lineNumber, aCallbacks)
+    // Create lines search results entries and add them to this container.
+    // Afterwards, if the number of matches is reasonable, expand this
+    // container automatically.
+    for (let lineResults of this._store) {
+      lineResults.createView(resultsContainer, aCallbacks);
     }
-
-    aElementNode.arrow = arrow;
-    aElementNode.resultsHeader = resultsHeader;
-    aElementNode.resultsContainer = resultsContainer;
-
-    if ((aExpandFlag || this.alwaysExpand) &&
-         aMatchCount < GLOBAL_SEARCH_EXPAND_MAX_RESULTS) {
+    if (this.matchCount < GLOBAL_SEARCH_EXPAND_MAX_RESULTS) {
       this.expand();
     }
 
     let resultsBox = document.createElement("vbox");
     resultsBox.setAttribute("flex", "1");
     resultsBox.appendChild(resultsHeader);
     resultsBox.appendChild(resultsContainer);
 
-    aElementNode.id = "source-results-" + aLocation;
+    aElementNode.id = "source-results-" + this.url;
     aElementNode.className = "dbg-source-results";
     aElementNode.appendChild(resultsBox);
 
-    SourceResults._itemsByElement.set(aElementNode, {
-      location: aLocation,
-      matchCount: aMatchCount,
-      autoExpand: aExpandFlag,
-      instance: this
-    });
+    SourceResults._itemsByElement.set(aElementNode, { instance: this });
   },
 
+  url: "",
+  _globalResults: null,
   _store: null,
   _target: null,
-  _userToggled: false
+  _arrow: null,
+  _resultsHeader: null,
+  _resultsContainer: null
 };
 
 /**
  * An object containing all the matches for a specific line.
  * Iterable via "for (let chunk in lineResults) { }".
+ *
+ * @param number aLine
+ *        The target line in the source.
+ * @param SourceResults aSourceResults
+ *        An object containing all the matched lines for a specific source.
  */
-function LineResults() {
+function LineResults(aLine, aSourceResults) {
+  this.line = aLine;
+  this._sourceResults = aSourceResults;
   this._store = [];
+  this._matchCount = 0;
 }
 
 LineResults.prototype = {
   /**
    * Adds string details to this store.
    *
    * @param string aString
    *        The text contents chunk in the line.
    * @param object aRange
    *        An object containing the { start, length } of the chunk.
    * @param boolean aMatchFlag
    *        True if the chunk is a matched string, false if just text content.
    */
   add: function(aString, aRange, aMatchFlag) {
-    this._store.push({
-      string: aString,
-      range: aRange,
-      match: !!aMatchFlag
-    });
+    this._store.push({ string: aString, range: aRange, match: !!aMatchFlag });
+    this._matchCount += aMatchFlag ? 1 : 0;
   },
 
   /**
+   * Gets the number of word results in this store.
+   */
+  get matchCount() this._matchCount,
+
+  /**
    * Gets the element associated with this item.
    * @return nsIDOMNode
    */
   get target() this._target,
 
   /**
    * Customization function for creating this item's UI.
    *
-   * @param nsIDOMNode aContainer
+   * @param nsIDOMNode aElementNode
    *        The element associated with the displayed item.
-   * @param number aLineNumber
-   *        The line location in the source.
    * @param object aCallbacks
    *        An object containing all the necessary callback functions:
    *          - onMatchClick
    *          - onLineClick
    */
-  createView: function(aContainer, aLineNumber, aCallbacks) {
-    this._target = aContainer;
+  createView: function(aElementNode, aCallbacks) {
+    this._target = aElementNode;
 
     let lineNumberNode = document.createElement("label");
+    lineNumberNode.className = "plain dbg-results-line-number";
+    lineNumberNode.classList.add("devtools-monospace");
+    lineNumberNode.setAttribute("value", this.line + 1);
+
     let lineContentsNode = document.createElement("hbox");
+    lineContentsNode.className = "light list-widget-item dbg-results-line-contents";
+    lineContentsNode.classList.add("devtools-monospace");
+    lineContentsNode.setAttribute("flex", "1");
+
     let lineString = "";
     let lineLength = 0;
     let firstMatch = null;
 
-    lineNumberNode.className = "plain dbg-results-line-number devtools-monospace";
-    lineNumberNode.setAttribute("value", aLineNumber + 1);
-    lineContentsNode.className = "light list-widget-item devtools-monospace" +
-                                 " dbg-results-line-contents";
-    lineContentsNode.setAttribute("flex", "1");
-
-    for (let chunk of this._store) {
-      let { string, range, match } = chunk;
+    for (let lineChunk of this._store) {
+      let { string, range, match } = lineChunk;
       lineString = string.substr(0, GLOBAL_SEARCH_LINE_MAX_LENGTH - lineLength);
       lineLength += string.length;
 
-      let label = document.createElement("label");
-      label.className = "plain dbg-results-line-contents-string";
-      label.setAttribute("value", lineString);
-      label.setAttribute("match", match);
-      lineContentsNode.appendChild(label);
+      let lineChunkNode = document.createElement("label");
+      lineChunkNode.className = "plain dbg-results-line-contents-string";
+      lineChunkNode.setAttribute("value", lineString);
+      lineChunkNode.setAttribute("match", match);
+      lineContentsNode.appendChild(lineChunkNode);
 
       if (match) {
-        this._entangleMatch(aLineNumber, label, chunk);
-        label.addEventListener("click", aCallbacks.onMatchClick, false);
-        firstMatch = firstMatch || label;
+        this._entangleMatch(lineChunkNode, lineChunk);
+        lineChunkNode.addEventListener("click", aCallbacks.onMatchClick, false);
+        firstMatch = firstMatch || lineChunkNode;
       }
       if (lineLength >= GLOBAL_SEARCH_LINE_MAX_LENGTH) {
         lineContentsNode.appendChild(this._ellipsis.cloneNode());
         break;
       }
     }
 
     this._entangleLine(lineContentsNode, firstMatch);
     lineContentsNode.addEventListener("click", aCallbacks.onLineClick, false);
 
     let searchResult = document.createElement("hbox");
     searchResult.className = "dbg-search-result";
     searchResult.appendChild(lineNumberNode);
     searchResult.appendChild(lineContentsNode);
-    aContainer.appendChild(searchResult);
+
+    aElementNode.appendChild(searchResult);
   },
 
   /**
    * Handles a match while creating the view.
-   * @param number aLineNumber
    * @param nsIDOMNode aNode
    * @param object aMatchChunk
    */
-  _entangleMatch: function(aLineNumber, aNode, aMatchChunk) {
+  _entangleMatch: function(aNode, aMatchChunk) {
     LineResults._itemsByElement.set(aNode, {
-      lineNumber: aLineNumber,
+      instance: this,
       lineData: aMatchChunk
     });
   },
 
   /**
    * Handles a line while creating the view.
    * @param nsIDOMNode aNode
    * @param nsIDOMNode aFirstMatch
    */
   _entangleLine: function(aNode, aFirstMatch) {
     LineResults._itemsByElement.set(aNode, {
+      instance: this,
       firstMatch: aFirstMatch,
-      nonenumerable: true
+      ignored: true
     });
   },
 
   /**
    * An nsIDOMNode label with an ellipsis value.
    */
   _ellipsis: (function() {
     let label = document.createElement("label");
     label.className = "plain dbg-results-line-contents-string";
     label.setAttribute("value", L10N.ellipsis);
     return label;
   })(),
 
+  line: 0,
+  _sourceResults: null,
   _store: null,
   _target: null
 };
 
 /**
  * A generator-iterator over the global, source or line results.
  */
 GlobalResults.prototype.__iterator__ =
@@ -2289,31 +2126,31 @@ LineResults.prototype.__iterator__ = fun
  *
  * @param nsIDOMNode aElement
  *        The element used to identify the item.
  * @return object
  *         The matched item, or null if nothing is found.
  */
 SourceResults.getItemForElement =
 LineResults.getItemForElement = function(aElement) {
-  return WidgetMethods.getItemForElement.call(this, aElement);
+  return WidgetMethods.getItemForElement.call(this, aElement, { noSiblings: true });
 };
 
 /**
  * Gets the element associated with a particular item at a specified index.
  *
  * @param number aIndex
  *        The index used to identify the item.
  * @return nsIDOMNode
  *         The matched element, or null if nothing is found.
  */
 SourceResults.getElementAtIndex =
 LineResults.getElementAtIndex = function(aIndex) {
   for (let [element, item] of this._itemsByElement) {
-    if (!item.nonenumerable && !aIndex--) {
+    if (!item.ignored && !aIndex--) {
       return element;
     }
   }
   return null;
 };
 
 /**
  * Gets the index of an item associated with the specified element.
@@ -2325,34 +2162,34 @@ LineResults.getElementAtIndex = function
  */
 SourceResults.indexOfElement =
 LineResults.indexOfElement = function(aElement) {
   let count = 0;
   for (let [element, item] of this._itemsByElement) {
     if (element == aElement) {
       return count;
     }
-    if (!item.nonenumerable) {
+    if (!item.ignored) {
       count++;
     }
   }
   return -1;
 };
 
 /**
  * Gets the number of cached items associated with a specified element.
  *
  * @return number
  *         The number of key/value pairs in the corresponding map.
  */
 SourceResults.size =
 LineResults.size = function() {
   let count = 0;
   for (let [, item] of this._itemsByElement) {
-    if (!item.nonenumerable) {
+    if (!item.ignored) {
       count++;
     }
   }
   return count;
 };
 
 /**
  * Preliminary setup for the DebuggerView object.
--- a/browser/devtools/debugger/debugger-toolbar.js
+++ b/browser/devtools/debugger/debugger-toolbar.js
@@ -228,30 +228,31 @@ OptionsView.prototype = {
     // Nothing to do here yet.
   },
 
   /**
    * Listener handling the 'gear menu' popup showing event.
    */
   _onPopupShowing: function() {
     this._button.setAttribute("open", "true");
+    window.emit(EVENTS.OPTIONS_POPUP_SHOWING);
   },
 
   /**
    * Listener handling the 'gear menu' popup hiding event.
    */
   _onPopupHiding: function() {
     this._button.removeAttribute("open");
   },
 
   /**
    * Listener handling the 'gear menu' popup hidden event.
    */
   _onPopupHidden: function() {
-    window.dispatchEvent(document, "Debugger:OptionsPopupHidden");
+    window.emit(EVENTS.OPTIONS_POPUP_HIDDEN);
   },
 
   /**
    * Listener handling the 'pause on exceptions' menuitem command.
    */
   _togglePauseOnExceptions: function() {
     Prefs.pauseOnExceptions =
       this._pauseOnExceptionsItem.getAttribute("checked") == "true";
@@ -297,30 +298,26 @@ OptionsView.prototype = {
 
     DebuggerView.Variables.searchEnabled = pref;
   },
 
   /**
    * Listener handling the 'show original source' menuitem command.
    */
   _toggleShowOriginalSource: function() {
-    function reconfigure() {
-      window.removeEventListener("Debugger:OptionsPopupHidden", reconfigure, false);
+    let pref = Prefs.sourceMapsEnabled =
+      this._showOriginalSourceItem.getAttribute("checked") == "true";
 
+    // Don't block the UI while reconfiguring the server.
+    window.once(EVENTS.OPTIONS_POPUP_HIDDEN, () => {
       // The popup panel needs more time to hide after triggering onpopuphidden.
       window.setTimeout(() => {
         DebuggerController.reconfigureThread(pref);
       }, POPUP_HIDDEN_DELAY);
-    }
-
-    let pref = Prefs.sourceMapsEnabled =
-      this._showOriginalSourceItem.getAttribute("checked") == "true";
-
-    // Don't block the UI while reconfiguring the server.
-    window.addEventListener("Debugger:OptionsPopupHidden", reconfigure, false);
+    }, false);
   },
 
   _button: null,
   _pauseOnExceptionsItem: null,
   _showPanesOnStartupItem: null,
   _showVariablesOnlyEnumItem: null,
   _showVariablesFilterBoxItem: null,
   _showOriginalSourceItem: null
@@ -340,17 +337,16 @@ ChromeGlobalsView.prototype = Heritage.e
   /**
    * Initialization function, called when the debugger is started.
    */
   initialize: function() {
     dumpn("Initializing the ChromeGlobalsView");
 
     this.widget = document.getElementById("chrome-globals");
     this.emptyText = L10N.getStr("noGlobalsText");
-    this.unavailableText = L10N.getStr("noMatchingGlobalsText");
 
     this.widget.addEventListener("select", this._onSelect, false);
     this.widget.addEventListener("click", this._onClick, false);
 
     // Show an empty label by default.
     this.empty();
   },
 
@@ -425,148 +421,158 @@ StackFramesView.prototype = Heritage.ext
     this.widget.removeEventListener("select", this._onSelect, false);
     this.widget.removeEventListener("scroll", this._onScroll, true);
     window.removeEventListener("resize", this._onScroll, true);
   },
 
   /**
    * Adds a frame in this stackframes container.
    *
-   * @param string aFrameTitle
-   *        The frame title to be displayed in the list.
-   * @param string aSourceLocation
-   *        The source location to be displayed in the list.
-   * @param string aLineNumber
-   *        The line number to be displayed in the list.
+   * @param string aTitle
+   *        The frame title (function name).
+   * @param string aUrl
+   *        The frame source url.
+   * @param string aLine
+   *        The frame line number.
    * @param number aDepth
-   *        The frame depth specified by the debugger.
+   *        The frame depth in the stack.
    * @param boolean aIsBlackBoxed
    *        Whether or not the frame is black boxed.
    */
-  addFrame: function(aFrameTitle, aSourceLocation, aLineNumber, aDepth, aIsBlackBoxed) {
+  addFrame: function(aTitle, aUrl, aLine, aDepth, aIsBlackBoxed) {
+    // Blackboxed stack frames are collapsed into a single entry in
+    // the view. By convention, only the first frame is displayed.
+    if (aIsBlackBoxed) {
+      if (this._prevBlackBoxedUrl == aUrl) {
+        return;
+      }
+      this._prevBlackBoxedUrl = aUrl;
+    } else {
+      this._prevBlackBoxedUrl = null;
+    }
+
     // Create the element node and menu entry for the stack frame item.
     let frameView = this._createFrameView.apply(this, arguments);
     let menuEntry = this._createMenuEntry.apply(this, arguments);
 
     // Append a stack frame item to this container.
-    this.push([frameView], {
+    this.push([frameView, aTitle, aUrl], {
       index: 0, /* specifies on which position should the item be appended */
       attachment: {
         popup: menuEntry,
         depth: aDepth
       },
       attributes: [
-        ["contextmenu", "stackframesMenupopup"],
-        ["tooltiptext", aSourceLocation]
+        ["contextmenu", "stackframesMenupopup"]
       ],
       // Make sure that when the stack frame item is removed, the corresponding
       // menuitem and command are also destroyed.
       finalize: this._onStackframeRemoved
     });
   },
 
   /**
    * Selects the frame at the specified depth in this container.
    * @param number aDepth
    */
   set selectedDepth(aDepth) {
-    this.selectedItem = (aItem) => aItem.attachment.depth == aDepth;
+    this.selectedItem = aItem => aItem.attachment.depth == aDepth;
   },
 
   /**
    * Specifies if the active thread has more frames that need to be loaded.
    */
   dirty: false,
 
   /**
    * Customization function for creating an item's UI.
    *
-   * @param string aFrameTitle
+   * @param string aTitle
    *        The frame title to be displayed in the list.
-   * @param string aSourceLocation
-   *        The source location to be displayed in the list.
-   * @param string aLineNumber
-   *        The line number to be displayed in the list.
+   * @param string aUrl
+   *        The frame source url.
+   * @param string aLine
+   *        The frame line number.
    * @param number aDepth
-   *        The frame depth specified by the debugger.
+   *        The frame depth in the stack.
    * @param boolean aIsBlackBoxed
    *        Whether or not the frame is black boxed.
    * @return nsIDOMNode
    *         The stack frame view.
    */
-  _createFrameView: function(aFrameTitle, aSourceLocation, aLineNumber, aDepth, aIsBlackBoxed) {
+  _createFrameView: function(aTitle, aUrl, aLine, aDepth, aIsBlackBoxed) {
     let container = document.createElement("hbox");
     container.id = "stackframe-" + aDepth;
     container.className = "dbg-stackframe";
 
     let frameDetails = SourceUtils.trimUrlLength(
-      SourceUtils.getSourceLabel(aSourceLocation),
+      SourceUtils.getSourceLabel(aUrl),
       STACK_FRAMES_SOURCE_URL_MAX_LENGTH,
       STACK_FRAMES_SOURCE_URL_TRIM_SECTION);
 
     if (aIsBlackBoxed) {
       container.classList.add("dbg-stackframe-black-boxed");
     } else {
       let frameTitleNode = document.createElement("label");
       frameTitleNode.className = "plain dbg-stackframe-title breadcrumbs-widget-item-tag";
-      frameTitleNode.setAttribute("value", aFrameTitle);
+      frameTitleNode.setAttribute("value", aTitle);
       container.appendChild(frameTitleNode);
 
-      frameDetails += SEARCH_LINE_FLAG + aLineNumber;
+      frameDetails += SEARCH_LINE_FLAG + aLine;
     }
 
     let frameDetailsNode = document.createElement("label");
     frameDetailsNode.className = "plain dbg-stackframe-details breadcrumbs-widget-item-id";
     frameDetailsNode.setAttribute("value", frameDetails);
     container.appendChild(frameDetailsNode);
 
     return container;
   },
 
   /**
    * Customization function for populating an item's context menu.
    *
-   * @param string aFrameTitle
+   * @param string aTitle
    *        The frame title to be displayed in the list.
-   * @param string aSourceLocation
-   *        The source location to be displayed in the list.
-   * @param string aLineNumber
-   *        The line number to be displayed in the list.
+   * @param string aUrl
+   *        The frame source url.
+   * @param string aLine
+   *        The frame line number.
    * @param number aDepth
-   *        The frame depth specified by the debugger.
+   *        The frame depth in the stack.
    * @param boolean aIsBlackBoxed
    *        Whether or not the frame is black boxed.
    * @return object
    *         An object containing the stack frame command and menu item.
    */
-  _createMenuEntry: function(aFrameTitle, aSourceLocation, aLineNumber, aDepth, aIsBlackBoxed) {
-    let frameDescription =
-      SourceUtils.trimUrlLength(
-        SourceUtils.getSourceLabel(aSourceLocation),
-        STACK_FRAMES_POPUP_SOURCE_URL_MAX_LENGTH,
-        STACK_FRAMES_POPUP_SOURCE_URL_TRIM_SECTION) + SEARCH_LINE_FLAG + aLineNumber;
+  _createMenuEntry: function(aTitle, aUrl, aLine, aDepth, aIsBlackBoxed) {
+    let frameDescription = SourceUtils.trimUrlLength(
+      SourceUtils.getSourceLabel(aUrl),
+      STACK_FRAMES_POPUP_SOURCE_URL_MAX_LENGTH,
+      STACK_FRAMES_POPUP_SOURCE_URL_TRIM_SECTION) +
+      SEARCH_LINE_FLAG + aLine;
 
     let prefix = "sf-cMenu-"; // "stackframes context menu"
     let commandId = prefix + aDepth + "-" + "-command";
     let menuitemId = prefix + aDepth + "-" + "-menuitem";
 
     let command = document.createElement("command");
     command.id = commandId;
     command.addEventListener("command", () => this.selectedDepth = aDepth, false);
 
     let menuitem = document.createElement("menuitem");
     menuitem.id = menuitemId;
     menuitem.className = "dbg-stackframe-menuitem";
     menuitem.setAttribute("type", "checkbox");
     menuitem.setAttribute("command", commandId);
-    menuitem.setAttribute("tooltiptext", aSourceLocation);
+    menuitem.setAttribute("tooltiptext", aUrl);
 
     let labelNode = document.createElement("label");
     labelNode.className = "plain dbg-stackframe-menuitem-title";
-    labelNode.setAttribute("value", aFrameTitle);
+    labelNode.setAttribute("value", aTitle);
     labelNode.setAttribute("flex", "1");
 
     let descriptionNode = document.createElement("label");
     descriptionNode.className = "plain dbg-stackframe-menuitem-details";
     descriptionNode.setAttribute("value", frameDescription);
 
     menuitem.appendChild(labelNode);
     menuitem.appendChild(descriptionNode);
@@ -588,26 +594,29 @@ StackFramesView.prototype = Heritage.ext
    */
   _onStackframeRemoved: function(aItem) {
     dumpn("Finalizing stackframe item: " + aItem);
 
     // Destroy the context menu item for the stack frame.
     let contextItem = aItem.attachment.popup;
     contextItem.command.remove();
     contextItem.menuitem.remove();
+
+    // Forget the previously blackboxed stack frame url.
+    this._prevBlackBoxedUrl = null;
   },
 
   /**
    * The select listener for the stackframes container.
    */
   _onSelect: function(e) {
     let stackframeItem = this.selectedItem;
     if (stackframeItem) {
       // The container is not empty and an actual item was selected.
-      gStackFrames.selectFrame(stackframeItem.attachment.depth);
+      DebuggerController.StackFrames.selectFrame(stackframeItem.attachment.depth);
 
       // Update the context menu to show the currently selected stackframe item
       // as a checked entry.
       for (let otherItem in this) {
         if (otherItem != stackframeItem) {
           otherItem.attachment.popup.menuitem.removeAttribute("checked");
         } else {
           otherItem.attachment.popup.menuitem.setAttribute("checked", "");
@@ -619,18 +628,18 @@ StackFramesView.prototype = Heritage.ext
   /**
    * The scroll listener for the stackframes container.
    */
   _onScroll: function() {
     // Update the stackframes container only if we have to.
     if (!this.dirty) {
       return;
     }
-    window.clearTimeout(this._scrollTimeout);
-    this._scrollTimeout = window.setTimeout(this._afterScroll, STACK_FRAMES_SCROLL_DELAY);
+    // Allow requests to settle down first.
+    setNamedTimeout("stack-scroll", STACK_FRAMES_SCROLL_DELAY, this._afterScroll);
   },
 
   /**
    * Requests the addition of more frames from the controller.
    */
   _afterScroll: function() {
     // TODO: Accessing private widget properties. Figure out what's the best
     // way to expose such things. Bug 876271.
@@ -646,17 +655,17 @@ StackFramesView.prototype = Heritage.ext
 
       // Loads more stack frames from the debugger server cache.
       DebuggerController.StackFrames.addMoreFrames();
     }
   },
 
   _commandset: null,
   _menupopup: null,
-  _scrollTimeout: null
+  _prevBlackBoxedUrl: null
 });
 
 /**
  * Utility functions for handling stackframes.
  */
 let StackFrameUtils = {
   /**
    * Create a textual representation for the specified stack frame
@@ -712,17 +721,17 @@ let StackFrameUtils = {
 
 /**
  * Functions handling the filtering UI.
  */
 function FilterView() {
   dumpn("FilterView was instantiated");
 
   this._onClick = this._onClick.bind(this);
-  this._onSearch = this._onSearch.bind(this);
+  this._onInput = this._onInput.bind(this);
   this._onKeyPress = this._onKeyPress.bind(this);
   this._onBlur = this._onBlur.bind(this);
 }
 
 FilterView.prototype = {
   /**
    * Initialization function, called when the debugger is started.
    */
@@ -746,18 +755,18 @@ FilterView.prototype = {
     this._fileSearchKey = DevtoolsHelpers.prettyKey(document.getElementById("fileSearchKey"), true);
     this._globalSearchKey = DevtoolsHelpers.prettyKey(document.getElementById("globalSearchKey"), true);
     this._filteredFunctionsKey = DevtoolsHelpers.prettyKey(document.getElementById("functionSearchKey"), true);
     this._tokenSearchKey = DevtoolsHelpers.prettyKey(document.getElementById("tokenSearchKey"), true);
     this._lineSearchKey = DevtoolsHelpers.prettyKey(document.getElementById("lineSearchKey"), true);
     this._variableSearchKey = DevtoolsHelpers.prettyKey(document.getElementById("variableSearchKey"), true);
 
     this._searchbox.addEventListener("click", this._onClick, false);
-    this._searchbox.addEventListener("select", this._onSearch, false);
-    this._searchbox.addEventListener("input", this._onSearch, false);
+    this._searchbox.addEventListener("select", this._onInput, false);
+    this._searchbox.addEventListener("input", this._onInput, false);
     this._searchbox.addEventListener("keypress", this._onKeyPress, false);
     this._searchbox.addEventListener("blur", this._onBlur, false);
 
     this._globalOperatorButton.setAttribute("label", SEARCH_GLOBAL_FLAG);
     this._functionOperatorButton.setAttribute("label", SEARCH_FUNCTION_FLAG);
     this._tokenOperatorButton.setAttribute("label", SEARCH_TOKEN_FLAG);
     this._lineOperatorButton.setAttribute("label", SEARCH_LINE_FLAG);
     this._variableOperatorButton.setAttribute("label", SEARCH_VARIABLE_FLAG);
@@ -785,18 +794,18 @@ FilterView.prototype = {
 
   /**
    * Destruction function, called when the debugger is closed.
    */
   destroy: function() {
     dumpn("Destroying the FilterView");
 
     this._searchbox.removeEventListener("click", this._onClick, false);
-    this._searchbox.removeEventListener("select", this._onSearch, false);
-    this._searchbox.removeEventListener("input", this._onSearch, false);
+    this._searchbox.removeEventListener("select", this._onInput, false);
+    this._searchbox.removeEventListener("input", this._onInput, false);
     this._searchbox.removeEventListener("keypress", this._onKeyPress, false);
     this._searchbox.removeEventListener("blur", this._onBlur, false);
   },
 
   /**
    * Sets the target container to be currently filtered.
    * @param object aView
    */
@@ -816,412 +825,324 @@ FilterView.prototype = {
 
   /**
    * Gets the target container to be currently filtered.
    * @return object
    */
   get target() this._target,
 
   /**
-   * Gets the entered file, line and token entered in the searchbox.
+   * Gets the entered operator and arguments in the searchbox.
    * @return array
    */
-  get searchboxInfo() {
-    let operator, file, line, token;
+  get searchData() {
+    let operator = "", args = [];
 
     let rawValue = this._searchbox.value;
     let rawLength = rawValue.length;
     let globalFlagIndex = rawValue.indexOf(SEARCH_GLOBAL_FLAG);
     let functionFlagIndex = rawValue.indexOf(SEARCH_FUNCTION_FLAG);
     let variableFlagIndex = rawValue.indexOf(SEARCH_VARIABLE_FLAG);
+    let tokenFlagIndex = rawValue.lastIndexOf(SEARCH_TOKEN_FLAG);
     let lineFlagIndex = rawValue.lastIndexOf(SEARCH_LINE_FLAG);
-    let tokenFlagIndex = rawValue.lastIndexOf(SEARCH_TOKEN_FLAG);
 
     // This is not a global, function or variable search, allow file/line flags.
     if (globalFlagIndex != 0 && functionFlagIndex != 0 && variableFlagIndex != 0) {
-      let fileEnd = lineFlagIndex != -1
-        ? lineFlagIndex
-        : tokenFlagIndex != -1
-          ? tokenFlagIndex
-          : rawLength;
-
-      let lineEnd = tokenFlagIndex != -1
-        ? tokenFlagIndex
-        : rawLength;
-
-      operator = "";
-      file = rawValue.slice(0, fileEnd);
-      line = ~~(rawValue.slice(fileEnd + 1, lineEnd)) || 0;
-      token = rawValue.slice(lineEnd + 1);
+      // Token search has precedence over line search.
+      if (tokenFlagIndex != -1) {
+        operator = SEARCH_TOKEN_FLAG;
+        args.push(rawValue.slice(0, tokenFlagIndex)); // file
+        args.push(rawValue.substr(tokenFlagIndex + 1, rawLength)); // token
+      } else if (lineFlagIndex != -1) {
+        operator = SEARCH_LINE_FLAG;
+        args.push(rawValue.slice(0, lineFlagIndex)); // file
+        args.push(+rawValue.substr(lineFlagIndex + 1, rawLength) || 0); // line
+      } else {
+        args.push(rawValue);
+      }
     }
     // Global searches dissalow the use of file or line flags.
     else if (globalFlagIndex == 0) {
       operator = SEARCH_GLOBAL_FLAG;
-      file = "";
-      line = 0;
-      token = rawValue.slice(1);
+      args.push(rawValue.slice(1));
     }
     // Function searches dissalow the use of file or line flags.
     else if (functionFlagIndex == 0) {
       operator = SEARCH_FUNCTION_FLAG;
-      file = "";
-      line = 0;
-      token = rawValue.slice(1);
+      args.push(rawValue.slice(1));
     }
     // Variable searches dissalow the use of file or line flags.
     else if (variableFlagIndex == 0) {
       operator = SEARCH_VARIABLE_FLAG;
-      file = "";
-      line = 0;
-      token = rawValue.slice(1);
+      args.push(rawValue.slice(1));
     }
 
-    return [operator, file, line, token];
-  },
-
-  /**
-   * Returns the current searched operator.
-   * @return string
-   */
-  get currentOperator() this.searchboxInfo[0],
-
-  /**
-   * Returns the currently searched file.
-   * @return string
-   */
-  get searchedFile() this.searchboxInfo[1],
-
-  /**
-   * Returns the currently searched line.
-   * @return number
-   */
-  get searchedLine() this.searchboxInfo[2],
-
-  /**
-   * Returns the currently searched token.
-   * @return string
-   */
-  get searchedToken() this.searchboxInfo[3],
-
-  /**
-   * Clears the text from the searchbox and resets any changed view.
-   */
-  clearSearch: function() {
-    this._searchbox.value = "";
-    this._searchboxHelpPanel.hidePopup();
+    return [operator, args];
   },
 
   /**
-   * Performs a file search if necessary.
-   *
-   * @param string aFile
-   *        The source location to search for.
+   * Returns the current search operator.
+   * @return string
    */
-  _performFileSearch: function(aFile) {
-    // Don't search for files if the input hasn't changed.
-    if (this._prevSearchedFile == aFile) {
-      return;
-    }
-
-    // This is the target container to be currently filtered. Clicking on a
-    // container generally means it should become a target.
-    let view = this._target;
+  get searchOperator() this.searchData[0],
 
-    // If we're not searching for a file anymore, unhide all the items.
-    if (!aFile) {
-      for (let item in view) {
-        item.target.hidden = false;
-      }
-      view.refresh();
-    }
-    // If the searched file string is valid, hide non-matched items.
-    else {
-      let found = false;
-      let lowerCaseFile = aFile.toLowerCase();
-
-      for (let item in view) {
-        let element = item.target;
-        let lowerCaseLabel = item.label.toLowerCase();
+  /**
+   * Returns the current search arguments.
+   * @return array
+   */
+  get searchArguments() this.searchData[1],
 
-        // Search is not case sensitive, and is tied to the label not the value.
-        if (lowerCaseLabel.match(lowerCaseFile)) {
-          element.hidden = false;
+  /**
+   * Clears the text from the searchbox and any changed views.
+   */
+  clearSearch: function() {
+    this._searchbox.value = "";
+    this.clearViews();
+  },
 
-          // Automatically select the first match.
-          if (!found) {
-            found = true;
-            view.selectedItem = item;
-            view.refresh();
-          }
-        }
-        // Item not matched, hide the corresponding node.
-        else {
-          element.hidden = true;
-        }
-      }
-      // If no matches were found, display the appropriate info.
-      if (!found) {
-        view.setUnavailable();
-      }
-    }
-    // Synchronize with the view's filtered sources container.
-    DebuggerView.FilteredSources.syncFileSearch();
-
-    // Hide all the groups with no visible children.
-    view.widget.hideEmptyGroups();
-
-    // Ensure the currently selected item is visible.
-    view.widget.ensureSelectionIsVisible({ withGroup: true });
-
-    // Remember the previously searched file to avoid redundant filtering.
-    this._prevSearchedFile = aFile;
+  /**
+   * Clears all the views that may pop up when searching.
+   */
+  clearViews: function() {
+    DebuggerView.GlobalSearch.clearView();
+    DebuggerView.FilteredSources.clearView();
+    DebuggerView.FilteredFunctions.clearView();
+    this._searchboxHelpPanel.hidePopup();
   },
 
   /**
    * Performs a line search if necessary.
    * (Jump to lines in the currently visible source).
    *
    * @param number aLine
    *        The source line number to jump to.
    */
   _performLineSearch: function(aLine) {
-    // Don't search for lines if the input hasn't changed.
-    if (this._prevSearchedLine != aLine && aLine) {
-      DebuggerView.editor.setCaretPosition(aLine - 1);
+    // Make sure we're actually searching for a valid line.
+    if (!aLine) {
+      return;
     }
-    // Can't search for lines and tokens at the same time.
-    if (this._prevSearchedToken && !aLine) {
-      this._target.refresh();
-    }
-
-    // Remember the previously searched line to avoid redundant filtering.
-    this._prevSearchedLine = aLine;
+    DebuggerView.editor.setCaretPosition(aLine - 1);
   },
 
   /**
    * Performs a token search if necessary.
    * (Search for tokens in the currently visible source).
    *
    * @param string aToken
    *        The source token to find.
    */
   _performTokenSearch: function(aToken) {
-    // Don't search for tokens if the input hasn't changed.
-    if (this._prevSearchedToken != aToken && aToken) {
-      let editor = DebuggerView.editor;
-      let offset = editor.find(aToken, { ignoreCase: true });
-      if (offset > -1) {
-        editor.setSelection(offset, offset + aToken.length)
-      }
+    // Make sure we're actually searching for a valid token.
+    if (!aToken) {
+      return;
     }
-    // Can't search for tokens and lines at the same time.
-    if (this._prevSearchedLine && !aToken) {
-      this._target.refresh();
+    let offset = DebuggerView.editor.find(aToken, { ignoreCase: true });
+    if (offset > -1) {
+      DebuggerView.editor.setSelection(offset, offset + aToken.length)
     }
-
-    // Remember the previously searched token to avoid redundant filtering.
-    this._prevSearchedToken = aToken;
   },
 
   /**
    * The click listener for the search container.
    */
   _onClick: function() {
-    this._searchboxHelpPanel.openPopup(this._searchbox);
+    // If there's some text in the searchbox, displaying a panel would
+    // interfere with double/triple click default behaviors.
+    if (!this._searchbox.value) {
+      this._searchboxHelpPanel.openPopup(this._searchbox);
+    }
   },
 
   /**
-   * The search listener for the search container.
+   * The input listener for the search container.
    */
-  _onSearch: function() {
-    this._searchboxHelpPanel.hidePopup();
-    let [operator, file, line, token] = this.searchboxInfo;
+  _onInput: function() {
+    this.clearViews();
 
-    // If this is a global search, schedule it for when the user stops typing,
-    // or hide the corresponding pane otherwise.
-    if (operator == SEARCH_GLOBAL_FLAG) {
-      DebuggerView.GlobalSearch.scheduleSearch(token);
-      this._prevSearchedToken = token;
+    // Make sure we're actually searching for something.
+    if (!this._searchbox.value) {
       return;
     }
 
-    // If this is a function search, schedule it for when the user stops typing,
-    // or hide the corresponding panel otherwise.
-    if (operator == SEARCH_FUNCTION_FLAG) {
-      DebuggerView.FilteredFunctions.scheduleSearch(token);
-      this._prevSearchedToken = token;
-      return;
+    // Perform the required search based on the specified operator.
+    switch (this.searchOperator) {
+      case SEARCH_GLOBAL_FLAG:
+        // Schedule a global search for when the user stops typing.
+        DebuggerView.GlobalSearch.scheduleSearch(this.searchArguments[0]);
+        break;
+      case SEARCH_FUNCTION_FLAG:
+        // Schedule a function search for when the user stops typing.
+        DebuggerView.FilteredFunctions.scheduleSearch(this.searchArguments[0]);
+        break;
+      case SEARCH_VARIABLE_FLAG:
+        // Schedule a variable search for when the user stops typing.
+        DebuggerView.Variables.scheduleSearch(this.searchArguments[0]);
+        break;
+      case SEARCH_TOKEN_FLAG:
+        // Schedule a file+token search for when the user stops typing.
+        DebuggerView.FilteredSources.scheduleSearch(this.searchArguments[0]);
+        this._performTokenSearch(this.searchArguments[1]);
+        break;
+      case SEARCH_LINE_FLAG:
+        // Schedule a file+line search for when the user stops typing.
+        DebuggerView.FilteredSources.scheduleSearch(this.searchArguments[0]);
+        this._performLineSearch(this.searchArguments[1]);
+        break;
+      default:
+        // Schedule a file only search for when the user stops typing.
+        DebuggerView.FilteredSources.scheduleSearch(this.searchArguments[0]);
+        break;
     }
-
-    // If this is a variable search, defer the action to the corresponding
-    // variables view instance.
-    if (operator == SEARCH_VARIABLE_FLAG) {
-      DebuggerView.Variables.scheduleSearch(token);
-      this._prevSearchedToken = token;
-      return;
-    }
-
-    DebuggerView.GlobalSearch.clearView();
-    DebuggerView.FilteredFunctions.clearView();
-
-    this._performFileSearch(file);
-    this._performLineSearch(line);
-    this._performTokenSearch(token);
   },
 
   /**
    * The key press listener for the search container.
    */
   _onKeyPress: function(e) {
     // This attribute is not implemented in Gecko at this time, see bug 680830.
     e.char = String.fromCharCode(e.charCode);
 
-    let [operator, file, line, token] = this.searchboxInfo;
-    let isGlobal = operator == SEARCH_GLOBAL_FLAG;
-    let isFunction = operator == SEARCH_FUNCTION_FLAG;
-    let isVariable = operator == SEARCH_VARIABLE_FLAG;
-    let action = -1;
+    // Perform the required action based on the specified operator.
+    let [operator, args] = this.searchData;
+    let isGlobalSearch = operator == SEARCH_GLOBAL_FLAG;
+    let isFunctionSearch = operator == SEARCH_FUNCTION_FLAG;
+    let isVariableSearch = operator == SEARCH_VARIABLE_FLAG;
+    let isTokenSearch = operator == SEARCH_TOKEN_FLAG;
+    let isLineSearch = operator == SEARCH_LINE_FLAG;
+    let isFileOnlySearch = !operator && args.length == 1;
 
-    if (file && !line && !token) {
-      var isFileSearch = true;
-    }
-    if (line && !token) {
-      var isLineSearch = true;
-    }
-    if (this._prevSearchedToken != token) {
-      var isDifferentToken = true;
-    }
+    // Depending on the pressed keys, determine to correct action to perform.
+    let actionToPerform;
 
     // Meta+G and Ctrl+N focus next matches.
     if ((e.char == "g" && e.metaKey) || e.char == "n" && e.ctrlKey) {
-      action = 0;
+      actionToPerform = "selectNext";
     }
     // Meta+Shift+G and Ctrl+P focus previous matches.
     else if ((e.char == "G" && e.metaKey) || e.char == "p" && e.ctrlKey) {
-      action = 1;
+      actionToPerform = "selectPrev";
     }
     // Return, enter, down and up keys focus next or previous matches, while
     // the escape key switches focus from the search container.
     else switch (e.keyCode) {
       case e.DOM_VK_RETURN:
       case e.DOM_VK_ENTER:
-        var isReturnKey = true;
-        // fall through
+        var isReturnKey = true; // Fall through.
       case e.DOM_VK_DOWN:
-        action = 0;
+        actionToPerform = "selectNext";
         break;
       case e.DOM_VK_UP:
-        action = 1;
-        break;
-      case e.DOM_VK_ESCAPE:
-        action = 2;
+        actionToPerform = "selectPrev";
         break;
     }
 
-    if (action == 2) {
-      DebuggerView.editor.focus();
-      return;
-    }
-    if (action == -1 || (!operator && !file && !line && !token)) {
+    // If there's no action to perform, or no operator, file line or token
+    // were specified, then this is either a broken or empty search.
+    if (!actionToPerform || (!operator && !args.length)) {
+      DebuggerView.editor.dropSelection();
       return;
     }
 
     e.preventDefault();
     e.stopPropagation();
 
-    // Select the next or previous file search entry.
-    if (isFileSearch) {
-      if (isReturnKey) {
-        DebuggerView.FilteredSources.clearView();
-        DebuggerView.editor.focus();
-        this.clearSearch();
-      } else {
-        DebuggerView.FilteredSources[["selectNext", "selectPrev"][action]]();
+    // Jump to the next/previous entry in the global search, or perform
+    // a new global search immediately
+    if (isGlobalSearch) {
+      let targetView = DebuggerView.GlobalSearch;
+      if (!isReturnKey) {
+        targetView[actionToPerform]();
+      } else if (targetView.hidden) {
+        targetView.scheduleSearch(args[0], 0);
       }
-      this._prevSearchedFile = file;
       return;
     }
 
-    // Perform a global search based on the specified operator.
-    if (isGlobal) {
-      if (isReturnKey && (isDifferentToken || DebuggerView.GlobalSearch.hidden)) {
-        DebuggerView.GlobalSearch.performSearch(token);
+    // Jump to the next/previous entry in the function search, perform
+    // a new function search immediately, or clear it.
+    if (isFunctionSearch) {
+      let targetView = DebuggerView.FilteredFunctions;
+      if (!isReturnKey) {
+        targetView[actionToPerform]();
+      } else if (targetView.hidden) {
+        targetView.scheduleSearch(args[0], 0);
       } else {
-        DebuggerView.GlobalSearch[["selectNext", "selectPrev"][action]]();
+        this.clearSearch();
       }
-      this._prevSearchedToken = token;
+      return;
+    }
+
+    // Perform a new variable search immediately.
+    if (isVariableSearch) {
+      let targetView = DebuggerView.Variables;
+      if (isReturnKey) {
+        targetView.scheduleSearch(args[0], 0);
+      }
       return;
     }
 
-    // Perform a function search based on the specified operator.
-    if (isFunction) {
-      if (isReturnKey && (isDifferentToken || DebuggerView.FilteredFunctions.hidden)) {
-        DebuggerView.FilteredFunctions.performSearch(token);
-      } else if (!isReturnKey) {
-        DebuggerView.FilteredFunctions[["selectNext", "selectPrev"][action]]();
+    // Jump to the next/previous entry in the file search, perform
+    // a new file search immediately, or clear it.
+    if (isFileOnlySearch) {
+      let targetView = DebuggerView.FilteredSources;
+      if (!isReturnKey) {
+        targetView[actionToPerform]();
+      } else if (targetView.hidden) {
+        targetView.scheduleSearch(args[0], 0);
       } else {
-        DebuggerView.FilteredFunctions.clearView();
-        DebuggerView.editor.focus();
         this.clearSearch();
       }
-      this._prevSearchedToken = token;
       return;
     }
 
-    // Perform a variable search based on the specified operator.
-    if (isVariable) {
-      if (isReturnKey && isDifferentToken) {
-        DebuggerView.Variables.performSearch(token);
-      } else {
-        DebuggerView.Variables.expandFirstSearchResults();
+    // Jump to the next/previous instance of the currently searched token.
+    if (isTokenSearch) {
+      let [, token] = args;
+      let methods = { selectNext: "findNext", selectPrev: "findPrevious" };
+
+      // Search for the token and select it.
+      let offset = DebuggerView.editor[methods[actionToPerform]](true);
+      if (offset > -1) {
+        DebuggerView.editor.setSelection(offset, offset + token.length)
       }
-      this._prevSearchedToken = token;
       return;
     }
 
-    // Increment or decrement the specified line.
-    if (isLineSearch && !isReturnKey) {
-      line += action == 0 ? 1 : -1;
+    // Increment/decrement the currently searched caret line.
+    if (isLineSearch) {
+      let [, line] = args;
+      let amounts = { selectNext: 1, selectPrev: -1 };
+
+      // Modify the line number and jump to it.
+      line += !isReturnKey ? amounts[actionToPerform] : 0;
       let lineCount = DebuggerView.editor.getLineCount();
       let lineTarget = line < 1 ? 1 : line > lineCount ? lineCount : line;
-
-      DebuggerView.editor.setCaretPosition(lineTarget - 1);
-      this._searchbox.value = file + SEARCH_LINE_FLAG + lineTarget;
-      this._prevSearchedLine = lineTarget;
+      this._doSearch(SEARCH_LINE_FLAG, lineTarget);
       return;
     }
-
-    let editor = DebuggerView.editor;
-    let offset = editor[["findNext", "findPrevious"][action]](true);
-    if (offset > -1) {
-      editor.setSelection(offset, offset + token.length)
-    }
   },
 
   /**
    * The blur listener for the search container.
    */
   _onBlur: function() {
-    DebuggerView.GlobalSearch.clearView();
-    DebuggerView.FilteredSources.clearView();
-    DebuggerView.FilteredFunctions.clearView();
-    DebuggerView.Variables.performSearch(null);
-    this._searchboxHelpPanel.hidePopup();
+    this.clearViews();
   },
 
   /**
    * Called when a filtering key sequence was pressed.
    *
    * @param string aOperator
    *        The operator to use for filtering.
    */
-  _doSearch: function(aOperator = "") {
+  _doSearch: function(aOperator = "", aText = "") {
     this._searchbox.focus();
     this._searchbox.value = ""; // Need to clear value beforehand. Bug 779738.
-    this._searchbox.value = aOperator + DebuggerView.getEditorSelectionText();
+    this._searchbox.value = aOperator + (aText || DebuggerView.editor.getSelectedText());
   },
 
   /**
    * Called when the source location filter key sequence was pressed.
    */
   _doFileSearch: function() {
     this._doSearch();
     this._searchboxHelpPanel.openPopup(this._searchbox);
@@ -1258,17 +1179,16 @@ FilterView.prototype = {
     this._doSearch(SEARCH_LINE_FLAG);
     this._searchboxHelpPanel.hidePopup();
   },
 
   /**
    * Called when the variable search filter key sequence was pressed.
    */
   _doVariableSearch: function() {
-    DebuggerView.Variables.performSearch("");
     this._doSearch(SEARCH_VARIABLE_FLAG);
     this._searchboxHelpPanel.hidePopup();
   },
 
   /**
    * Called when the variables focus key sequence was pressed.
    */
   _doVariablesFocus: function() {
@@ -1289,20 +1209,17 @@ FilterView.prototype = {
   _variableOperatorButton: null,
   _variableOperatorLabel: null,
   _fileSearchKey: "",
   _globalSearchKey: "",
   _filteredFunctionsKey: "",
   _tokenSearchKey: "",
   _lineSearchKey: "",
   _variableSearchKey: "",
-  _target: null,
-  _prevSearchedFile: "",
-  _prevSearchedLine: 0,
-  _prevSearchedToken: ""
+  _target: null
 };
 
 /**
  * Functions handling the filtered sources UI.
  */
 function FilteredSourcesView() {
   dumpn("FilteredSourcesView was instantiated");
 
@@ -1329,51 +1246,100 @@ FilteredSourcesView.prototype = Heritage
     dumpn("Destroying the FilteredSourcesView");
 
     this.widget.removeEventListener("select", this._onSelect, false);
     this.widget.removeEventListener("click", this._onClick, false);
     this.anchor = null;
   },
 
   /**
-   * Updates the list of sources displayed in this container.
+   * Schedules searching for a source.
+   *
+   * @param string aToken
+   *        The function to search for.
+   * @param number aWait
+   *        The amount of milliseconds to wait until draining.
    */
-  syncFileSearch: function() {
-    this.empty();
+  scheduleSearch: function(aToken, aWait) {
+    // The amount of time to wait for the requests to settle.
+    let maxDelay = FILE_SEARCH_ACTION_MAX_DELAY;
+    let delay = aWait === undefined ? maxDelay / aToken.length : aWait;
 
-    // If there's no currently searched file, or there are no matches found,
-    // hide the popup and avoid creating the view again.
-    if (!DebuggerView.Filtering.searchedFile ||
-        !DebuggerView.Sources.visibleItems.length) {
-      this.hidden = true;
+    // Allow requests to settle down first.
+    setNamedTimeout("sources-search", delay, () => this._doSearch(aToken));
+  },
+
+  /**
+   * Finds file matches in all the displayed sources.
+   *
+   * @param string aToken
+   *        The string to search for.
+   */
+  _doSearch: function(aToken, aStore = []) {
+    // Don't continue filtering if the searched token is an empty string.
+    // In contrast with function searching, in this case we don't want to
+    // show a list of all the files when no search token was supplied.
+    if (!aToken) {
       return;
     }
 
-    // Get the currently visible items in the sources container.
-    let visibleItems = DebuggerView.Sources.visibleItems;
-    let displayedItems = visibleItems.slice(0, RESULTS_PANEL_MAX_RESULTS);
+    for (let item of DebuggerView.Sources.items) {
+      let lowerCaseLabel = item.label.toLowerCase();
+      let lowerCaseToken = aToken.toLowerCase();
+      if (lowerCaseLabel.match(lowerCaseToken)) {
+        aStore.push(item);
+      }
+
+      // Once the maximum allowed number of results is reached, proceed
+      // with building the UI immediately.
+      if (aStore.length >= RESULTS_PANEL_MAX_RESULTS) {
+        this._syncView(aStore);
+        return;
+      }
+    }
 
-    // Append a location item item to this container.
-    for (let item of displayedItems) {
+    // Couldn't reach the maximum allowed number of results, but that's ok,
+    // continue building the UI.
+    this._syncView(aStore);
+  },
+
+  /**
+   * Updates the list of sources displayed in this container.
+   *
+   * @param array aSearchResults
+   *        The results array, containing search details for each source.
+   */
+  _syncView: function(aSearchResults) {
+    // If there are no matches found, keep the popup hidden and avoid
+    // creating the view.
+    if (!aSearchResults.length) {
+      window.emit(EVENTS.FILE_SEARCH_MATCH_NOT_FOUND);
+      return;
+    }
+
+    for (let item of aSearchResults) {
+      // Append a location item to this container for each match.
       let trimmedLabel = SourceUtils.trimUrlLength(item.label);
       let trimmedValue = SourceUtils.trimUrlLength(item.value, 0, "start");
-      let locationItem = this.push([trimmedLabel, trimmedValue], {
+
+      this.push([trimmedLabel, trimmedValue], {
+        index: -1, /* specifies on which position should the item be appended */
         relaxed: true, /* this container should allow dupes & degenerates */
         attachment: {
-          fullLabel: item.label,
-          fullValue: item.value
+          url: item.value
         }
       });
     }
 
     // Select the first entry in this container.
     this.selectedIndex = 0;
+    this.hidden = false;
 
-    // Only display the results panel if there's at least one entry available.
-    this.hidden = this.itemCount == 0;
+    // Signal that file search matches were found and displayed.
+    window.emit(EVENTS.FILE_SEARCH_MATCH_FOUND);
   },
 
   /**
    * The click listener for this container.
    */
   _onClick: function(e) {
     let locationItem = this.getItemForElement(e.target);
     if (locationItem) {
@@ -1385,28 +1351,30 @@ FilteredSourcesView.prototype = Heritage
   /**
    * The select listener for this container.
    *
    * @param object aItem
    *        The item associated with the element to select.
    */
   _onSelect: function({ detail: locationItem }) {
     if (locationItem) {
-      DebuggerView.updateEditor(locationItem.attachment.fullValue, 0);
+      DebuggerView.setEditorLocation(locationItem.attachment.url, undefined, {
+        noCaret: true,
+        noDebug: true
+      });
     }
   }
 });
 
 /**
  * Functions handling the function search UI.
  */
 function FilteredFunctionsView() {
   dumpn("FilteredFunctionsView was instantiated");
 
-  this._performFunctionSearch = this._performFunctionSearch.bind(this);
   this._onClick = this._onClick.bind(this);
   this._onSelect = this._onSelect.bind(this);
 }
 
 FilteredFunctionsView.prototype = Heritage.extend(ResultsPanelContainer.prototype, {
   /**
    * Initialization function, called when the debugger is started.
    */
@@ -1425,131 +1393,105 @@ FilteredFunctionsView.prototype = Herita
     dumpn("Destroying the FilteredFunctionsView");
 
     this.widget.removeEventListener("select", this._onSelect, false);
     this.widget.removeEventListener("click", this._onClick, false);
     this.anchor = null;
   },
 
   /**
-   * Allows searches to be scheduled and delayed to avoid redundant calls.
-   */
-  delayedSearch: true,
-
-  /**
    * Schedules searching for a function in all of the sources.
    *
-   * @param string aQuery
-   *        The function to search for.
-   */
-  scheduleSearch: function(aQuery) {
-    if (!this.delayedSearch) {
-      this.performSearch(aQuery);
-      return;
-    }
-    let delay = Math.max(FUNCTION_SEARCH_ACTION_MAX_DELAY / aQuery.length, 0);
-
-    window.clearTimeout(this._searchTimeout);
-    this._searchFunction = this._startSearch.bind(this, aQuery);
-    this._searchTimeout = window.setTimeout(this._searchFunction, delay);
-  },
-
-  /**
-   * Immediately searches for a function in all of the sources.
-   *
-   * @param string aQuery
+   * @param string aToken
    *        The function to search for.
+   * @param number aWait
+   *        The amount of milliseconds to wait until draining.
    */
-  performSearch: function(aQuery) {
-    window.clearTimeout(this._searchTimeout);
-    this._searchFunction = null;
-    this._startSearch(aQuery);
-  },
+  scheduleSearch: function(aToken, aWait) {
+    // The amount of time to wait for the requests to settle.
+    let maxDelay = FUNCTION_SEARCH_ACTION_MAX_DELAY;
+    let delay = aWait === undefined ? maxDelay / aToken.length : aWait;
 
-  /**
-   * Starts searching for a function in all of the sources.
-   *
-   * @param string aQuery
-   *        The function to search for.
-   */
-  _startSearch: function(aQuery) {
-    this._searchedToken = aQuery;
-
-    // Start fetching as many sources as possible, then perform the search.
-    DebuggerController.SourceScripts
-      .getTextForSources(DebuggerView.Sources.values)
-      .then(this._performFunctionSearch);
+    // Allow requests to settle down first.
+    setNamedTimeout("function-search", delay, () => {
+      // Start fetching as many sources as possible, then perform the search.
+      let urls = DebuggerView.Sources.values;
+      let sourcesFetched = DebuggerController.SourceScripts.getTextForSources(urls);
+      sourcesFetched.then(aSources => this._doSearch(aToken, aSources));
+    });
   },
 
   /**
    * Finds function matches in all the sources stored in the cache, and groups
    * them by location and line number.
+   *
+   * @param string aToken
+   *        The string to search for.
+   * @param array aSources
+   *        An array of [url, text] tuples for each source.
    */
-  _performFunctionSearch: function(aSources) {
-    // Get the currently searched token from the filtering input.
+  _doSearch: function(aToken, aSources, aStore = []) {
     // Continue parsing even if the searched token is an empty string, to
     // cache the syntax tree nodes generated by the reflection API.
-    let token = this._searchedToken;
 
     // Make sure the currently displayed source is parsed first. Once the
     // maximum allowed number of resutls are found, parsing will be halted.
     let currentUrl = DebuggerView.Sources.selectedValue;
-    aSources.sort(([sourceUrl]) => sourceUrl == currentUrl ? -1 : 1);
+    let currentSource = aSources.filter(([sourceUrl]) => sourceUrl == currentUrl)[0];
+    aSources.splice(aSources.indexOf(currentSource), 1);
+    aSources.unshift(currentSource);
 
-    // If not searching for a specific function, only parse the displayed source.
-    if (!token) {
+    // If not searching for a specific function, only parse the displayed source,
+    // which is now the first item in the sources array.
+    if (!aToken) {
       aSources.splice(1);
     }
 
-    // Prepare the results array, containing search details for each source.
-    let searchResults = [];
-
     for (let [location, contents] of aSources) {
       let parserMethods = DebuggerController.Parser.get(location, contents);
-      let sourceResults = parserMethods.getNamedFunctionDefinitions(token);
+      let sourceResults = parserMethods.getNamedFunctionDefinitions(aToken);
 
       for (let scriptResult of sourceResults) {
         for (let parseResult of scriptResult.parseResults) {
-          searchResults.push({
+          aStore.push({
             sourceUrl: scriptResult.sourceUrl,
             scriptOffset: scriptResult.scriptOffset,
             functionName: parseResult.functionName,
             functionLocation: parseResult.functionLocation,
             inferredName: parseResult.inferredName,
             inferredChain: parseResult.inferredChain,
             inferredLocation: parseResult.inferredLocation
           });
 
           // Once the maximum allowed number of results is reached, proceed
           // with building the UI immediately.
-          if (searchResults.length >= RESULTS_PANEL_MAX_RESULTS) {
-            this._syncFunctionSearch(searchResults);
+          if (aStore.length >= RESULTS_PANEL_MAX_RESULTS) {
+            this._syncView(aStore);
             return;
           }
         }
       }
     }
+
     // Couldn't reach the maximum allowed number of results, but that's ok,
     // continue building the UI.
-    this._syncFunctionSearch(searchResults);
+    this._syncView(aStore);
   },
 
   /**
    * Updates the list of functions displayed in this container.
    *
    * @param array aSearchResults
    *        The results array, containing search details for each source.
    */
-  _syncFunctionSearch: function(aSearchResults) {
-    this.empty();
-
-    // Show the popup even if the search token is an empty string. If there are
-    // no matches found, hide the popup and avoid creating the view again.
+  _syncView: function(aSearchResults) {
+    // If there are no matches found, keep the popup hidden and avoid
+    // creating the view.
     if (!aSearchResults.length) {
-      this.hidden = true;
+      window.emit(EVENTS.FUNCTION_SEARCH_MATCH_NOT_FOUND);
       return;
     }
 
     for (let item of aSearchResults) {
       // Some function expressions don't necessarily have a name, but the
       // parser provides us with an inferred name from an enclosing
       // VariableDeclarator, AssignmentExpression, ObjectExpression node.
       if (item.functionName && item.inferredName &&
@@ -1569,33 +1511,34 @@ FilteredFunctionsView.prototype = Herita
       // Some function expressions have unexpected bounds, since they may not
       // necessarily have an associated name defining them.
       if (item.inferredLocation) {
         item.actualLocation = item.inferredLocation;
       } else {
         item.actualLocation = item.functionLocation;
       }
 
-      // Append a function item to this container.
+      // Append a function item to this container for each match.
       let trimmedLabel = SourceUtils.trimUrlLength(item.displayedName + "()");
       let trimmedValue = SourceUtils.trimUrlLength(item.sourceUrl, 0, "start");
       let description = (item.inferredChain || []).join(".");
 
-      let functionItem = this.push([trimmedLabel, trimmedValue, description], {
+      this.push([trimmedLabel, trimmedValue, description], {
         index: -1, /* specifies on which position should the item be appended */
         relaxed: true, /* this container should allow dupes & degenerates */
         attachment: item
       });
     }
 
     // Select the first entry in this container.
     this.selectedIndex = 0;
+    this.hidden = false;
 
-    // Only display the results panel if there's at least one entry available.
-    this.hidden = this.itemCount == 0;
+    // Signal that function search matches were found and displayed.
+    window.emit(EVENTS.FUNCTION_SEARCH_MATCH_FOUND);
   },
 
   /**
    * The click listener for this container.
    */
   _onClick: function(e) {
     let functionItem = this.getItemForElement(e.target);
     if (functionItem) {
@@ -1608,17 +1551,17 @@ FilteredFunctionsView.prototype = Herita
    * The select listener for this container.
    */
   _onSelect: function({ detail: functionItem }) {
     if (functionItem) {
       let sourceUrl = functionItem.attachment.sourceUrl;
       let scriptOffset = functionItem.attachment.scriptOffset;
       let actualLocation = functionItem.attachment.actualLocation;
 
-      DebuggerView.updateEditor(sourceUrl, actualLocation.start.line, {
+      DebuggerView.setEditorLocation(sourceUrl, actualLocation.start.line, {
         charOffset: scriptOffset,
         columnOffset: actualLocation.start.column,
         noDebug: true
       });
     }
   },
 
   _searchTimeout: null,
--- a/browser/devtools/debugger/debugger-view.js
+++ b/browser/devtools/debugger/debugger-view.js
@@ -1,94 +1,109 @@
 /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* 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 SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE = 1048576; // 1 MB in bytes
 const SOURCE_URL_DEFAULT_MAX_LENGTH = 64; // chars
-const SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE = 1048576; // 1 MB in bytes
 const STACK_FRAMES_SOURCE_URL_MAX_LENGTH = 15; // chars
 const STACK_FRAMES_SOURCE_URL_TRIM_SECTION = "center";
 const STACK_FRAMES_POPUP_SOURCE_URL_MAX_LENGTH = 32; // chars
 const STACK_FRAMES_POPUP_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 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
 const SEARCH_GLOBAL_FLAG = "!";
 const SEARCH_FUNCTION_FLAG = "@";
 const SEARCH_TOKEN_FLAG = "#";
 const SEARCH_LINE_FLAG = ":";
 const SEARCH_VARIABLE_FLAG = "*";
-
-Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm");
+const DEFAULT_EDITOR_CONFIG = {
+  mode: SourceEditor.MODES.TEXT,
+  readOnly: true,
+  showLineNumbers: true,
+  showAnnotationRuler: true,
+  showOverviewRuler: true
+};
 
 /**
  * Object defining the debugger view components.
  */
 let DebuggerView = {
   /**
    * Initializes the debugger view.
    *
-   * @param function aCallback
-   *        Called after the view finishes initializing.
+   * @return object
+   *         A promise that is resolved when the view finishes initializing.
    */
-  initialize: function(aCallback) {
-    dumpn("Initializing the DebuggerView");
+  initialize: function() {
+    if (this._startup) {
+      return this._startup;
+    }
+
+    let deferred = promise.defer();
+    this._startup = deferred.promise;
 
     this._initializePanes();
-
     this.Toolbar.initialize();
     this.Options.initialize();
     this.Filtering.initialize();
     this.FilteredSources.initialize();
     this.FilteredFunctions.initialize();
     this.ChromeGlobals.initialize();
     this.StackFrames.initialize();
     this.Sources.initialize();
     this.WatchExpressions.initialize();
     this.GlobalSearch.initialize();
+    this._initializeVariablesView();
+    this._initializeEditor(deferred.resolve);
 
-    this._initializeVariablesView();
-    this._initializeEditor(aCallback);
+    return deferred.promise;
   },
 
   /**
    * Destroys the debugger view.
    *
-   * @param function aCallback
-   *        Called after the view finishes destroying.
+   * @return object
+   *         A promise that is resolved when the view finishes destroying.
    */
-  destroy: function(aCallback) {
-    dumpn("Destroying the DebuggerView");
+  destroy: function() {
+    if (this._shutdown) {
+      return this._shutdown;
+    }
+
+    let deferred = promise.defer();
+    this._shutdown = deferred.promise;
 
     this.Toolbar.destroy();
     this.Options.destroy();
     this.Filtering.destroy();
     this.FilteredSources.destroy();
     this.FilteredFunctions.destroy();
     this.ChromeGlobals.destroy();
     this.StackFrames.destroy();
     this.Sources.destroy();
     this.WatchExpressions.destroy();
     this.GlobalSearch.destroy();
+    this._destroyPanes();
+    this._destroyEditor(deferred.resolve);
 
-    this._destroyPanes();
-    this._destroyEditor();
-
-    aCallback();
+    return deferred.promise;
   },
 
   /**
    * Initializes the UI for all the displayed panes.
    */
   _initializePanes: function() {
     dumpn("Initializing the DebuggerView panes");
 
@@ -135,86 +150,98 @@ let DebuggerView = {
     VariablesViewController.attach(this.Variables, {
       getObjectClient: aObject => gThreadClient.pauseGrip(aObject)
     });
 
     // Relay events from the VariablesView.
     this.Variables.on("fetched", (aEvent, aType) => {
       switch (aType) {
         case "variables":
-          window.dispatchEvent(document, "Debugger:FetchedVariables");
+          window.emit(EVENTS.FETCHED_VARIABLES);
           break;
         case "properties":
-          window.dispatchEvent(document, "Debugger:FetchedProperties");
+          window.emit(EVENTS.FETCHED_PROPERTIES);
           break;
       }
     });
   },
 
   /**
    * Initializes the SourceEditor instance.
    *
    * @param function aCallback
    *        Called after the editor finishes initializing.
    */
   _initializeEditor: function(aCallback) {
     dumpn("Initializing the DebuggerView editor");
 
-    let placeholder = document.getElementById("editor");
-    let config = {
-      mode: SourceEditor.MODES.JAVASCRIPT,
-      readOnly: true,
-      showLineNumbers: true,
-      showAnnotationRuler: true,
-      showOverviewRuler: true
-    };
-
     this.editor = new SourceEditor();
-    this.editor.init(placeholder, config, () => {
+    this.editor.init(document.getElementById("editor"), DEFAULT_EDITOR_CONFIG, () => {
       this._loadingText = L10N.getStr("loadingText");
-      this._onEditorLoad();
-      aCallback();
+      this._onEditorLoad(aCallback);
     });
   },
 
   /**
    * 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() {
+  _onEditorLoad: function(aCallback) {
     dumpn("Finished loading the DebuggerView editor");
 
-    DebuggerController.Breakpoints.initialize();
-    window.dispatchEvent(document, "Debugger:EditorLoaded", this.editor);
-    this.editor.focus();
+    DebuggerController.Breakpoints.initialize().then(() => {
+      window.emit(EVENTS.EDITOR_LOADED, this.editor);
+      aCallback();
+    });
   },
 
   /**
    * Destroys the SourceEditor instance and also executes any necessary
    * post-unload operations.
+   *
+   * @param function aCallback
+   *        Called after the editor finishes destroying.
    */
-  _destroyEditor: function() {
+  _destroyEditor: function(aCallback) {
     dumpn("Destroying the DebuggerView editor");
 
-    DebuggerController.Breakpoints.destroy();
-    window.dispatchEvent(document, "Debugger:EditorUnloaded", this.editor);
+    DebuggerController.Breakpoints.destroy().then(() => {
+      window.emit(EVENTS.EDITOR_UNLOADED, this.editor);
+      aCallback();
+    });
+  },
+
+  /**
+   * 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(SourceEditor.MODES.TEXT);
+    this.editor.setText(aTextContent);
+    this.editor.resetUndo();
   },
 
   /**
    * Sets the proper editor mode (JS or HTML) according to the specified
    * content type, or by determining the type from the url or text content.
    *
    * @param string aUrl
    *        The source url.
    * @param string aContentType [optional]
    *        The source content type.
    * @param string aTextContent [optional]
    *        The source text content.
    */
-  setEditorMode: function(aUrl, aContentType = "", aTextContent = "") {
+  _setEditorMode: function(aUrl, aContentType = "", aTextContent = "") {
     // Avoid setting the editor mode for very large files.
     if (aTextContent.length >= SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE) {
       this.editor.setMode(SourceEditor.MODES.TEXT);
       return;
     }
 
     if (aContentType) {
       if (/javascript/.test(aContentType)) {
@@ -234,176 +261,144 @@ let DebuggerView = {
         this.editor.setMode(SourceEditor.MODES.TEXT);
       }
     }
   },
 
   /**
    * Sets the currently displayed source text in the editor.
    *
-   * To update the source editor's current caret and debug location based on
-   * a requested url and line, use the DebuggerView.updateEditor method.
+   * 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.
+   * @return object
+   *         A promise that is resolved after the source text has been set.
    */
-  set editorSource(aSource) {
-    if (!this._isInitialized || this._isDestroyed || this._editorSource == aSource) {
-      return;
+  _setEditorSource: function(aSource) {
+    // Avoid setting the same source text in the editor again.
+    if (this._editorSource.url == aSource.url) {
+      return this._editorSource.promise;
     }
 
-    dumpn("Setting the DebuggerView editor source: " + aSource.url +
-          ", fetched: " + !!aSource._fetched);
+    let deferred = promise.defer();
+
+    this._setEditorText(L10N.getStr("loadingText"));
+    this._editorSource = { url: aSource.url, promise: deferred.promise };
 
-    this.editor.setMode(SourceEditor.MODES.TEXT);
-    this.editor.setText(L10N.getStr("loadingText"));
-    this.editor.resetUndo();
-    this._editorSource = aSource;
-
-    DebuggerController.SourceScripts.getTextForSource(aSource).then(([, aText]) => {
-      // Avoid setting an unexpected source. This may happen when fast switching
-      // between sources that haven't been fetched yet.
-      if (this._editorSource != aSource) {
+    DebuggerController.SourceScripts.getText(aSource).then(([, aText]) => {
+      // Avoid setting an unexpected source. This may happen when switching
+      // very fast between sources that haven't been fetched yet.
+      if (this._editorSource.url != aSource.url) {
         return;
       }
 
-      this.editor.setText(aText);
-      this.editor.resetUndo();
-      this.setEditorMode(aSource.url, aSource.contentType, aText);
-
-      // Update the editor's current caret and debug locations given by the
-      // currently active frame in the stack, if there's one available.
-      this.updateEditor();
+      this._setEditorText(aText);
+      this._setEditorMode(aSource.url, aSource.contentType, aText);
 
       // Synchronize any other components with the currently displayed source.
       DebuggerView.Sources.selectedValue = aSource.url;
       DebuggerController.Breakpoints.updateEditorBreakpoints();
 
-      // Notify that we've shown a source file.
-      window.dispatchEvent(document, "Debugger:SourceShown", aSource);
+      // Resolve and notify that a source file was shown.
+      window.emit(EVENTS.SOURCE_SHOWN, aSource);
+      deferred.resolve([aSource, aText]);
     },
     ([, aError]) => {
-      // Rejected.
       let msg = L10N.getStr("errorLoadingText") + DevToolsUtils.safeErrorString(aError);
-      this.editor.setText(msg);
-      window.dispatchEvent(document, "Debugger:SourceErrorShown", aError);
+      this._setEditorText(msg);
+      Cu.reportError(msg);
       dumpn(msg);
-      Cu.reportError(msg);
+
+      // Reject and notify that there was an error showing the source file.
+      window.emit(EVENTS.SOURCE_ERROR_SHOWN, aSource);
+      deferred.reject([aSource, aError]);
     });
+
+    return deferred.promise;
   },
 
   /**
-   * Gets the currently displayed source text in the editor.
+   * Update the source editor's current caret and debug location based on
+   * a requested url and line.
    *
-   * @return object
-   *         The source object coming from the active thread.
-   */
-  get editorSource() this._editorSource,
-
-  /**
-   * Update the source editor's current caret and debug location based on
-   * a requested url and line. If unspecified, they default to the location
-   * given by the currently active frame in the stack.
-   *
-   * @param string aUrl [optional]
+   * @param string aUrl
    *        The target source url.
    * @param number aLine [optional]
-   *        The target line number in the source.
+   *        The target line in the source.
    * @param object aFlags [optional]
    *        Additional options for showing the source. Supported options:
    *          - charOffset: character offset for the caret or debug location
    *          - lineOffset: line offset for the caret or debug location
    *          - columnOffset: column offset for the caret or debug location
-   *          - noSwitch: don't switch to the source if not currently selected
    *          - noCaret: don't set the caret location at the specified line
    *          - noDebug: don't set the debug location at the specified line
+   * @return object
+   *         A promise that is resolved after the source text has been set.
    */
-  updateEditor: function(aUrl, aLine, aFlags = {}) {
-    if (!this._isInitialized || this._isDestroyed) {
-      return;
+  setEditorLocation: function(aUrl, aLine = 0, aFlags = {}) {
+    // Avoid trying to set a source for a url that isn't known yet.
+    if (!this.Sources.containsValue(aUrl)) {
+      return promise.reject(new Error("Unknown source for the specified URL."));
     }
-    // If the location is not specified, default to the location given by
-    // the currently active frame in the stack.
-    if (!aUrl && !aLine) {
+    // 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 currentFrame = DebuggerController.StackFrames.currentFrame;
-      let frame = cachedFrames[currentFrame];
-      if (frame) {
-        let { url, line } = frame.where;
-        this.updateEditor(url, line, { noSwitch: true });
+      let currentDepth = DebuggerController.StackFrames.currentFrameDepth;
+      let frame = cachedFrames[currentDepth];
+      if (frame && frame.where.url == aUrl) {
+        aLine = frame.where.line;
       }
-      return;
     }
 
-    dumpn("Updating the DebuggerView editor: " + aUrl + " @ " + aLine +
-          ", flags: " + aFlags.toSource());
+    let sourceItem = this.Sources.getItemByValue(aUrl);
+    let sourceForm = sourceItem.attachment.source;
 
-    // If the currently displayed source is the requested one, update.
-    if (this.Sources.selectedValue == aUrl) {
-      set(aLine);
-    }
-    // If the requested source exists, display it and update.
-    else if (this.Sources.containsValue(aUrl) && !aFlags.noSwitch) {
-      this.Sources.selectedValue = aUrl;
-      set(aLine);
-    }
-    // Dumb request, invalidate the caret position and debug location.
-    else {
-      set(0);
-    }
-
-    // Updates the source editor's caret position and debug location.
-    // @param number a Line
-    function set(aLine) {
-      let editor = DebuggerView.editor;
-
-      // Handle any additional options for showing the source.
+    // 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).then(() => {
+      // Line numbers in the source editor should start from 1. If invalid
+      // or not specified, then don't do anything.
+      if (aLine < 1) {
+        return;
+      }
       if (aFlags.charOffset) {
-        aLine += editor.getLineAtOffset(aFlags.charOffset);
+        aLine += this.editor.getLineAtOffset(aFlags.charOffset);
       }
       if (aFlags.lineOffset) {
         aLine += aFlags.lineOffset;
       }
       if (!aFlags.noCaret) {
-        editor.setCaretPosition(aLine - 1, aFlags.columnOffset);
+        this.editor.setCaretPosition(aLine - 1, aFlags.columnOffset);
       }
       if (!aFlags.noDebug) {
-        editor.setDebugLocation(aLine - 1, aFlags.columnOffset);
+        this.editor.setDebugLocation(aLine - 1, aFlags.columnOffset);
       }
-    }
+    });
   },
 
   /**
    * Gets the text in the source editor's specified line.
    *
    * @param number aLine [optional]
-   *        The line to get the text from.
-   *        If unspecified, it defaults to the current caret position line.
+   *        The line to get the text from. If unspecified, it defaults to
+   *        the current caret position.
    * @return string
    *         The specified line's text.
    */
   getEditorLineText: function(aLine) {
     let line = aLine || this.editor.getCaretPosition().line;
     let start = this.editor.getLineStart(line);
     let end = this.editor.getLineEnd(line);
     return this.editor.getText(start, end);
   },
 
   /**
-   * Gets the text in the source editor's selection bounds.
-   *
-   * @return string
-   *         The selected text.
-   */
-  getEditorSelectionText: function() {
-    let selection = this.editor.getSelection();
-    return this.editor.getText(selection.start, selection.end);
-  },
-
-  /**
    * Gets the visibility state of the instruments pane.
    * @return boolean
    */
   get instrumentsPaneHidden()
     this._instrumentsPane.hasAttribute("pane-collapsed"),
 
   /**
    * Sets the instruments pane hidden or visible.
@@ -456,43 +451,44 @@ let DebuggerView = {
     this.FilteredFunctions.clearView();
     this.GlobalSearch.clearView();
     this.ChromeGlobals.empty();
     this.StackFrames.empty();
     this.Sources.empty();
     this.Variables.empty();
 
     if (this.editor) {
+      this.editor.setMode(SourceEditor.MODES.TEXT);
       this.editor.setText("");
-      this.editor.focus();
-      this._editorSource = null;
+      this.editor.resetUndo();
+      this._editorSource = {};
     }
   },
 
+  _startup: null,
+  _shutdown: null,
   Toolbar: null,
   Options: null,
   Filtering: null,
   FilteredSources: null,
   FilteredFunctions: null,
   GlobalSearch: null,
   ChromeGlobals: null,
   StackFrames: null,
   Sources: null,
   Variables: null,
   WatchExpressions: null,
-  _editor: null,
-  _editorSource: null,
+  editor: null,
+  _editorSource: {},
   _loadingText: "",
   _sourcesPane: null,
   _instrumentsPane: null,
   _instrumentsPaneToggleButton: null,
   _collapsePaneString: "",
   _expandPaneString: "",
-  _isInitialized: false,
-  _isDestroyed: false
 };
 
 /**
  * A stacked list of items, compatible with WidgetMethods instances, used for
  * displaying views like the watch expressions, filtering or search results etc.
  *
  * You should never need to access these methods directly, use the wrapped
  * WidgetMethods instead.
@@ -755,20 +751,21 @@ ResultsPanelContainer.prototype = Herita
   get anchor() this._anchor,
 
   /**
    * Sets the container panel hidden or visible. It's hidden by default.
    * @param boolean aFlag
    */
   set hidden(aFlag) {
     if (aFlag) {
+      this._panel.hidden = true;
       this._panel.hidePopup();
     } else {
+      this._panel.hidden = false;
       this._panel.openPopup(this._anchor, this.position, this.left, this.top);
-      this.anchor.focus();
     }
   },
 
   /**
    * Gets this container's visibility state.
    * @return boolean
    */
   get hidden()
--- a/browser/devtools/debugger/test/Makefile.in
+++ b/browser/devtools/debugger/test/Makefile.in
@@ -1,185 +1,192 @@
 # 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/.
 
 MOCHITEST_BROWSER_TESTS = \
 	browser_dbg_aaa_run_first_leaktest.js \
+	browser_dbg_bfcache.js \
 	browser_dbg_blackboxing-01.js \
 	browser_dbg_blackboxing-02.js \
 	browser_dbg_blackboxing-03.js \
 	browser_dbg_blackboxing-04.js \
 	browser_dbg_blackboxing-05.js \
 	browser_dbg_blackboxing-06.js \
 	browser_dbg_blackboxing-07.js \
+	browser_dbg_breadcrumbs-access.js \
+	browser_dbg_breakpoints-actual-location.js \
+	browser_dbg_breakpoints-contextmenu.js \
+	browser_dbg_breakpoints-editor.js \
+	browser_dbg_breakpoints-highlight.js \
+	browser_dbg_breakpoints-new-script.js \
+	browser_dbg_breakpoints-pane.js \
+	browser_dbg_chrome-debugging.js \
 	browser_dbg_clean-exit.js \
-	browser_dbg_cmd.js \
-	browser_dbg_cmd_blackbox.js \
-	browser_dbg_cmd_break.js \
-	browser_dbg_debuggerstatement.js \
+	browser_dbg_cmd-blackbox.js \
+	browser_dbg_cmd-break.js \
+	browser_dbg_cmd-dbg.js \
+	browser_dbg_conditional-breakpoints-01.js \
+	browser_dbg_conditional-breakpoints-02.js \
+	browser_dbg_debugger-statement.js \
+	browser_dbg_editor-contextmenu.js \
+	browser_dbg_editor-mode.js \
+	browser_dbg_function-display-name.js \
+	browser_dbg_globalactor.js \
+	browser_dbg_iframes.js \
+	browser_dbg_instruments-pane-collapse.js \
 	browser_dbg_listaddons.js \
 	browser_dbg_listtabs-01.js \
 	browser_dbg_listtabs-02.js \
-	browser_dbg_tabactor-01.js \
-	browser_dbg_tabactor-02.js \
-	browser_dbg_globalactor-01.js \
-	browser_dbg_nav-01.js \
-	browser_dbg_propertyview-01.js \
-	browser_dbg_propertyview-02.js \
-	browser_dbg_propertyview-03.js \
-	browser_dbg_propertyview-04.js \
-	browser_dbg_propertyview-05.js \
-	browser_dbg_propertyview-06.js \
-	browser_dbg_propertyview-07.js \
-	browser_dbg_propertyview-08.js \
-	browser_dbg_propertyview-09.js \
-	browser_dbg_propertyview-10.js \
-	browser_dbg_propertyview-11.js \
-	browser_dbg_propertyview-12.js \
-	browser_dbg_propertyview-edit-value.js \
-	browser_dbg_propertyview-edit-watch.js \
-	browser_dbg_propertyview-data-big.js \
-	browser_dbg_propertyview-data-getset-01.js \
-	browser_dbg_propertyview-data-getset-02.js \
-	browser_dbg_propertyview-data.js \
-	browser_dbg_propertyview-filter-01.js \
-	browser_dbg_propertyview-filter-02.js \
-	browser_dbg_propertyview-filter-03.js \
-	browser_dbg_propertyview-filter-04.js \
-	browser_dbg_propertyview-filter-05.js \
-	browser_dbg_propertyview-filter-06.js \
-	browser_dbg_propertyview-filter-07.js \
-	browser_dbg_propertyview-filter-08.js \
-	browser_dbg_propertyview-reexpand.js \
-	$(filter disabled-temporarily--bug-782179, browser_dbg_reload-same-script.js) \
-	browser_dbg_reload-preferred-script.js \
-	browser_dbg_pane-collapse.js \
-	browser_dbg_panesize-inner.js \
-	browser_dbg_breadcrumbs-access.js \
+	browser_dbg_location-changes-01-simple.js \
+	browser_dbg_location-changes-02-blank.js \
+	browser_dbg_location-changes-03-new.js \
+	browser_dbg_location-changes-04-breakpoint.js \
+	browser_dbg_multiple-windows.js \
+	browser_dbg_navigation.js \
+	browser_dbg_on-pause-highlight.js \
+	browser_dbg_panel-size.js \
+	browser_dbg_pause-exceptions-01.js \
+	browser_dbg_pause-exceptions-02.js \
+	browser_dbg_pause-resume.js \
+	browser_dbg_pause-warning.js \
+	browser_dbg_progress-listener-bug.js \
+	browser_dbg_reload-preferred-script-01.js \
+	browser_dbg_reload-preferred-script-02.js \
+	browser_dbg_reload-preferred-script-03.js \
+	browser_dbg_reload-same-script.js \
+	browser_dbg_scripts-switching-01.js \
+	browser_dbg_scripts-switching-02.js \
+	browser_dbg_scripts-switching-03.js \
+	browser_dbg_search-basic-01.js \
+	browser_dbg_search-basic-02.js \
+	browser_dbg_search-basic-03.js \
+	browser_dbg_search-basic-04.js \
+	browser_dbg_search-global-01.js \
+	browser_dbg_search-global-02.js \
+	browser_dbg_search-global-03.js \
+	browser_dbg_search-global-04.js \
+	browser_dbg_search-global-05.js \
+	browser_dbg_search-global-06.js \
+	browser_dbg_search-sources-01.js \
+	browser_dbg_search-sources-02.js \
+	browser_dbg_search-sources-03.js \
+	browser_dbg_search-symbols.js \
+	browser_dbg_searchbox-help-popup-01.js \
+	browser_dbg_searchbox-help-popup-02.js \
+	browser_dbg_searchbox-parse.js \
+	browser_dbg_source-maps-01.js \
+	browser_dbg_source-maps-02.js \
+	browser_dbg_source-maps-03.js \
+	browser_dbg_sources-cache.js \
+	browser_dbg_sources-labels.js \
+	browser_dbg_sources-sorting.js \
 	browser_dbg_stack-01.js \
 	browser_dbg_stack-02.js \
 	browser_dbg_stack-03.js \
 	browser_dbg_stack-04.js \
 	browser_dbg_stack-05.js \
-	browser_dbg_location-changes.js \
-	browser_dbg_location-changes-new.js \
-	browser_dbg_location-changes-blank.js \
-	browser_dbg_location-changes-bp.js \
-	browser_dbg_sources-cache.js \
-	browser_dbg_scripts-switching.js \
-	browser_dbg_scripts-switching-02.js \
-	browser_dbg_scripts-switching-03.js \
-	browser_dbg_scripts-sorting.js \
-	browser_dbg_scripts-searching-01.js \
-	browser_dbg_scripts-searching-02.js \
-	browser_dbg_scripts-searching-03.js \
-	browser_dbg_scripts-searching-04.js \
-	browser_dbg_scripts-searching-05.js \
-	browser_dbg_scripts-searching-06.js \
-	browser_dbg_scripts-searching-07.js \
-	browser_dbg_scripts-searching-08.js \
-	browser_dbg_scripts-searching-files_ui.js \
-	browser_dbg_scripts-searching-popup.js \
-	browser_dbg_function-search.js \
-	browser_dbg_pause-resume.js \
-	browser_dbg_pause-warning.js \
-	browser_dbg_update-editor-mode.js \
-	browser_dbg_select-line.js \
-	browser_dbg_breakpoint-new-script.js \
-	browser_dbg_bug723069_editor-breakpoints.js \
-	browser_dbg_bug723071_editor-breakpoints-pane.js \
-	browser_dbg_bug723071_editor-breakpoints-highlight.js \
-	browser_dbg_bug723071_editor-breakpoints-contextmenu.js \
-	browser_dbg_bug740825_conditional-breakpoints-01.js \
-	browser_dbg_bug740825_conditional-breakpoints-02.js \
-	browser_dbg_bug727429_watch-expressions-01.js \
-	browser_dbg_bug727429_watch-expressions-02.js \
-	browser_dbg_bug731394_editor-contextmenu.js \
-	browser_dbg_bug737803_editor_actual_location.js \
-	browser_dbg_bug786070_hide_nonenums.js \
-	browser_dbg_bug868163_highight_on_pause.js \
-	browser_dbg_displayName.js \
-	browser_dbg_pause-exceptions.js \
-	browser_dbg_pause-exceptions-reload.js \
-	browser_dbg_multiple-windows.js \
-	browser_dbg_iframes.js \
-	browser_dbg_bfcache.js \
-	browser_dbg_progress-listener-bug.js \
-	browser_dbg_chrome-debugging.js \
-	browser_dbg_source_maps-01.js \
-	browser_dbg_source_maps-02.js \
-	browser_dbg_source_maps-03.js \
+	browser_dbg_stack-06.js \
 	browser_dbg_step-out.js \
-	browser_dbg_event-listeners.js \
+	browser_dbg_tabactor-01.js \
+	browser_dbg_tabactor-02.js \
+	browser_dbg_variables-view-01.js \
+	browser_dbg_variables-view-02.js \
+	browser_dbg_variables-view-03.js \
+	browser_dbg_variables-view-04.js \
+	browser_dbg_variables-view-05.js \
+	browser_dbg_variables-view-accessibility.js \
+	browser_dbg_variables-view-data.js \
+	browser_dbg_variables-view-edit-getset-01.js \
+	browser_dbg_variables-view-edit-getset-02.js \
+	browser_dbg_variables-view-edit-value.js \
+	browser_dbg_variables-view-edit-watch.js \
+	browser_dbg_variables-view-filter-01.js \
+	browser_dbg_variables-view-filter-02.js \
+	browser_dbg_variables-view-filter-03.js \
+	browser_dbg_variables-view-filter-04.js \
+	browser_dbg_variables-view-filter-05.js \
+	browser_dbg_variables-view-filter-pref.js \
+	browser_dbg_variables-view-filter-searchbox.js \
+	browser_dbg_variables-view-frame-parameters-01.js \
+	browser_dbg_variables-view-frame-parameters-02.js \
+	browser_dbg_variables-view-frame-parameters-03.js \
+	browser_dbg_variables-view-frame-with.js \
+	browser_dbg_variables-view-frozen-sealed-nonext.js \
+	browser_dbg_variables-view-hide-non-enums.js \
+	browser_dbg_variables-view-large-array-buffer.js \
+	browser_dbg_variables-view-reexpand-01.js \
+	browser_dbg_variables-view-reexpand-02.js \
+	browser_dbg_variables-view-webidl.js \
+	browser_dbg_watch-expressions-01.js \
+	browser_dbg_watch-expressions-02.js \
 	head.js \
 	$(NULL)
 
 MOCHITEST_BROWSER_PAGES = \
-	browser_dbg_blackboxing.html \
-	blackboxing_blackboxme.js \
-	blackboxing_one.js \
-	blackboxing_two.js \
-	blackboxing_three.js \
-	browser_dbg_cmd_break.html \
-	browser_dbg_cmd.html \
+	doc_binary_search.html \
+	doc_blackboxing.html \
+	doc_cmd-break.html \
+	doc_cmd-dbg.html \
+	doc_conditional-breakpoints.html \
+	doc_editor-mode.html \
+	doc_empty-tab-01.html \
+	doc_empty-tab-02.html \
+	doc_event-listeners.html \
+	doc_frame-parameters.html \
+	doc_function-display-name.html \
+	doc_function-search.html \
+	doc_iframes.html \
+	doc_included-script.html \
+	doc_inline-debugger-statement.html \
+	doc_inline-script.html \
+	doc_large-array-buffer.html \
+	doc_minified.html \
+	doc_pause-exceptions.html \
+	doc_recursion-stack.html \
+	doc_script-switching-01.html \
+	doc_script-switching-02.html \
+	doc_step-out.html \
+	doc_watch-expressions.html \
+	doc_with-frame.html \
+	code_binary_search.coffee \
+	code_binary_search.js \
+	code_binary_search.map \
+	code_blackboxing_blackboxme.js \
+	code_blackboxing_one.js \
+	code_blackboxing_two.js \
+	code_blackboxing_three.js \
+	code_function-search-01.js \
+	code_function-search-02.js \
+	code_function-search-03.js \
+	code_location-changes.js \
+	code_math.js \
+	code_math.map \
+	code_math.min.js \
+	code_script-switching-01.js \
+	code_script-switching-02.js \
+	code_test-editor-mode \
 	testactors.js \
-	browser_dbg_tab1.html \
-	browser_dbg_tab2.html \
-	browser_dbg_addon1.xpi \
-	browser_dbg_addon2.xpi \
-	browser_dbg_debuggerstatement.html \
-	browser_dbg_stack.html \
-	browser_dbg_script-switching.html \
-	browser_dbg_script-switching-02.html \
-	test-script-switching-01.js \
-	test-script-switching-02.js \
-	browser_dbg_big-data.html \
-	browser_dbg_frame-parameters.html \
-	browser_dbg_update-editor-mode.html \
-	test-editor-mode \
-	browser_dbg_displayName.html \
-	browser_dbg_iframes.html \
-	browser_dbg_with-frame.html \
-	browser_dbg_pause-exceptions.html \
-	browser_dbg_breakpoint-new-script.html \
-	browser_dbg_conditional-breakpoints.html \
-	browser_dbg_watch-expressions.html \
-	browser_dbg_function-search-01.html \
-	browser_dbg_function-search-02.html \
-	test-function-search-01.js \
-	test-function-search-02.js \
-	test-function-search-03.js \
-	binary_search.html \
-	binary_search.coffee \
-	binary_search.js \
-	binary_search.map \
-	math.js \
-	math.min.js \
-	math.map \
-	minified.html \
-	test-location-changes-bp.js \
-	test-location-changes-bp.html \
-	test-step-out.html \
-	test-pause-exceptions-reload.html \
-	test-event-listeners.html \
+	addon1.xpi \
+	addon2.xpi \
 	$(NULL)
 
 # Bug 888811 & bug 891176:
-#   Disable browser_dbg_bug883220_raise_on_pause.js due to frequent failures
+#   Disable browser_dbg_on-pause-raise.js due to frequent failures
+# Bug 847558:
+#   Disable browser_dbg_chrome-create.js to fix Ubuntu hangs
 ifneq (Linux,$(OS_ARCH))
 MOCHITEST_BROWSER_TESTS += \
-	browser_dbg_bug883220_raise_on_pause.js \
-	browser_dbg_createChrome.js \
+	browser_dbg_chrome-create.js \
+	browser_dbg_on-pause-raise.js \
 	$(NULL)
-else
-$(browser_dbg_createChrome.js disabled to fix for ubuntu hangs, bug 847558)
 endif
 
 # Bug 895426:
 #   Disable browser_dbg_break-on-dom-event.js due to frequent failures
 ifneq (Darwin,$(OS_ARCH))
 MOCHITEST_BROWSER_TESTS += \
 	browser_dbg_break-on-dom-event.js \
+	browser_dbg_event-listeners.js \
 	$(NULL)
 endif
 
 MOCHITEST_BROWSER_FILES_PARTS = MOCHITEST_BROWSER_TESTS MOCHITEST_BROWSER_PAGES
rename from browser/devtools/debugger/test/browser_dbg_addon1.xpi
rename to browser/devtools/debugger/test/addon1.xpi
rename from browser/devtools/debugger/test/browser_dbg_addon2.xpi
rename to browser/devtools/debugger/test/addon2.xpi
--- a/browser/devtools/debugger/test/browser_dbg_aaa_run_first_leaktest.js
+++ b/browser/devtools/debugger/test/browser_dbg_aaa_run_first_leaktest.js
@@ -1,73 +1,28 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
- * This tests if the debugger leaks.
+ * This tests if the debugger leaks on initialization and sudden destruction.
+ * You can also use this initialization format as a template for other tests.
  * If leaks happen here, there's something very, very fishy going on.
  */
 
-const TAB_URL = EXAMPLE_URL + "browser_dbg_script-switching.html";
-
-let gPane = null;
-let gTab = null;
-let gDebuggee = null;
-let gDebugger = null;
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
 
-function test()
-{
-  let scriptShown = false;
-  let framesAdded = false;
-  let resumed = false;
-  let testStarted = false;
-
+function test() {
   // Wait longer for this very simple test that comes first, to make sure that
   // GC from previous tests does not interfere with the debugger suite.
   requestLongerTimeout(2);
 
-  debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
-    gTab = aTab;
-    gDebuggee = aDebuggee;
-    gPane = aPane;
-    gDebugger = gPane.panelWin;
-    resumed = true;
+  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
+    ok(aTab, "Should have a tab available.");
+    ok(aDebuggee, "Should have a debuggee available.");
+    ok(aPanel, "Should have a debugger pane available.");
 
-    gDebugger.addEventListener("Debugger:SourceShown", onScriptShown);
-
-    gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
-      framesAdded = true;
-      executeSoon(startTest);
+    waitForSourceAndCaretAndScopes(aPanel, "-02.js", 6).then(() => {
+      resumeDebuggerThenCloseAndFinish(aPanel);
     });
 
-    executeSoon(function() {
-      gDebuggee.firstCall();
-    });
-  });
-
-  function onScriptShown(aEvent)
-  {
-    scriptShown = aEvent.detail.url.indexOf("-02.js") != -1;
-    executeSoon(startTest);
-  }
-
-  function startTest()
-  {
-    if (scriptShown && framesAdded && resumed && !testStarted) {
-      gDebugger.removeEventListener("Debugger:SourceShown", onScriptShown);
-      testStarted = true;
-      Services.tm.currentThread.dispatch({ run: performTest }, 0);
-    }
-  }
-
-  function performTest()
-  {
-    closeDebuggerAndFinish();
-  }
-
-  registerCleanupFunction(function() {
-    removeTab(gTab);
-    gPane = null;
-    gTab = null;
-    gDebuggee = null;
-    gDebugger = null;
+    aDebuggee.firstCall();
   });
 }
--- a/browser/devtools/debugger/test/browser_dbg_bfcache.js
+++ b/browser/devtools/debugger/test/browser_dbg_bfcache.js
@@ -1,129 +1,92 @@
-/* vim:set ts=2 sw=2 sts=2 et: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
- * Make sure that the debugger is updated with the correct scripts when moving
+ * Make sure that the debugger is updated with the correct sources when moving
  * back and forward in the tab.
  */
 
-const TAB_URL = EXAMPLE_URL + "browser_dbg_script-switching.html";
-var gPane = null;
-var gTab = null;
-var gDebuggee = null;
-var gDebugger = null;
-var gSources = null;
+const TAB_URL_1 = EXAMPLE_URL + "doc_script-switching-01.html";
+const TAB_URL_2 = EXAMPLE_URL + "doc_recursion-stack.html";
 
-function test()
-{
-  debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
+let gTab, gDebuggee, gPanel, gDebugger;
+let gSources;
+
+function test() {
+  initDebugger(TAB_URL_1).then(([aTab, aDebuggee, aPanel]) => {
     gTab = aTab;
     gDebuggee = aDebuggee;
-    gPane = aPane;
-    gDebugger = gPane.panelWin;
+    gPanel = aPanel;
+    gDebugger = gPanel.panelWin;
+    gSources = gDebugger.DebuggerView.Sources;
 
-    testInitialLoad();
+    testFirstPage()
+      .then(testLocationChange)
+      .then(testBack)
+      .then(testForward)
+      .then(() => closeDebuggerAndFinish(gPanel))
+      .then(null, aError => {
+        ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+      });
   });
 }
 
-function testInitialLoad() {
-  gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
-    executeSoon(function() {
-      validateFirstPage();
-      testLocationChange();
-    });
-  });
-
-  gDebuggee.firstCall();
-}
+function testFirstPage() {
+  info("Testing first page.");
 
-function testLocationChange()
-{
-  gDebugger.DebuggerController.activeThread.resume(function() {
-    gDebugger.DebuggerController._target.once("navigate", function onTabNavigated(aEvent, aPacket) {
-      ok(true, "tabNavigated event was fired.");
-      info("Still attached to the tab.");
+  // Spin the event loop before causing the debuggee to pause, to allow
+  // this function to return first.
+  executeSoon(() => gDebuggee.firstCall());
 
-      gDebugger.addEventListener("Debugger:AfterSourcesAdded", function _onEvent(aEvent) {
-        gDebugger.removeEventListener(aEvent.type, _onEvent);
-
-        executeSoon(function() {
-          validateSecondPage();
-          testBack();
-        });
-      });
-    });
-    gDebugger.DebuggerController.client.activeTab.navigateTo(STACK_URL);
+  return waitForSourceAndCaretAndScopes(gPanel, "-02.js", 6).then(() => {
+    validateFirstPage();
   });
 }
 
-function testBack()
-{
-  gDebugger.DebuggerController._target.once("navigate", function onTabNavigated(aEvent, aPacket) {
-    ok(true, "tabNavigated event was fired after going back.");
-    info("Still attached to the tab.");
-
-    gDebugger.addEventListener("Debugger:AfterSourcesAdded", function _onEvent(aEvent) {
-      gDebugger.removeEventListener(aEvent.type, _onEvent);
+function testLocationChange() {
+  info("Navigating to a different page.");
 
-      executeSoon(function() {
-        validateFirstPage();
-        testForward();
-      });
-    });
+  return navigateActiveTabTo(gPanel, TAB_URL_2, gDebugger.EVENTS.SOURCES_ADDED).then(() => {
+    validateSecondPage();
   });
-
-  info("Going back.");
-  content.history.back();
 }
 
-function testForward()
-{
-  gDebugger.DebuggerController._target.once("navigate", function onTabNavigated(aEvent, aPacket) {
-    ok(true, "tabNavigated event was fired after going forward.");
-    info("Still attached to the tab.");
-
-    gDebugger.addEventListener("Debugger:AfterSourcesAdded", function _onEvent(aEvent) {
-      gDebugger.removeEventListener(aEvent.type, _onEvent);
+function testBack() {
+  info("Going back.");
 
-      executeSoon(function() {
-        validateSecondPage();
-        closeDebuggerAndFinish();
-      });
-    });
+  return navigateActiveTabInHistory(gPanel, "back", gDebugger.EVENTS.SOURCES_ADDED).then(() => {
+    validateFirstPage();
   });
+}
 
+function testForward() {
   info("Going forward.");
-  content.history.forward();
+
+  return navigateActiveTabInHistory(gPanel, "forward", gDebugger.EVENTS.SOURCES_ADDED).then(() => {
+    validateSecondPage();
+  });
 }
 
 function validateFirstPage() {
-  gSources = gDebugger.DebuggerView.Sources;
-
   is(gSources.itemCount, 2,
-    "Found the expected number of scripts.");
-
-  ok(gDebugger.DebuggerView.Sources.containsLabel("test-script-switching-01.js"),
-     "Found the first script label.");
-  ok(gDebugger.DebuggerView.Sources.containsLabel("test-script-switching-02.js"),
-     "Found the second script label.");
+    "Found the expected number of sources.");
+  ok(gSources.containsLabel("code_script-switching-01.js"),
+    "Found the first source label.");
+  ok(gSources.containsLabel("code_script-switching-02.js"),
+    "Found the second source label.");
 }
 
 function validateSecondPage() {
-  gSources = gDebugger.DebuggerView.Sources;
-
   is(gSources.itemCount, 1,
-    "Found the expected number of scripts.");
-
-  ok(gDebugger.DebuggerView.Sources.containsLabel("browser_dbg_stack.html"),
-     "Found the single script label.");
+    "Found the expected number of sources.");
+  ok(gSources.containsLabel("doc_recursion-stack.html"),
+    "Found the single source label.");
 }
 
 registerCleanupFunction(function() {
-  removeTab(gTab);
-  gPane = null;
   gTab = null;
   gDebuggee = null;
+  gPanel = null;
   gDebugger = null;
   gSources = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_blackboxing-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_blackboxing-01.js
@@ -1,76 +1,56 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Test that if we black box a source and then refresh, it is still black boxed.
  */
 
-const TAB_URL = EXAMPLE_URL + "binary_search.html";
-
-var gPane = null;
-var gTab = null;
-var gDebuggee = null;
-var gDebugger = null;
+const TAB_URL = EXAMPLE_URL + "doc_binary_search.html";
 
-function test()
-{
-  let scriptShown = false;
-  let framesAdded = false;
-  let resumed = false;
-  let testStarted = false;
+let gTab, gDebuggee, gPanel, gDebugger;
 
-  debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
-    resumed = true;
+function test() {
+  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
     gTab = aTab;
     gDebuggee = aDebuggee;
-    gPane = aPane;
-    gDebugger = gPane.panelWin;
+    gPanel = aPanel;
+    gDebugger = gPanel.panelWin;
 
-    testBlackBoxSource();
+    waitForSourceShown(gPanel, ".coffee")
+      .then(testBlackBoxSource)
+      .then(testBlackBoxReload)
+      .then(() => closeDebuggerAndFinish(gPanel))
+      .then(null, aError => {
+        ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+      });
   });
 }
 
 function testBlackBoxSource() {
-  once(gDebugger, "Debugger:SourceShown", function () {
-    const checkbox = gDebugger.document.querySelector(".side-menu-widget-item-checkbox");
-    ok(checkbox, "Should get the checkbox for black boxing the source");
-    ok(checkbox.checked, "Should not be black boxed by default");
+  const checkbox = gDebugger.document.querySelector(".side-menu-widget-item-checkbox");
+  ok(checkbox, "Should get the checkbox for black boxing the source.");
+  ok(checkbox.checked, "Should not be black boxed by default.");
 
-    const { activeThread } = gDebugger.DebuggerController;
-    activeThread.addOneTimeListener("blackboxchange", function (event, sourceClient) {
-      ok(sourceClient.isBlackBoxed, "The source should be black boxed now");
-      ok(!checkbox.checked, "The checkbox should no longer be checked.");
+  let finished = waitForThreadEvents(gPanel, "blackboxchange").then(aSource => {
+    ok(aSource.isBlackBoxed, "The source should be black boxed now.");
+    ok(!checkbox.checked, "The checkbox should no longer be checked.");
+  });
 
-      testBlackBoxReload();
-    });
-
-    checkbox.click();
-  });
+  checkbox.click();
+  return finished;
 }
 
 function testBlackBoxReload() {
-  once(gDebugger, "Debugger:SourceShown", function () {
+  return reloadActiveTab(gPanel, gDebugger.EVENTS.SOURCE_SHOWN).then(() => {
     const checkbox = gDebugger.document.querySelector(".side-menu-widget-item-checkbox");
-    ok(checkbox, "Should get the checkbox for black boxing the source");
-    ok(!checkbox.checked, "Should still be black boxed");
-
-    closeDebuggerAndFinish();
+    ok(checkbox, "Should get the checkbox for black boxing the source.");
+    ok(!checkbox.checked, "Should still be black boxed.");
   });
-
-  gDebuggee.location.reload();
-}
-
-function once(target, event, callback) {
-  target.addEventListener(event, function _listener(...args) {
-    target.removeEventListener(event, _listener, false);
-    callback.apply(null, args);
-  }, false);
 }
 
 registerCleanupFunction(function() {
-  removeTab(gTab);
-  gPane = null;
   gTab = null;
   gDebuggee = null;
+  gPanel = null;
   gDebugger = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_blackboxing-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_blackboxing-02.js
@@ -1,87 +1,68 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Test that black boxed frames are compressed into a single frame on the stack
  * view.
  */
 
-const TAB_URL = EXAMPLE_URL + "browser_dbg_blackboxing.html";
-const BLACKBOXME_URL = EXAMPLE_URL + "blackboxing_blackboxme.js"
-
-var gPane = null;
-var gTab = null;
-var gDebuggee = null;
-var gDebugger = null;
+const TAB_URL = EXAMPLE_URL + "doc_blackboxing.html";
+const BLACKBOXME_URL = EXAMPLE_URL + "code_blackboxing_blackboxme.js"
 
-function test()
-{
-  let scriptShown = false;
-  let framesAdded = false;
-  let resumed = false;
-  let testStarted = false;
+let gTab, gDebuggee, gPanel, gDebugger;
+let gFrames;
 
-  debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
-    resumed = true;
+function test() {
+  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
     gTab = aTab;
     gDebuggee = aDebuggee;
-    gPane = aPane;
-    gDebugger = gPane.panelWin;
+    gPanel = aPanel;
+    gDebugger = gPanel.panelWin;
+    gFrames = gDebugger.DebuggerView.StackFrames;
 
-    testBlackBoxSource();
+    waitForSourceShown(gPanel, BLACKBOXME_URL)
+      .then(testBlackBoxSource)
+      .then(testBlackBoxStack)
+      .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+      .then(null, aError => {
+        ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+      });
   });
 }
 
 function testBlackBoxSource() {
-  once(gDebugger, "Debugger:SourceShown", function () {
-    const checkbox = getBlackBoxCheckbox(BLACKBOXME_URL);
-    ok(checkbox, "Should get the checkbox for blackBoxing the source");
+  let finished = waitForThreadEvents(gPanel, "blackboxchange").then(aSource => {
+    ok(aSource.isBlackBoxed, "The source should be black boxed now.");
+  });
 
-    const { activeThread } = gDebugger.DebuggerController;
-    activeThread.addOneTimeListener("blackboxchange", function (event, sourceClient) {
-      ok(sourceClient.isBlackBoxed, "The source should be black boxed now");
-
-      testBlackBoxStack();
-    });
-
-    checkbox.click();
-  });
+  getBlackBoxCheckbox(BLACKBOXME_URL).click();
+  return finished;
 }
 
 function testBlackBoxStack() {
-  const { activeThread } = gDebugger.DebuggerController;
-  activeThread.addOneTimeListener("framesadded", function () {
-    const frames = gDebugger.DebuggerView.StackFrames.widget._list;
-
-    is(frames.querySelectorAll(".dbg-stackframe").length, 3,
-       "Should only get 3 frames");
-
-    is(frames.querySelectorAll(".dbg-stackframe-black-boxed").length, 1,
-       "And one of them should be the combined black boxed frames");
-
-    closeDebuggerAndFinish();
+  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,
+      "And one of them should be the combined black boxed frames.");
   });
 
-  gDebuggee.runTest();
+  // Spin the event loop before causing the debuggee to pause, to allow
+  // this function to return first.
+  executeSoon(() => gDebuggee.runTest());
+  return finished;
 }
 
-function getBlackBoxCheckbox(url) {
+function getBlackBoxCheckbox(aUrl) {
   return gDebugger.document.querySelector(
-    ".side-menu-widget-item[tooltiptext=\""
-      + url + "\"] .side-menu-widget-item-checkbox");
-}
-
-function once(target, event, callback) {
-  target.addEventListener(event, function _listener(...args) {
-    target.removeEventListener(event, _listener, false);
-    callback.apply(null, args);
-  }, false);
+    ".side-menu-widget-item[tooltiptext=\"" + aUrl + "\"] " +
+    ".side-menu-widget-item-checkbox");
 }
 
 registerCleanupFunction(function() {
-  removeTab(gTab);
-  gPane = null;
   gTab = null;
   gDebuggee = null;
+  gPanel = null;
   gDebugger = null;
+  gFrames = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_blackboxing-03.js
+++ b/browser/devtools/debugger/test/browser_dbg_blackboxing-03.js
@@ -1,93 +1,68 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Test that black boxed frames are compressed into a single frame on the stack
  * view when we are already paused.
  */
 
-const TAB_URL = EXAMPLE_URL + "browser_dbg_blackboxing.html";
-const BLACKBOXME_URL = EXAMPLE_URL + "blackboxing_blackboxme.js"
-
-var gPane = null;
-var gTab = null;
-var gDebuggee = null;
-var gDebugger = null;
+const TAB_URL = EXAMPLE_URL + "doc_blackboxing.html";
+const BLACKBOXME_URL = EXAMPLE_URL + "code_blackboxing_blackboxme.js"
 
-function test()
-{
-  let scriptShown = false;
-  let framesAdded = false;
-  let resumed = false;
-  let testStarted = false;
+let gTab, gDebuggee, gPanel, gDebugger;
+let gFrames;
 
-  debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
-    resumed = true;
+function test() {
+  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
     gTab = aTab;
     gDebuggee = aDebuggee;
-    gPane = aPane;
-    gDebugger = gPane.panelWin;
+    gPanel = aPanel;
+    gDebugger = gPanel.panelWin;
+    gFrames = gDebugger.DebuggerView.StackFrames;
 
-    once(gDebugger, "Debugger:SourceShown", function () {
-      testBlackBoxStack();
-    });
+    waitForSourceAndCaretAndScopes(gPanel, ".html", 21)
+      .then(testBlackBoxStack)
+      .then(testBlackBoxSource)
+      .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+      .then(null, aError => {
+        ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+      });
+
+    gDebuggee.runTest();
   });
 }
 
 function testBlackBoxStack() {
-  const { activeThread } = gDebugger.DebuggerController;
-  activeThread.addOneTimeListener("framesadded", function () {
-    const frames = gDebugger.DebuggerView.StackFrames.widget._list;
-
-    is(frames.querySelectorAll(".dbg-stackframe").length, 6,
-       "Should get 6 frames");
-
-    is(frames.querySelectorAll(".dbg-stackframe-black-boxed").length, 0,
-       "And none of them are black boxed");
-
-    testBlackBoxSource();
-  });
-
-  gDebuggee.runTest();
+  is(gFrames.itemCount, 6,
+    "Should get 6 frames.");
+  is(gDebugger.document.querySelectorAll(".dbg-stackframe-black-boxed").length, 0,
+    "And none of them are black boxed.");
 }
 
 function testBlackBoxSource() {
-  const checkbox = getBlackBoxCheckbox(BLACKBOXME_URL);
-  ok(checkbox, "Should get the checkbox for black boxing the source");
-
-  const { activeThread } = gDebugger.DebuggerController;
-  activeThread.addOneTimeListener("blackboxchange", function (event, sourceClient) {
-    ok(sourceClient.isBlackBoxed, "The source should be black boxed now");
+  let finished = waitForThreadEvents(gPanel, "blackboxchange").then(aSource => {
+    ok(aSource.isBlackBoxed, "The source should be black boxed now.");
 
-    const frames = gDebugger.DebuggerView.StackFrames.widget._list;
-    is(frames.querySelectorAll(".dbg-stackframe").length, 3,
-       "Should only get 3 frames");
-    is(frames.querySelectorAll(".dbg-stackframe-black-boxed").length, 1,
-       "And one of them is the combined black boxed frames");
-
-    closeDebuggerAndFinish();
+    is(gFrames.itemCount, 3,
+      "Should only get 3 frames.");
+    is(gDebugger.document.querySelectorAll(".dbg-stackframe-black-boxed").length, 1,
+      "And one of them should be the combined black boxed frames.");
   });
 
-  checkbox.click();
+  getBlackBoxCheckbox(BLACKBOXME_URL).click();
+  return finished;
 }
 
-function getBlackBoxCheckbox(url) {
+function getBlackBoxCheckbox(aUrl) {
   return gDebugger.document.querySelector(
-    ".side-menu-widget-item[tooltiptext=\""
-      + url + "\"] .side-menu-widget-item-checkbox");
-}
-
-function once(target, event, callback) {
-  target.addEventListener(event, function _listener(...args) {
-    target.removeEventListener(event, _listener, false);
-    callback.apply(null, args);
-  }, false);
+    ".side-menu-widget-item[tooltiptext=\"" + aUrl + "\"] " +
+    ".side-menu-widget-item-checkbox");
 }
 
 registerCleanupFunction(function() {
-  removeTab(gTab);
-  gPane = null;
   gTab = null;
   gDebuggee = null;
+  gPanel = null;
   gDebugger = null;
+  gFrames = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_blackboxing-04.js
+++ b/browser/devtools/debugger/test/browser_dbg_blackboxing-04.js
@@ -1,83 +1,67 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Test that we get a stack frame for each black boxed source, not a single one
  * for all of them.
  */
 
-const TAB_URL = EXAMPLE_URL + "browser_dbg_blackboxing.html";
-
-var gPane = null;
-var gTab = null;
-var gDebuggee = null;
-var gDebugger = null;
+const TAB_URL = EXAMPLE_URL + "doc_blackboxing.html";
+const BLACKBOXME_URL = EXAMPLE_URL + "code_blackboxing_blackboxme.js"
 
-function test()
-{
-  let scriptShown = false;
-  let framesAdded = false;
-  let resumed = false;
-  let testStarted = false;
+let gTab, gDebuggee, gPanel, gDebugger;
+let gFrames;
 
-  debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
-    resumed = true;
+function test() {
+  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
     gTab = aTab;
     gDebuggee = aDebuggee;
-    gPane = aPane;
-    gDebugger = gPane.panelWin;
+    gPanel = aPanel;
+    gDebugger = gPanel.panelWin;
+    gFrames = gDebugger.DebuggerView.StackFrames;
 
-    once(gDebugger, "Debugger:SourceShown", function () {
-      blackBoxSources();
-    });
+    waitForSourceShown(gPanel, BLACKBOXME_URL)
+      .then(blackBoxSources)
+      .then(testBlackBoxStack)
+      .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+      .then(null, aError => {
+        ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+      });
   });
 }
 
 function blackBoxSources() {
-  let timesFired = 0;
-  const { activeThread } = gDebugger.DebuggerController;
-  activeThread.addListener("blackboxchange", function _onBlackBoxChange() {
-    if (++timesFired !== 3) {
-      return;
-    }
-    activeThread.removeListener("blackboxchange", _onBlackBoxChange);
-
-    activeThread.addOneTimeListener("framesadded", testStackFrames);
-    gDebuggee.one();
-  }, false);
-
-  getBlackBoxCheckbox(EXAMPLE_URL + "blackboxing_one.js").click();
-  getBlackBoxCheckbox(EXAMPLE_URL + "blackboxing_two.js").click();
-  getBlackBoxCheckbox(EXAMPLE_URL + "blackboxing_three.js").click();
+  let finished = waitForThreadEvents(gPanel, "blackboxchange", 3);
+  getBlackBoxCheckbox(EXAMPLE_URL + "code_blackboxing_one.js").click();
+  getBlackBoxCheckbox(EXAMPLE_URL + "code_blackboxing_two.js").click();
+  getBlackBoxCheckbox(EXAMPLE_URL + "code_blackboxing_three.js").click();
+  return finished;
 }
 
-function testStackFrames() {
-  const frames = gDebugger.DebuggerView.StackFrames.widget._list;
-  is(frames.querySelectorAll(".dbg-stackframe").length, 4,
-     "Should get 4 frames (one -> two -> three -> doDebuggerStatement)");
-  is(frames.querySelectorAll(".dbg-stackframe-black-boxed").length, 3,
-     "And one, two, and three should each have their own black boxed frame.");
+function testBlackBoxStack() {
+  let finished = waitForSourceAndCaretAndScopes(gPanel, ".html", 21).then(() => {
+    is(gFrames.itemCount, 4,
+      "Should get 4 frames (one -> two -> three -> doDebuggerStatement).");
+    is(gDebugger.document.querySelectorAll(".dbg-stackframe-black-boxed").length, 3,
+      "And 'one', 'two', and 'three' should each have their own black boxed frame.");
+  });
 
-  closeDebuggerAndFinish();
+  // Spin the event loop before causing the debuggee to pause, to allow
+  // this function to return first.
+  executeSoon(() => gDebuggee.one());
+  return finished;
 }
 
-function getBlackBoxCheckbox(url) {
+function getBlackBoxCheckbox(aUrl) {
   return gDebugger.document.querySelector(
-    ".side-menu-widget-item[tooltiptext=\""
-      + url + "\"] .side-menu-widget-item-checkbox");
-}
-
-function once(target, event, callback) {
-  target.addEventListener(event, function _listener(...args) {
-    target.removeEventListener(event, _listener, false);
-    callback.apply(null, args);
-  }, false);
+    ".side-menu-widget-item[tooltiptext=\"" + aUrl + "\"] " +
+    ".side-menu-widget-item-checkbox");
 }
 
 registerCleanupFunction(function() {
-  removeTab(gTab);
-  gPane = null;
   gTab = null;
   gDebuggee = null;
+  gPanel = null;
   gDebugger = null;
+  gFrames = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_blackboxing-05.js
+++ b/browser/devtools/debugger/test/browser_dbg_blackboxing-05.js
@@ -1,86 +1,77 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
- * Test that we get a stack frame for each black boxed source, not a single one
- * for all of them.
+ * Test that a "this source is blackboxed" message is shown when necessary
+ * and can be properly dismissed.
  */
 
-const TAB_URL = EXAMPLE_URL + "binary_search.html";
-
-var gPane = null;
-var gTab = null;
-var gDebuggee = null;
-var gDebugger = null;
+const TAB_URL = EXAMPLE_URL + "doc_binary_search.html";
 
-function test()
-{
-  let scriptShown = false;
-  let framesAdded = false;
-  let resumed = false;
-  let testStarted = false;
+let gTab, gDebuggee, gPanel, gDebugger;
+let gDeck;
 
-  debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
-    resumed = true;
+function test() {
+  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
     gTab = aTab;
     gDebuggee = aDebuggee;
-    gPane = aPane;
-    gDebugger = gPane.panelWin;
+    gPanel = aPanel;
+    gDebugger = gPanel.panelWin;
+    gDeck = gDebugger.document.getElementById("editor-deck");
 
-    once(gDebugger, "Debugger:SourceShown", testSourceEditorShown);
+    waitForSourceShown(gPanel, ".coffee")
+      .then(testSourceEditorShown)
+      .then(blackBoxSource)
+      .then(testBlackBoxMessageShown)
+      .then(clickStopBlackBoxingButton)
+      .then(testSourceEditorShownAgain)
+      .then(() => closeDebuggerAndFinish(gPanel))
+      .then(null, aError => {
+        ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+      });
   });
 }
 
 function testSourceEditorShown() {
-  const deck = gDebugger.document.getElementById("editor-deck");
-  is(deck.selectedIndex, "0",
-     "The first item in the deck should be selected (the source editor)");
-  blackBoxSource();
+  is(gDeck.selectedIndex, "0",
+    "The first item in the deck should be selected (the source editor).");
 }
 
 function blackBoxSource() {
-  const { activeThread } = gDebugger.DebuggerController;
-  activeThread.addOneTimeListener("blackboxchange", testBlackBoxMessageShown);
+  let finished = waitForThreadEvents(gPanel, "blackboxchange");
   getAnyBlackBoxCheckbox().click();
+  return finished;
 }
 
 function testBlackBoxMessageShown() {
-  const deck = gDebugger.document.getElementById("editor-deck");
-  is(deck.selectedIndex, "1",
-     "The second item in the deck should be selected (the black box message)");
-  clickStopBlackBoxingButton();
+  is(gDeck.selectedIndex, "1",
+    "The second item in the deck should be selected (the black box message).");
 }
 
 function clickStopBlackBoxingButton() {
-  const button = gDebugger.document.getElementById("black-boxed-message-button");
-  const { activeThread } = gDebugger.DebuggerController;
-  activeThread.addOneTimeListener("blackboxchange", testSourceEditorShownAgain);
-  button.click();
+  let finished = waitForThreadEvents(gPanel, "blackboxchange");
+  getEditorBlackboxMessageButton().click();
+  return finished;
 }
 
 function testSourceEditorShownAgain() {
-  const deck = gDebugger.document.getElementById("editor-deck");
-  is(deck.selectedIndex, "0",
-     "The first item in the deck should be selected again (the source editor)");
-  closeDebuggerAndFinish();
+  is(gDeck.selectedIndex, "0",
+    "The first item in the deck should be selected again (the source editor).");
 }
 
 function getAnyBlackBoxCheckbox() {
   return gDebugger.document.querySelector(
     ".side-menu-widget-item .side-menu-widget-item-checkbox");
 }
 
-function once(target, event, callback) {
-  target.addEventListener(event, function _listener(...args) {
-    target.removeEventListener(event, _listener, false);
-    callback.apply(null, args);
-  }, false);
+function getEditorBlackboxMessageButton() {
+  return gDebugger.document.getElementById("black-boxed-message-button");
 }
 
 registerCleanupFunction(function() {
-  removeTab(gTab);
-  gPane = null;
   gTab = null;
   gDebuggee = null;
+  gPanel = null;
   gDebugger = null;
+  gDeck = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_blackboxing-06.js
+++ b/browser/devtools/debugger/test/browser_dbg_blackboxing-06.js
@@ -1,70 +1,56 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Test that clicking the black box checkbox doesn't select that source.
  */
 
-const TAB_URL = EXAMPLE_URL + "browser_dbg_blackboxing.html";
-
-var gPane = null;
-var gTab = null;
-var gDebuggee = null;
-var gDebugger = null;
+const TAB_URL = EXAMPLE_URL + "doc_blackboxing.html";
 
-function test()
-{
-  let scriptShown = false;
-  let framesAdded = false;
-  let resumed = false;
-  let testStarted = false;
+let gTab, gDebuggee, gPanel, gDebugger;
+let gSources;
 
-  debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
-    resumed = true;
+function test() {
+  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
     gTab = aTab;
     gDebuggee = aDebuggee;
-    gPane = aPane;
-    gDebugger = gPane.panelWin;
+    gPanel = aPanel;
+    gDebugger = gPanel.panelWin;
+    gSources = gDebugger.DebuggerView.Sources;
 
-    once(gDebugger, "Debugger:SourceShown", testBlackBox);
+    waitForSourceShown(gPanel, ".js")
+      .then(testBlackBox)
+      .then(() => closeDebuggerAndFinish(gPanel))
+      .then(null, aError => {
+        ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+      });
   });
 }
 
 function testBlackBox() {
-  const sources = gDebugger.DebuggerView.Sources;
-
-  const selectedUrl = sources.selectedItem.attachment.source.url;
+  const selectedUrl = gSources.selectedValue;
   const checkbox = getDifferentBlackBoxCheckbox(selectedUrl);
-  ok(checkbox, "We should be able to grab a checkbox");
+  ok(checkbox, "We should be able to grab a different checkbox.");
 
-  const { activeThread } = gDebugger.DebuggerController;
-  activeThread.addOneTimeListener("blackboxchange", function () {
-    is(selectedUrl,
-       sources.selectedItem.attachment.source.url,
-       "The same source should be selected");
-    closeDebuggerAndFinish();
+  let finished = waitForThreadEvents(gPanel, "blackboxchange").then(() => {
+    is(selectedUrl, gSources.selectedValue,
+      "The same source should still be selected.");
   });
 
   checkbox.click();
+  return finished;
 }
 
-function getDifferentBlackBoxCheckbox(url) {
+function getDifferentBlackBoxCheckbox(aUrl) {
   return gDebugger.document.querySelector(
-    ".side-menu-widget-item:not([tooltiptext=\""
-      + url + "\"]) .side-menu-widget-item-checkbox");
-}
-
-function once(target, event, callback) {
-  target.addEventListener(event, function _listener(...args) {
-    target.removeEventListener(event, _listener, false);
-    callback.apply(null, args);
-  }, false);
+    ".side-menu-widget-item:not([tooltiptext=\"" + aUrl + "\"]) " +
+    ".side-menu-widget-item-checkbox");
 }
 
 registerCleanupFunction(function() {
-  removeTab(gTab);
-  gPane = null;
   gTab = null;
   gDebuggee = null;
+  gPanel = null;
   gDebugger = null;
+  gSources = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_blackboxing-07.js
+++ b/browser/devtools/debugger/test/browser_dbg_blackboxing-07.js
@@ -1,86 +1,66 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Test that clicking the black box checkbox when paused doesn't re-select the
  * currently paused frame's source.
  */
 
-const TAB_URL = EXAMPLE_URL + "browser_dbg_blackboxing.html";
-
-var gPane = null;
-var gTab = null;
-var gDebuggee = null;
-var gDebugger = null;
+const TAB_URL = EXAMPLE_URL + "doc_blackboxing.html";
 
-function test()
-{
-  let scriptShown = false;
-  let framesAdded = false;
-  let resumed = false;
-  let testStarted = false;
+let gTab, gDebuggee, gPanel, gDebugger;
+let gSources;
 
-  debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
-    resumed = true;
+function test() {
+  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
     gTab = aTab;
     gDebuggee = aDebuggee;
-    gPane = aPane;
-    gDebugger = gPane.panelWin;
+    gPanel = aPanel;
+    gDebugger = gPanel.panelWin;
+    gSources = gDebugger.DebuggerView.Sources;
 
-    once(gDebugger, "Debugger:SourceShown", runTest);
+    waitForSourceAndCaretAndScopes(gPanel, ".html", 21)
+      .then(testBlackBox)
+      .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+      .then(null, aError => {
+        ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+      });
+
+    gDebuggee.runTest();
   });
 }
 
-function runTest() {
-  const { activeThread } = gDebugger.DebuggerController;
-  activeThread.addOneTimeListener("paused", function () {
-    const sources = gDebugger.DebuggerView.Sources;
-    const selectedUrl = sources.selectedItem.attachment.source.url;
+function testBlackBox() {
+  const selectedUrl = gSources.selectedValue;
 
-    once(gDebugger, "Debugger:SourceShown", function () {
-      const newSelectedUrl = sources.selectedItem.attachment.source.url;
-      isnot(selectedUrl, newSelectedUrl,
-            "Should not have the same url selected");
+  let finished = waitForSourceShown(gPanel, "blackboxme.js").then(() => {
+    const newSelectedUrl = gSources.selectedValue;
+    isnot(selectedUrl, newSelectedUrl,
+      "Should not have the same url selected.");
 
-      activeThread.addOneTimeListener("blackboxchange", function () {
-        isnot(sources.selectedItem.attachment.source.url,
-              selectedUrl,
-              "The selected source did not change");
-        closeDebuggerAndFinish();
-      });
-
-      getBlackBoxCheckbox(newSelectedUrl).click();
+    let finished = waitForThreadEvents(gPanel, "blackboxchange").then(() => {
+      is(gSources.selectedValue, newSelectedUrl,
+        "The selected source did not change.");
     });
 
-    getDifferentSource(selectedUrl).click();
+    getBlackBoxCheckbox(newSelectedUrl).click()
+    return finished;
   });
 
-  gDebuggee.runTest();
-}
-
-function getDifferentSource(url) {
-  return gDebugger.document.querySelector(
-    ".side-menu-widget-item:not([tooltiptext=\""
-      + url + "\"])");
+  gSources.selectedIndex = 0;
+  return finished;
 }
 
-function getBlackBoxCheckbox(url) {
+function getBlackBoxCheckbox(aUrl) {
   return gDebugger.document.querySelector(
-    ".side-menu-widget-item[tooltiptext=\""
-      + url + "\"] .side-menu-widget-item-checkbox");
-}
-
-function once(target, event, callback) {
-  target.addEventListener(event, function _listener(...args) {
-    target.removeEventListener(event, _listener, false);
-    callback.apply(null, args);
-  }, false);
+    ".side-menu-widget-item[tooltiptext=\"" + aUrl + "\"] " +
+    ".side-menu-widget-item-checkbox");
 }
 
 registerCleanupFunction(function() {
-  removeTab(gTab);
-  gPane = null;
   gTab = null;
   gDebuggee = null;
+  gPanel = null;
   gDebugger = null;
+  gSources = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_breadcrumbs-access.js
+++ b/browser/devtools/debugger/test/browser_dbg_breadcrumbs-access.js
@@ -1,154 +1,89 @@
-/* vim:set ts=2 sw=2 sts=2 et: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-const TAB_URL = EXAMPLE_URL + "browser_dbg_script-switching.html";
+/**
+ * Tests if the stackframe breadcrumbs are keyboard accessible.
+ */
 
-let gPane = null;
-let gTab = null;
-let gDebuggee = null;
-let gDebugger = null;
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
 
-function test()
-{
-  let scriptShown = false;
-  let framesAdded = false;
-  let resumed = false;
-  let testStarted = false;
+function test() {
+  let gTab, gDebuggee, gPanel, gDebugger;
+  let gSources, gFrames;
 
-  debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
+  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
     gTab = aTab;
     gDebuggee = aDebuggee;
-    gPane = aPane;
-    gDebugger = gPane.panelWin;
-    resumed = true;
-
-    gDebugger.addEventListener("Debugger:SourceShown", onScriptShown);
+    gPanel = aPanel;
+    gDebugger = gPanel.panelWin;
+    gSources = gDebugger.DebuggerView.Sources;
+    gFrames = gDebugger.DebuggerView.StackFrames;
 
-    gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
-      framesAdded = true;
-      executeSoon(startTest);
-    });
+    waitForSourceAndCaretAndScopes(gPanel, "-02.js", 6)
+      .then(checkNavigationWhileNotFocused)
+      .then(focusCurrentStackFrame)
+      .then(checkNavigationWhileFocused)
+      .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+      .then(null, aError => {
+        ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+      });
 
-    executeSoon(function() {
-      gDebuggee.firstCall();
-    });
+    gDebuggee.firstCall();
   });
 
-  function onScriptShown(aEvent)
-  {
-    scriptShown = aEvent.detail.url.indexOf("-02.js") != -1;
-    executeSoon(startTest);
-  }
-
-  function startTest()
-  {
-    if (scriptShown && framesAdded && resumed && !testStarted) {
-      gDebugger.removeEventListener("Debugger:SourceShown", onScriptShown);
-      testStarted = true;
-      Services.tm.currentThread.dispatch({ run: performTest }, 0);
-    }
-  }
-
-  function performTest()
-  {
-    let editor = gDebugger.DebuggerView.editor;
-    let sources = gDebugger.DebuggerView.Sources;
-    let stackframes = gDebugger.DebuggerView.StackFrames;
-
-    is(editor.getCaretPosition().line, 5,
-      "The source editor caret position was incorrect (1).");
-    is(sources.selectedLabel, "test-script-switching-02.js",
-      "The currently selected source is incorrect (1).");
-    is(stackframes.selectedIndex, 3,
-      "The currently selected stackframe is incorrect (1).");
+  function checkNavigationWhileNotFocused() {
+    checkState({ frame: 3, source: 1, line: 6 });
 
     EventUtils.sendKey("DOWN", gDebugger);
-    is(editor.getCaretPosition().line, 6,
-      "The source editor caret position was incorrect (2).");
-    is(sources.selectedLabel, "test-script-switching-02.js",
-      "The currently selected source is incorrect (2).");
-    is(stackframes.selectedIndex, 3,
-      "The currently selected stackframe is incorrect (2).");
+    checkState({ frame: 3, source: 1, line: 7 });
 
     EventUtils.sendKey("UP", gDebugger);
-    is(editor.getCaretPosition().line, 5,
-      "The source editor caret position was incorrect (3).");
-    is(sources.selectedLabel, "test-script-switching-02.js",
-      "The currently selected source is incorrect (3).");
-    is(stackframes.selectedIndex, 3,
-      "The currently selected stackframe is incorrect (3).");
+    checkState({ frame: 3, source: 1, line: 6 });
+  }
 
-
+  function focusCurrentStackFrame() {
     EventUtils.sendMouseEvent({ type: "mousedown" },
-      stackframes.selectedItem.target,
+      gFrames.selectedItem.target,
       gDebugger);
+  }
 
+  function checkNavigationWhileFocused() {
+    let deferred = promise.defer();
 
     EventUtils.sendKey("UP", gDebugger);
-    is(editor.getCaretPosition().line, 5,
-      "The source editor caret position was incorrect (4).");
-    is(sources.selectedLabel, "test-script-switching-02.js",
-      "The currently selected source is incorrect (4).");
-    is(stackframes.selectedIndex, 2,
-      "The currently selected stackframe is incorrect (4).");
+    checkState({ frame: 2, source: 1, line: 6 });
 
-    gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
-      gDebugger.removeEventListener(aEvent.type, _onEvent);
-
-      is(editor.getCaretPosition().line, 4,
-        "The source editor caret position was incorrect (5).");
-      is(sources.selectedLabel, "test-script-switching-01.js",
-        "The currently selected source is incorrect (5).");
-      is(stackframes.selectedIndex, 1,
-        "The currently selected stackframe is incorrect (5).");
+    waitForSourceAndCaret(gPanel, "-01.js", 5).then(() => {
+      checkState({ frame: 1, source: 0, line: 5 });
 
       EventUtils.sendKey("UP", gDebugger);
-
-      is(editor.getCaretPosition().line, 4,
-        "The source editor caret position was incorrect (6).");
-      is(sources.selectedLabel, "test-script-switching-01.js",
-        "The currently selected source is incorrect (6).");
-      is(stackframes.selectedIndex, 0,
-        "The currently selected stackframe is incorrect (6).");
-
-      gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
-        gDebugger.removeEventListener(aEvent.type, _onEvent);
+      checkState({ frame: 0, source: 0, line: 5 });
 
-        is(editor.getCaretPosition().line, 5,
-          "The source editor caret position was incorrect (7).");
-        is(sources.selectedLabel, "test-script-switching-02.js",
-          "The currently selected source is incorrect (7).");
-        is(stackframes.selectedIndex, 3,
-          "The currently selected stackframe is incorrect (7).");
+      waitForSourceAndCaret(gPanel, "-02.js", 6).then(() => {
+        checkState({ frame: 3, source: 1, line: 6 });
 
-        gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
-          gDebugger.removeEventListener(aEvent.type, _onEvent);
-
-          is(editor.getCaretPosition().line, 4,
-            "The source editor caret position was incorrect (8).");
-          is(sources.selectedLabel, "test-script-switching-01.js",
-            "The currently selected source is incorrect (8).");
-          is(stackframes.selectedIndex, 0,
-            "The currently selected stackframe is incorrect (8).");
-
-          closeDebuggerAndFinish();
+        waitForSourceAndCaret(gPanel, "-01.js", 5).then(() => {
+          checkState({ frame: 0, source: 0, line: 5 });
+          deferred.resolve();
         });
 
-        EventUtils.sendKey("HOME", gDebugger);
+        EventUtils.sendKey("HOME", gDebugger)
       });
 
-      EventUtils.sendKey("END", gDebugger);
+      EventUtils.sendKey("END", gDebugger)
     });
 
-    EventUtils.sendKey("UP", gDebugger);
+    EventUtils.sendKey("UP", gDebugger)
+
+    return deferred.promise;
   }
 
-  registerCleanupFunction(function() {
-    removeTab(gTab);
-    gPane = null;
-    gTab = null;
-    gDebuggee = null;
-    gDebugger = null;
-  });
+  function checkState({ frame, source, line }) {
+    is(gFrames.selectedIndex, frame,
+      "The currently selected stackframe is incorrect.");
+    is(gSources.selectedIndex, source,
+      "The currently selected source is incorrect.");
+    ok(isCaretPos(gPanel, line),
+      "The source editor caret position was incorrect.");
+  }
 }
--- a/browser/devtools/debugger/test/browser_dbg_break-on-dom-event.js
+++ b/browser/devtools/debugger/test/browser_dbg_break-on-dom-event.js
@@ -1,197 +1,230 @@
-/*
- * Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the break-on-dom-events request works.
  */
 
-// Tests that the break-on-dom-events request works.
+const TAB_URL = EXAMPLE_URL + "doc_event-listeners.html";
+
+let gClient, gThreadClient, gInput, gButton;
 
-var gClient = null;
-var gTab = null;
-var gThreadClient = null;
-var gInput = null;
-var gButton = null;
-const DEBUGGER_TAB_URL = EXAMPLE_URL + "test-event-listeners.html";
+function test() {
+  if (!DebuggerServer.initialized) {
+    DebuggerServer.init(() => true);
+    DebuggerServer.addBrowserActors();
+  }
 
-function test()
-{
   let transport = DebuggerServer.connectPipe();
   gClient = new DebuggerClient(transport);
-  gClient.connect(function(type, traits) {
-    gTab = addTab(DEBUGGER_TAB_URL, function() {
-      attach_thread_actor_for_url(gClient,
-                                  DEBUGGER_TAB_URL,
-                                  function(threadClient) {
-        gThreadClient = threadClient;
-        gInput = content.document.querySelector("input");
-        gButton = content.document.querySelector("button");
-        testBreakOnAll();
+  gClient.connect((aType, aTraits) => {
+    is(aType, "browser",
+      "Root actor should identify itself as a browser.");
+
+    addTab(TAB_URL)
+      .then(() => attachThreadActorForUrl(gClient, TAB_URL))
+      .then(setupGlobals)
+      .then(pauseDebuggee)
+      .then(testBreakOnAll)
+      .then(testBreakOnDisabled)
+      .then(testBreakOnNone)
+      .then(testBreakOnClick)
+      .then(closeConnection)
+      .then(finish)
+      .then(null, aError => {
+        ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
-    });
   });
 }
 
+function setupGlobals(aThreadClient) {
+  gThreadClient = aThreadClient;
+  gInput = content.document.querySelector("input");
+  gButton = content.document.querySelector("button");
+}
+
+function pauseDebuggee() {
+  let deferred = promise.defer();
+
+  gClient.addOneTimeListener("paused", (aEvent, aPacket) => {
+    is(aPacket.type, "paused",
+      "We should now be paused.");
+    is(aPacket.why.type, "debuggerStatement",
+      "The debugger statement was hit.");
+
+    deferred.resolve();
+  });
+
+  // Spin the event loop before causing the debuggee to pause, to allow
+  // this function to return first.
+  executeSoon(triggerButtonClick);
+
+  return deferred.promise;
+}
+
 // Test pause on all events.
-function testBreakOnAll()
-{
-  gClient.addOneTimeListener("paused", function(event, packet) {
-    is(packet.why.type, "debuggerStatement", "debugger statement was hit.");
-    // Test calling pauseOnDOMEvents from a paused state.
-    gThreadClient.pauseOnDOMEvents("*", function(packet) {
-      is(packet, undefined, "The pause-on-any-event request completed successfully.");
+function testBreakOnAll() {
+  let deferred = promise.defer();
 
-      gClient.addOneTimeListener("paused", function(event, packet) {
-        is(packet.why.type, "pauseOnDOMEvents", "A hidden breakpoint was hit.");
-        is(packet.frame.callee.name, "keyupHandler", "The keyupHandler is entered.");
+  // Test calling pauseOnDOMEvents from a paused state.
+  gThreadClient.pauseOnDOMEvents("*", (aPacket) => {
+    is(aPacket, undefined,
+      "The pause-on-any-event request completed successfully.");
+
+    gClient.addOneTimeListener("paused", (aEvent, aPacket) => {
+      is(aPacket.why.type, "pauseOnDOMEvents",
+        "A hidden breakpoint was hit.");
+      is(aPacket.frame.callee.name, "keyupHandler",
+        "The keyupHandler is entered.");
 
-        gClient.addOneTimeListener("paused", function(event, packet) {
-          is(packet.why.type, "pauseOnDOMEvents", "A hidden breakpoint was hit.");
-          is(packet.frame.callee.name, "clickHandler", "The clickHandler is entered.");
-
-          gClient.addOneTimeListener("paused", function(event, packet) {
-            is(packet.why.type, "pauseOnDOMEvents", "A hidden breakpoint was hit.");
-            is(packet.frame.callee.name, "onchange", "The onchange handler is entered.");
+      gClient.addOneTimeListener("paused", (aEvent, aPacket) => {
+        is(aPacket.why.type, "pauseOnDOMEvents",
+          "A hidden breakpoint was hit.");
+        is(aPacket.frame.callee.name, "clickHandler",
+          "The clickHandler is entered.");
 
-            gThreadClient.resume(testBreakOnDisabled);
-          });
+        gClient.addOneTimeListener("paused", (aEvent, aPacket) => {
+          is(aPacket.why.type, "pauseOnDOMEvents",
+            "A hidden breakpoint was hit.");
+          is(aPacket.frame.callee.name, "onchange",
+            "The onchange handler is entered.");
 
-          gThreadClient.resume(function() {
-            gInput.focus();
-            gInput.value = "foo";
-            gInput.blur();
-          });
+          gThreadClient.resume(deferred.resolve);
         });
 
-        gThreadClient.resume(function() {
-          EventUtils.sendMouseEvent({ type: "click" }, gButton);
-        });
+        gThreadClient.resume(triggerInputChange);
       });
 
-      gThreadClient.resume(function() {
-        // Make sure that the focus is not on the input box so that a focus event
-        // will be triggered.
-        window.focus();
-        gBrowser.selectedBrowser.focus();
-        gButton.focus();
+      gThreadClient.resume(triggerButtonClick);
+    });
 
-        // Focus the element and wait for focus event.
-        gInput.addEventListener("focus", function onfocus() {
-          gInput.removeEventListener("focus", onfocus, false);
-          executeSoon(function() {
-            EventUtils.synthesizeKey("e", { shiftKey: 1 }, content);
-          });
-        }, false);
-
-        gInput.focus();
-      });
-    });
+    gThreadClient.resume(triggerInputKeyup);
   });
 
-  EventUtils.sendMouseEvent({ type: "click" }, gButton);
+  return deferred.promise;
 }
 
 // Test that removing events from the array disables them.
-function testBreakOnDisabled()
-{
+function testBreakOnDisabled() {
+  let deferred = promise.defer();
+
   // Test calling pauseOnDOMEvents from a running state.
-  gThreadClient.pauseOnDOMEvents(["click"], function(packet) {
-    is(packet.error, undefined, "The pause-on-click-only request completed successfully.");
+  gThreadClient.pauseOnDOMEvents(["click"], (aPacket) => {
+    is(aPacket.error, undefined,
+      "The pause-on-click-only request completed successfully.");
 
     gClient.addListener("paused", unexpectedListener);
 
     // This non-capturing event listener is guaranteed to run after the page's
     // capturing one had a chance to execute and modify window.foobar.
-    gInput.addEventListener("keyup", function tempHandler() {
-      gInput.removeEventListener("keyup", tempHandler, false);
-      is(content.wrappedJSObject.foobar, "keyupHandler", "No hidden breakpoint was hit.");
-      gClient.removeListener("paused", unexpectedListener);
-      testBreakOnNone();
-    }, false);
+    once(gInput, "keyup").then(() => {
+      is(content.wrappedJSObject.foobar, "keyupHandler",
+        "No hidden breakpoint was hit.");
 
-    // Make sure that the focus is not on the input box so that a focus event
-    // will be triggered.
-    window.focus();
-    gBrowser.selectedBrowser.focus();
-    gButton.focus();
+      gClient.removeListener("paused", unexpectedListener);
+      deferred.resolve();
+    });
 
-    // Focus the element and wait for focus event.
-    gInput.addEventListener("focus", function onfocus() {
-      gInput.removeEventListener("focus", onfocus, false);
-      executeSoon(function() {
-        EventUtils.synthesizeKey("e", { shiftKey: 1 }, content);
-      });
-    }, false);
+    triggerInputKeyup();
+  });
 
-    gInput.focus();
-  });
+  return deferred.promise;
 }
 
 // Test that specifying an empty event array clears all hidden breakpoints.
-function testBreakOnNone()
-{
+function testBreakOnNone() {
+  let deferred = promise.defer();
+
   // Test calling pauseOnDOMEvents from a running state.
-  gThreadClient.pauseOnDOMEvents([], function(packet) {
-    is(packet.error, undefined, "The pause-on-none request completed successfully.");
+  gThreadClient.pauseOnDOMEvents([], (aPacket) => {
+    is(aPacket.error, undefined,
+      "The pause-on-none request completed successfully.");
 
     gClient.addListener("paused", unexpectedListener);
 
     // This non-capturing event listener is guaranteed to run after the page's
     // capturing one had a chance to execute and modify window.foobar.
-    gInput.addEventListener("keyup", function tempHandler() {
-      gInput.removeEventListener("keyup", tempHandler, false);
-      is(content.wrappedJSObject.foobar, "keyupHandler", "No hidden breakpoint was hit.");
-      gClient.removeListener("paused", unexpectedListener);
-      testBreakOnClick();
-    }, false);
+    once(gInput, "keyup").then(() => {
+      is(content.wrappedJSObject.foobar, "keyupHandler",
+        "No hidden breakpoint was hit.");
 
-    // Make sure that the focus is not on the input box so that a focus event
-    // will be triggered.
-    window.focus();
-    gBrowser.selectedBrowser.focus();
-    gButton.focus();
+      gClient.removeListener("paused", unexpectedListener);
+      deferred.resolve();
+    });
 
-    // Focus the element and wait for focus event.
-    gInput.addEventListener("focus", function onfocus() {
-      gInput.removeEventListener("focus", onfocus, false);
-      executeSoon(function() {
-        EventUtils.synthesizeKey("g", { shiftKey: 1 }, content);
-      });
-    }, false);
+    triggerInputKeyup();
+  });
 
-    gInput.focus();
-  });
+  return deferred.promise;
 }
 
-function unexpectedListener(event, packet, callback) {
+// Test pause on a single event.
+function testBreakOnClick() {
+  let deferred = promise.defer();
+
+  // Test calling pauseOnDOMEvents from a running state.
+  gThreadClient.pauseOnDOMEvents(["click"], (aPacket) => {
+    is(aPacket.error, undefined,
+      "The pause-on-click request completed successfully.");
+
+    gClient.addOneTimeListener("paused", (aEvent, aPacket) => {
+      is(aPacket.why.type, "pauseOnDOMEvents",
+        "A hidden breakpoint was hit.");
+      is(aPacket.frame.callee.name, "clickHandler",
+        "The clickHandler is entered.");
+
+      gThreadClient.resume(deferred.resolve);
+    });
+
+    triggerButtonClick();
+  });
+
+  return deferred.promise;
+}
+
+function closeConnection() {
+  let deferred = promise.defer();
+  gClient.close(deferred.resolve);
+  return deferred.promise;
+}
+
+function unexpectedListener() {
   gClient.removeListener("paused", unexpectedListener);
   ok(false, "An unexpected hidden breakpoint was hit.");
   gThreadClient.resume(testBreakOnClick);
 }
 
-// Test pause on a single event.
-function testBreakOnClick()
-{
-  // Test calling pauseOnDOMEvents from a running state.
-  gThreadClient.pauseOnDOMEvents(["click"], function(packet) {
-    is(packet.error, undefined, "The pause-on-click request completed successfully.");
+function triggerInputKeyup() {
+  // Make sure that the focus is not on the input box so that a focus event
+  // will be triggered.
+  window.focus();
+  gBrowser.selectedBrowser.focus();
+  gButton.focus();
 
-    gClient.addOneTimeListener("paused", function(event, packet) {
-      is(packet.why.type, "pauseOnDOMEvents", "A hidden breakpoint was hit.");
-      is(packet.frame.callee.name, "clickHandler", "The clickHandler is entered.");
+  // Focus the element and wait for focus event.
+  once(gInput, "focus").then(() => {
+    executeSoon(() => {
+      EventUtils.synthesizeKey("e", { shiftKey: 1 }, content);
+    });
+  });
 
-      gThreadClient.resume(function() {
-        gClient.close(finish);
-      });
-    });
+  gInput.focus();
+}
 
-    EventUtils.sendMouseEvent({ type: "click" }, gButton);
-  });
+function triggerButtonClick() {
+  EventUtils.sendMouseEvent({ type: "click" }, gButton)
+}
+
+function triggerInputChange() {
+  gInput.focus();
+  gInput.value = "foo";
+  gInput.blur();
 }
 
 registerCleanupFunction(function() {
-  removeTab(gTab);
-  gTab = null;
+  removeTab(gBrowser.selectedTab);
   gClient = null;
   gThreadClient = null;
   gInput = null;
   gButton = null;
 });
rename from browser/devtools/debugger/test/browser_dbg_bug737803_editor_actual_location.js
rename to browser/devtools/debugger/test/browser_dbg_breakpoints-actual-location.js
--- a/browser/devtools/debugger/test/browser_dbg_bug737803_editor_actual_location.js
+++ b/browser/devtools/debugger/test/browser_dbg_breakpoints-actual-location.js
@@ -1,121 +1,97 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Bug 737803: Setting a breakpoint in a line without code should move
  * the icon to the actual location.
  */
 
-const TAB_URL = EXAMPLE_URL + "browser_dbg_script-switching.html";
-
-let gPane = null;
-let gTab = null;
-let gDebuggee = null;
-let gDebugger = null;
-let gSources = null;
-let gEditor = null;
-let gBreakpoints = null;
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
 
 function test() {
-  let scriptShown = false;
-  let framesAdded = false;
-  let testStarted = false;
-  let resumed = false;
+  let gTab, gDebuggee, gPanel, gDebugger;
+  let gEditor, gSources, gBreakpoints, gBreakpointsAdded, gBreakpointsRemoving;
 
-  debug_tab_pane(TAB_URL, function (aTab, aDebuggee, aPane) {
+  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
     gTab = aTab;
-    gPane = aPane;
     gDebuggee = aDebuggee;
-    gDebugger = gPane.panelWin;
-    resumed = true;
-
-    gDebugger.addEventListener("Debugger:SourceShown", onSourceShown);
+    gPanel = aPanel;
+    gDebugger = gPanel.panelWin;
+    gEditor = gDebugger.DebuggerView.editor;
+    gSources = gDebugger.DebuggerView.Sources;
+    gBreakpoints = gDebugger.DebuggerController.Breakpoints;
+    gBreakpointsAdded = gBreakpoints._added;
+    gBreakpointsRemoving = gBreakpoints._removing;
 
-    gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function () {
-      framesAdded = true;
-      executeSoon(startTest);
-    });
-
-    executeSoon(function () {
-      gDebuggee.firstCall();
-    });
+    waitForSourceAndCaretAndScopes(gPanel, "-02.js", 6).then(performTest);
+    gDebuggee.firstCall();
   });
 
-  function onSourceShown(aEvent) {
-    scriptShown = aEvent.detail.url.indexOf("-02.js") != -1;
-    executeSoon(startTest);
-  }
-
-  function startTest() {
-    if (scriptShown && framesAdded && resumed && !testStarted) {
-      gDebugger.removeEventListener("Debugger:SourceShown", onSourceShown);
-      testStarted = true;
-      Services.tm.currentThread.dispatch({ run: performTest }, 0);
-    }
-  }
+  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 performTest() {
-    gSources = gDebugger.DebuggerView.Sources;
-    gEditor = gDebugger.editor;
-    gBreakpoints = gPane.getAllBreakpoints();
-    is(Object.keys(gBreakpoints), 0, "There are no breakpoints");
-
-    gEditor.addEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE,
-      onEditorBreakpointAdd);
-
-    let location = { url: gSources.selectedValue, line: 4 };
-    executeSoon(function () {
-      gPane.addBreakpoint(location, onBreakpointAdd);
-    });
+    gEditor.addEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE, onEditorBreakpointAdd);
+    gPanel.addBreakpoint({ url: gSources.selectedValue, line: 4 }).then(onBreakpointAdd);
   }
 
   let onBpDebuggerAdd = false;
   let onBpEditorAdd = false;
 
-  function onBreakpointAdd(aBpClient) {
-    is(aBpClient.location.url, gSources.selectedValue, "URL is the same");
-    is(aBpClient.location.line, 6, "Line number is new");
-    is(aBpClient.requestedLocation.line, 4, "Requested location is correct");
+  function onBreakpointAdd(aBreakpointClient) {
+    ok(aBreakpointClient,
+      "Breakpoint added, client received.");
+    is(aBreakpointClient.location.url, gSources.selectedValue,
+      "Breakpoint client url is the same.");
+    is(aBreakpointClient.location.line, 6,
+      "Breakpoint client line is new.");
+
+    is(aBreakpointClient.requestedLocation.url, gSources.selectedValue,
+      "Requested location url is correct");
+    is(aBreakpointClient.requestedLocation.line, 4,
+      "Requested location line is correct");
 
     onBpDebuggerAdd = true;
-    tryFinish();
+    maybeFinish();
   }
 
   function onEditorBreakpointAdd(aEvent) {
-    gEditor.removeEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE,
-      onEditorBreakpointAdd);
+    gEditor.removeEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE, onEditorBreakpointAdd);
 
     is(gEditor.getBreakpoints().length, 1,
       "There is only one breakpoint in the editor");
 
-    ok(!gPane.getBreakpoint(gSources.selectedValue, 4),
-      "There are no breakpoints on an invalid line");
+    ok(!gBreakpoints._getAdded({ url: gSources.selectedValue, line: 4 }),
+      "There isn't any breakpoint added on an invalid line.");
+    ok(!gBreakpoints._getRemoving({ url: gSources.selectedValue, line: 4 }),
+      "There isn't any breakpoint removed from an invalid line.");
+
+    ok(gBreakpoints._getAdded({ url: gSources.selectedValue, line: 6 }),
+      "There is a breakpoint added on the actual line.");
+    ok(!gBreakpoints._getRemoving({ url: gSources.selectedValue, line: 6 }),
+      "There isn't any breakpoint removed from the actual line.");