Merge mozilla-central to inbound. a=merge CLOSED TREE
authorshindli <shindli@mozilla.com>
Wed, 13 Feb 2019 23:36:24 +0200
changeset 458989 87ed9a736435
parent 458988 7e7f4d97d178 (current diff)
parent 458937 08f794a4928e (diff)
child 458990 f300070152e9
push id35553
push usershindli@mozilla.com
push dateThu, 14 Feb 2019 04:41:18 +0000
treeherdermozilla-central@f0ea53f47215 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone67.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 mozilla-central to inbound. a=merge CLOSED TREE
devtools/client/sourceeditor/.eslintrc.js
devtools/client/sourceeditor/README
devtools/client/sourceeditor/autocomplete.js
devtools/client/sourceeditor/codemirror/LICENSE
devtools/client/sourceeditor/codemirror/addon/comment/comment.js
devtools/client/sourceeditor/codemirror/addon/comment/continuecomment.js
devtools/client/sourceeditor/codemirror/addon/dialog/dialog.css
devtools/client/sourceeditor/codemirror/addon/dialog/dialog.js
devtools/client/sourceeditor/codemirror/addon/edit/closebrackets.js
devtools/client/sourceeditor/codemirror/addon/edit/closetag.js
devtools/client/sourceeditor/codemirror/addon/edit/continuelist.js
devtools/client/sourceeditor/codemirror/addon/edit/matchbrackets.js
devtools/client/sourceeditor/codemirror/addon/edit/matchtags.js
devtools/client/sourceeditor/codemirror/addon/edit/trailingspace.js
devtools/client/sourceeditor/codemirror/addon/fold/brace-fold.js
devtools/client/sourceeditor/codemirror/addon/fold/comment-fold.js
devtools/client/sourceeditor/codemirror/addon/fold/foldcode.js
devtools/client/sourceeditor/codemirror/addon/fold/foldgutter.css
devtools/client/sourceeditor/codemirror/addon/fold/foldgutter.js
devtools/client/sourceeditor/codemirror/addon/fold/indent-fold.js
devtools/client/sourceeditor/codemirror/addon/fold/markdown-fold.js
devtools/client/sourceeditor/codemirror/addon/fold/xml-fold.js
devtools/client/sourceeditor/codemirror/addon/hint/show-hint.js
devtools/client/sourceeditor/codemirror/addon/runmode/runmode.js
devtools/client/sourceeditor/codemirror/addon/search/match-highlighter.js
devtools/client/sourceeditor/codemirror/addon/search/search.js
devtools/client/sourceeditor/codemirror/addon/search/searchcursor.js
devtools/client/sourceeditor/codemirror/addon/selection/active-line.js
devtools/client/sourceeditor/codemirror/addon/selection/mark-selection.js
devtools/client/sourceeditor/codemirror/addon/tern/tern.css
devtools/client/sourceeditor/codemirror/addon/tern/tern.js
devtools/client/sourceeditor/codemirror/cmiframe.html
devtools/client/sourceeditor/codemirror/codemirror.bundle.js
devtools/client/sourceeditor/codemirror/keymap/emacs.js
devtools/client/sourceeditor/codemirror/keymap/sublime.js
devtools/client/sourceeditor/codemirror/keymap/vim.js
devtools/client/sourceeditor/codemirror/lib/codemirror.css
devtools/client/sourceeditor/codemirror/lib/codemirror.js
devtools/client/sourceeditor/codemirror/mode/clike/clike.js
devtools/client/sourceeditor/codemirror/mode/clojure/clojure.js
devtools/client/sourceeditor/codemirror/mode/coffeescript/coffeescript.js
devtools/client/sourceeditor/codemirror/mode/css/css.js
devtools/client/sourceeditor/codemirror/mode/elm/elm.js
devtools/client/sourceeditor/codemirror/mode/haxe/haxe.js
devtools/client/sourceeditor/codemirror/mode/htmlmixed/htmlmixed.js
devtools/client/sourceeditor/codemirror/mode/javascript/javascript.js
devtools/client/sourceeditor/codemirror/mode/jsx/jsx.js
devtools/client/sourceeditor/codemirror/mode/wasm/wasm.js
devtools/client/sourceeditor/codemirror/mode/xml/xml.js
devtools/client/sourceeditor/codemirror/mozilla.css
devtools/client/sourceeditor/css-autocompleter.js
devtools/client/sourceeditor/debugger.js
devtools/client/sourceeditor/editor-commands-controller.js
devtools/client/sourceeditor/editor.js
devtools/client/sourceeditor/moz.build
devtools/client/sourceeditor/package.json
devtools/client/sourceeditor/tern/README
devtools/client/sourceeditor/tern/browser.js
devtools/client/sourceeditor/tern/comment.js
devtools/client/sourceeditor/tern/condense.js
devtools/client/sourceeditor/tern/def.js
devtools/client/sourceeditor/tern/ecma5.js
devtools/client/sourceeditor/tern/infer.js
devtools/client/sourceeditor/tern/moz.build
devtools/client/sourceeditor/tern/signal.js
devtools/client/sourceeditor/tern/tern.js
devtools/client/sourceeditor/tern/tests/unit/head_tern.js
devtools/client/sourceeditor/tern/tests/unit/test_autocompletion.js
devtools/client/sourceeditor/tern/tests/unit/test_import_tern.js
devtools/client/sourceeditor/tern/tests/unit/xpcshell.ini
devtools/client/sourceeditor/test/.eslintrc.js
devtools/client/sourceeditor/test/browser.ini
devtools/client/sourceeditor/test/browser_codemirror.js
devtools/client/sourceeditor/test/browser_css_autocompletion.js
devtools/client/sourceeditor/test/browser_css_getInfo.js
devtools/client/sourceeditor/test/browser_css_statemachine.js
devtools/client/sourceeditor/test/browser_detectindent.js
devtools/client/sourceeditor/test/browser_editor_addons.js
devtools/client/sourceeditor/test/browser_editor_alt_b_f.js
devtools/client/sourceeditor/test/browser_editor_autocomplete_basic.js
devtools/client/sourceeditor/test/browser_editor_autocomplete_events.js
devtools/client/sourceeditor/test/browser_editor_autocomplete_js.js
devtools/client/sourceeditor/test/browser_editor_basic.js
devtools/client/sourceeditor/test/browser_editor_cursor.js
devtools/client/sourceeditor/test/browser_editor_find_again.js
devtools/client/sourceeditor/test/browser_editor_goto_line.js
devtools/client/sourceeditor/test/browser_editor_history.js
devtools/client/sourceeditor/test/browser_editor_markers.js
devtools/client/sourceeditor/test/browser_editor_movelines.js
devtools/client/sourceeditor/test/browser_editor_prefs.js
devtools/client/sourceeditor/test/browser_editor_script_injection.js
devtools/client/sourceeditor/test/browser_vimemacs.js
devtools/client/sourceeditor/test/cm_mode_ruby.js
devtools/client/sourceeditor/test/cm_script_injection_test.js
devtools/client/sourceeditor/test/codemirror/codemirror.html
devtools/client/sourceeditor/test/codemirror/comment_test.js
devtools/client/sourceeditor/test/codemirror/doc_test.js
devtools/client/sourceeditor/test/codemirror/driver.js
devtools/client/sourceeditor/test/codemirror/emacs_test.js
devtools/client/sourceeditor/test/codemirror/mode/javascript/test.js
devtools/client/sourceeditor/test/codemirror/mode_test.css
devtools/client/sourceeditor/test/codemirror/mode_test.js
devtools/client/sourceeditor/test/codemirror/multi_test.js
devtools/client/sourceeditor/test/codemirror/search_test.js
devtools/client/sourceeditor/test/codemirror/sublime_test.js
devtools/client/sourceeditor/test/codemirror/test.js
devtools/client/sourceeditor/test/codemirror/vim_test.js
devtools/client/sourceeditor/test/codemirror/vimemacs.html
devtools/client/sourceeditor/test/css_autocompletion_tests.json
devtools/client/sourceeditor/test/css_statemachine_testcases.css
devtools/client/sourceeditor/test/css_statemachine_tests.json
devtools/client/sourceeditor/test/head.js
devtools/client/sourceeditor/test/head.xul
devtools/client/sourceeditor/test/helper_codemirror_runner.js
devtools/client/sourceeditor/wasm.js
devtools/client/sourceeditor/webpack.config.js
devtools/server/tests/unit/test_stepping-with-pause-points.js
--- a/.clang-format-ignore
+++ b/.clang-format-ignore
@@ -54,18 +54,18 @@ tools/clang-tidy/test/.*
 xpcom/reflect/xptcall/md/win32/.*
 xpcom/reflect/xptcall/md/unix/.*
 
 # Generated from ./tools/rewriting/ThirdPartyPaths.txt
 # awk '{print ""$1".*"}' ./tools/rewriting/ThirdPartyPaths.txt
 browser/components/translation/cld2/.*
 browser/extensions/mortar/ppapi/.*
 db/sqlite3/src/.*
-devtools/client/sourceeditor/codemirror/.*
-devtools/client/sourceeditor/tern/.*
+devtools/client/shared/sourceeditor/codemirror/.*
+devtools/client/shared/sourceeditor/tern/.*
 dom/canvas/test/webgl-conf/checkout/closure-library/.*
 dom/media/gmp/rlz/.*
 dom/media/gmp/widevine-adapter/content_decryption_module.h
 dom/media/gmp/widevine-adapter/content_decryption_module_export.h
 dom/media/gmp/widevine-adapter/content_decryption_module_ext.h
 dom/media/gmp/widevine-adapter/content_decryption_module_proxy.h
 dom/media/platforms/ffmpeg/ffmpeg57/.*
 dom/media/platforms/ffmpeg/ffmpeg58/.*
--- a/.eslintignore
+++ b/.eslintignore
@@ -138,21 +138,21 @@ devtools/shared/sourcemap/*
 devtools/shared/sprintfjs/*
 devtools/shared/qrcode/decoder/*
 devtools/shared/qrcode/encoder/*
 devtools/client/inspector/markup/test/lib_*
 devtools/client/jsonview/lib/require.js
 devtools/client/shared/demangle.js
 devtools/client/shared/source-map/*
 devtools/client/shared/vendor/*
-devtools/client/sourceeditor/codemirror/*.js
-devtools/client/sourceeditor/codemirror/**/*.js
-devtools/client/sourceeditor/tern/*
-devtools/client/sourceeditor/test/cm_mode_ruby.js
-devtools/client/sourceeditor/test/codemirror/*
+devtools/client/shared/sourceeditor/codemirror/*.js
+devtools/client/shared/sourceeditor/codemirror/**/*.js
+devtools/client/shared/sourceeditor/tern/*
+devtools/client/shared/sourceeditor/test/cm_mode_ruby.js
+devtools/client/shared/sourceeditor/test/codemirror/*
 devtools/server/actors/utils/automation-timeline.js
 
 # Ignore devtools files testing sourcemaps / code style
 devtools/client/debugger/test/mochitest/code_*.js
 devtools/client/framework/test/code_*
 devtools/client/inspector/markup/test/events_bundle.js
 devtools/client/netmonitor/test/xhr_bundle.js
 devtools/client/webconsole/test/mochitest/code_bundle_nosource.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -232,18 +232,16 @@ pref("browser.defaultbrowser.notificatio
 
 // 0 = blank, 1 = home (browser.startup.homepage), 2 = last visited page, 3 = resume previous browser session
 // The behavior of option 3 is detailed at: http://wiki.mozilla.org/Session_Restore
 pref("browser.startup.page",                1);
 pref("browser.startup.homepage",            "about:home");
 // Whether we should skip the homepage when opening the first-run page
 pref("browser.startup.firstrunSkipsHomepage", true);
 
-pref("browser.dedicatedprofile.welcome.accounts.endpoint", "https://accounts.firefox.com/");
-
 // Show an about:blank window as early as possible for quick startup feedback.
 // Held to nightly on Linux due to bug 1450626.
 // Disabled on Mac because the bouncing dock icon already provides feedback.
 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) && defined(NIGHTLY_BUILD)
 pref("browser.startup.blankWindow", true);
 #else
 pref("browser.startup.blankWindow", false);
 #endif
--- a/browser/base/content/newInstallPage.js
+++ b/browser/base/content/newInstallPage.js
@@ -1,35 +1,37 @@
 /* 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/. */
 
+/* global RPMGetUpdateChannel, RPMGetFxAccountsEndpoint */
+
 const PARAMS = new URL(location).searchParams;
 const ENTRYPOINT = "new-install-page";
-const SOURCE = `new-install-page-${PARAMS.get("channel")}`;
+const SOURCE = `new-install-page-${RPMGetUpdateChannel()}`;
 const CAMPAIGN = "dedicated-profiles";
 const ENDPOINT = PARAMS.get("endpoint");
-const CONTEXT = "fx_desktop_v3";
 
 function appendAccountsParams(url) {
   url.searchParams.set("entrypoint", ENTRYPOINT);
   url.searchParams.set("utm_source", SOURCE);
   url.searchParams.set("utm_campaign", CAMPAIGN);
 }
 
 function appendParams(url, params) {
   appendAccountsParams(url);
 
   for (let [key, value] of Object.entries(params)) {
     url.searchParams.set(key, value);
   }
 }
 
 async function requestFlowMetrics() {
-  let requestURL = new URL(`${ENDPOINT}metrics-flow`);
+  let requestURL = new URL(await endpoint);
+  requestURL.pathname = "metrics-flow";
   appendParams(requestURL, {
     "form_type": "email",
   });
 
   let response = await fetch(requestURL, { credentials: "omit" });
   if (response.status === 200) {
     return response.json();
   }
@@ -42,28 +44,28 @@ async function submitForm(event) {
   event.preventDefault();
 
   let input = document.getElementById("sync-input");
   input.disabled = true;
   document.getElementById("sync-button").disabled = true;
 
   let { flowId, flowBeginTime } = await metrics;
 
-  let requestURL = new URL(ENDPOINT);
+  let requestURL = new URL(await endpoint);
   appendParams(requestURL, {
-    "service": "sync",
     "action": "email",
-    "context": CONTEXT,
     "utm_campaign": CAMPAIGN,
     "email": input.value,
     "flow_id": flowId,
     "flow_begin_time": flowBeginTime,
   });
 
   window.open(requestURL, "_blank", "noopener");
 }
 
+const endpoint = RPMGetFxAccountsEndpoint(ENTRYPOINT);
+
 // This must come before the CSP is set or it will be blocked.
 const metrics = requestFlowMetrics();
 
 document.addEventListener("DOMContentLoaded", () => {
   document.getElementById("sync").addEventListener("submit", submitForm);
 }, { once: true });
--- a/browser/components/nsBrowserContentHandler.js
+++ b/browser/components/nsBrowserContentHandler.js
@@ -10,24 +10,27 @@ XPCOMUtils.defineLazyModuleGetters(this,
   BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
   HeadlessShell: "resource:///modules/HeadlessShell.jsm",
   HomePage: "resource:///modules/HomePage.jsm",
   LaterRun: "resource:///modules/LaterRun.jsm",
   PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
   SessionStartup: "resource:///modules/sessionstore/SessionStartup.jsm",
   ShellService: "resource:///modules/ShellService.jsm",
   UpdatePing: "resource://gre/modules/UpdatePing.jsm",
+  RemotePages: "resource://gre/modules/remotepagemanager/RemotePageManagerParent.jsm",
 });
 XPCOMUtils.defineLazyServiceGetter(this, "WindowsUIUtils",
   "@mozilla.org/windows-ui-utils;1", "nsIWindowsUIUtils");
 
 XPCOMUtils.defineLazyGetter(this, "gSystemPrincipal",
   () => Services.scriptSecurityManager.getSystemPrincipal());
 XPCOMUtils.defineLazyGlobalGetters(this, [URL]);
 
+const NEWINSTALL_PAGE = "about:newinstall";
+
 function shouldLoadURI(aURI) {
   if (aURI && !aURI.schemeIs("chrome"))
     return true;
 
   dump("*** Preventing external load of chrome: URI into browser window\n");
   dump("    Use --chrome <uri> instead\n");
   return false;
 }
@@ -55,22 +58,24 @@ function resolveURIInternal(aCmdLine, aA
     uri = uriFixup.createFixupURI(aArgument, 0);
   } catch (e) {
     Cu.reportError(e);
   }
 
   return uri;
 }
 
+let gRemoteInstallPage = null;
+
 function getNewInstallPage() {
-  let url = new URL("about:newinstall");
-  let endpoint = Services.prefs.getCharPref("browser.dedicatedprofile.welcome.accounts.endpoint");
-  url.searchParams.set("endpoint", endpoint);
-  url.searchParams.set("channel", AppConstants.MOZ_UPDATE_CHANNEL);
-  return url.toString();
+  if (!gRemoteInstallPage) {
+    gRemoteInstallPage = new RemotePages(NEWINSTALL_PAGE);
+  }
+
+  return NEWINSTALL_PAGE;
 }
 
 var gFirstWindow = false;
 
 const OVERRIDE_NONE        = 0;
 const OVERRIDE_NEW_PROFILE = 1;
 const OVERRIDE_NEW_MSTONE  = 2;
 const OVERRIDE_NEW_BUILD_ID = 3;
--- a/browser/components/preferences/connection.xul
+++ b/browser/components/preferences/connection.xul
@@ -120,33 +120,33 @@
             <box pack="start">
             <radiogroup id="networkProxySOCKSVersion" orient="horizontal"
                         preference="network.proxy.socks_version">
               <radio id="networkProxySOCKSVersion4" value="4" data-l10n-id="connection-proxy-socks4" />
               <radio id="networkProxySOCKSVersion5" value="5" data-l10n-id="connection-proxy-socks5" />
             </radiogroup>
             </box>
           </row>
-          <label data-l10n-id="connection-proxy-noproxy" control="networkProxyNone"/>
-          <textbox id="networkProxyNone" preference="network.proxy.no_proxies_on" multiline="true" rows="2"/>
-          <label control="networkProxyNone" data-l10n-id="connection-proxy-noproxy-desc" />
         </rows>
       </grid>
       <radio value="2" data-l10n-id="connection-proxy-autotype" />
       <hbox class="indent" flex="1" align="center">
         <textbox id="networkProxyAutoconfigURL" flex="1" preference="network.proxy.autoconfig_url"
                  oninput="gConnectionsDialog.updateReloadButton();"/>
         <button id="autoReload" icon="refresh"
                 data-l10n-id="connection-proxy-reload"
                 oncommand="gConnectionsDialog.reloadPAC();"
                 preference="pref.advanced.proxies.disable_button.reload"/>
       </hbox>
     </radiogroup>
   </groupbox>
   <separator class="thin"/>
+  <label data-l10n-id="connection-proxy-noproxy" control="networkProxyNone"/>
+  <textbox id="networkProxyNone" preference="network.proxy.no_proxies_on" multiline="true" rows="2"/>
+  <label control="networkProxyNone" data-l10n-id="connection-proxy-noproxy-desc" />
   <checkbox id="autologinProxy"
             data-l10n-id="connection-proxy-autologin"
             preference="signon.autologin.proxy" />
   <checkbox id="networkProxySOCKSRemoteDNS"
             preference="network.proxy.socks_remote_dns"
             data-l10n-id="connection-proxy-socks-remote-dns" />
   <checkbox id="networkDnsOverHttps"
             data-l10n-id="connection-dns-over-https"
--- a/browser/components/preferences/in-content/tests/browser_extension_controlled.js
+++ b/browser/components/preferences/in-content/tests/browser_extension_controlled.js
@@ -757,17 +757,17 @@ add_task(async function testExtensionCon
         return {
           manualControls: [
             ...manualControlContainer.querySelectorAll("label:not([control=networkProxyNone])"),
             ...manualControlContainer.querySelectorAll("textbox:not(#networkProxyNone)"),
             ...manualControlContainer.querySelectorAll("checkbox"),
             ...doc.querySelectorAll("#networkProxySOCKSVersion > radio")],
           pacControls: [doc.getElementById("networkProxyAutoconfigURL")],
           otherControls: [
-            manualControlContainer.querySelector("label[control=networkProxyNone]"),
+            doc.querySelector("label[control=networkProxyNone]"),
             doc.getElementById("networkProxyNone"),
             ...controlGroup.querySelectorAll(":scope > radio"),
             ...doc.querySelectorAll("#ConnectionsDialogPane > checkbox")],
         };
       }
       let controlState = isControlled ? "disabled" : "enabled";
       let controls = getProxyControls();
       for (let element of controls.manualControls) {
--- a/browser/components/search/content/search-one-offs.js
+++ b/browser/components/search/content/search-one-offs.js
@@ -293,17 +293,17 @@ class SearchOneOffs {
       val.setAttribute("selected", "true");
     }
     this._selectedButton = val;
     this._updateStateForButton(null);
     if (val && !val.engine) {
       // If the button doesn't have an engine, then clear the popup's
       // selection to indicate that pressing Return while the button is
       // selected will do the button's command, not search.
-      this.popup.selectedIndex = -1;
+      this.selectedAutocompleteIndex = -1;
     }
     let event = new CustomEvent("SelectedOneOffButtonChanged", {
       previousSelectedButton: previousButton,
     });
     this.dispatchEvent(event);
     return val;
   }
 
@@ -329,16 +329,24 @@ class SearchOneOffs {
     for (let i = 0; i < buttons.length; i++) {
       if (buttons[i] == this._selectedButton) {
         return i;
       }
     }
     return -1;
   }
 
+  get selectedAutocompleteIndex() {
+    return (this._view || this.popup).selectedIndex;
+  }
+
+  set selectedAutocompleteIndex(val) {
+    return (this._view || this.popup).selectedIndex = val;
+  }
+
   get compact() {
     return this.getAttribute("compact") == "true";
   }
 
   get bundle() {
     if (!this._bundle) {
       const kBundleURI = "chrome://browser/locale/search.properties";
       this._bundle = Services.strings.createBundle(kBundleURI);
@@ -882,40 +890,40 @@ class SearchOneOffs {
         !event.getModifierState("Control") &&
         !event.getModifierState("Meta")) {
       if (this.getAttribute("disabletab") == "true" ||
           (event.shiftKey && this.selectedButtonIndex <= 0) ||
           (!event.shiftKey && this.selectedButtonIndex == this.getSelectableButtons(true).length - 1)) {
         this.selectedButton = null;
         return false;
       }
-      this.popup.selectedIndex = -1;
+      this.selectedAutocompleteIndex = -1;
       this.advanceSelection(!event.shiftKey, true, false);
       return !!this.selectedButton;
     }
 
     if (event.keyCode == KeyboardEvent.DOM_VK_UP) {
       if (event.altKey) {
         // Keep the currently selected result in the list (if any) as a
         // secondary "alt" selection and move the selection up within the
         // buttons.
         this.advanceSelection(false, false, false);
         return true;
       }
       if (numListItems == 0) {
         this.advanceSelection(false, true, false);
         return true;
       }
-      if (this.popup.selectedIndex > 0) {
+      if (this.selectedAutocompleteIndex > 0) {
         // Moving up within the list.  The autocomplete controller should
         // handle this case.  A button may be selected, so null it.
         this.selectedButton = null;
         return false;
       }
-      if (this.popup.selectedIndex == 0) {
+      if (this.selectedAutocompleteIndex == 0) {
         // Moving up from the top of the list.
         if (allowEmptySelection) {
           // Let the autocomplete controller remove selection in the list
           // and revert the typed text in the textbox.
           return false;
         }
         // Wrap selection around to the last button.
         if (this.textbox && typeof textboxUserValue == "string") {
@@ -947,35 +955,35 @@ class SearchOneOffs {
         // the buttons.
         this.advanceSelection(true, false, false);
         return true;
       }
       if (numListItems == 0) {
         this.advanceSelection(true, true, false);
         return true;
       }
-      if (this.popup.selectedIndex >= 0 &&
-          this.popup.selectedIndex < numListItems - 1) {
+      if (this.selectedAutocompleteIndex >= 0 &&
+          this.selectedAutocompleteIndex < numListItems - 1) {
         // Moving down within the list.  The autocomplete controller
         // should handle this case.  A button may be selected, so null it.
         this.selectedButton = null;
         return false;
       }
-      if (this.popup.selectedIndex == numListItems - 1) {
+      if (this.selectedAutocompleteIndex == numListItems - 1) {
         // Moving down from the last item in the list to the buttons.
         this.selectedButtonIndex = 0;
         if (allowEmptySelection) {
           // Let the autocomplete controller remove selection in the list
           // and revert the typed text in the textbox.
           return false;
         }
         if (this.textbox && typeof textboxUserValue == "string") {
           this.textbox.value = textboxUserValue;
         }
-        this.popup.selectedIndex = -1;
+        this.selectedAutocompleteIndex = -1;
         return true;
       }
       if (this.selectedButton) {
         let buttons = this.getSelectableButtons(true);
         if (this.selectedButtonIndex == buttons.length - 1) {
           // Moving down from the buttons back up to the top of the list.
           this.selectedButton = null;
           if (allowEmptySelection) {
--- a/browser/components/urlbar/UrlbarController.jsm
+++ b/browser/components/urlbar/UrlbarController.jsm
@@ -193,16 +193,30 @@ class UrlbarController {
         this.view.isOpen &&
         event.ctrlKey &&
         (event.key == "n" || event.key == "p")) {
       this.view.selectNextItem({ reverse: event.key == "p" });
       event.preventDefault();
       return;
     }
 
+    if (this.view.isOpen) {
+      let queryContext = this._lastQueryContext;
+      if (queryContext) {
+        this.view.oneOffSearchButtons.handleKeyPress(
+          event,
+          queryContext.results.length,
+          this.view.allowEmptySelection,
+          queryContext.searchString);
+        if (event.defaultPrevented) {
+          return;
+        }
+      }
+    }
+
     switch (event.keyCode) {
       case KeyEvent.DOM_VK_ESCAPE:
         this.input.handleRevert();
         event.preventDefault();
         break;
       case KeyEvent.DOM_VK_RETURN:
         if (isMac &&
             event.metaKey) {
--- a/browser/components/urlbar/UrlbarView.jsm
+++ b/browser/components/urlbar/UrlbarView.jsm
@@ -59,16 +59,22 @@ class UrlbarView {
   /**
    * @returns {boolean}
    *   Whether the panel is open.
    */
   get isOpen() {
     return this.panel.state == "open" || this.panel.state == "showing";
   }
 
+  get allowEmptySelection() {
+    return !(this._queryContext &&
+             this._queryContext.results[0] &&
+             this._queryContext.results[0].heuristic);
+  }
+
   get selectedIndex() {
     if (!this.isOpen || !this._selected) {
       return -1;
     }
     return parseInt(this._selected.getAttribute("resultIndex"));
   }
 
   set selectedIndex(val) {
@@ -110,25 +116,23 @@ class UrlbarView {
    *   Set to true to select the previous item. By default the next item
    *   will be selected.
    */
   selectNextItem({reverse = false} = {}) {
     if (!this.isOpen) {
       throw new Error("UrlbarView: Cannot select an item if the view isn't open.");
     }
 
-    // TODO bug 1527260: handle one-off search buttons
-
     let row;
     if (reverse) {
       row = (this._selected && this._selected.previousElementSibling) ||
-            this._rows.lastElementChild;
+            ((this._selected && this.allowEmptySelection) ? null : this._rows.lastElementChild);
     } else {
       row = (this._selected && this._selected.nextElementSibling) ||
-            this._rows.firstElementChild;
+            ((this._selected && this.allowEmptySelection) ? null : this._rows.firstElementChild);
     }
     this._selectItem(row);
   }
 
   /**
    * Closes the autocomplete results popup.
    */
   close() {
--- a/browser/locales/en-US/browser/preferences/connection.ftl
+++ b/browser/locales/en-US/browser/preferences/connection.ftl
@@ -55,17 +55,17 @@ connection-proxy-socks-port = Port
     .accesskey = t
 
 connection-proxy-socks4 =
     .label = SOCKS v4
     .accesskey = K
 connection-proxy-socks5 =
     .label = SOCKS v5
     .accesskey = v
-connection-proxy-noproxy = No Proxy for
+connection-proxy-noproxy = No proxy for
     .accesskey = N
 
 connection-proxy-noproxy-desc = Example: .mozilla.org, .net.nz, 192.168.1.0/24
 
 connection-proxy-autotype =
     .label = Automatic proxy configuration URL
     .accesskey = A
 
--- a/devtools/client/aboutdebugging-new/src/components/debugtarget/ServiceWorkerAction.js
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/ServiceWorkerAction.js
@@ -13,16 +13,17 @@ const FluentReact = require("devtools/cl
 const Localized = createFactory(FluentReact.Localized);
 
 const { getCurrentRuntimeDetails } = require("../../modules/runtimes-state-helper");
 
 const InspectAction = createFactory(require("./InspectAction"));
 
 const Actions = require("../../actions/index");
 const Types = require("../../types/index");
+const { SERVICE_WORKER_STATUSES } = require("../../constants");
 
 /**
  * This component displays buttons for service worker.
  */
 class ServiceWorkerAction extends PureComponent {
   static get propTypes() {
     return {
       dispatch: PropTypes.func.isRequired,
@@ -100,35 +101,39 @@ class ServiceWorkerAction extends PureCo
       className: "default-button js-unregister-button",
       key: "service-worker-unregister-button",
       labelId: "about-debugging-worker-action-unregister",
       onClick: this.unregister.bind(this),
     });
   }
 
   _renderAction() {
-    const { isActive, isRunning } = this.props.target.details;
-
-    if (!isRunning) {
-      return [
-        this._renderUnregisterButton(),
-        this._renderStartButton(),
-      ];
-    }
+    const { status } = this.props.target.details;
 
-    if (!isActive) {
-      // Only inspect is available if the service worker is not active.
-      return [this._renderInspectAction()];
+    switch (status) {
+      case SERVICE_WORKER_STATUSES.RUNNING:
+        return [
+          this._renderUnregisterButton(),
+          this._renderPushButton(),
+          this._renderInspectAction(),
+        ];
+      case SERVICE_WORKER_STATUSES.REGISTERING:
+        // Only inspect is available if the service worker is not active.
+        return [
+          this._renderInspectAction(),
+        ];
+      case SERVICE_WORKER_STATUSES.STOPPED:
+        return [
+          this._renderUnregisterButton(),
+          this._renderStartButton(),
+        ];
+      default:
+        console.error("Unexpected service worker status: " + status);
+        return [];
     }
-
-    return [
-      this._renderUnregisterButton(),
-      this._renderPushButton(),
-      this._renderInspectAction(),
-    ];
   }
 
   render() {
     return dom.div(
       {
         className: "toolbar",
       },
       this._renderAction()
--- a/devtools/client/aboutdebugging-new/src/middleware/worker-component-data.js
+++ b/devtools/client/aboutdebugging-new/src/middleware/worker-component-data.js
@@ -23,17 +23,20 @@ const workerComponentDataMiddleware = st
       action.sharedWorkers = toComponentData(action.sharedWorkers);
       break;
     }
   }
 
   return next(action);
 };
 
-function getServiceWorkerStatus(isActive, isRunning) {
+function getServiceWorkerStatus(worker) {
+  const isActive = worker.active;
+  const isRunning = !!worker.workerTargetFront;
+
   if (isActive && isRunning) {
     return SERVICE_WORKER_STATUSES.RUNNING;
   } else if (isActive) {
     return SERVICE_WORKER_STATUSES.STOPPED;
   }
   // We cannot get service worker registrations unless the registration is in
   // ACTIVE state. Unable to know the actual state ("installing", "waiting"), we
   // display a custom state "registering" for now. See Bug 1153292.
@@ -54,35 +57,29 @@ function toComponentData(workers, isServ
       workerTargetFront,
     } = worker;
 
     // For registering service workers, workerTargetFront will not be available.
     // The only valid identifier we can use at that point is the actorID for the
     // service worker registration.
     const id = workerTargetFront ? workerTargetFront.actorID : registrationFront.actorID;
 
-    let isActive = false;
-    let isRunning = false;
     let pushServiceEndpoint = null;
     let status = null;
 
     if (isServiceWorker) {
       fetch = fetch ? SERVICE_WORKER_FETCH_STATES.LISTENING
                     : SERVICE_WORKER_FETCH_STATES.NOT_LISTENING;
-      isActive = worker.active;
-      isRunning = !!worker.workerTargetFront;
-      status = getServiceWorkerStatus(isActive, isRunning);
+      status = getServiceWorkerStatus(worker);
       pushServiceEndpoint = subscription ? subscription.endpoint : null;
     }
 
     return {
       details: {
         fetch,
-        isActive,
-        isRunning,
         pushServiceEndpoint,
         registrationFront,
         scope,
         status,
       },
       icon,
       id,
       name,
--- a/devtools/client/aboutdebugging-new/src/types/debug-target.js
+++ b/devtools/client/aboutdebugging-new/src/types/debug-target.js
@@ -22,20 +22,16 @@ const extensionTargetDetails = {
 const tabTargetDetails = {
   // the url of the tab.
   url: PropTypes.string.isRequired,
 };
 
 const workerTargetDetails = {
   // (service worker specific) one of "LISTENING", "NOT_LISTENING". undefined otherwise.
   fetch: PropTypes.string,
-  // (service worker specific) true if they reached the activated state.
-  isActive: PropTypes.bool,
-  // (service worker specific) true if they are currently running.
-  isRunning: PropTypes.bool,
   // front for the ServiceWorkerRegistration related to this service worker.
   registrationFront: PropTypes.object,
   // (service worker specific) scope of the service worker registration.
   scope: PropTypes.string,
   // (service worker specific) one of "RUNNING", "REGISTERING", "STOPPED".
   status: PropTypes.string,
 };
 
--- a/devtools/client/aboutdebugging-new/test/browser/resources/service-workers/controlled-sw.html
+++ b/devtools/client/aboutdebugging-new/test/browser/resources/service-workers/controlled-sw.html
@@ -1,13 +1,13 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <meta charset="UTF-8">
-  <title>Service worker push test</title>
+  <title>Service worker controlled</title>
 </head>
 <body>
 <script type="text/javascript">
 
 "use strict";
 
 let registration;
 
--- a/devtools/client/aboutdebugging/test/browser_service_workers_push.js
+++ b/devtools/client/aboutdebugging/test/browser_service_workers_push.js
@@ -41,16 +41,21 @@ add_task(async function() {
 
   info("Ensure that the registration resolved before trying to interact with " +
     "the service worker.");
   await waitForServiceWorkerRegistered(swTab);
   ok(true, "Service worker registration resolved");
 
   await waitForServiceWorkerActivation(SERVICE_WORKER, document);
 
+  info("Wait until the service worker is running");
+  const container = await waitUntilServiceWorkerContainer(SERVICE_WORKER, document);
+  await waitUntil(
+    () => container.querySelector(".target-status").textContent === "Running", 100);
+
   // Retrieve the Push button for the worker.
   const names = [...document.querySelectorAll("#service-workers .target-name")];
   const name = names.filter(element => element.textContent === SERVICE_WORKER)[0];
   ok(name, "Found the service worker in the list");
 
   const targetElement = name.parentNode.parentNode;
 
   const pushBtn = targetElement.querySelector(".push-button");
--- a/devtools/client/accessibility/accessibility.css
+++ b/devtools/client/accessibility/accessibility.css
@@ -378,32 +378,41 @@ body {
 .accessible .tree:not(:focus) .node.focused .theme-twisty {
   fill: var(--accessibility-unfocused-tree-focused-node-twisty-fill);
 }
 
 .accessible .tree .node:not(.focused):hover {
   background-color: var(--theme-selection-background-hover);
 }
 
-.accessible .tree:focus .node.focused {
+.accessible .tree:focus .node.focused,
+.accessible .tree .tree-node-active .node.focused {
   background-color: var(--theme-selection-background);
 }
 
-.accessible .tree:focus .node.focused * {
+.accessible .tree:focus .node.focused *,
+.accessible .tree .tree-node-active .node.focused * {
   color: var(--theme-selection-color);
 }
 
-.accessible .tree:focus .node.focused .open-inspector {
+.accessible .tree:focus .node.focused .open-inspector,
+.accessible .tree .tree-node-active .node.focused .open-inspector {
   background-color: var(--grey-30);
 }
 
-.accessible .tree:focus .node.focused:hover .open-inspector {
+.accessible .tree:focus .node.focused:hover .open-inspector,
+.accessible .tree .tree-node-active .node.focused:hover .open-inspector {
   background-color: var(--theme-selection-color);
 }
 
+.accessible .tree .tree-node-active .node.focused .open-inspector:focus,
+.accessible .tree .tree-node-active .node.focused:hover .open-inspector:focus {
+  background-color: var(--grey-40);
+}
+
 .accessible .tree .arrow {
   flex-shrink: 0;
 }
 
 .accessible .tree .object-value {
   overflow: hidden;
   text-overflow: ellipsis;
 }
@@ -420,30 +429,38 @@ body {
   background-color: var(--accessible-label-background-color);
   color: var(--accessible-label-color);
   border: 1px solid var(--accessible-label-border-color);
   border-radius: 3px;
   padding: 0px 2px;
   margin-inline-start: 5px;
 }
 
-.accessible .tree:focus .node.focused .objectBox-accessible .accessible-role {
+.accessible .tree:focus .node.focused .objectBox-accessible .accessible-role,
+.accessible .tree .tree-node-active .node.focused .objectBox-accessible .accessible-role {
   background-color: var(--accessible-role-active-background-color);
   border-color: var(--accessible-role-active-border-color);
   color: var(--theme-selection-color);
 }
 
-.accessible .tree:focus .node.focused .open-accessibility-inspector {
+.accessible .tree:focus .node.focused .open-accessibility-inspector,
+.accessible .tree .tree-node-active .node.focused .open-accessibility-inspector {
   background-color: var(--grey-30);
 }
 
-.accessible .tree:focus .node.focused:hover .open-accessibility-inspector {
+.accessible .tree:focus .node.focused:hover .open-accessibility-inspector,
+.accessible .tree .tree-node-active .node.focused:hover .open-accessibility-inspector {
   background-color: var(--theme-selection-color);
 }
 
+.accessible .tree .tree-node-active .node.focused .open-accessibility-inspector:focus,
+.accessible .tree .tree-node-active .node.focused:hover .open-accessibility-inspector:focus {
+  background-color: var(--grey-40);
+}
+
 .accessible .tree .objectBox-accessible,
 .accessible .tree .objectBox-node {
   width: 100%;
   display: flex;
   align-items: center;
 }
 
 .accessible .tree .objectBox-accessible .accessible-name,
--- a/devtools/client/accessibility/components/Accessible.js
+++ b/devtools/client/accessibility/components/Accessible.js
@@ -88,16 +88,17 @@ class Accessible extends Component {
     };
   }
 
   constructor(props) {
     super(props);
 
     this.state = {
       expanded: new Set(),
+      active: null,
       focused: null,
     };
 
     this.onAccessibleInspected = this.onAccessibleInspected.bind(this);
     this.renderItem = this.renderItem.bind(this);
     this.update = this.update.bind(this);
   }
 
@@ -225,17 +226,17 @@ class Accessible extends Component {
     }
 
     await dispatch(select(walker, accessible));
 
     const { props } = this.refs;
     if (props) {
       props.refs.tree.blur();
     }
-    await this.setState({ focused: null });
+    await this.setState({ active: null, focused: null });
 
     window.emit(EVENTS.NEW_ACCESSIBLE_FRONT_INSPECTED);
   }
 
   openLink(link, e) {
     openContentLink(link);
   }
 
@@ -295,17 +296,17 @@ class Accessible extends Component {
         span({ className: "object-label" }, item.name),
         span({ className: "object-delimiter" }, ":"),
         span({ className: "object-value" }, Rep(valueProps) || "")
       )
     );
   }
 
   render() {
-    const { expanded, focused } = this.state;
+    const { expanded, active, focused } = this.state;
     const { items, parents, accessible, labelledby } = this.props;
 
     if (accessible) {
       return Tree({
         ref: "props",
         key: "accessible-properties",
         itemHeight: TREE_ROW_HEIGHT,
         getRoots: () => items,
@@ -315,27 +316,25 @@ class Accessible extends Component {
         isExpanded: item => expanded.has(item.path),
         onExpand: item => this.setExpanded(item, true),
         onCollapse: item => this.setExpanded(item, false),
         onFocus: item => {
           if (this.state.focused !== item.path) {
             this.setState({ focused: item.path });
           }
         },
-        onActivate: ({ contents }) => {
-          if (isNode(contents)) {
-            this.selectNode(this.props.DOMNode, "accessibility-keyboard");
-          } else if (isAccessible(contents)) {
-            const target = findAccessibleTarget(this.props.relations, contents.actor);
-            if (target) {
-              this.selectAccessible(target);
-            }
+        onActivate: item => {
+          if (item == null) {
+            this.setState({ active: null });
+          } else if (this.state.active !== item.path) {
+            this.setState({ active: item.path });
           }
         },
-        focused: findFocused(focused, items),
+        focused: findByPath(focused, items),
+        active: findByPath(active, items),
         renderItem: this.renderItem,
         labelledby,
       });
     }
 
     return div({ className: "info" },
                L10N.getStr("accessibility.accessible.notAvailable"));
   }
@@ -357,27 +356,31 @@ const findAccessibleTarget = (relations,
       }
     }
   }
 
   return null;
 };
 
 /**
- * Find currently focused item.
- * @param  {String} focused Key of the currently focused item.
- * @param  {Array}  items   Accessibility properties array.
- * @return {Object?}        Possibly found focused item.
+ * Find an item based on a given path.
+ * @param  {String} path
+ *         Key of the item to be looked up.
+ * @param  {Array}  items
+ *         Accessibility properties array.
+ * @return {Object?}
+ *         Possibly found item.
  */
-const findFocused = (focused, items) => {
+const findByPath = (path, items) => {
   for (const item of items) {
-    if (item.path === focused) {
+    if (item.path === path) {
       return item;
     }
-    const found = findFocused(focused, item.children);
+
+    const found = findByPath(path, item.children);
     if (found) {
       return found;
     }
   }
   return null;
 };
 
 /**
--- a/devtools/client/debugger/new/build/copy-module.js
+++ b/devtools/client/debugger/new/build/copy-module.js
@@ -11,18 +11,18 @@ const EXCLUDED_FILES = {
   "../assets/panel/debugger.properties": "devtools/shared/flags",
   "devtools-connection": "devtools/shared/flags",
   "chrome-remote-interface": "devtools/shared/flags",
   "devtools-launchpad": "devtools/shared/flags"
 };
 
 const mappings =  Object.assign(
   {
-    "./source-editor": "devtools/client/sourceeditor/editor",
-    "../editor/source-editor": "devtools/client/sourceeditor/editor",
+    "./source-editor": "devtools/client/shared/sourceeditor/editor",
+    "../editor/source-editor": "devtools/client/shared/sourceeditor/editor",
     "./test-flag": "devtools/shared/flags",
     "./fronts-device": "devtools/shared/fronts/device",
     immutable: "devtools/client/shared/vendor/immutable",
     lodash: "devtools/client/shared/vendor/lodash",
     react: "devtools/client/shared/vendor/react",
     "react-dom": "devtools/client/shared/vendor/react-dom",
     "react-dom-factories": "devtools/client/shared/vendor/react-dom-factories",
     "react-redux": "devtools/client/shared/vendor/react-redux",
--- a/devtools/client/debugger/new/index.html
+++ b/devtools/client/debugger/new/index.html
@@ -1,18 +1,18 @@
 <!-- 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/. -->
 <!DOCTYPE html>
 <html dir="">
 
 <head>
-  <link rel="stylesheet" type="text/css" href="chrome://devtools/content/sourceeditor/codemirror/lib/codemirror.css" />
-  <link rel="stylesheet" type="text/css" href="chrome://devtools/content/sourceeditor/codemirror/addon/dialog/dialog.css" />
-  <link rel="stylesheet" type="text/css" href="chrome://devtools/content/sourceeditor/codemirror/mozilla.css" />
+  <link rel="stylesheet" type="text/css" href="chrome://devtools/content/shared/sourceeditor/codemirror/lib/codemirror.css" />
+  <link rel="stylesheet" type="text/css" href="chrome://devtools/content/shared/sourceeditor/codemirror/addon/dialog/dialog.css" />
+  <link rel="stylesheet" type="text/css" href="chrome://devtools/content/shared/sourceeditor/codemirror/mozilla.css" />
   <link rel="stylesheet" type="text/css" href="resource://devtools/client/debugger/new/dist/debugger.css" />
 </head>
 
 <body>
   <div id="mount"></div>
   <script type="application/javascript" src="chrome://devtools/content/shared/theme-switching.js"></script>
   <script type="text/javascript">
     const { BrowserLoader } = ChromeUtils.import("resource://devtools/client/shared/browser-loader.js");
--- a/devtools/client/debugger/new/src/actions/ast/setPausePoints.js
+++ b/devtools/client/debugger/new/src/actions/ast/setPausePoints.js
@@ -9,29 +9,16 @@ import * as parser from "../../workers/p
 import { isGenerated } from "../../utils/source";
 import { convertToList } from "../../utils/pause/pausePoints";
 import { features } from "../../utils/prefs";
 import { getGeneratedLocation } from "../../utils/source-maps";
 
 import type { SourceId } from "../../types";
 import type { ThunkArgs, Action } from "../types";
 
-function compressPausePoints(pausePoints) {
-  const compressed = {};
-  for (const line in pausePoints) {
-    compressed[line] = {};
-    for (const col in pausePoints[line]) {
-      const { types } = pausePoints[line][col];
-      compressed[line][col] = (types.break ? 1 : 0) | (types.step ? 2 : 0);
-    }
-  }
-
-  return compressed;
-}
-
 async function mapLocations(pausePoints, state, source, sourceMaps) {
   const pausePointList = convertToList(pausePoints);
   const sourceId = source.id;
 
   return Promise.all(
     pausePointList.map(async ({ types, location }) => {
       const generatedLocation = await getGeneratedLocation(
         state,
@@ -53,27 +40,18 @@ export function setPausePoints(sourceId:
     if (!features.pausePoints || !source || !source.text) {
       return;
     }
 
     if (source.isWasm) {
       return;
     }
 
-    let pausePoints = await parser.getPausePoints(sourceId);
-
-    if (isGenerated(source)) {
-      const compressed = compressPausePoints(pausePoints);
-      for (const sourceActor of getSourceActors(getState(), sourceId)) {
-        await client.setPausePoints(sourceActor, compressed);
-      }
-    }
-
-    pausePoints = await mapLocations(
-      pausePoints,
+    const pausePoints = await mapLocations(
+      await parser.getPausePoints(sourceId),
       getState(),
       source,
       sourceMaps
     );
 
     dispatch(
       ({
         type: "SET_PAUSE_POINTS",
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg-breakpoints-actions.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-breakpoints-actions.js
@@ -23,61 +23,60 @@ add_task(async function() {
 });
 
 // Tests "disable others", "enable others" and "remove others" context actions
 add_task(async function() {
   const dbg = await initDebugger("doc-scripts.html");
   await selectSource(dbg, "simple1");
   await waitForSelectedSource(dbg, "simple1");
 
-  await addBreakpoint(dbg, "simple1", 1);
   await addBreakpoint(dbg, "simple1", 4);
   await addBreakpoint(dbg, "simple1", 5);
   await addBreakpoint(dbg, "simple1", 6);
 
   openFirstBreakpointContextMenu(dbg);
   // select "Disable Others"
   // FIXME bug 1524374 this waitForDispatch call only sees one dispatch for
   // DISABLE_BREAKPOINT even though three are triggered, due to the order in
   // which promises get resolved. The problem seems to indicate a coverage gap
   // in waitUntilService(). Workaround this by only waiting for one dispatch,
   // though this is fragile and could break again in the future.
-  let dispatched = waitForDispatch(dbg, "DISABLE_BREAKPOINT", /*3*/ 1);
+  let dispatched = waitForDispatch(dbg, "DISABLE_BREAKPOINT", /*2*/ 1);
   selectContextMenuItem(dbg, selectors.breakpointContextMenu.disableOthers);
   await waitForState(dbg, state =>
     dbg.selectors.getBreakpointsList(state)
-      .every(bp => (bp.location.line !== 1) === bp.disabled)
+      .every(bp => (bp.location.line !== 4) === bp.disabled)
   );
   await dispatched;
-  ok("breakpoint at 1 is the only enabled breakpoint");
+  ok("breakpoint at 4 is the only enabled breakpoint");
 
   openFirstBreakpointContextMenu(dbg);
   // select "Disable All"
   dispatched = waitForDispatch(dbg, "DISABLE_ALL_BREAKPOINTS");
   selectContextMenuItem(dbg, selectors.breakpointContextMenu.disableAll);
   await waitForState(dbg, state =>
     dbg.selectors.getBreakpointsList(state).every(bp => bp.disabled)
   );
   await dispatched;
   ok("all breakpoints are disabled");
 
   openFirstBreakpointContextMenu(dbg);
   // select "Enable Others"
-  dispatched = waitForDispatch(dbg, "ENABLE_BREAKPOINT", 3);
+  dispatched = waitForDispatch(dbg, "ENABLE_BREAKPOINT", 2);
   selectContextMenuItem(dbg, selectors.breakpointContextMenu.enableOthers);
   await waitForState(dbg, state =>
     dbg.selectors.getBreakpointsList(state)
-      .every(bp => (bp.location.line === 1) === bp.disabled)
+      .every(bp => (bp.location.line === 4) === bp.disabled)
   );
   await dispatched;
   ok("all breakpoints except line 1 are enabled");
 
   openFirstBreakpointContextMenu(dbg);
   // select "Remove Others"
-  dispatched = waitForDispatch(dbg, "REMOVE_BREAKPOINT", 3);
+  dispatched = waitForDispatch(dbg, "REMOVE_BREAKPOINT", 2);
   selectContextMenuItem(dbg, selectors.breakpointContextMenu.removeOthers);
   await waitForState(dbg, state =>
     dbg.selectors.getBreakpointsList(state).length === 1 &&
-    dbg.selectors.getBreakpointsList(state)[0].location.line === 1
+    dbg.selectors.getBreakpointsList(state)[0].location.line === 4
   );
   await dispatched;
-  ok("remaining breakpoint should be on line 1");
+  ok("remaining breakpoint should be on line 4");
 });
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg-keyboard-shortcuts.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-keyboard-shortcuts.js
@@ -31,16 +31,19 @@ add_task(async function() {
   await reload(dbg);
   await waitForPaused(dbg);
   await waitForLoadedSource(dbg, "doc-debugger-statements.html");
   assertPausedLocation(dbg, "doc-debugger-statements");
 
   await pressResume(dbg);
   assertPausedLocation(dbg);
 
+  await pressStepOver(dbg);
+  assertPausedLocation(dbg);
+
   await pressStepIn(dbg);
   assertPausedLocation(dbg);
 
   await pressStepOut(dbg);
   assertPausedLocation(dbg);
 
   await pressStepOver(dbg);
   assertPausedLocation(dbg);
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg-pause-points.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-pause-points.js
@@ -28,50 +28,35 @@ async function testCase(dbg, { name, ste
 }
 
 add_task(async function test() {
   const dbg = await initDebugger("doc-pause-points.html", "pause-points.js");
 
   await selectSource(dbg, "pause-points.js")
   await testCase(dbg, {
     name: "statements",
-    steps: [
-      [9, 2],
-      [10, 4],
-      [10, 13],
-      [11, 2],
-      [11, 10],
-      [11, 21],
-      [11, 29],
-      [12, 2],
-      [12, 12],
-      [13, 0]
-    ]
+    steps: [[9,2], [10,4], [10,13], [11,2], [11,21], [12,2], [12,12], [13,0]]
   });
 
   await testCase(dbg, {
     name: "expressions",
-    steps: [[40,2], [41,2], [41,8], [42,8], [43,0]]
+    steps: [[40,2], [41,2], [42,12], [43,0]]
   });
 
   await testCase(dbg, {
     name: "sequences",
-    steps: [[23,2], [25,8], [29,8], [31,4], [34,2], [37,0]]
+    steps: [[23,2], [25,12], [29,12], [34,2], [37,0]]
   });
 
   await testCase(dbg, {
     name: "flow",
     steps: [
       [16, 2],
       [17, 12],
-      [17, 20],
-      [18, 6],
-      [19, 2],
+      [18, 10],
       [19, 8],
       [19, 17],
-      [19, 25],
       [19, 8],
       [19, 17],
-      [19, 25],
       [19, 8]
     ]
   });
 });
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemapped-stepping.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemapped-stepping.js
@@ -51,16 +51,17 @@ async function runSteps(dbg, source, ste
 
 function testStepOverForOf(dbg) {
   return breakpointSteps(
     dbg,
     "webpack3-babel6",
     "step-over-for-of",
     { line: 4, column: 2 },
     [
+      ["stepOver", { line: 6, column: 20 }],
       ["stepOver", { line: 6, column: 2 }],
       ["stepOver", { line: 7, column: 4 }],
       ["stepOver", { line: 6, column: 2 }],
       ["stepOver", { line: 7, column: 4 }],
       ["stepOver", { line: 6, column: 2 }],
       ["stepOver", { line: 10, column: 2 }]
     ]
   );
@@ -71,36 +72,37 @@ function testStepOverForOf(dbg) {
 function testStepOverForOfArray(dbg) {
   return breakpointSteps(
     dbg,
     "webpack3-babel6",
     "step-over-for-of-array",
     { line: 3, column: 2 },
     [
       ["stepOver", { line: 5, column: 2 }],
-      ["stepOver", { line: 5, column: 7 }],
+      ["stepOver", { line: 5, column: 13 }],
       ["stepOver", { line: 6, column: 4 }],
       ["stepOver", { line: 5, column: 2 }],
-      ["stepOver", { line: 5, column: 7 }],
+      ["stepOver", { line: 5, column: 13 }],
       ["stepOver", { line: 6, column: 4 }],
       ["stepOver", { line: 5, column: 2 }],
       ["stepOver", { line: 9, column: 2 }]
     ]
   );
 }
 
 // The closure means it isn't actually possible to step into the for body,
 // and Babel doesn't map the _loop() call, so we step past it automatically.
 function testStepOveForOfClosure(dbg) {
   return breakpointSteps(
     dbg,
     "webpack3-babel6",
     "step-over-for-of-closure",
     { line: 6, column: 2 },
     [
+      ["stepOver", { line: 8, column: 20 }],
       ["stepOver", { line: 8, column: 2 }],
       ["stepOver", { line: 12, column: 2 }]
     ]
   );
 }
 
 // Same as the previous, not possible to step into the body. The less
 // complicated array logic makes it possible to step into the header at least,
@@ -108,19 +110,19 @@ function testStepOveForOfClosure(dbg) {
 function testStepOverForOfArrayClosure(dbg) {
   return breakpointSteps(
     dbg,
     "webpack3-babel6",
     "step-over-for-of-array-closure",
     { line: 3, column: 2 },
     [
       ["stepOver", { line: 5, column: 2 }],
-      ["stepOver", { line: 5, column: 7 }],
+      ["stepOver", { line: 5, column: 13 }],
       ["stepOver", { line: 5, column: 2 }],
-      ["stepOver", { line: 5, column: 7 }],
+      ["stepOver", { line: 5, column: 13 }],
       ["stepOver", { line: 5, column: 2 }],
       ["stepOver", { line: 9, column: 2 }]
     ]
   );
 }
 
 function testStepOverFunctionParams(dbg) {
   return breakpointSteps(
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemaps.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemaps.js
@@ -77,17 +77,16 @@ add_task(async function() {
   is(getBreakpointCount(getState()), 1, "One breakpoint exists");
   assertBreakpointExists(dbg, entrySrc, 15);
 
   invokeInTab("keepMeAlive");
   await waitForPaused(dbg);
   assertPausedLocation(dbg);
 
   await stepIn(dbg);
-  await stepIn(dbg);
   assertPausedLocation(dbg);
 
   await dbg.actions.jumpToMappedSelectedLocation();
   await stepOver(dbg);
   assertPausedLocation(dbg);
   assertDebugLine(dbg, 71);
 
   await dbg.actions.jumpToMappedSelectedLocation();
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg-stepping.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-stepping.js
@@ -19,11 +19,11 @@ add_task(async function test() {
   await stepIn(dbg);
   await stepIn(dbg);
   await stepIn(dbg);
   await stepIn(dbg);
   await stepIn(dbg);
   await stepIn(dbg);
   await stepIn(dbg);
 
-  assertDebugLine(dbg, 42267);
+  assertDebugLine(dbg, 42271);
   assertPausedLocation(dbg);
 });
--- a/devtools/client/framework/moz.build
+++ b/devtools/client/framework/moz.build
@@ -1,17 +1,19 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 BROWSER_CHROME_MANIFESTS += [
     'test/browser.ini',
+    'test/metrics/browser_metrics_debugger.ini',
     'test/metrics/browser_metrics_inspector.ini',
+    'test/metrics/browser_metrics_netmonitor.ini',
     'test/metrics/browser_metrics_webconsole.ini',
 ]
 XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
 
 DIRS += [
     'components',
 ]
 
copy from devtools/client/framework/test/metrics/browser_metrics_webconsole.ini
copy to devtools/client/framework/test/metrics/browser_metrics_debugger.ini
--- a/devtools/client/framework/test/metrics/browser_metrics_webconsole.ini
+++ b/devtools/client/framework/test/metrics/browser_metrics_debugger.ini
@@ -3,10 +3,10 @@ tags = devtools
 subsuite = devtools
 support-files =
   head.js
   !/devtools/client/shared/test/shared-head.js
   !/devtools/client/shared/test/telemetry-test-helpers.js
 
 # Each metrics tests is loaded in a separate .ini file. This way the test is executed
 # individually, without any other test being executed before or after.
-[browser_metrics_webconsole.js]
+[browser_metrics_debugger.js]
 skip-if = os != 'linux' || debug || asan # Results should be platform agnostic - only run on linux64-opt
copy from devtools/client/framework/test/metrics/browser_metrics_webconsole.js
copy to devtools/client/framework/test/metrics/browser_metrics_debugger.js
--- a/devtools/client/framework/test/metrics/browser_metrics_webconsole.js
+++ b/devtools/client/framework/test/metrics/browser_metrics_debugger.js
@@ -4,66 +4,28 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 /* import-globals-from ../../../shared/test/shared-head.js */
 
 /**
  * This test records the number of modules loaded by DevTools, as well as the total count
- * of characters in those modules, when opening the webconsole. These metrics are
+ * of characters in those modules, when opening the debugger. These metrics are
  * retrieved by perfherder via logs.
  */
 
-const TEST_URL = "data:text/html;charset=UTF-8,<div>Webconsole modules load test</div>";
+const TEST_URL = "data:text/html;charset=UTF-8,<div>Debugger modules load test</div>";
 
 add_task(async function() {
-  const toolbox = await openNewTabAndToolbox(TEST_URL, "webconsole");
-  const hud = toolbox.getCurrentPanel().hud;
-
-  // Retrieve the browser loader dedicated to the WebConsole.
-  const webconsoleLoader = hud.ui.browserLoader;
-  const loaders = [loader.provider.loader, webconsoleLoader.loader];
+  const toolbox = await openNewTabAndToolbox(TEST_URL, "jsdebugger");
+  const panel = toolbox.getCurrentPanel();
 
-  const allModules = getFilteredModules("", loaders);
-  const webconsoleModules = getFilteredModules("devtools/client/webconsole", loaders);
-
-  const allModulesCount = allModules.length;
-  const webconsoleModulesCount = webconsoleModules.length;
-
-  const allModulesChars = countCharsInModules(allModules);
-  const webconsoleModulesChars = countCharsInModules(webconsoleModules);
+  // Retrieve the browser loader dedicated to the Debugger.
+  const debuggerLoader = panel.panelWin.getBrowserLoaderForWindow();
+  const loaders = [loader.provider.loader, debuggerLoader.loader];
 
-  const PERFHERDER_DATA = {
-    framework: {
-      name: "devtools",
-    },
-    suites: [{
-      name: "webconsole-metrics",
-      value: allModulesChars,
-      subtests: [
-        {
-          name: "webconsole-modules",
-          value: webconsoleModulesCount,
-        },
-        {
-          name: "webconsole-chars",
-          value: webconsoleModulesChars,
-        },
-        {
-          name: "all-modules",
-          value: allModulesCount,
-        },
-        {
-          name: "all-chars",
-          value: allModulesChars,
-        },
-      ],
-    }],
-  };
-  info("PERFHERDER_DATA: " + JSON.stringify(PERFHERDER_DATA));
-
-  // Simply check that we found valid values.
-  ok(allModulesCount > webconsoleModulesCount &&
-     webconsoleModulesCount > 0, "Successfully recorded module count for WebConsole");
-  ok(allModulesChars > webconsoleModulesChars &&
-     webconsoleModulesChars > 0, "Successfully recorded char count for WebConsole");
+  runMetricsTest({
+    filterString: "devtools/client/debugger",
+    loaders,
+    panelName: "debugger",
+  });
 });
--- a/devtools/client/framework/test/metrics/browser_metrics_inspector.js
+++ b/devtools/client/framework/test/metrics/browser_metrics_inspector.js
@@ -16,52 +16,14 @@
 const TEST_URL = "data:text/html;charset=UTF-8,<div>Inspector modules load test</div>";
 
 add_task(async function() {
   await openNewTabAndToolbox(TEST_URL, "inspector");
 
   // The inspector does not use a dedicated browser loader.
   const loaders = [loader.provider.loader];
 
-  const allModules = getFilteredModules("", loaders);
-  const inspectorModules = getFilteredModules("devtools/client/inspector", loaders);
-
-  const allModulesCount = allModules.length;
-  const inspectorModulesCount = inspectorModules.length;
-
-  const allModulesChars = countCharsInModules(allModules);
-  const inspectorModulesChars = countCharsInModules(inspectorModules);
-
-  const PERFHERDER_DATA = {
-    framework: {
-      name: "devtools",
-    },
-    suites: [{
-      name: "inspector-metrics",
-      value: allModulesChars,
-      subtests: [
-        {
-          name: "inspector-modules",
-          value: inspectorModulesCount,
-        },
-        {
-          name: "inspector-chars",
-          value: inspectorModulesChars,
-        },
-        {
-          name: "all-modules",
-          value: allModulesCount,
-        },
-        {
-          name: "all-chars",
-          value: allModulesChars,
-        },
-      ],
-    }],
-  };
-  info("PERFHERDER_DATA: " + JSON.stringify(PERFHERDER_DATA));
-
-  // Simply check that we found valid values.
-  ok(allModulesCount > inspectorModulesCount &&
-     inspectorModulesCount > 0, "Successfully recorded module count for Inspector");
-  ok(allModulesChars > inspectorModulesChars &&
-     inspectorModulesChars > 0, "Successfully recorded char count for Inspector");
+  runMetricsTest({
+    filterString: "devtools/client/inspector",
+    loaders,
+    panelName: "inspector",
+  });
 });
copy from devtools/client/framework/test/metrics/browser_metrics_webconsole.ini
copy to devtools/client/framework/test/metrics/browser_metrics_netmonitor.ini
--- a/devtools/client/framework/test/metrics/browser_metrics_webconsole.ini
+++ b/devtools/client/framework/test/metrics/browser_metrics_netmonitor.ini
@@ -3,10 +3,10 @@ tags = devtools
 subsuite = devtools
 support-files =
   head.js
   !/devtools/client/shared/test/shared-head.js
   !/devtools/client/shared/test/telemetry-test-helpers.js
 
 # Each metrics tests is loaded in a separate .ini file. This way the test is executed
 # individually, without any other test being executed before or after.
-[browser_metrics_webconsole.js]
+[browser_metrics_netmonitor.js]
 skip-if = os != 'linux' || debug || asan # Results should be platform agnostic - only run on linux64-opt
copy from devtools/client/framework/test/metrics/browser_metrics_webconsole.js
copy to devtools/client/framework/test/metrics/browser_metrics_netmonitor.js
--- a/devtools/client/framework/test/metrics/browser_metrics_webconsole.js
+++ b/devtools/client/framework/test/metrics/browser_metrics_netmonitor.js
@@ -4,66 +4,28 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 /* import-globals-from ../../../shared/test/shared-head.js */
 
 /**
  * This test records the number of modules loaded by DevTools, as well as the total count
- * of characters in those modules, when opening the webconsole. These metrics are
+ * of characters in those modules, when opening the netmonitor. These metrics are
  * retrieved by perfherder via logs.
  */
 
-const TEST_URL = "data:text/html;charset=UTF-8,<div>Webconsole modules load test</div>";
+const TEST_URL = "data:text/html;charset=UTF-8,<div>Netmonitor modules load test</div>";
 
 add_task(async function() {
-  const toolbox = await openNewTabAndToolbox(TEST_URL, "webconsole");
-  const hud = toolbox.getCurrentPanel().hud;
-
-  // Retrieve the browser loader dedicated to the WebConsole.
-  const webconsoleLoader = hud.ui.browserLoader;
-  const loaders = [loader.provider.loader, webconsoleLoader.loader];
+  const toolbox = await openNewTabAndToolbox(TEST_URL, "netmonitor");
+  const panel = toolbox.getCurrentPanel();
 
-  const allModules = getFilteredModules("", loaders);
-  const webconsoleModules = getFilteredModules("devtools/client/webconsole", loaders);
-
-  const allModulesCount = allModules.length;
-  const webconsoleModulesCount = webconsoleModules.length;
-
-  const allModulesChars = countCharsInModules(allModules);
-  const webconsoleModulesChars = countCharsInModules(webconsoleModules);
+  // Retrieve the browser loader dedicated to the Netmonitor.
+  const netmonitorLoader = panel.panelWin.getBrowserLoaderForWindow();
+  const loaders = [loader.provider.loader, netmonitorLoader.loader];
 
-  const PERFHERDER_DATA = {
-    framework: {
-      name: "devtools",
-    },
-    suites: [{
-      name: "webconsole-metrics",
-      value: allModulesChars,
-      subtests: [
-        {
-          name: "webconsole-modules",
-          value: webconsoleModulesCount,
-        },
-        {
-          name: "webconsole-chars",
-          value: webconsoleModulesChars,
-        },
-        {
-          name: "all-modules",
-          value: allModulesCount,
-        },
-        {
-          name: "all-chars",
-          value: allModulesChars,
-        },
-      ],
-    }],
-  };
-  info("PERFHERDER_DATA: " + JSON.stringify(PERFHERDER_DATA));
-
-  // Simply check that we found valid values.
-  ok(allModulesCount > webconsoleModulesCount &&
-     webconsoleModulesCount > 0, "Successfully recorded module count for WebConsole");
-  ok(allModulesChars > webconsoleModulesChars &&
-     webconsoleModulesChars > 0, "Successfully recorded char count for WebConsole");
+  runMetricsTest({
+    filterString: "devtools/client/netmonitor",
+    loaders,
+    panelName: "netmonitor",
+  });
 });
--- a/devtools/client/framework/test/metrics/browser_metrics_webconsole.js
+++ b/devtools/client/framework/test/metrics/browser_metrics_webconsole.js
@@ -12,58 +12,20 @@
  * of characters in those modules, when opening the webconsole. These metrics are
  * retrieved by perfherder via logs.
  */
 
 const TEST_URL = "data:text/html;charset=UTF-8,<div>Webconsole modules load test</div>";
 
 add_task(async function() {
   const toolbox = await openNewTabAndToolbox(TEST_URL, "webconsole");
-  const hud = toolbox.getCurrentPanel().hud;
+  const panel = toolbox.getCurrentPanel();
 
   // Retrieve the browser loader dedicated to the WebConsole.
-  const webconsoleLoader = hud.ui.browserLoader;
+  const webconsoleLoader = panel._frameWindow.getBrowserLoaderForWindow();
   const loaders = [loader.provider.loader, webconsoleLoader.loader];
 
-  const allModules = getFilteredModules("", loaders);
-  const webconsoleModules = getFilteredModules("devtools/client/webconsole", loaders);
-
-  const allModulesCount = allModules.length;
-  const webconsoleModulesCount = webconsoleModules.length;
-
-  const allModulesChars = countCharsInModules(allModules);
-  const webconsoleModulesChars = countCharsInModules(webconsoleModules);
-
-  const PERFHERDER_DATA = {
-    framework: {
-      name: "devtools",
-    },
-    suites: [{
-      name: "webconsole-metrics",
-      value: allModulesChars,
-      subtests: [
-        {
-          name: "webconsole-modules",
-          value: webconsoleModulesCount,
-        },
-        {
-          name: "webconsole-chars",
-          value: webconsoleModulesChars,
-        },
-        {
-          name: "all-modules",
-          value: allModulesCount,
-        },
-        {
-          name: "all-chars",
-          value: allModulesChars,
-        },
-      ],
-    }],
-  };
-  info("PERFHERDER_DATA: " + JSON.stringify(PERFHERDER_DATA));
-
-  // Simply check that we found valid values.
-  ok(allModulesCount > webconsoleModulesCount &&
-     webconsoleModulesCount > 0, "Successfully recorded module count for WebConsole");
-  ok(allModulesChars > webconsoleModulesChars &&
-     webconsoleModulesChars > 0, "Successfully recorded char count for WebConsole");
+  runMetricsTest({
+    filterString: "devtools/client/webconsole",
+    loaders,
+    panelName: "webconsole",
+  });
 });
--- a/devtools/client/framework/test/metrics/head.js
+++ b/devtools/client/framework/test/metrics/head.js
@@ -25,8 +25,62 @@ function countCharsInModules(modules) {
     try {
       return sum + require("raw!" + uri).length;
     } catch (e) {
       // Ignore failures
       return sum;
     }
   }, 0);
 }
+
+/**
+ * Record module loading data.
+ *
+ * @param {Object}
+ * - filterString {String} path to use to filter modules specific to the current panel
+ * - loaders {Array} Array of Loaders to check for modules
+ * - panelName {String} reused in identifiers for perfherder data
+ */
+function runMetricsTest({ filterString, loaders, panelName }) {
+  const allModules = getFilteredModules("", loaders);
+  const panelModules = getFilteredModules(filterString, loaders);
+
+  const allModulesCount = allModules.length;
+  const panelModulesCount = panelModules.length;
+
+  const allModulesChars = countCharsInModules(allModules);
+  const panelModulesChars = countCharsInModules(panelModules);
+
+  const PERFHERDER_DATA = {
+    framework: {
+      name: "devtools",
+    },
+    suites: [{
+      name: panelName + "-metrics",
+      value: allModulesChars,
+      subtests: [
+        {
+          name: panelName + "-modules",
+          value: panelModulesCount,
+        },
+        {
+          name: panelName + "-chars",
+          value: panelModulesChars,
+        },
+        {
+          name: "all-modules",
+          value: allModulesCount,
+        },
+        {
+          name: "all-chars",
+          value: allModulesChars,
+        },
+      ],
+    }],
+  };
+  info("PERFHERDER_DATA: " + JSON.stringify(PERFHERDER_DATA));
+
+  // Simply check that we found valid values.
+  ok(allModulesCount > panelModulesCount &&
+     panelModulesCount > 0, "Successfully recorded module count for " + panelName);
+  ok(allModulesChars > panelModulesChars &&
+     panelModulesChars > 0, "Successfully recorded char count for " + panelName);
+}
--- a/devtools/client/inspector/markup/markup.xhtml
+++ b/devtools/client/inspector/markup/markup.xhtml
@@ -4,19 +4,19 @@
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 <!DOCTYPE html>
 
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
   <link rel="stylesheet" href="chrome://devtools/skin/badge.css" type="text/css"/>
   <link rel="stylesheet" href="chrome://devtools/skin/markup.css" type="text/css"/>
-  <link rel="stylesheet" href="chrome://devtools/content/sourceeditor/codemirror/lib/codemirror.css" type="text/css"/>
-  <link rel="stylesheet" href="chrome://devtools/content/sourceeditor/codemirror/addon/dialog/dialog.css" type="text/css"/>
-  <link rel="stylesheet" href="chrome://devtools/content/sourceeditor/codemirror/mozilla.css" type="text/css"/>
+  <link rel="stylesheet" href="chrome://devtools/content/shared/sourceeditor/codemirror/lib/codemirror.css" type="text/css"/>
+  <link rel="stylesheet" href="chrome://devtools/content/shared/sourceeditor/codemirror/addon/dialog/dialog.css" type="text/css"/>
+  <link rel="stylesheet" href="chrome://devtools/content/shared/sourceeditor/codemirror/mozilla.css" type="text/css"/>
 
   <script type="application/javascript"
           src="chrome://devtools/content/shared/theme-switching.js"></script>
 </head>
 <body class="theme-body devtools-monospace" role="application">
   <div id="root-wrapper" role="presentation">
     <div id="root" role="presentation"></div>
   </div>
--- a/devtools/client/inspector/markup/views/html-editor.js
+++ b/devtools/client/inspector/markup/views/html-editor.js
@@ -1,15 +1,15 @@
 /* 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 Editor = require("devtools/client/sourceeditor/editor");
+const Editor = require("devtools/client/shared/sourceeditor/editor");
 const Services = require("Services");
 const EventEmitter = require("devtools/shared/event-emitter");
 
 /**
  * A wrapper around the Editor component, that allows editing of HTML.
  *
  * The main functionality this provides around the Editor is the ability
  * to show/hide/position an editor inplace. It only appends once to the
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -10,23 +10,23 @@ devtools.jar:
     content/shared/widgets/VariablesView.xul (shared/widgets/VariablesView.xul)
     content/webconsole/index.html (webconsole/index.html)
 *   content/scratchpad/index.xul (scratchpad/index.xul)
     content/shared/splitview.css (shared/splitview.css)
     content/shared/theme-switching.js (shared/theme-switching.js)
 *   content/styleeditor/index.xul (styleeditor/index.xul)
 *   content/storage/index.xul (storage/index.xul)
     content/inspector/markup/markup.xhtml (inspector/markup/markup.xhtml)
-    content/sourceeditor/codemirror/addon/dialog/dialog.css (sourceeditor/codemirror/addon/dialog/dialog.css)
-    content/sourceeditor/codemirror/addon/hint/show-hint.js (sourceeditor/codemirror/addon/hint/show-hint.js)
-    content/sourceeditor/codemirror/addon/tern/tern.js (sourceeditor/codemirror/addon/tern/tern.js)
-    content/sourceeditor/codemirror/codemirror.bundle.js (sourceeditor/codemirror/codemirror.bundle.js)
-    content/sourceeditor/codemirror/lib/codemirror.css (sourceeditor/codemirror/lib/codemirror.css)
-    content/sourceeditor/codemirror/mozilla.css (sourceeditor/codemirror/mozilla.css)
-    content/sourceeditor/codemirror/cmiframe.html (sourceeditor/codemirror/cmiframe.html)
+    content/shared/sourceeditor/codemirror/addon/dialog/dialog.css (shared/sourceeditor/codemirror/addon/dialog/dialog.css)
+    content/shared/sourceeditor/codemirror/addon/hint/show-hint.js (shared/sourceeditor/codemirror/addon/hint/show-hint.js)
+    content/shared/sourceeditor/codemirror/addon/tern/tern.js (shared/sourceeditor/codemirror/addon/tern/tern.js)
+    content/shared/sourceeditor/codemirror/codemirror.bundle.js (shared/sourceeditor/codemirror/codemirror.bundle.js)
+    content/shared/sourceeditor/codemirror/lib/codemirror.css (shared/sourceeditor/codemirror/lib/codemirror.css)
+    content/shared/sourceeditor/codemirror/mozilla.css (shared/sourceeditor/codemirror/mozilla.css)
+    content/shared/sourceeditor/codemirror/cmiframe.html (shared/sourceeditor/codemirror/cmiframe.html)
     content/debugger/new/index.html (debugger/new/index.html)
     content/shadereditor/index.xul (shadereditor/index.xul)
     content/canvasdebugger/index.xul (canvasdebugger/index.xul)
     content/canvasdebugger/canvasdebugger.js (canvasdebugger/canvasdebugger.js)
     content/canvasdebugger/snapshotslist.js (canvasdebugger/snapshotslist.js)
     content/canvasdebugger/callslist.js (canvasdebugger/callslist.js)
     content/webaudioeditor/index.xul (webaudioeditor/index.xul)
     content/webaudioeditor/includes.js (webaudioeditor/includes.js)
--- a/devtools/client/moz.build
+++ b/devtools/client/moz.build
@@ -22,17 +22,16 @@ DIRS += [
     'netmonitor',
     'performance',
     'performance-new',
     'preferences',
     'responsive.html',
     'scratchpad',
     'shadereditor',
     'shared',
-    'sourceeditor',
     'storage',
     'styleeditor',
     'themes',
     'webaudioeditor',
     'webconsole',
     'webide',
     'webreplay',
 ]
--- a/devtools/client/netmonitor/src/assets/styles/httpi.css
+++ b/devtools/client/netmonitor/src/assets/styles/httpi.css
@@ -3,16 +3,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 @import "chrome://devtools/skin/widgets.css";
 @import "resource://devtools/client/shared/components/splitter/SplitBox.css";
 @import "resource://devtools/client/shared/components/tree/TreeView.css";
 @import "resource://devtools/client/shared/components/tabs/Tabs.css";
 @import "resource://devtools/client/shared/components/tabs/TabBar.css";
 @import "chrome://devtools/skin/components-frame.css";
-@import "chrome://devtools/content/sourceeditor/codemirror/lib/codemirror.css";
-@import "chrome://devtools/content/sourceeditor/codemirror/addon/dialog/dialog.css";
-@import "chrome://devtools/content/sourceeditor/codemirror/mozilla.css";
+@import "chrome://devtools/content/shared/sourceeditor/codemirror/lib/codemirror.css";
+@import "chrome://devtools/content/shared/sourceeditor/codemirror/addon/dialog/dialog.css";
+@import "chrome://devtools/content/shared/sourceeditor/codemirror/mozilla.css";
 @import "resource://devtools/client/shared/components/MdnLink.css";
 
 /* Network panel components & styles */
 @import "chrome://devtools/content/netmonitor/src/assets/styles/variables.css";
 @import "chrome://devtools/content/netmonitor/src/assets/styles/NetworkDetailsPanel.css";
--- a/devtools/client/netmonitor/src/assets/styles/netmonitor.css
+++ b/devtools/client/netmonitor/src/assets/styles/netmonitor.css
@@ -3,19 +3,19 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 @import "resource://devtools/client/shared/components/SidebarToggle.css";
 @import "resource://devtools/client/shared/components/splitter/SplitBox.css";
 @import "resource://devtools/client/shared/components/tree/TreeView.css";
 @import "resource://devtools/client/shared/components/tabs/Tabs.css";
 @import "resource://devtools/client/shared/components/tabs/TabBar.css";
 @import "chrome://devtools/skin/components-frame.css";
-@import "chrome://devtools/content/sourceeditor/codemirror/lib/codemirror.css";
-@import "chrome://devtools/content/sourceeditor/codemirror/addon/dialog/dialog.css";
-@import "chrome://devtools/content/sourceeditor/codemirror/mozilla.css";
+@import "chrome://devtools/content/shared/sourceeditor/codemirror/lib/codemirror.css";
+@import "chrome://devtools/content/shared/sourceeditor/codemirror/addon/dialog/dialog.css";
+@import "chrome://devtools/content/shared/sourceeditor/codemirror/mozilla.css";
 @import "resource://devtools/client/shared/components/MdnLink.css";
 
 /* Network panel components & styles */
 @import "chrome://devtools/content/netmonitor/src/assets/styles/variables.css";
 @import "chrome://devtools/content/netmonitor/src/assets/styles/Toolbar.css";
 @import "chrome://devtools/content/netmonitor/src/assets/styles/StatusBar.css";
 @import "chrome://devtools/content/netmonitor/src/assets/styles/RequestList.css";
 @import "chrome://devtools/content/netmonitor/src/assets/styles/NetworkDetailsPanel.css";
--- a/devtools/client/netmonitor/src/components/SourceEditor.js
+++ b/devtools/client/netmonitor/src/components/SourceEditor.js
@@ -2,17 +2,17 @@
  * 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 { Component } = require("devtools/client/shared/vendor/react");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
-const Editor = require("devtools/client/sourceeditor/editor");
+const Editor = require("devtools/client/shared/sourceeditor/editor");
 
 const { div } = dom;
 
 /**
  * CodeMirror editor as a React component
  */
 class SourceEditor extends Component {
   static get propTypes() {
--- a/devtools/client/netmonitor/test/browser_net_simple-request-details.js
+++ b/devtools/client/netmonitor/test/browser_net_simple-request-details.js
@@ -15,17 +15,17 @@ add_task(async function() {
 
   const { document, store, windowRequire, NetMonitorView } = monitor.panelWin;
   const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
   const { EVENTS } = windowRequire("devtools/client/netmonitor/src/constants");
   const {
     getSelectedRequest,
     getSortedRequests,
   } = windowRequire("devtools/client/netmonitor/src/selectors/index");
-  const Editor = require("devtools/client/sourceeditor/editor");
+  const Editor = require("devtools/client/shared/sourceeditor/editor");
 
   store.dispatch(Actions.batchEnable(false));
 
   const wait = waitForNetworkEvents(monitor, 1);
   tab.linkedBrowser.reload();
   await wait;
 
   is(getSelectedRequest(store.getState()), undefined,
--- a/devtools/client/netmonitor/webpack.config.js
+++ b/devtools/client/netmonitor/webpack.config.js
@@ -78,17 +78,17 @@ const webpackConfig = {
 
       "devtools/client/shared/vendor/react": "react",
       "devtools/client/shared/vendor/react-dom": "react-dom",
       "devtools/client/shared/vendor/react-redux": "react-redux",
       "devtools/client/shared/vendor/redux": "redux",
       "devtools/client/shared/vendor/reselect": "reselect",
       "devtools/client/shared/vendor/jszip": "jszip",
 
-      "devtools/client/sourceeditor/editor": "devtools-source-editor/src/source-editor",
+      "devtools/client/shared/sourceeditor/editor": "devtools-source-editor/src/source-editor",
 
       "devtools/shared/event-emitter": "devtools-modules/src/utils/event-emitter",
       "devtools/shared/platform/clipboard": path.join(__dirname, "../../client/shared/webpack/shims/platform-clipboard-stub"),
       "devtools/client/netmonitor/src/utils/firefox/open-request-in-tab": path.join(__dirname, "src/utils/open-request-in-tab"),
       "devtools/client/shared/unicode-url": "./node_modules/devtools-modules/src/unicode-url",
 
       // Locales need to be explicitly mapped to the en-US subfolder
       "devtools/client/locales": path.join(__dirname, "../../client/locales/en-US"),
--- a/devtools/client/preferences/debugger.js
+++ b/devtools/client/preferences/debugger.js
@@ -67,9 +67,9 @@ pref("devtools.debugger.features.compone
 pref("devtools.debugger.features.async-stepping", true);
 pref("devtools.debugger.features.skip-pausing", true);
 pref("devtools.debugger.features.autocomplete-expressions", false);
 pref("devtools.debugger.features.map-expression-bindings", true);
 pref("devtools.debugger.features.xhr-breakpoints", true);
 pref("devtools.debugger.features.original-blackbox", true);
 pref("devtools.debugger.features.windowless-workers", false);
 pref("devtools.debugger.features.event-listeners-breakpoints", false);
-pref("devtools.debugger.features.log-points", false);
+pref("devtools.debugger.features.log-points", true);
--- a/devtools/client/scratchpad/scratchpad.js
+++ b/devtools/client/scratchpad/scratchpad.js
@@ -40,17 +40,17 @@ const SHOW_TRAILING_SPACE = "devtools.sc
 const EDITOR_FONT_SIZE = "devtools.scratchpad.editorFontSize";
 const ENABLE_AUTOCOMPLETION = "devtools.scratchpad.enableAutocompletion";
 const FALLBACK_CHARSET_LIST = "intl.fallbackCharsetList.ISO-8859-1";
 
 const VARIABLES_VIEW_URL = "chrome://devtools/content/shared/widgets/VariablesView.xul";
 
 const {require, loader} = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
 
-const Editor = require("devtools/client/sourceeditor/editor");
+const Editor = require("devtools/client/shared/sourceeditor/editor");
 const TargetFactory = require("devtools/client/framework/target").TargetFactory;
 const EventEmitter = require("devtools/shared/event-emitter");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const Services = require("Services");
 const {gDevTools} = require("devtools/client/framework/devtools");
 const { extend } = require("devtools/shared/extend");
 
 const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
--- a/devtools/client/shadereditor/shadereditor.js
+++ b/devtools/client/shadereditor/shadereditor.js
@@ -4,17 +4,17 @@
 "use strict";
 
 const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
 const {SideMenuWidget} = require("devtools/client/shared/widgets/SideMenuWidget.jsm");
 const promise = require("promise");
 const {Task} = require("devtools/shared/task");
 const EventEmitter = require("devtools/shared/event-emitter");
 const { HTMLTooltip } = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
-const Editor = require("devtools/client/sourceeditor/editor");
+const Editor = require("devtools/client/shared/sourceeditor/editor");
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const {extend} = require("devtools/shared/extend");
 const {WidgetMethods, setNamedTimeout} =
   require("devtools/client/shared/widgets/view-helpers");
 
 // The panel's window global is an EventEmitter firing the following events:
 const EVENTS = {
   // When new programs are received from the server.
--- a/devtools/client/shared/browser-loader.js
+++ b/devtools/client/shared/browser-loader.js
@@ -183,16 +183,20 @@ function BrowserLoaderBuilder({ baseURI,
         lazyServiceGetter: devtools.lazyServiceGetter,
         lazyRequireGetter: this.lazyRequireGetter.bind(this),
       },
     },
   };
 
   const mainModule = loaders.Module(baseURI, joinURI(baseURI, "main.js"));
   this.loader = loaders.Loader(opts);
+  // When running tests, expose the BrowserLoader instance for metrics tests.
+  if (flags.testing) {
+    window.getBrowserLoaderForWindow = () => this;
+  }
   this.require = loaders.Require(this.loader, mainModule);
 }
 
 BrowserLoaderBuilder.prototype = {
   /**
    * Define a getter property on the given object that requires the given
    * module. This enables delaying importing modules until the module is
    * actually used.
--- a/devtools/client/shared/components/List.js
+++ b/devtools/client/shared/components/List.js
@@ -9,16 +9,17 @@ const {
   createRef,
   Component,
   cloneElement,
 } = require("devtools/client/shared/vendor/react");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const { ul, li, div } = require("devtools/client/shared/vendor/react-dom-factories");
 
 const { scrollIntoView } = require("devtools/client/shared/scroll");
+const { preventDefaultAndStopPropagation } = require("devtools/client/shared/events");
 
 loader.lazyRequireGetter(this, "focusableSelector", "devtools/client/shared/focus", true);
 
 class ListItemClass extends Component {
   static get propTypes() {
     return {
       active: PropTypes.bool,
       current: PropTypes.bool,
@@ -177,18 +178,16 @@ class List extends Component {
     this.state = {
       active: null,
       current: null,
       mouseDown: false,
     };
 
     this._setCurrentItem = this._setCurrentItem.bind(this);
     this._preventArrowKeyScrolling = this._preventArrowKeyScrolling.bind(this);
-    this._preventDefaultAndStopPropagation =
-      this._preventDefaultAndStopPropagation.bind(this);
     this._onKeyDown = this._onKeyDown.bind(this);
   }
 
   shouldComponentUpdate(nextProps, nextState) {
     const { active, current, mouseDown } = this.state;
 
     return current !== nextState.current ||
            active !== nextState.active ||
@@ -196,34 +195,21 @@ class List extends Component {
   }
 
   _preventArrowKeyScrolling(e) {
     switch (e.key) {
       case "ArrowUp":
       case "ArrowDown":
       case "ArrowLeft":
       case "ArrowRight":
-        this._preventDefaultAndStopPropagation(e);
+        preventDefaultAndStopPropagation(e);
         break;
     }
   }
 
-  _preventDefaultAndStopPropagation(e) {
-    e.preventDefault();
-    e.stopPropagation();
-    if (e.nativeEvent) {
-      if (e.nativeEvent.preventDefault) {
-        e.nativeEvent.preventDefault();
-      }
-      if (e.nativeEvent.stopPropagation) {
-        e.nativeEvent.stopPropagation();
-      }
-    }
-  }
-
   /**
    * Sets the passed in item to be the current item.
    *
    * @param {null|Number} index
    *        The index of the item in to be set as current, or undefined to unset the
    *        current item.
    */
   _setCurrentItem(index = -1, options = {}) {
@@ -288,27 +274,27 @@ class List extends Component {
         this._setCurrentItem(length - 1, { alignTo: "bottom" });
         break;
 
       case "Enter":
       case " ":
         // On space or enter make current list item active. This means keyboard focus
         // handling is passed on to the component within the list item.
         if (document.activeElement === this.listRef.current) {
-          this._preventDefaultAndStopPropagation(e);
+          preventDefaultAndStopPropagation(e);
           if (active !== current) {
             this.setState({ active: current });
           }
         }
         break;
 
       case "Escape":
         // If current list item is active, make it inactive and let keyboard focusing be
         // handled normally.
-        this._preventDefaultAndStopPropagation(e);
+        preventDefaultAndStopPropagation(e);
         if (active != null) {
           this.setState({ active: null });
         }
 
         this.listRef.current.focus();
         break;
     }
   }
--- a/devtools/client/shared/components/VirtualizedTree.js
+++ b/devtools/client/shared/components/VirtualizedTree.js
@@ -3,16 +3,19 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 /* eslint-env browser */
 "use strict";
 
 const { Component, createFactory } = require("devtools/client/shared/vendor/react");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const { scrollIntoView } = require("devtools/client/shared/scroll");
+const { preventDefaultAndStopPropagation } = require("devtools/client/shared/events");
+
+loader.lazyRequireGetter(this, "focusableSelector", "devtools/client/shared/focus", true);
 
 const AUTO_EXPAND_DEPTH = 0;
 const NUMBER_OF_OFFSCREEN_ITEMS = 1;
 
 /**
  * A fast, generic, expandable and collapsible tree component.
  *
  * This tree component is fast: it can handle trees with *many* items. It only
@@ -191,16 +194,19 @@ class Tree extends Component {
       // Optional props
 
       // The currently focused item, if any such item exists.
       focused: PropTypes.any,
 
       // Handle when a new item is focused.
       onFocus: PropTypes.func,
 
+      // The currently active (keyboard) item, if any such item exists.
+      active: PropTypes.any,
+
       // Handle when item is activated with a keyboard (using Space or Enter)
       onActivate: PropTypes.func,
 
       // Indicates if pressing ArrowRight key should only expand expandable node
       // or if the selection should also move to the next node.
       preventNavigationOnArrowRight: PropTypes.bool,
 
       // The depth to which we should automatically expand new items.
@@ -250,26 +256,25 @@ class Tree extends Component {
     this._onExpand = oncePerAnimationFrame(this._onExpand).bind(this);
     this._onCollapse = oncePerAnimationFrame(this._onCollapse).bind(this);
     this._onScroll = oncePerAnimationFrame(this._onScroll).bind(this);
     this._focusPrevNode = oncePerAnimationFrame(this._focusPrevNode).bind(this);
     this._focusNextNode = oncePerAnimationFrame(this._focusNextNode).bind(this);
     this._focusParentNode = oncePerAnimationFrame(this._focusParentNode).bind(this);
     this._focusFirstNode = oncePerAnimationFrame(this._focusFirstNode).bind(this);
     this._focusLastNode = oncePerAnimationFrame(this._focusLastNode).bind(this);
-    this._activateNode = oncePerAnimationFrame(this._activateNode).bind(this);
 
     this._autoExpand = this._autoExpand.bind(this);
     this._preventArrowKeyScrolling = this._preventArrowKeyScrolling.bind(this);
     this._updateHeight = this._updateHeight.bind(this);
     this._onResize = this._onResize.bind(this);
     this._dfs = this._dfs.bind(this);
     this._dfsFromRoots = this._dfsFromRoots.bind(this);
     this._focus = this._focus.bind(this);
-    this._onBlur = this._onBlur.bind(this);
+    this._activate = this._activate.bind(this);
     this._onKeyDown = this._onKeyDown.bind(this);
   }
 
   componentDidMount() {
     window.addEventListener("resize", this._onResize);
     this._autoExpand();
     this._updateHeight();
   }
@@ -324,26 +329,18 @@ class Tree extends Component {
   }
 
   _preventArrowKeyScrolling(e) {
     switch (e.key) {
       case "ArrowUp":
       case "ArrowDown":
       case "ArrowLeft":
       case "ArrowRight":
-        e.preventDefault();
-        e.stopPropagation();
-        if (e.nativeEvent) {
-          if (e.nativeEvent.preventDefault) {
-            e.nativeEvent.preventDefault();
-          }
-          if (e.nativeEvent.stopPropagation) {
-            e.nativeEvent.stopPropagation();
-          }
-        }
+        preventDefaultAndStopPropagation(e);
+        break;
     }
   }
 
   /**
    * Updates the state's height based on clientHeight.
    */
   _updateHeight() {
     this.setState({ height: this.refs.tree.clientHeight });
@@ -435,43 +432,49 @@ class Tree extends Component {
       const treeElement = this.refs.tree;
       const element = document.getElementById(this.props.getKey(item));
       scrollIntoView(element, {
         ...options,
         container: treeElement,
       });
     }
 
+    if (this.props.active != null) {
+      this._activate(null);
+      if (this.refs.tree !== this.activeElement) {
+        this.refs.tree.focus();
+      }
+    }
+
     if (this.props.onFocus) {
       this.props.onFocus(item);
     }
   }
 
+  _activate(item) {
+    if (this.props.onActivate) {
+      this.props.onActivate(item);
+    }
+  }
+
   /**
    * Update state height and tree's scrollTop if necessary.
    */
   _onResize() {
     // When tree size changes without direct user action, scroll top cat get re-set to 0
     // (for example, when tree height changes via CSS rule change). We need to ensure that
     // the tree's scrollTop is in sync with the scroll state.
     if (this.state.scroll !== this.refs.tree.scrollTop) {
       this.refs.tree.scrollTo({ left: 0, top: this.state.scroll });
     }
 
     this._updateHeight();
   }
 
   /**
-   * Sets the state to have no focused item.
-   */
-  _onBlur() {
-    this._focus(0, undefined);
-  }
-
-  /**
    * Fired on a scroll within the tree's container, updates
    * the stored position of the view port to handle virtual view rendering.
    *
    * @param {Event} e
    */
   _onScroll(e) {
     this.setState({
       scroll: Math.max(this.refs.tree.scrollTop, 0),
@@ -528,25 +531,41 @@ class Tree extends Component {
         break;
 
       case "End":
         this._focusLastNode();
         break;
 
       case "Enter":
       case " ":
-        this._activateNode();
+        // On space or enter make focused tree node active. This means keyboard focus
+        // handling is passed on to the tree node itself.
+        if (this.refs.tree === this.activeElement) {
+          preventDefaultAndStopPropagation(e);
+          if (this.props.active !== this.props.focused) {
+            this._activate(this.props.focused);
+          }
+        }
+        break;
+
+      case "Escape":
+        preventDefaultAndStopPropagation(e);
+        if (this.props.active != null) {
+          this._activate(null);
+        }
+
+        if (this.refs.tree !== this.activeElement) {
+          this.refs.tree.focus();
+        }
         break;
     }
   }
 
-  _activateNode() {
-    if (this.props.onActivate) {
-      this.props.onActivate(this.props.focused);
-    }
+  get activeElement() {
+    return this.refs.tree.ownerDocument.activeElement;
   }
 
   _focusFirstNode() {
     const traversal = this._dfsFromRoots();
     this._focus(0, traversal[0].item, { alignTo: "top" });
   }
 
   _focusLastNode() {
@@ -635,17 +654,17 @@ class Tree extends Component {
     const traversal = this._dfsFromRoots();
 
     // 'begin' and 'end' are the index of the first (at least partially) visible item
     // and the index after the last (at least partially) visible item, respectively.
     // `NUMBER_OF_OFFSCREEN_ITEMS` is removed from `begin` and added to `end` so that
     // the top and bottom of the page are filled with the `NUMBER_OF_OFFSCREEN_ITEMS`
     // previous and next items respectively, which helps the user to see fewer empty
     // gaps when scrolling quickly.
-    const { itemHeight, focused } = this.props;
+    const { itemHeight, active, focused } = this.props;
     const { scroll, height } = this.state;
     const begin = Math.max(((scroll / itemHeight) | 0) - NUMBER_OF_OFFSCREEN_ITEMS, 0);
     const end = Math.ceil((scroll + height) / itemHeight) + NUMBER_OF_OFFSCREEN_ITEMS;
     const toRender = traversal.slice(begin, end);
     const topSpacerHeight = begin * itemHeight;
     const bottomSpacerHeight = Math.max(traversal.length - end, 0) * itemHeight;
 
     const nodes = [
@@ -662,25 +681,29 @@ class Tree extends Component {
 
     for (let i = 0; i < toRender.length; i++) {
       const index = begin + i;
       const first = index == 0;
       const last = index == traversal.length - 1;
       const { item, depth } = toRender[i];
       const key = this.props.getKey(item);
       nodes.push(TreeNode({
-        key,
+        // We make a key unique depending on whether the tree node is in active or
+        // inactive state to make sure that it is actually replaced and the tabbable
+        // state is reset.
+        key: `${key}-${active === item ? "active" : "inactive"}`,
         index,
         first,
         last,
         item,
         depth,
         id: key,
         renderItem: this.props.renderItem,
         focused: focused === item,
+        active: active === item,
         expanded: this.props.isExpanded(item),
         hasChildren: !!this.props.getChildren(item).length,
         onExpand: this._onExpand,
         onCollapse: this._onCollapse,
         // Since the user just clicked the node, there's no need to check if
         // it should be scrolled into view.
         onClick: () => this._focus(begin + i, item, { preventAutoScroll: true }),
       }));
@@ -713,16 +736,24 @@ class Tree extends Component {
             return;
           }
 
           // Only set default focus to the first tree node if focused node is
           // not yet set and the focus event is not the result of a mouse
           // interarction.
           this._focus(begin, toRender[0].item);
         },
+        onBlur: e => {
+          if (active != null) {
+            const { relatedTarget } = e;
+            if (!this.refs.tree.contains(relatedTarget)) {
+              this._activate(null);
+            }
+          }
+        },
         onClick: () => {
           // Focus should always remain on the tree container itself.
           this.refs.tree.focus();
         },
         "aria-label": this.props.label,
         "aria-labelledby": this.props.labelledby,
         "aria-activedescendant": focused && this.props.getKey(focused),
         style: {
@@ -754,16 +785,18 @@ class ArrowExpanderClass extends Compone
     return this.props.item !== nextProps.item
       || this.props.visible !== nextProps.visible
       || this.props.expanded !== nextProps.expanded;
   }
 
   render() {
     const attrs = {
       className: "arrow theme-twisty",
+      // To collapse/expand the tree rows use left/right arrow keys.
+      tabIndex: "-1",
       onClick: this.props.expanded
         ? () => this.props.onCollapse(this.props.item)
         : e => this.props.onExpand(this.props.item, e.altKey),
     };
 
     if (this.props.expanded) {
       attrs.className += " open";
     }
@@ -778,30 +811,110 @@ class ArrowExpanderClass extends Compone
   }
 }
 
 class TreeNodeClass extends Component {
   static get propTypes() {
     return {
       id: PropTypes.any.isRequired,
       focused: PropTypes.bool.isRequired,
+      active: PropTypes.boool.isRequired,
       item: PropTypes.any.isRequired,
       expanded: PropTypes.bool.isRequired,
       hasChildren: PropTypes.bool.isRequired,
       onExpand: PropTypes.func.isRequired,
       index: PropTypes.number.isRequired,
       first: PropTypes.bool,
       last: PropTypes.bool,
       onClick: PropTypes.func,
       onCollapse: PropTypes.func.isRequired,
       depth: PropTypes.number.isRequired,
       renderItem: PropTypes.func.isRequired,
     };
   }
 
+  constructor(props) {
+    super(props);
+
+    this._onKeyDown = this._onKeyDown.bind(this);
+  }
+
+  componentDidMount() {
+    // Make sure that none of the focusable elements inside the tree node container are
+    // tabbable if the tree node is not active. If the tree node is active and focus is
+    // outside its container, focus on the first focusable element inside.
+    const elms = this.getFocusableElements();
+    if (elms.length === 0) {
+      return;
+    }
+
+    if (!this.props.active) {
+      elms.forEach(elm => elm.setAttribute("tabindex", "-1"));
+      return;
+    }
+
+    if (!elms.includes(this.refs.treenode.ownerDocument.activeElement)) {
+      elms[0].focus();
+    }
+  }
+
+  /**
+   * Get a list of all elements that are focusable with a keyboard inside the tree node.
+   */
+  getFocusableElements() {
+    return Array.from(this.refs.treenode.querySelectorAll(focusableSelector));
+  }
+
+  /**
+   * Wrap and move keyboard focus to first/last focusable element inside the tree node to
+   * prevent the focus from escaping the tree node boundaries.
+   * element).
+   *
+   * @param  {DOMNode} current  currently focused element
+   * @param  {Boolean} back     direction
+   * @return {Boolean}          true there is a newly focused element.
+   */
+  _wrapMoveFocus(current, back) {
+    const elms = this.getFocusableElements();
+    let next;
+
+    if (elms.length === 0) {
+      return false;
+    }
+
+    if (back) {
+      if (elms.indexOf(current) === 0) {
+        next = elms[elms.length - 1];
+        next.focus();
+      }
+    } else if (elms.indexOf(current) === elms.length - 1) {
+      next = elms[0];
+      next.focus();
+    }
+
+    return !!next;
+  }
+
+  _onKeyDown(e) {
+    const { target, key, shiftKey } = e;
+
+    if (key !== "Tab") {
+      return;
+    }
+
+    const focusMoved = this._wrapMoveFocus(target, shiftKey);
+    if (focusMoved) {
+      // Focus was moved to the begining/end of the list, so we need to prevent the
+      // default focus change that would happen here.
+      e.preventDefault();
+    }
+
+    e.stopPropagation();
+  }
+
   render() {
     const arrow = ArrowExpander({
       item: this.props.item,
       expanded: this.props.expanded,
       visible: this.props.hasChildren,
       onExpand: this.props.onExpand,
       onCollapse: this.props.onCollapse,
     });
@@ -811,32 +924,37 @@ class TreeNodeClass extends Component {
       classList.push("tree-node-odd");
     }
     if (this.props.first) {
       classList.push("tree-node-first");
     }
     if (this.props.last) {
       classList.push("tree-node-last");
     }
+    if (this.props.active) {
+      classList.push("tree-node-active");
+    }
 
     let ariaExpanded;
     if (this.props.hasChildren) {
       ariaExpanded = false;
     }
     if (this.props.expanded) {
       ariaExpanded = true;
     }
 
     return dom.div(
       {
         id: this.props.id,
         className: classList.join(" "),
         role: "treeitem",
+        ref: "treenode",
         "aria-level": this.props.depth + 1,
         onClick: this.props.onClick,
+        onKeyDownCapture: this.props.active && this._onKeyDown,
         "aria-expanded": ariaExpanded,
         "data-expanded": this.props.expanded ? "" : undefined,
         "data-depth": this.props.depth,
         style: {
           padding: 0,
           margin: 0,
         },
       },
--- a/devtools/client/shared/components/test/mochitest/chrome.ini
+++ b/devtools/client/shared/components/test/mochitest/chrome.ini
@@ -30,8 +30,9 @@ support-files =
 [test_tree_06.html]
 [test_tree_07.html]
 [test_tree_08.html]
 [test_tree_09.html]
 [test_tree_10.html]
 [test_tree_11.html]
 [test_tree_12.html]
 [test_tree_13.html]
+[test_tree_14.html]
--- a/devtools/client/shared/components/test/mochitest/test_tree_12.html
+++ b/devtools/client/shared/components/test/mochitest/test_tree_12.html
@@ -33,22 +33,16 @@ window.onload = async function () {
         ...TEST_TREE_INTERFACE,
         onFocus: x => renderTree({ focused: x }),
         ...props
       };
 
       return ReactDOM.render(Tree(treeProps), window.document.body);
     }
 
-    const checker = Symbol();
-    let isActivated;
-    const mockFn = activated => {
-      isActivated = activated;
-    };
-
     const tree = renderTree();
 
     TEST_TREE.expanded = new Set("ABCDEFGHIJKLMNO".split(""));
 
     // Test Home key -----------------------------------------------------------
 
     info("Press Home to move to the first node.");
     renderTree({ focused: "L" });
@@ -135,122 +129,16 @@ window.onload = async function () {
       "--H:false",
       "--I:false",
       "-D:false",
       "--J:false",
       "M:false",
       "-N:false",
       "--O:true",
     ], "After the End key again, O should still be focused.");
-
-    // Test Enter key ----------------------------------------------------------
-
-    info("Press Enter to activate node, when onActivate is not passed.");
-    isActivated = checker;
-    renderTree({ focused: "L" });
-    Simulate.keyDown(document.querySelector(".tree"), { key: "Enter" });
-    await forceRender(tree);
-
-    isRenderedTree(document.body.textContent, [
-      "A:false",
-      "-B:false",
-      "--E:false",
-      "---K:false",
-      "---L:true",
-      "--F:false",
-      "--G:false",
-      "-C:false",
-      "--H:false",
-      "--I:false",
-      "-D:false",
-      "--J:false",
-      "M:false",
-      "-N:false",
-      "--O:false",
-    ], "After the Enter, L should be focused and the tree remained unchanged.");
-    ok(isActivated === checker,
-       "Since onActivate was not specified, 'isActivated' should not be set.");
-
-    info("Press Enter to activate node, when onActivate is passed.");
-    isActivated = checker;
-    renderTree({ focused: "L", onActivate: mockFn });
-    Simulate.keyDown(document.querySelector(".tree"), { key: "Enter" });
-    await forceRender(tree);
-
-    isRenderedTree(document.body.textContent, [
-      "A:false",
-      "-B:false",
-      "--E:false",
-      "---K:false",
-      "---L:true",
-      "--F:false",
-      "--G:false",
-      "-C:false",
-      "--H:false",
-      "--I:false",
-      "-D:false",
-      "--J:false",
-      "M:false",
-      "-N:false",
-      "--O:false",
-    ], "After the Enter, L should be focused and the tree remained unchanged.");
-    is(isActivated, "L", "onActivate function was called with the right node.");
-
-    // Test Space key ----------------------------------------------------------
-
-    info("Press Space to activate node, when onActivate is not passed.");
-    isActivated = checker;
-    renderTree({ focused: "K" });
-    Simulate.keyDown(document.querySelector(".tree"), { key: " " });
-    await forceRender(tree);
-
-    isRenderedTree(document.body.textContent, [
-      "A:false",
-      "-B:false",
-      "--E:false",
-      "---K:true",
-      "---L:false",
-      "--F:false",
-      "--G:false",
-      "-C:false",
-      "--H:false",
-      "--I:false",
-      "-D:false",
-      "--J:false",
-      "M:false",
-      "-N:false",
-      "--O:false",
-    ], "After the Space, K should be focused and the tree remained unchanged.");
-    ok(isActivated === checker,
-       "Since onActivate was not specified, 'isActivated' should not be set.");
-
-    info("Press Space to activate node, when onActivate is passed.");
-    isActivated = checker;
-    renderTree({ focused: "K", onActivate: mockFn });
-    Simulate.keyDown(document.querySelector(".tree"), { key: " " });
-    await forceRender(tree);
-
-    isRenderedTree(document.body.textContent, [
-      "A:false",
-      "-B:false",
-      "--E:false",
-      "---K:true",
-      "---L:false",
-      "--F:false",
-      "--G:false",
-      "-C:false",
-      "--H:false",
-      "--I:false",
-      "-D:false",
-      "--J:false",
-      "M:false",
-      "-N:false",
-      "--O:false",
-    ], "After the Space, K should be focused and the tree remained unchanged.");
-    is(isActivated, "K", "onActivate function was called with the right node.");
   } catch (e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 };
 </script>
 </pre>
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/test/mochitest/test_tree_14.html
@@ -0,0 +1,245 @@
+<!-- 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/. -->
+<!DOCTYPE HTML>
+<html>
+<!--
+Test that Tree component has working keyboard interactions.
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Tree component keyboard test</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script src="head.js" type="application/javascript"></script>
+<script type="application/javascript">
+
+"use strict";
+
+window.onload = async function() {
+  try {
+    const { a, button, div } =
+      require("devtools/client/shared/vendor/react-dom-factories");
+    const { createFactory } = browserRequire("devtools/client/shared/vendor/react");
+    const {
+      Simulate,
+      findRenderedDOMComponentWithClass,
+      findRenderedDOMComponentWithTag,
+      scryRenderedDOMComponentsWithTag,
+    } = browserRequire("devtools/client/shared/vendor/react-dom-test-utils");
+    const Tree = createFactory(
+      browserRequire("devtools/client/shared/components/VirtualizedTree"));
+
+    let gTree, gFocused, gActive;
+    function renderTree(props = {}) {
+      let toggle = true;
+      const treeProps = {
+        ...TEST_TREE_INTERFACE,
+        onFocus: x => {
+          gFocused = x;
+          renderTree({ focused: gFocused, active: gActive });
+        },
+        onActivate: x => {
+          gActive = x;
+          renderTree({ focused: gFocused, active: gActive });
+        },
+        renderItem: (x, depth, focused) => {
+          toggle = !toggle;
+          return toggle ?
+            (div(
+              {},
+                `${"-".repeat(depth)}${x}:${focused}`,
+                a({ href: "#" }, "Focusable 1"),
+                button({ }, "Focusable 2"),
+                "\n",
+              )
+            ) : `${"-".repeat(depth)}${x}:${focused}`;
+        },
+        ...props
+      };
+
+      gTree = ReactDOM.render(Tree(treeProps), document.body);
+    }
+
+    renderTree();
+    const els = {
+      get tree() {
+        // React will replace the tree via renderTree.
+        return findRenderedDOMComponentWithClass(gTree, "tree");
+      },
+      get anchor() {
+        // When tree node becomes active/inactive, it is replaced with a newly rendered
+        // one.
+        return findRenderedDOMComponentWithTag(gTree, "a");
+      },
+      get button() {
+        // When tree node becomes active/inactive, it is replaced with a newly rendered
+        // one.
+        return findRenderedDOMComponentWithTag(gTree, "button");
+      },
+    };
+
+    const tests = [{
+      name: "Test default Tree props. Keyboard focus is set to document body by default.",
+      props: { focused: undefined, active: undefined },
+      activeElement: document.body,
+    }, {
+      name: "Focused props must be set to the first node on initial focus. " +
+            "Keyboard focus should be set on the tree.",
+      action: () => els.tree.focus(),
+      activeElement: "tree",
+      props: { focused: "A" },
+    }, {
+      name: "Focused node should remain set even when the tree is blured. " +
+            "Keyboard focus should be set back to document body.",
+      action: () => els.tree.blur(),
+      props: { focused: "A" },
+      activeElement: document.body,
+    }, {
+      name: "Unset tree's focused prop.",
+      action: () => renderTree({ focused: null }),
+      props: { focused: null },
+    }, {
+      name: "Focused node must be re-set again to the first tree node on initial " +
+            "focus. Keyboard focus should be set on tree's conatiner.",
+      action: () => els.tree.focus(),
+      activeElement: "tree",
+      props: { focused: "A" },
+    }, {
+      name: "Focused node should be set as active on Enter.",
+      event: { type: "keyDown", el: "tree", options: { key: "Enter" }},
+      props: { focused: "A", active: "A" },
+      activeElement: "tree",
+    }, {
+      name: "Active node should be unset on Escape.",
+      event: { type: "keyDown", el: "tree", options: { key: "Escape" }},
+      props: { focused: "A", active: null },
+    }, {
+      name: "Focused node should be set as active on Space.",
+      event: { type: "keyDown", el: "tree", options: { key: " " }},
+      props: { focused: "A", active: "A" },
+      activeElement: "tree",
+    }, {
+      name: "Active node should unset when focus leaves the tree.",
+      action: () => els.tree.blur(),
+      props: { focused: "A", active: null },
+      activeElement: document.body,
+    }, {
+      name: "Keyboard focus should be set on tree's conatiner on focus.",
+      action: () => els.tree.focus(),
+      activeElement: "tree",
+    }, {
+      name: "Focused node should be updated to next on ArrowDown.",
+      event: { type: "keyDown", el: "tree", options: { key: "ArrowDown" }},
+      props: { focused: "M", active: null },
+    }, {
+      name: "Focused item should be set as active on Enter. Keyboard focus should be " +
+            "set on the first focusable element inside the tree node, if available.",
+      event: { type: "keyDown", el: "tree", options: { key: "Enter" }},
+      props: { focused: "M", active: "M" },
+      activeElement: "anchor",
+    }, {
+      name: "Keyboard focus should be set to next tabbable element inside the active " +
+            "node on Tab.",
+      action() {
+        synthesizeKey("KEY_Tab");
+      },
+      props: { focused: "M", active: "M" },
+      activeElement: "button",
+    }, {
+      name: "Keyboard focus should wrap inside the tree node when focused on last " +
+            "tabbable element.",
+      action() {
+        synthesizeKey("KEY_Tab");
+      },
+      props: { focused: "M", active: "M" },
+      activeElement: "anchor",
+    }, {
+      name: "Keyboard focus should wrap inside the tree node when focused on first " +
+            "tabbable element.",
+      action() {
+        synthesizeKey("KEY_Tab", { shiftKey: true });
+      },
+      props: { focused: "M", active: "M" },
+      activeElement: "button",
+    }, {
+      name: "Active tree node should be unset on Escape. Focus should move back to the " +
+            "tree container.",
+      event: { type: "keyDown", el: "tree", options: { key: "Escape" }},
+      props: { focused: "M", active: null },
+      activeElement: "tree",
+    }, {
+      name: "Focused node should be set as active on Space. Keyboard focus should be " +
+            "set on the first focusable element inside the tree node, if available.",
+      event: { type: "keyDown", el: "tree", options: { key: " " }},
+      props: { focused: "M", active: "M" },
+      activeElement: "anchor",
+    }, {
+      name: "Focused tree node should remain set even when the tree is blured. " +
+            "Keyboard focus should be set back to document body.",
+      action: () => document.activeElement.blur(),
+      props: { focused: "M", active: null, },
+      activeElement: document.body,
+    }, {
+      name: "Keyboard focus should be set on tree's conatiner on focus.",
+      action: () => els.tree.focus(),
+      props: { focused: "M", active: null },
+      activeElement: "tree",
+    }, {
+      name: "Focused tree node should be updated to previous on ArrowUp.",
+      event: { type: "keyDown", el: "tree", options: { key: "ArrowUp" }},
+      props: { focused: "A", active: null },
+    }, {
+      name: "Focused item should be set as active on Enter.",
+      event: { type: "keyDown", el: "tree", options: { key: "Enter" }},
+      props: { focused: "A", active: "A" },
+      activeElement: "tree",
+    }, {
+      name: "Keyboard focus should move to another focusable element outside of the " +
+            "tree when there's nothing to focus on inside the tree node.",
+      action() {
+        synthesizeKey("KEY_Tab", { shiftKey: true });
+      },
+      props: { focused: "A", active: null },
+      activeElement: document.documentElement,
+    }];
+
+    for (const test of tests) {
+      const { action, condition, event, props, name } = test;
+
+      info(name);
+      if (event) {
+        const { type, options, el } = event;
+        const target = typeof el === "string" ? els[el] : el;
+        Simulate[type](target, options);
+      } else if (action) {
+        action();
+      }
+
+      await forceRender(gTree);
+
+      if (test.activeElement) {
+        const expected = typeof test.activeElement === "string" ?
+          els[test.activeElement] : test.activeElement;
+        if (document.activeElement!==expected) {debugger;}
+        is(document.activeElement, expected, "Focus is set correctly.");
+      }
+
+      for (let key in props) {
+        is(gTree.props[key], props[key], `${key} prop is correct.`);
+      }
+    }
+  } catch (e) {
+    ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
+  } finally {
+    SimpleTest.finish();
+  }
+};
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/events.js
@@ -0,0 +1,22 @@
+/* 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";
+
+/**
+ * Prevent event default behaviour and stop its propagation.
+ * @param  {Object} event
+ *         Event or react synthetic event.
+ */
+exports.preventDefaultAndStopPropagation = function(event) {
+  event.preventDefault();
+  event.stopPropagation();
+  if (event.nativeEvent) {
+    if (event.nativeEvent.preventDefault) {
+      event.nativeEvent.preventDefault();
+    }
+    if (event.nativeEvent.stopPropagation) {
+      event.nativeEvent.stopPropagation();
+    }
+  }
+};
--- a/devtools/client/shared/moz.build
+++ b/devtools/client/shared/moz.build
@@ -10,31 +10,33 @@ TEST_HARNESS_FILES.xpcshell.devtools.cli
     'test/shared-redux-head.js',
 ]
 
 DIRS += [
     'components',
     'redux',
     'remote-debugging',
     'source-map',
+    'sourceeditor',
     'vendor',
     'webpack',
     'widgets',
 ]
 
 DevToolsModules(
     'autocomplete-popup.js',
     'browser-loader-mocks.js',
     'browser-loader.js',
     'css-angle.js',
     'curl.js',
     'demangle.js',
     'devices.js',
     'DOMHelpers.jsm',
     'enum.js',
+    'events.js',
     'file-saver.js',
     'focus.js',
     'getjson.js',
     'inplace-editor.js',
     'key-shortcuts.js',
     'keycodes.js',
     'link.js',
     'natural-sort.js',
--- a/devtools/client/shared/remote-debugging/moz.build
+++ b/devtools/client/shared/remote-debugging/moz.build
@@ -1,12 +1,17 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
     'remote-client-manager.js',
+    'version-checker.js',
 )
 
+XPCSHELL_TESTS_MANIFESTS += [
+    'test/unit/xpcshell.ini'
+]
+
 with Files('**'):
     BUG_COMPONENT = ('DevTools', 'about:debugging')
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/remote-debugging/test/unit/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict";
+
+module.exports = {
+  // Extend from the common devtools xpcshell eslintrc config.
+  "extends": "../../../../../.eslintrc.xpcshell.js"
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/remote-debugging/test/unit/test_version_checker.js
@@ -0,0 +1,110 @@
+/* global equal */
+
+"use strict";
+
+const {
+  _compareVersionCompatibility,
+  checkVersionCompatibility,
+  COMPATIBILITY_STATUS,
+} = require("devtools/client/shared/remote-debugging/version-checker");
+
+const TEST_DATA = [
+  {
+    description: "same build date and same version number",
+    localBuildId: "20190131000000",
+    localVersion: "60.0",
+    runtimeBuildId: "20190131000000",
+    runtimeVersion: "60.0",
+    expected: COMPATIBILITY_STATUS.COMPATIBLE,
+  },
+  {
+    description: "same build date and older version in range (-1)",
+    localBuildId: "20190131000000",
+    localVersion: "60.0",
+    runtimeBuildId: "20190131000000",
+    runtimeVersion: "59.0",
+    expected: COMPATIBILITY_STATUS.COMPATIBLE,
+  },
+  {
+    description: "same build date and older version in range (-2)",
+    localBuildId: "20190131000000",
+    localVersion: "60.0",
+    runtimeBuildId: "20190131000000",
+    runtimeVersion: "58.0",
+    expected: COMPATIBILITY_STATUS.COMPATIBLE,
+  },
+  {
+    description: "same build date and older version in range (-2 Nightly)",
+    localBuildId: "20190131000000",
+    localVersion: "60.0",
+    runtimeBuildId: "20190131000000",
+    runtimeVersion: "58.0a1",
+    expected: COMPATIBILITY_STATUS.COMPATIBLE,
+  },
+  {
+    description: "same build date and older version out of range (-3)",
+    localBuildId: "20190131000000",
+    localVersion: "60.0",
+    runtimeBuildId: "20190131000000",
+    runtimeVersion: "57.0",
+    expected: COMPATIBILITY_STATUS.TOO_OLD,
+  },
+  {
+    description: "same build date and newer version out of range (+1)",
+    localBuildId: "20190131000000",
+    localVersion: "60.0",
+    runtimeBuildId: "20190131000000",
+    runtimeVersion: "61.0",
+    expected: COMPATIBILITY_STATUS.TOO_RECENT,
+  },
+  {
+    description: "same major version and build date in range (-10 days)",
+    localBuildId: "20190131000000",
+    localVersion: "60.0",
+    runtimeBuildId: "20190121000000",
+    runtimeVersion: "60.0",
+    expected: COMPATIBILITY_STATUS.COMPATIBLE,
+  },
+  {
+    description: "same major version and build date in range (+2 days)",
+    localBuildId: "20190131000000",
+    localVersion: "60.0",
+    runtimeBuildId: "20190202000000",
+    runtimeVersion: "60.0",
+    expected: COMPATIBILITY_STATUS.COMPATIBLE,
+  },
+  {
+    description: "same major version and build date out of range (+8 days)",
+    localBuildId: "20190131000000",
+    localVersion: "60.0",
+    runtimeBuildId: "20190208000000",
+    runtimeVersion: "60.0",
+    expected: COMPATIBILITY_STATUS.TOO_RECENT,
+  },
+];
+
+add_task(async function testVersionChecker() {
+  for (const testData of TEST_DATA) {
+    const localDescription = {
+      appbuildid: testData.localBuildId,
+      platformversion: testData.localVersion,
+    };
+
+    const runtimeDescription = {
+      appbuildid: testData.runtimeBuildId,
+      platformversion: testData.runtimeVersion,
+    };
+
+    const report = _compareVersionCompatibility(localDescription, runtimeDescription);
+    equal(report.status, testData.expected,
+      "Expected status for test: " + testData.description);
+  }
+});
+
+add_task(async function testVersionCheckWithVeryOldClient() {
+  // Use an empty object as debugger client, calling any method on it will fail.
+  const emptyClient = {};
+  const report = await checkVersionCompatibility(emptyClient);
+  equal(report.status, COMPATIBILITY_STATUS.TOO_OLD,
+      "Report status too old if debugger client is not implementing expected interface");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/remote-debugging/test/unit/xpcshell-head.js
@@ -0,0 +1,8 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* eslint no-unused-vars: [2, {"vars": "local"}] */
+
+const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/remote-debugging/test/unit/xpcshell.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+tags = devtools
+head = xpcshell-head.js
+firefox-appdir = browser
+skip-if = toolkit == 'android'
+
+[test_version_checker.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/remote-debugging/version-checker.js
@@ -0,0 +1,147 @@
+/* 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 Services = require("Services");
+const {AppConstants} = require("resource://gre/modules/AppConstants.jsm");
+
+const MS_PER_DAY = 1000 * 60 * 60 * 24;
+
+const COMPATIBILITY_STATUS = {
+  COMPATIBLE: "compatible",
+  TOO_OLD: "too-old",
+  TOO_RECENT: "too-recent",
+};
+exports.COMPATIBILITY_STATUS = COMPATIBILITY_STATUS;
+
+function getDateFromBuildID(buildID) {
+  // Build IDs are a timestamp in the yyyyMMddHHmmss format.
+  // Extract the year, month and day information.
+  const fields = buildID.match(/(\d{4})(\d{2})(\d{2})/);
+  // Date expects 0 - 11 for months
+  const month = Number.parseInt(fields[2], 10) - 1;
+  return new Date(fields[1], month, fields[3]);
+}
+
+function getMajorVersion(platformVersion) {
+  // Retrieve the major platform version, i.e. if we are on Firefox 64.0a1, it will be 64.
+  return Number.parseInt(platformVersion.match(/\d+/)[0], 10);
+}
+
+/**
+ * Compute the minimum and maximum supported version for remote debugging for the provided
+ * version of Firefox. Backward compatibility policy for devtools supports at most 2
+ * versions older than the current version.
+ *
+ * @param {String} localVersion
+ *        The version of the local Firefox instance, eg "67.0"
+ * @return {Object}
+ *         - minVersion {String} the minimum supported version, eg "65.0a1"
+ *         - maxVersion {String} the first unsupported version, eg "68.0a1"
+ */
+function computeMinMaxVersion(localVersion) {
+  // Retrieve the major platform version, i.e. if we are on Firefox 64.0a1, it will be 64.
+  const localMajorVersion = getMajorVersion(localVersion);
+
+  return {
+    // Define the minimum officially supported version of Firefox when connecting to a
+    // remote runtime. (Use ".0a1" to support the very first nightly version)
+    // This matches the release channel's version when we are on nightly,
+    // or 2 versions before when we are on other channels.
+    minVersion: (localMajorVersion - 2) + ".0a1",
+    // The maximum version is the first excluded from the support range. That's why we
+    // increase the current version by 1 and use ".0a1" to point to the first Nightly.
+    // We do not support forward compatibility at all.
+    maxVersion: (localMajorVersion + 1) + ".0a1",
+  };
+}
+
+/**
+ * Tells if the remote device is using a supported version of Firefox.
+ *
+ * @param {DebuggerClient} debuggerClient
+ *        DebuggerClient instance connected to the target remote Firefox.
+ * @return Object with the following attributes:
+ *   * String status, one of COMPATIBILITY_STATUS
+ *            COMPATIBLE if the runtime is compatible,
+ *            TOO_RECENT if the runtime uses a too recent version,
+ *            TOO_OLD if the runtime uses a too old version.
+ *   * String minVersion
+ *            The minimum supported version.
+ *   * String runtimeVersion
+ *            The remote runtime version.
+ *   * String localID
+ *            Build ID of local runtime. A date with like this: YYYYMMDD.
+ *   * String deviceID
+ *            Build ID of remote runtime. A date with like this: YYYYMMDD.
+ */
+async function checkVersionCompatibility(debuggerClient) {
+  const localDescription = {
+    appbuildid: Services.appinfo.appBuildID,
+    platformversion: AppConstants.MOZ_APP_VERSION,
+  };
+
+  try {
+    const deviceFront = await debuggerClient.mainRoot.getFront("device");
+    const description = await deviceFront.getDescription();
+    return _compareVersionCompatibility(localDescription, description);
+  } catch (e) {
+    // If we failed to retrieve the device description, assume we are trying to connect to
+    // a really old version of Firefox.
+    const localVersion = localDescription.platformversion;
+    const { minVersion } = computeMinMaxVersion(localVersion);
+    return {
+      minVersion,
+      runtimeVersion: "<55",
+      status: COMPATIBILITY_STATUS.TOO_OLD,
+    };
+  }
+}
+exports.checkVersionCompatibility = checkVersionCompatibility;
+
+function _compareVersionCompatibility(localDescription, deviceDescription) {
+  const runtimeID = deviceDescription.appbuildid.substr(0, 8);
+  const localID = localDescription.appbuildid.substr(0, 8);
+
+  const runtimeDate = getDateFromBuildID(runtimeID);
+  const localDate = getDateFromBuildID(localID);
+
+  const runtimeVersion = deviceDescription.platformversion;
+  const localVersion = localDescription.platformversion;
+
+  const { minVersion, maxVersion } = computeMinMaxVersion(localVersion);
+  const isTooOld = Services.vc.compare(runtimeVersion, minVersion) < 0;
+  const isTooRecent = Services.vc.compare(runtimeVersion, maxVersion) >= 0;
+
+  const runtimeMajorVersion = getMajorVersion(runtimeVersion);
+  const localMajorVersion = getMajorVersion(localVersion);
+  const isSameMajorVersion = runtimeMajorVersion === localMajorVersion;
+
+  let status;
+  if (isTooOld) {
+    status = COMPATIBILITY_STATUS.TOO_OLD;
+  } else if (isTooRecent) {
+    status = COMPATIBILITY_STATUS.TOO_RECENT;
+  } else if (isSameMajorVersion && runtimeDate - localDate > 7 * MS_PER_DAY) {
+    // If both local and remote runtimes have the same major version, compare build dates.
+    // This check is useful for Gecko developers as we might introduce breaking changes
+    // within a Nightly cycle.
+    // Still allow devices to be newer by up to a week. This accommodates those with local
+    // device builds, since their devices will almost always be newer than the client.
+    status = COMPATIBILITY_STATUS.TOO_RECENT;
+  } else {
+    status = COMPATIBILITY_STATUS.COMPATIBLE;
+  }
+
+  return {
+    localID,
+    minVersion,
+    runtimeID,
+    runtimeVersion,
+    status,
+  };
+}
+// Exported for tests.
+exports._compareVersionCompatibility = _compareVersionCompatibility;
rename from devtools/client/sourceeditor/.eslintrc.js
rename to devtools/client/shared/sourceeditor/.eslintrc.js
--- a/devtools/client/sourceeditor/.eslintrc.js
+++ b/devtools/client/shared/sourceeditor/.eslintrc.js
@@ -1,13 +1,13 @@
 "use strict";
 
 module.exports = {
   // Extend from the devtools eslintrc.
-  "extends": "../../.eslintrc.js",
+  "extends": "../../../.eslintrc.js",
 
   "rules": {
     // The inspector is being migrated to HTML and cleaned of
     // chrome-privileged code, so this rule disallows requiring chrome
     // code. Some files here disable this rule still. The
     // goal is to enable the rule globally on all files.
     /* eslint-disable max-len */
     "mozilla/reject-some-requires": ["error", "^(chrome|chrome:.*|resource:.*|devtools/server/.*|.*\\.jsm)$"],
rename from devtools/client/sourceeditor/README
rename to devtools/client/shared/sourceeditor/README
--- a/devtools/client/sourceeditor/README
+++ b/devtools/client/shared/sourceeditor/README
@@ -5,17 +5,17 @@ code, and optionally help with indentati
 
 # Upgrade
 
 Currently used version is 5.40.0. To upgrade: download a new version of
 CodeMirror from the project's page [1] and replace all JavaScript and
 CSS files inside the codemirror directory [2].
 
 Then to recreate codemirror.bundle.js:
- > cd devtools/client/sourceeditor
+ > cd devtools/client/shared/sourceeditor
  > npm install
  > webpack
 
 To confirm the functionality run mochitests for the following components:
 
  * sourceeditor
  * scratchpad
  * debugger
@@ -41,24 +41,24 @@ CodeMirror itself.
 # Addons
 
 To install a new CodeMirror addon add it to the codemirror directory,
 jar.mn [4] file and editor.js [5]. Also, add it to the License section
 below.
 
 # License
 
-The following files in this directory and devtools/client/sourceeditor/test/codemirror/
+The following files in this directory and devtools/client/shared/sourceeditor/test/codemirror/
 are licensed according to the contents in the LICENSE file.
 
 # Localization patches
 
-diff --git a/devtools/client/sourceeditor/codemirror/addon/search/search.js b/devtools/client/sourceeditor/codemirror/addon/search/search.js
---- a/devtools/client/sourceeditor/codemirror/addon/search/search.js
-+++ b/devtools/client/sourceeditor/codemirror/addon/search/search.js
+diff --git a/devtools/client/shared/sourceeditor/codemirror/addon/search/search.js b/devtools/client/shared/sourceeditor/codemirror/addon/search/search.js
+--- a/devtools/client/shared/sourceeditor/codemirror/addon/search/search.js
++++ b/devtools/client/shared/sourceeditor/codemirror/addon/search/search.js
 @@ -93,32 +93,47 @@
      } else {
        query = parseString(query)
      }
      if (typeof query == "string" ? query == "" : query.test(""))
        query = /x^/;
      return query;
    }
@@ -226,19 +226,19 @@ diff --git a/devtools/client/sourceedito
              advance();
            };
 
 # Middle-click pasting patch
 
 See Bug 1482875. Not needed anymore when https://github.com/codemirror/CodeMirror/pull/5751 lands.
 
 ```diff
-diff --git a/devtools/client/sourceeditor/codemirror/lib/codemirror.js b/devtools/client/sourceeditor/codemirror/lib/codemirror.js
---- a/devtools/client/sourceeditor/codemirror/lib/codemirror.js
-+++ b/devtools/client/sourceeditor/codemirror/lib/codemirror.js
+diff --git a/devtools/client/shared/sourceeditor/codemirror/lib/codemirror.js b/devtools/client/shared/sourceeditor/codemirror/lib/codemirror.js
+--- a/devtools/client/shared/sourceeditor/codemirror/lib/codemirror.js
++++ b/devtools/client/shared/sourceeditor/codemirror/lib/codemirror.js
 @@ -9256,8 +9256,9 @@ TextareaInput.prototype.init = function
 
    on(display.scroller, "paste", function (e) {
      if (eventInWidget(display, e) || signalDOMEvent(cm, e)) { return }
 -    cm.state.pasteIncoming = true
 -    input.focus()
 +    const event = new Event("paste");
 +    event.clipboardData = e.clipboardData;
@@ -246,12 +246,12 @@ diff --git a/devtools/client/sourceedito
    })
 
    // Prevent normal selection in the editor (we handle our own)
 ```
 
 # Footnotes
 
 [1] http://codemirror.net
-[2] devtools/client/sourceeditor/codemirror
-[3] devtools/client/sourceeditor/test/browser_codemirror.js
+[2] devtools/client/shared/sourceeditor/codemirror
+[3] devtools/client/shared/sourceeditor/test/browser_codemirror.js
 [4] devtools/client/jar.mn
-[5] devtools/client/sourceeditor/editor.js
+[5] devtools/client/shared/sourceeditor/editor.js
rename from devtools/client/sourceeditor/autocomplete.js
rename to devtools/client/shared/sourceeditor/autocomplete.js
--- a/devtools/client/sourceeditor/autocomplete.js
+++ b/devtools/client/shared/sourceeditor/autocomplete.js
@@ -3,21 +3,21 @@
  * 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 AutocompletePopup = require("devtools/client/shared/autocomplete-popup");
 
 loader.lazyRequireGetter(this, "KeyCodes", "devtools/client/shared/keycodes", true);
-loader.lazyRequireGetter(this, "CSSCompleter", "devtools/client/sourceeditor/css-autocompleter");
+loader.lazyRequireGetter(this, "CSSCompleter", "devtools/client/shared/sourceeditor/css-autocompleter");
 
 const CM_TERN_SCRIPTS = [
-  "chrome://devtools/content/sourceeditor/codemirror/addon/tern/tern.js",
-  "chrome://devtools/content/sourceeditor/codemirror/addon/hint/show-hint.js",
+  "chrome://devtools/content/shared/sourceeditor/codemirror/addon/tern/tern.js",
+  "chrome://devtools/content/shared/sourceeditor/codemirror/addon/hint/show-hint.js",
 ];
 
 const autocompleteMap = new WeakMap();
 
 /**
  * Prepares an editor instance for autocompletion.
  */
 function initializeAutoCompletion(ctx, options = {}) {
rename from devtools/client/sourceeditor/codemirror/LICENSE
rename to devtools/client/shared/sourceeditor/codemirror/LICENSE
rename from devtools/client/sourceeditor/codemirror/addon/comment/comment.js
rename to devtools/client/shared/sourceeditor/codemirror/addon/comment/comment.js
rename from devtools/client/sourceeditor/codemirror/addon/comment/continuecomment.js
rename to devtools/client/shared/sourceeditor/codemirror/addon/comment/continuecomment.js
rename from devtools/client/sourceeditor/codemirror/addon/dialog/dialog.css
rename to devtools/client/shared/sourceeditor/codemirror/addon/dialog/dialog.css
rename from devtools/client/sourceeditor/codemirror/addon/dialog/dialog.js
rename to devtools/client/shared/sourceeditor/codemirror/addon/dialog/dialog.js
rename from devtools/client/sourceeditor/codemirror/addon/edit/closebrackets.js
rename to devtools/client/shared/sourceeditor/codemirror/addon/edit/closebrackets.js
rename from devtools/client/sourceeditor/codemirror/addon/edit/closetag.js
rename to devtools/client/shared/sourceeditor/codemirror/addon/edit/closetag.js
rename from devtools/client/sourceeditor/codemirror/addon/edit/continuelist.js
rename to devtools/client/shared/sourceeditor/codemirror/addon/edit/continuelist.js
rename from devtools/client/sourceeditor/codemirror/addon/edit/matchbrackets.js
rename to devtools/client/shared/sourceeditor/codemirror/addon/edit/matchbrackets.js
rename from devtools/client/sourceeditor/codemirror/addon/edit/matchtags.js
rename to devtools/client/shared/sourceeditor/codemirror/addon/edit/matchtags.js
rename from devtools/client/sourceeditor/codemirror/addon/edit/trailingspace.js
rename to devtools/client/shared/sourceeditor/codemirror/addon/edit/trailingspace.js
rename from devtools/client/sourceeditor/codemirror/addon/fold/brace-fold.js
rename to devtools/client/shared/sourceeditor/codemirror/addon/fold/brace-fold.js
rename from devtools/client/sourceeditor/codemirror/addon/fold/comment-fold.js
rename to devtools/client/shared/sourceeditor/codemirror/addon/fold/comment-fold.js
rename from devtools/client/sourceeditor/codemirror/addon/fold/foldcode.js
rename to devtools/client/shared/sourceeditor/codemirror/addon/fold/foldcode.js
rename from devtools/client/sourceeditor/codemirror/addon/fold/foldgutter.css
rename to devtools/client/shared/sourceeditor/codemirror/addon/fold/foldgutter.css
rename from devtools/client/sourceeditor/codemirror/addon/fold/foldgutter.js
rename to devtools/client/shared/sourceeditor/codemirror/addon/fold/foldgutter.js
rename from devtools/client/sourceeditor/codemirror/addon/fold/indent-fold.js
rename to devtools/client/shared/sourceeditor/codemirror/addon/fold/indent-fold.js
rename from devtools/client/sourceeditor/codemirror/addon/fold/markdown-fold.js
rename to devtools/client/shared/sourceeditor/codemirror/addon/fold/markdown-fold.js
rename from devtools/client/sourceeditor/codemirror/addon/fold/xml-fold.js
rename to devtools/client/shared/sourceeditor/codemirror/addon/fold/xml-fold.js
rename from devtools/client/sourceeditor/codemirror/addon/hint/show-hint.js
rename to devtools/client/shared/sourceeditor/codemirror/addon/hint/show-hint.js
rename from devtools/client/sourceeditor/codemirror/addon/runmode/runmode.js
rename to devtools/client/shared/sourceeditor/codemirror/addon/runmode/runmode.js
rename from devtools/client/sourceeditor/codemirror/addon/search/match-highlighter.js
rename to devtools/client/shared/sourceeditor/codemirror/addon/search/match-highlighter.js
rename from devtools/client/sourceeditor/codemirror/addon/search/search.js
rename to devtools/client/shared/sourceeditor/codemirror/addon/search/search.js
rename from devtools/client/sourceeditor/codemirror/addon/search/searchcursor.js
rename to devtools/client/shared/sourceeditor/codemirror/addon/search/searchcursor.js
rename from devtools/client/sourceeditor/codemirror/addon/selection/active-line.js
rename to devtools/client/shared/sourceeditor/codemirror/addon/selection/active-line.js
rename from devtools/client/sourceeditor/codemirror/addon/selection/mark-selection.js
rename to devtools/client/shared/sourceeditor/codemirror/addon/selection/mark-selection.js
rename from devtools/client/sourceeditor/codemirror/addon/tern/tern.css
rename to devtools/client/shared/sourceeditor/codemirror/addon/tern/tern.css
rename from devtools/client/sourceeditor/codemirror/addon/tern/tern.js
rename to devtools/client/shared/sourceeditor/codemirror/addon/tern/tern.js
rename from devtools/client/sourceeditor/codemirror/cmiframe.html
rename to devtools/client/shared/sourceeditor/codemirror/cmiframe.html
--- a/devtools/client/sourceeditor/codemirror/cmiframe.html
+++ b/devtools/client/shared/sourceeditor/codemirror/cmiframe.html
@@ -1,14 +1,14 @@
 <!DOCTYPE html>
 <html dir='ltr'>
 <head>
   <style id="cmBaseStyle">
     html, body { height: 100%; }
     body { margin: 0; overflow: hidden; }
     .CodeMirror { width: 100% !important; line-height: 1.25 !important; }
   </style>
-  <link rel='stylesheet' href="chrome://devtools/content/sourceeditor/codemirror/lib/codemirror.css">
-  <link rel='stylesheet' href="chrome://devtools/content/sourceeditor/codemirror/addon/dialog/dialog.css">
-  <link rel='stylesheet' href="chrome://devtools/content/sourceeditor/codemirror/mozilla.css">
+  <link rel='stylesheet' href="chrome://devtools/content/shared/sourceeditor/codemirror/lib/codemirror.css">
+  <link rel='stylesheet' href="chrome://devtools/content/shared/sourceeditor/codemirror/addon/dialog/dialog.css">
+  <link rel='stylesheet' href="chrome://devtools/content/shared/sourceeditor/codemirror/mozilla.css">
 </head>
 <body class='theme-body devtools-monospace'></body>
 </html>
rename from devtools/client/sourceeditor/codemirror/codemirror.bundle.js
rename to devtools/client/shared/sourceeditor/codemirror/codemirror.bundle.js
rename from devtools/client/sourceeditor/codemirror/keymap/emacs.js
rename to devtools/client/shared/sourceeditor/codemirror/keymap/emacs.js
rename from devtools/client/sourceeditor/codemirror/keymap/sublime.js
rename to devtools/client/shared/sourceeditor/codemirror/keymap/sublime.js
rename from devtools/client/sourceeditor/codemirror/keymap/vim.js
rename to devtools/client/shared/sourceeditor/codemirror/keymap/vim.js
rename from devtools/client/sourceeditor/codemirror/lib/codemirror.css
rename to devtools/client/shared/sourceeditor/codemirror/lib/codemirror.css
rename from devtools/client/sourceeditor/codemirror/lib/codemirror.js
rename to devtools/client/shared/sourceeditor/codemirror/lib/codemirror.js
rename from devtools/client/sourceeditor/codemirror/mode/clike/clike.js
rename to devtools/client/shared/sourceeditor/codemirror/mode/clike/clike.js
rename from devtools/client/sourceeditor/codemirror/mode/clojure/clojure.js
rename to devtools/client/shared/sourceeditor/codemirror/mode/clojure/clojure.js
rename from devtools/client/sourceeditor/codemirror/mode/coffeescript/coffeescript.js
rename to devtools/client/shared/sourceeditor/codemirror/mode/coffeescript/coffeescript.js
rename from devtools/client/sourceeditor/codemirror/mode/css/css.js
rename to devtools/client/shared/sourceeditor/codemirror/mode/css/css.js
rename from devtools/client/sourceeditor/codemirror/mode/elm/elm.js
rename to devtools/client/shared/sourceeditor/codemirror/mode/elm/elm.js
rename from devtools/client/sourceeditor/codemirror/mode/haxe/haxe.js
rename to devtools/client/shared/sourceeditor/codemirror/mode/haxe/haxe.js
rename from devtools/client/sourceeditor/codemirror/mode/htmlmixed/htmlmixed.js
rename to devtools/client/shared/sourceeditor/codemirror/mode/htmlmixed/htmlmixed.js
rename from devtools/client/sourceeditor/codemirror/mode/javascript/javascript.js
rename to devtools/client/shared/sourceeditor/codemirror/mode/javascript/javascript.js
rename from devtools/client/sourceeditor/codemirror/mode/jsx/jsx.js
rename to devtools/client/shared/sourceeditor/codemirror/mode/jsx/jsx.js
rename from devtools/client/sourceeditor/codemirror/mode/wasm/wasm.js
rename to devtools/client/shared/sourceeditor/codemirror/mode/wasm/wasm.js
rename from devtools/client/sourceeditor/codemirror/mode/xml/xml.js
rename to devtools/client/shared/sourceeditor/codemirror/mode/xml/xml.js
rename from devtools/client/sourceeditor/codemirror/mozilla.css
rename to devtools/client/shared/sourceeditor/codemirror/mozilla.css
rename from devtools/client/sourceeditor/css-autocompleter.js
rename to devtools/client/shared/sourceeditor/css-autocompleter.js
rename from devtools/client/sourceeditor/debugger.js
rename to devtools/client/shared/sourceeditor/debugger.js
rename from devtools/client/sourceeditor/editor-commands-controller.js
rename to devtools/client/shared/sourceeditor/editor-commands-controller.js
rename from devtools/client/sourceeditor/editor.js
rename to devtools/client/shared/sourceeditor/editor.js
--- a/devtools/client/sourceeditor/editor.js
+++ b/devtools/client/shared/sourceeditor/editor.js
@@ -47,20 +47,20 @@ const {
 } = require("./wasm");
 
 const { OS } = Services.appinfo;
 
 // CM_SCRIPTS and CM_IFRAME represent the HTML and JavaScript that is
 // injected into an iframe in order to initialize a CodeMirror instance.
 
 const CM_SCRIPTS = [
-  "chrome://devtools/content/sourceeditor/codemirror/codemirror.bundle.js",
+  "chrome://devtools/content/shared/sourceeditor/codemirror/codemirror.bundle.js",
 ];
 
-const CM_IFRAME = "chrome://devtools/content/sourceeditor/codemirror/cmiframe.html";
+const CM_IFRAME = "chrome://devtools/content/shared/sourceeditor/codemirror/cmiframe.html";
 
 const CM_MAPPING = [
   "clearHistory",
   "defaultCharWidth",
   "extendSelection",
   "focus",
   "getCursor",
   "getLine",
@@ -513,17 +513,17 @@ Editor.prototype = {
   },
 
   /**
    * The source editor can expose several commands linked from system and context menus.
    * Kept for backward compatibility with scratchpad and styleeditor.
    */
   insertCommandsController: function() {
     const { insertCommandsController } =
-      require("devtools/client/sourceeditor/editor-commands-controller");
+      require("devtools/client/shared/sourceeditor/editor-commands-controller");
     insertCommandsController(this);
   },
 
   /**
    * Returns text from the text area. If line argument is provided
    * the method returns only that line.
    */
   getText: function(line) {
rename from devtools/client/sourceeditor/moz.build
rename to devtools/client/shared/sourceeditor/moz.build
rename from devtools/client/sourceeditor/package.json
rename to devtools/client/shared/sourceeditor/package.json
rename from devtools/client/sourceeditor/tern/README
rename to devtools/client/shared/sourceeditor/tern/README
--- a/devtools/client/sourceeditor/tern/README
+++ b/devtools/client/shared/sourceeditor/tern/README
@@ -2,12 +2,12 @@ This is the Tern code-analysis engine pa
 
 Tern is a stand-alone code-analysis engine for JavaScript. It is intended to be used with a code editor plugin to enhance the editor's support for intelligent JavaScript editing
 
 
 # Upgrade
 
 Currently used version is 0.6.2.  To upgrade, download the latest release from http://ternjs.net/, and copy the files from lib/ into this directory.
 
-You may also need to update the CodeMirror plugin found in devtools/client/sourceeditor/codemirror/addon/tern, but it will most likely work without updating.
+You may also need to update the CodeMirror plugin found in devtools/client/shared/sourceeditor/codemirror/addon/tern, but it will most likely work without updating.
 
 Replace instances of `require("acorn")` with `require("acorn/acorn")`
 Replace instances of `acorn/dist/` with `acorn/`
\ No newline at end of file
rename from devtools/client/sourceeditor/tern/browser.js
rename to devtools/client/shared/sourceeditor/tern/browser.js
rename from devtools/client/sourceeditor/tern/comment.js
rename to devtools/client/shared/sourceeditor/tern/comment.js
rename from devtools/client/sourceeditor/tern/condense.js
rename to devtools/client/shared/sourceeditor/tern/condense.js
rename from devtools/client/sourceeditor/tern/def.js
rename to devtools/client/shared/sourceeditor/tern/def.js
rename from devtools/client/sourceeditor/tern/ecma5.js
rename to devtools/client/shared/sourceeditor/tern/ecma5.js
rename from devtools/client/sourceeditor/tern/infer.js
rename to devtools/client/shared/sourceeditor/tern/infer.js
rename from devtools/client/sourceeditor/tern/moz.build
rename to devtools/client/shared/sourceeditor/tern/moz.build
rename from devtools/client/sourceeditor/tern/signal.js
rename to devtools/client/shared/sourceeditor/tern/signal.js
rename from devtools/client/sourceeditor/tern/tern.js
rename to devtools/client/shared/sourceeditor/tern/tern.js
rename from devtools/client/sourceeditor/tern/tests/unit/head_tern.js
rename to devtools/client/shared/sourceeditor/tern/tests/unit/head_tern.js
rename from devtools/client/sourceeditor/tern/tests/unit/test_autocompletion.js
rename to devtools/client/shared/sourceeditor/tern/tests/unit/test_autocompletion.js
--- a/devtools/client/sourceeditor/tern/tests/unit/test_autocompletion.js
+++ b/devtools/client/shared/sourceeditor/tern/tests/unit/test_autocompletion.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Test that tern autocompletions work.
  */
 
-const tern = require("devtools/client/sourceeditor/tern/tern");
-const ecma5 = require("devtools/client/sourceeditor/tern/ecma5");
+const tern = require("devtools/client/shared/sourceeditor/tern/tern");
+const ecma5 = require("devtools/client/shared/sourceeditor/tern/ecma5");
 
 function run_test() {
   do_test_pending();
 
   const server = new tern.Server({ defs: [ecma5] });
   const code = "[].";
   const query = { type: "completions", file: "test", end: code.length };
   const files = [{ type: "full", name: "test", text: code }];
rename from devtools/client/sourceeditor/tern/tests/unit/test_import_tern.js
rename to devtools/client/shared/sourceeditor/tern/tests/unit/test_import_tern.js
--- a/devtools/client/sourceeditor/tern/tests/unit/test_import_tern.js
+++ b/devtools/client/shared/sourceeditor/tern/tests/unit/test_import_tern.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Test that we can require tern.
  */
 
 function run_test() {
-  const tern = require("devtools/client/sourceeditor/tern/tern");
-  const ecma5 = require("devtools/client/sourceeditor/tern/ecma5");
-  const browser = require("devtools/client/sourceeditor/tern/browser");
+  const tern = require("devtools/client/shared/sourceeditor/tern/tern");
+  const ecma5 = require("devtools/client/shared/sourceeditor/tern/ecma5");
+  const browser = require("devtools/client/shared/sourceeditor/tern/browser");
   Assert.ok(!!tern);
   Assert.ok(!!ecma5);
   Assert.ok(!!browser);
   Assert.equal(typeof tern.Server, "function");
 }
rename from devtools/client/sourceeditor/tern/tests/unit/xpcshell.ini
rename to devtools/client/shared/sourceeditor/tern/tests/unit/xpcshell.ini
rename from devtools/client/sourceeditor/test/.eslintrc.js
rename to devtools/client/shared/sourceeditor/test/.eslintrc.js
--- a/devtools/client/sourceeditor/test/.eslintrc.js
+++ b/devtools/client/shared/sourceeditor/test/.eslintrc.js
@@ -1,10 +1,10 @@
 "use strict";
 
 module.exports = {
   // Extend from the shared list of defined globals for mochitests.
-  "extends": "../../../.eslintrc.mochitests.js",
+  "extends": "../../../../.eslintrc.mochitests.js",
   "globals": {
     "runCodeMirrorTest": true,
     "gBrowser": true
   }
 };
rename from devtools/client/sourceeditor/test/browser.ini
rename to devtools/client/shared/sourceeditor/test/browser.ini
rename from devtools/client/sourceeditor/test/browser_codemirror.js
rename to devtools/client/shared/sourceeditor/test/browser_codemirror.js
--- a/devtools/client/sourceeditor/test/browser_codemirror.js
+++ b/devtools/client/shared/sourceeditor/test/browser_codemirror.js
@@ -1,24 +1,24 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-const URI = "chrome://mochitests/content/browser/devtools/client/" +
-            "sourceeditor/test/codemirror/codemirror.html";
+const URI = "chrome://mochitests/content/browser/devtools/client/shared/sourceeditor/" +
+            "test/codemirror/codemirror.html";
 loadHelperScript("helper_codemirror_runner.js");
 
 function test() {
   requestLongerTimeout(3);
   waitForExplicitFinish();
 
   /*
-   * In devtools/client/sourceeditor/test/codemirror/search_test.js there is a test
+   * In devtools/client/shared/sourceeditor/test/codemirror/search_test.js there is a test
    * multilineInsensitiveSlow which assumes an operation takes less than 100ms.
    * With a precision of 100ms, if we get unlikely and begin execution towards the
    * end of one spot (e.g. at 95 ms) we will clamp down, take (e.g.) 10ms to execute
    * and it will appear to take 100ms.
    *
    * To avoid this, we hardcode to 2ms of precision.
    *
    * In theory we don't need to set the pref for all of CodeMirror, in practice
rename from devtools/client/sourceeditor/test/browser_css_autocompletion.js
rename to devtools/client/shared/sourceeditor/test/browser_css_autocompletion.js
--- a/devtools/client/sourceeditor/test/browser_css_autocompletion.js
+++ b/devtools/client/shared/sourceeditor/test/browser_css_autocompletion.js
@@ -1,20 +1,20 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-const CSSCompleter = require("devtools/client/sourceeditor/css-autocompleter");
+const CSSCompleter = require("devtools/client/shared/sourceeditor/css-autocompleter");
 
-const CSS_URI = "http://mochi.test:8888/browser/devtools/client/sourceeditor" +
+const CSS_URI = "http://mochi.test:8888/browser/devtools/client/shared/sourceeditor" +
                 "/test/css_statemachine_testcases.css";
 const TESTS_URI = "http://mochi.test:8888/browser/devtools/client" +
-                  "/sourceeditor/test/css_autocompletion_tests.json";
+                  "/shared/sourceeditor/test/css_autocompletion_tests.json";
 
 const source = read(CSS_URI);
 const {tests} = JSON.parse(read(TESTS_URI));
 
 const TEST_URI = "data:text/html;charset=UTF-8," + encodeURIComponent(
   ["<!DOCTYPE html>",
    "<html>",
    " <head>",
rename from devtools/client/sourceeditor/test/browser_css_getInfo.js
rename to devtools/client/shared/sourceeditor/test/browser_css_getInfo.js
--- a/devtools/client/sourceeditor/test/browser_css_getInfo.js
+++ b/devtools/client/shared/sourceeditor/test/browser_css_getInfo.js
@@ -1,16 +1,15 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-const CSSCompleter =
-      require("devtools/client/sourceeditor/css-autocompleter");
+const CSSCompleter = require("devtools/client/shared/sourceeditor/css-autocompleter");
 
 const source = [
   ".devtools-toolbar {",
   "  -moz-appearance: none;",
   "           padding:4px 3px;border-bottom-width: 1px;",
   "  border-bottom-style: solid;",
   "}",
   "",
rename from devtools/client/sourceeditor/test/browser_css_statemachine.js
rename to devtools/client/shared/sourceeditor/test/browser_css_statemachine.js
--- a/devtools/client/sourceeditor/test/browser_css_statemachine.js
+++ b/devtools/client/shared/sourceeditor/test/browser_css_statemachine.js
@@ -1,20 +1,20 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-const CSSCompleter = require("devtools/client/sourceeditor/css-autocompleter");
+const CSSCompleter = require("devtools/client/shared/sourceeditor/css-autocompleter");
 
-const CSS_URI = "http://mochi.test:8888/browser/devtools/client/sourceeditor" +
+const CSS_URI = "http://mochi.test:8888/browser/devtools/client/shared/sourceeditor" +
                 "/test/css_statemachine_testcases.css";
 const TESTS_URI = "http://mochi.test:8888/browser/devtools/client" +
-                  "/sourceeditor/test/css_statemachine_tests.json";
+                  "/shared/sourceeditor/test/css_statemachine_tests.json";
 
 const source = read(CSS_URI);
 const {tests} = JSON.parse(read(TESTS_URI));
 
 const TEST_URI = "data:text/html;charset=UTF-8," + encodeURIComponent(
   ["<!DOCTYPE html>",
    "<html>",
    " <head>",
rename from devtools/client/sourceeditor/test/browser_detectindent.js
rename to devtools/client/shared/sourceeditor/test/browser_detectindent.js
rename from devtools/client/sourceeditor/test/browser_editor_addons.js
rename to devtools/client/shared/sourceeditor/test/browser_editor_addons.js
rename from devtools/client/sourceeditor/test/browser_editor_alt_b_f.js
rename to devtools/client/shared/sourceeditor/test/browser_editor_alt_b_f.js
rename from devtools/client/sourceeditor/test/browser_editor_autocomplete_basic.js
rename to devtools/client/shared/sourceeditor/test/browser_editor_autocomplete_basic.js
rename from devtools/client/sourceeditor/test/browser_editor_autocomplete_events.js
rename to devtools/client/shared/sourceeditor/test/browser_editor_autocomplete_events.js
rename from devtools/client/sourceeditor/test/browser_editor_autocomplete_js.js
rename to devtools/client/shared/sourceeditor/test/browser_editor_autocomplete_js.js
rename from devtools/client/sourceeditor/test/browser_editor_basic.js
rename to devtools/client/shared/sourceeditor/test/browser_editor_basic.js
rename from devtools/client/sourceeditor/test/browser_editor_cursor.js
rename to devtools/client/shared/sourceeditor/test/browser_editor_cursor.js
rename from devtools/client/sourceeditor/test/browser_editor_find_again.js
rename to devtools/client/shared/sourceeditor/test/browser_editor_find_again.js
rename from devtools/client/sourceeditor/test/browser_editor_goto_line.js
rename to devtools/client/shared/sourceeditor/test/browser_editor_goto_line.js
rename from devtools/client/sourceeditor/test/browser_editor_history.js
rename to devtools/client/shared/sourceeditor/test/browser_editor_history.js
rename from devtools/client/sourceeditor/test/browser_editor_markers.js
rename to devtools/client/shared/sourceeditor/test/browser_editor_markers.js
rename from devtools/client/sourceeditor/test/browser_editor_movelines.js
rename to devtools/client/shared/sourceeditor/test/browser_editor_movelines.js
rename from devtools/client/sourceeditor/test/browser_editor_prefs.js
rename to devtools/client/shared/sourceeditor/test/browser_editor_prefs.js
rename from devtools/client/sourceeditor/test/browser_editor_script_injection.js
rename to devtools/client/shared/sourceeditor/test/browser_editor_script_injection.js
--- a/devtools/client/sourceeditor/test/browser_editor_script_injection.js
+++ b/devtools/client/shared/sourceeditor/test/browser_editor_script_injection.js
@@ -7,17 +7,17 @@
 "use strict";
 
 add_task(async function() {
   await runTest();
 });
 
 async function runTest() {
   const baseURL =
-    "chrome://mochitests/content/browser/devtools/client/sourceeditor/test";
+    "chrome://mochitests/content/browser/devtools/client/shared/sourceeditor/test";
   const injectedText = "Script successfully injected!";
 
   const {ed, win} = await setup(null, {
     mode: "ruby",
     externalScripts: [`${baseURL}/cm_script_injection_test.js`,
                       `${baseURL}/cm_mode_ruby.js`],
   });
 
rename from devtools/client/sourceeditor/test/browser_vimemacs.js
rename to devtools/client/shared/sourceeditor/test/browser_vimemacs.js
--- a/devtools/client/sourceeditor/test/browser_vimemacs.js
+++ b/devtools/client/shared/sourceeditor/test/browser_vimemacs.js
@@ -1,15 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 const URI = "chrome://mochitests/content/browser/devtools/client" +
-            "/sourceeditor/test/codemirror/vimemacs.html";
+            "/shared/sourceeditor/test/codemirror/vimemacs.html";
 loadHelperScript("helper_codemirror_runner.js");
 
 function test() {
   requestLongerTimeout(4);
   waitForExplicitFinish();
 
   addTab(URI).then(function(tab) {
     runCodeMirrorTest(tab.linkedBrowser);
rename from devtools/client/sourceeditor/test/cm_mode_ruby.js
rename to devtools/client/shared/sourceeditor/test/cm_mode_ruby.js
rename from devtools/client/sourceeditor/test/cm_script_injection_test.js
rename to devtools/client/shared/sourceeditor/test/cm_script_injection_test.js
rename from devtools/client/sourceeditor/test/codemirror/codemirror.html
rename to devtools/client/shared/sourceeditor/test/codemirror/codemirror.html
--- a/devtools/client/sourceeditor/test/codemirror/codemirror.html
+++ b/devtools/client/shared/sourceeditor/test/codemirror/codemirror.html
@@ -1,18 +1,18 @@
 <!doctype html>
 <html>
   <head>
     <meta charset="utf-8">
     <title>CodeMirror: Basic Tests</title>
-    <link rel="stylesheet" href="chrome://devtools/content/sourceeditor/codemirror/lib/codemirror.css">
+    <link rel="stylesheet" href="chrome://devtools/content/shared/sourceeditor/codemirror/lib/codemirror.css">
     <link rel="stylesheet" href="cm_mode_test.css">
     <!--<link rel="stylesheet" href="../doc/docs.css">-->
 
-    <script src="chrome://devtools/content/sourceeditor/codemirror/codemirror.bundle.js"></script>
+    <script src="chrome://devtools/content/shared/sourceeditor/codemirror/codemirror.bundle.js"></script>
 
     <style type="text/css">
       .ok {color: #090;}
       .fail {color: #e00;}
       .error {color: #c90;}
       .done {font-weight: bold;}
       #progress {
         background: #45d;
rename from devtools/client/sourceeditor/test/codemirror/comment_test.js
rename to devtools/client/shared/sourceeditor/test/codemirror/comment_test.js
rename from devtools/client/sourceeditor/test/codemirror/doc_test.js
rename to devtools/client/shared/sourceeditor/test/codemirror/doc_test.js
rename from devtools/client/sourceeditor/test/codemirror/driver.js
rename to devtools/client/shared/sourceeditor/test/codemirror/driver.js
rename from devtools/client/sourceeditor/test/codemirror/emacs_test.js
rename to devtools/client/shared/sourceeditor/test/codemirror/emacs_test.js
rename from devtools/client/sourceeditor/test/codemirror/mode/javascript/test.js
rename to devtools/client/shared/sourceeditor/test/codemirror/mode/javascript/test.js
rename from devtools/client/sourceeditor/test/codemirror/mode_test.css
rename to devtools/client/shared/sourceeditor/test/codemirror/mode_test.css
rename from devtools/client/sourceeditor/test/codemirror/mode_test.js
rename to devtools/client/shared/sourceeditor/test/codemirror/mode_test.js
rename from devtools/client/sourceeditor/test/codemirror/multi_test.js
rename to devtools/client/shared/sourceeditor/test/codemirror/multi_test.js
rename from devtools/client/sourceeditor/test/codemirror/search_test.js
rename to devtools/client/shared/sourceeditor/test/codemirror/search_test.js
rename from devtools/client/sourceeditor/test/codemirror/sublime_test.js
rename to devtools/client/shared/sourceeditor/test/codemirror/sublime_test.js
rename from devtools/client/sourceeditor/test/codemirror/test.js
rename to devtools/client/shared/sourceeditor/test/codemirror/test.js
rename from devtools/client/sourceeditor/test/codemirror/vim_test.js
rename to devtools/client/shared/sourceeditor/test/codemirror/vim_test.js
rename from devtools/client/sourceeditor/test/codemirror/vimemacs.html
rename to devtools/client/shared/sourceeditor/test/codemirror/vimemacs.html
--- a/devtools/client/sourceeditor/test/codemirror/vimemacs.html
+++ b/devtools/client/shared/sourceeditor/test/codemirror/vimemacs.html
@@ -1,18 +1,18 @@
 <!doctype html>
 <html>
   <head>
     <meta charset="utf-8">
     <title>CodeMirror: VIM/Emacs tests</title>
-    <link rel="stylesheet" href="chrome://devtools/content/sourceeditor/codemirror/lib/codemirror.css">
+    <link rel="stylesheet" href="chrome://devtools/content/shared/sourceeditor/codemirror/lib/codemirror.css">
     <link rel="stylesheet" href="cm_mode_test.css">
     <!--<link rel="stylesheet" href="../doc/docs.css">-->
 
-    <script src="chrome://devtools/content/sourceeditor/codemirror/codemirror.bundle.js"></script>
+    <script src="chrome://devtools/content/shared/sourceeditor/codemirror/codemirror.bundle.js"></script>
 
     <style type="text/css">
       .ok {color: #090;}
       .fail {color: #e00;}
       .error {color: #c90;}
       .done {font-weight: bold;}
       #progress {
         background: #45d;
rename from devtools/client/sourceeditor/test/css_autocompletion_tests.json
rename to devtools/client/shared/sourceeditor/test/css_autocompletion_tests.json
rename from devtools/client/sourceeditor/test/css_statemachine_testcases.css
rename to devtools/client/shared/sourceeditor/test/css_statemachine_testcases.css
rename from devtools/client/sourceeditor/test/css_statemachine_tests.json
rename to devtools/client/shared/sourceeditor/test/css_statemachine_tests.json
rename from devtools/client/sourceeditor/test/head.js
rename to devtools/client/shared/sourceeditor/test/head.js
--- a/devtools/client/sourceeditor/test/head.js
+++ b/devtools/client/shared/sourceeditor/test/head.js
@@ -1,23 +1,23 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
-/* import-globals-from ../../shared/test/shared-head.js */
+/* import-globals-from ../../../shared/test/shared-head.js */
 /* exported promiseWaitForFocus, setup, ch, teardown, loadHelperScript,
             limit, ch, read, codemirrorSetStatus */
 
 "use strict";
 
 // shared-head.js handles imports, constants, and utility functions
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js",
   this);
 
-const Editor = require("devtools/client/sourceeditor/editor");
+const Editor = require("devtools/client/shared/sourceeditor/editor");
 const {getClientCssProperties} = require("devtools/shared/fronts/css-properties");
 
 function promiseWaitForFocus() {
   return new Promise(resolve =>
     waitForFocus(resolve));
 }
 
 function setup(cb, additionalOpts = {}) {
rename from devtools/client/sourceeditor/test/head.xul
rename to devtools/client/shared/sourceeditor/test/head.xul
rename from devtools/client/sourceeditor/test/helper_codemirror_runner.js
rename to devtools/client/shared/sourceeditor/test/helper_codemirror_runner.js
rename from devtools/client/sourceeditor/wasm.js
rename to devtools/client/shared/sourceeditor/wasm.js
rename from devtools/client/sourceeditor/webpack.config.js
rename to devtools/client/shared/sourceeditor/webpack.config.js
--- a/devtools/client/shared/test/browser_browserloader_mocks.js
+++ b/devtools/client/shared/test/browser_browserloader_mocks.js
@@ -97,10 +97,11 @@ function testWithRegularDevtoolsModule()
   ok(!getMockedModule(DEVTOOLS_MODULE_URI), "Has no mock entry for the full URI");
   const m4 = browserRequire(DEVTOOLS_MODULE_PATH);
   is(m4.joinURI("http://a", "b"), "http://a/b", "Original module can be required again");
 }
 
 function test() {
   testWithChromeScheme();
   testWithRegularDevtoolsModule();
+  delete window.getBrowserLoaderForWindow;
   finish();
 }
--- a/devtools/client/shared/test/browser_require_raw.js
+++ b/devtools/client/shared/test/browser_require_raw.js
@@ -11,10 +11,11 @@ const { require: browserRequire } = Brow
   baseURI: "resource://devtools/client/shared/",
   window,
 });
 
 const variableFileContents = browserRequire("raw!devtools/client/themes/variables.css");
 
 function test() {
   ok(variableFileContents.length > 0, "raw browserRequire worked");
+  delete window.getBrowserLoaderForWindow;
   finish();
 }
--- a/devtools/client/shared/widgets/tooltip/EventTooltipHelper.js
+++ b/devtools/client/shared/widgets/tooltip/EventTooltipHelper.js
@@ -4,17 +4,17 @@
  * 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 {LocalizationHelper} = require("devtools/shared/l10n");
 const L10N = new LocalizationHelper("devtools/client/locales/inspector.properties");
 
-const Editor = require("devtools/client/sourceeditor/editor");
+const Editor = require("devtools/client/shared/sourceeditor/editor");
 const beautify = require("devtools/shared/jsbeautify/beautify");
 
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
 const CONTAINER_WIDTH = 500;
 
 /**
  * Set the content of a provided HTMLTooltip instance to display a list of event
  * listeners, with their event type, capturing argument and a link to the code
--- a/devtools/client/styleeditor/StyleSheetEditor.jsm
+++ b/devtools/client/styleeditor/StyleSheetEditor.jsm
@@ -3,17 +3,17 @@
  * 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";
 
 this.EXPORTED_SYMBOLS = ["StyleSheetEditor"];
 
 const {require} = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
-const Editor = require("devtools/client/sourceeditor/editor");
+const Editor = require("devtools/client/shared/sourceeditor/editor");
 const promise = require("promise");
 const {shortSource, prettifyCSS} = require("devtools/shared/inspector/css-logic");
 const {throttle} = require("devtools/shared/throttle");
 const Services = require("Services");
 const EventEmitter = require("devtools/shared/event-emitter");
 const {FileUtils} = require("resource://gre/modules/FileUtils.jsm");
 const {NetUtil} = require("resource://gre/modules/NetUtil.jsm");
 const {OS} = ChromeUtils.import("resource://gre/modules/osfile.jsm");
--- a/devtools/client/themes/storage.css
+++ b/devtools/client/themes/storage.css
@@ -9,19 +9,50 @@
   max-width: 500px;
   overflow: auto;
 }
 
 #storage-tree {
   background: var(--theme-sidebar-background);
 }
 
-/* Columns with date should have a min width so that date is visible */
+/* The minimum column width needs to be determined by the header width */
+.table-widget-cell {
+  min-width: unset;
+}
+
+/* Set minimum column widths */
+#name {
+  min-width: 65px;
+}
+
+#host {
+  min-width: 80px;
+}
+
+#path {
+  min-width: 60px;
+}
+
 #expires, #lastAccessed, #creationTime {
-  min-width: 150px;
+  min-width: 115px;
+}
+
+#value {
+  min-width: 95px;
+}
+
+#hostOnly,
+#isHttpOnly,
+#sameSite {
+  min-width: 50px;
+}
+
+#isSecure {
+  min-width: 40px;
 }
 
 /* Variables View Sidebar */
 
 #storage-sidebar {
   max-width: 500px;
   min-width: 250px;
 }
--- a/devtools/client/webconsole/components/JSTerm.js
+++ b/devtools/client/webconsole/components/JSTerm.js
@@ -11,17 +11,17 @@ loader.lazyServiceGetter(this, "clipboar
                          "@mozilla.org/widget/clipboardhelper;1",
                          "nsIClipboardHelper");
 loader.lazyRequireGetter(this, "Debugger", "Debugger");
 loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
 loader.lazyRequireGetter(this, "AutocompletePopup", "devtools/client/shared/autocomplete-popup");
 loader.lazyRequireGetter(this, "PropTypes", "devtools/client/shared/vendor/react-prop-types");
 loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
 loader.lazyRequireGetter(this, "KeyCodes", "devtools/client/shared/keycodes", true);
-loader.lazyRequireGetter(this, "Editor", "devtools/client/sourceeditor/editor");
+loader.lazyRequireGetter(this, "Editor", "devtools/client/shared/sourceeditor/editor");
 loader.lazyRequireGetter(this, "Telemetry", "devtools/client/shared/telemetry");
 loader.lazyRequireGetter(this, "saveScreenshot", "devtools/shared/screenshot/save");
 loader.lazyRequireGetter(this, "focusableSelector", "devtools/client/shared/focus", true);
 
 const l10n = require("devtools/client/webconsole/webconsole-l10n");
 
 const HELP_URL = "https://developer.mozilla.org/docs/Tools/Web_Console/Helpers";
 
--- a/devtools/client/webconsole/main.js
+++ b/devtools/client/webconsole/main.js
@@ -7,19 +7,14 @@
 "use strict";
 
 const { BrowserLoader } = ChromeUtils.import("resource://devtools/client/shared/browser-loader.js");
 
 this.WebConsoleWrapper = function(parentNode, hud, toolbox, owner, document) {
   // Initialize module loader and load all modules of the new inline
   // preview feature. The entire code-base doesn't need any extra
   // privileges and runs entirely in content scope.
-  const browserLoader = BrowserLoader({
+  const WebConsoleWrapper = BrowserLoader({
     baseURI: "resource://devtools/client/webconsole/",
     window,
-  });
-
-  // Expose the browserLoader instance on the webconsole hud for metrics tests.
-  hud.browserLoader = browserLoader;
-
-  const WebConsoleWrapper = browserLoader.require("./webconsole-wrapper");
+  }).require("./webconsole-wrapper");
   return new WebConsoleWrapper(parentNode, hud, toolbox, owner, document);
 };
--- a/devtools/client/webide/content/webide.js
+++ b/devtools/client/webide/content/webide.js
@@ -15,16 +15,20 @@ const {AppProjects} = require("devtools/
 const {Connection} = require("devtools/shared/client/connection-manager");
 const {AppManager} = require("devtools/client/webide/modules/app-manager");
 const EventEmitter = require("devtools/shared/event-emitter");
 const promise = require("promise");
 const {getJSON} = require("devtools/client/shared/getjson");
 const Telemetry = require("devtools/client/shared/telemetry");
 const {RuntimeScanners} = require("devtools/client/webide/modules/runtimes");
 const {openContentLink} = require("devtools/client/shared/link");
+const {
+  checkVersionCompatibility,
+  COMPATIBILITY_STATUS,
+} = require("devtools/client/shared/remote-debugging/version-checker");
 
 loader.lazyRequireGetter(this, "adbAddon", "devtools/shared/adb/adb-addon", true);
 
 const Strings =
   Services.strings.createBundle("chrome://devtools/locale/webide.properties");
 
 const TELEMETRY_WEBIDE_IMPORT_PROJECT_COUNT = "DEVTOOLS_WEBIDE_IMPORT_PROJECT_COUNT";
 
@@ -732,22 +736,23 @@ var UI = {
     this.resetFocus();
     const deck = document.querySelector("#deck");
     deck.selectedPanel = null;
   },
 
   async checkRuntimeVersion() {
     if (AppManager.connected) {
       const { client } = AppManager.connection;
-      const report = await client.checkRuntimeVersion();
-      if (report.incompatible == "too-recent") {
+      const report = await checkVersionCompatibility(client);
+
+      if (report.status == COMPATIBILITY_STATUS.TOO_RECENT) {
         this.reportError("error_runtimeVersionTooRecent", report.runtimeID,
           report.localID);
       }
-      if (report.incompatible == "too-old") {
+      if (report.status == COMPATIBILITY_STATUS.TOO_OLD) {
         this.reportError("error_runtimeVersionTooOld", report.runtimeVersion,
           report.minVersion);
       }
     }
   },
 
   /** ******** TOOLBOX **********/
 
--- a/devtools/client/webreplay/mochitest/browser_dbg_rr_stepping-04.js
+++ b/devtools/client/webreplay/mochitest/browser_dbg_rr_stepping-04.js
@@ -23,22 +23,20 @@ add_task(async function() {
   await reverseStepOverToLine(client, 20);
   await reverseStepOverToLine(client, 12);
 
   // After reverse-stepping out of the topmost frame we should rewind to the
   // last breakpoint hit.
   await reverseStepOverToLine(client, 21);
   await checkEvaluateInTopFrame(client, "number", 9);
 
-  await stepOverToLine(client, 21);
   await stepOverToLine(client, 22);
   await stepOverToLine(client, 23);
   await stepOverToLine(client, 13);
   await stepOverToLine(client, 17);
-  await stepOverToLine(client, 17);
   await stepOverToLine(client, 18);
 
   // After forward-stepping out of the topmost frame we should run forward to
   // the next breakpoint hit.
   await stepOverToLine(client, 21);
   await checkEvaluateInTopFrame(client, "number", 10);
 
   await client.removeBreakpoint(bp);
--- a/devtools/server/actors/highlighters/flexbox.js
+++ b/devtools/server/actors/highlighters/flexbox.js
@@ -259,18 +259,18 @@ class FlexboxHighlighter extends AutoRef
     let flexboxPatternMap = null;
 
     if (gCachedFlexboxPattern.has(devicePixelRatio)) {
       flexboxPatternMap = gCachedFlexboxPattern.get(devicePixelRatio);
     } else {
       flexboxPatternMap = new Map();
     }
 
-    if (gCachedFlexboxPattern.has(JUSTIFY_CONTENT)) {
-      return gCachedFlexboxPattern.get(JUSTIFY_CONTENT);
+    if (flexboxPatternMap.has(JUSTIFY_CONTENT)) {
+      return flexboxPatternMap.get(JUSTIFY_CONTENT);
     }
 
     // Create the inversed diagonal lines pattern
     // for the rendering the justify content gaps.
     const canvas = createNode(this.win, { nodeType: "canvas" });
     const zoom = getCurrentZoom(this.win);
     const width = canvas.width =
       FLEXBOX_JUSTIFY_CONTENT_PATTERN_WIDTH * devicePixelRatio * zoom;
--- a/devtools/server/actors/replay/debugger.js
+++ b/devtools/server/actors/replay/debugger.js
@@ -732,16 +732,23 @@ ReplayDebuggerScript.prototype = {
     return this._dbg._sendRequest({ type, id: this._data.id, value });
   },
 
   getLineOffsets(line) { return this._forward("getLineOffsets", line); },
   getOffsetLocation(pc) { return this._forward("getOffsetLocation", pc); },
   getSuccessorOffsets(pc) { return this._forward("getSuccessorOffsets", pc); },
   getPredecessorOffsets(pc) { return this._forward("getPredecessorOffsets", pc); },
   getAllColumnOffsets() { return this._forward("getAllColumnOffsets"); },
+  getOffsetMetadata(pc) { return this._forward("getOffsetMetadata", pc); },
+  getPossibleBreakpoints(query) {
+    return this._forward("getPossibleBreakpoints", query);
+  },
+  getPossibleBreakpointOffsets(query) {
+    return this._forward("getPossibleBreakpointOffsets", query);
+  },
 
   setBreakpoint(offset, handler) {
     this._dbg._setBreakpoint(() => { handler.hit(this._dbg.getNewestFrame()); },
                              { kind: "Break", script: this._data.id, offset },
                              handler);
   },
 
   clearBreakpoint(handler) {
--- a/devtools/server/actors/replay/replay.js
+++ b/devtools/server/actors/replay/replay.js
@@ -778,16 +778,19 @@ const gRequestHandlers = {
     };
   },
 
   getLineOffsets: forwardToScript("getLineOffsets"),
   getOffsetLocation: forwardToScript("getOffsetLocation"),
   getSuccessorOffsets: forwardToScript("getSuccessorOffsets"),
   getPredecessorOffsets: forwardToScript("getPredecessorOffsets"),
   getAllColumnOffsets: forwardToScript("getAllColumnOffsets"),
+  getOffsetMetadata: forwardToScript("getOffsetMetadata"),
+  getPossibleBreakpoints: forwardToScript("getPossibleBreakpoints"),
+  getPossibleBreakpointOffsets: forwardToScript("getPossibleBreakpointOffsets"),
 
   frameEvaluate(request) {
     if (!RecordReplayControl.maybeDivergeFromRecording()) {
       return { throw: "Recording divergence in frameEvaluate" };
     }
 
     const frame = scriptFrameForIndex(request.index);
     const rv = frame.eval(request.text, request.options);
--- a/devtools/server/actors/source.js
+++ b/devtools/server/actors/source.js
@@ -263,17 +263,17 @@ const SourceActor = ActorClassWithSpec(s
 
   /**
    * Get all executable lines from the current source
    * @return Array - Executable lines of the current script
    */
   getExecutableLines: async function() {
     const offsetsLines = new Set();
     for (const s of this._findDebuggeeScripts()) {
-      for (const offset of s.getAllColumnOffsets()) {
+      for (const offset of s.getPossibleBreakpoints()) {
         offsetsLines.add(offset.lineNumber);
       }
     }
 
     const lines = [...offsetsLines];
     lines.sort((a, b) => {
       return a - b;
     });
@@ -302,17 +302,17 @@ const SourceActor = ActorClassWithSpec(s
       }
       scripts = Array.from(found);
     } else {
       scripts = this._findDebuggeeScripts();
     }
 
     const positions = [];
     for (const script of scripts) {
-      const offsets = script.getAllColumnOffsets();
+      const offsets = script.getPossibleBreakpoints();
       for (const { lineNumber, columnNumber } of offsets) {
         if (
           lineNumber < startLine ||
           (lineNumber === startLine && columnNumber < startColumn) ||
           lineNumber > endLine ||
           (lineNumber === endLine && columnNumber > endColumn)
         ) {
           continue;
@@ -325,23 +325,17 @@ const SourceActor = ActorClassWithSpec(s
       }
     }
 
     return positions
       // Sort the items by location.
       .sort((a, b) => {
         const lineDiff = a.line - b.line;
         return lineDiff === 0 ? a.column - b.column : lineDiff;
-      })
-      // Filter out duplicate locations since they are useless in this context.
-      .filter((item, i, arr) => (
-        i === 0 ||
-        item.line !== arr[i - 1].line ||
-        item.column !== arr[i - 1].column
-      ));
+      });
   },
 
   getBreakpointPositionsCompressed(query) {
     const items = this.getBreakpointPositions(query);
     const compressed = {};
     for (const { line, column } of items) {
       if (!compressed[line]) {
         compressed[line] = [];
@@ -444,32 +438,32 @@ const SourceActor = ActorClassWithSpec(s
     // Find all scripts that match the given source actor and line
     // number.
     let scripts = this._findDebuggeeScripts({ line });
     scripts = scripts.filter((script) => !actor.hasScript(script));
 
     // Find all entry points that correspond to the given location.
     const entryPoints = [];
     if (column === undefined) {
-      // This is a line breakpoint, so we are interested in all offsets
-      // that correspond to the given line number.
+      // This is a line breakpoint, so we add a breakpoint on the first
+      // breakpoint on the line.
       for (const script of scripts) {
-        const offsets = script.getLineOffsets(line);
+        const offsets = script.getPossibleBreakpointOffsets({ line });
         if (offsets.length > 0) {
-          entryPoints.push({ script, offsets });
+          entryPoints.push({ script, offsets: [offsets[0]] });
+          break;
         }
       }
     } else {
       // Compute columnToOffsetMaps for each script so that we can
       // find matching entrypoints for the column breakpoint.
       const columnToOffsetMaps = scripts.map(script =>
         [
           script,
-          script.getAllColumnOffsets()
-            .filter(({ lineNumber }) => lineNumber === line),
+          script.getPossibleBreakpoints({ line }),
         ]
       );
 
       // This is a column breakpoint, so we are interested in all column
       // offsets that correspond to the given line *and* column number.
       for (const [script, columnToOffsetMap] of columnToOffsetMaps) {
         for (const { columnNumber, offset } of columnToOffsetMap) {
           if (columnNumber >= column && columnNumber <= column + 1) {
--- a/devtools/server/actors/thread.js
+++ b/devtools/server/actors/thread.js
@@ -602,61 +602,47 @@ const ThreadActor = ActorClassWithSpec(t
 
     return result;
   },
 
   // Return whether reaching a script offset should be considered a distinct
   // "step" from another location.
   _intraFrameLocationIsStepTarget: function(startLocation, script, offset) {
     // Only allow stepping stops at entry points for the line.
-    if (!script.getOffsetLocation(offset).isEntryPoint) {
+    if (!script.getOffsetMetadata(offset).isBreakpoint) {
       return false;
     }
 
-    // Cases when we have executed enough within a frame to consider a "step"
-    // to have occured:
-    //
-    // 1. We change URLs (can happen without changing frames thanks to
-    //    source mapping).
-    // 2. The source has pause points and we change locations.
-    // 3. The source does not have pause points and we change lines.
-
     const generatedLocation = this.sources.getScriptOffsetLocation(script, offset);
 
-    // Case 1.
     if (startLocation.generatedUrl !== generatedLocation.generatedUrl) {
       return true;
     }
 
-    const pausePoints = generatedLocation.generatedSourceActor.pausePoints;
+    // TODO(logan): When we remove points points, this can be removed too as
+    // we assert that we're at a different frame offset from the last time
+    // we paused.
     const lineChanged = startLocation.generatedLine !== generatedLocation.generatedLine;
     const columnChanged =
       startLocation.generatedColumn !== generatedLocation.generatedColumn;
-
-    if (!pausePoints) {
-      // Case 3.
-      return lineChanged;
-    }
-
-    // Case 2.
     if (!lineChanged && !columnChanged) {
       return false;
     }
 
     // When pause points are specified for the source,
     // we should pause when we are at a stepOver pause point
-    const pausePoint = findPausePointForLocation(pausePoints, generatedLocation);
+    const pausePoints = generatedLocation.generatedSourceActor.pausePoints;
+    const pausePoint = pausePoints &&
+      findPausePointForLocation(pausePoints, generatedLocation);
 
     if (pausePoint) {
       return pausePoint.step;
     }
 
-    // NOTE: if we do not find a pause point we want to
-    // fall back on the old behavior (Case 3)
-    return lineChanged;
+    return script.getOffsetMetadata(offset).isStepStart;
   },
 
   _makeOnStep: function({ thread, pauseAndRespond, startFrame,
                           startLocation, steppingType, completion, rewinding }) {
     // Breaking in place: we should always pause.
     if (steppingType === "break") {
       return () => pauseAndRespond(this);
     }
@@ -780,30 +766,36 @@ const ThreadActor = ActorClassWithSpec(t
    * receive a resume request with a resumeLimit property.
    *
    * @param Object request
    *        The request packet received over the RDP.
    * @returns A promise that resolves to true once the hooks are attached, or is
    *          rejected with an error packet.
    */
   _handleResumeLimit: async function(request) {
-    const steppingType = request.resumeLimit.type;
+    let steppingType = request.resumeLimit.type;
     const rewinding = request.rewind;
     if (!["break", "step", "next", "finish", "warp"].includes(steppingType)) {
       return Promise.reject({
         error: "badParameterType",
         message: "Unknown resumeLimit type",
       });
     }
 
     if (steppingType == "warp") {
       // Time warp resume limits are handled by the caller.
       return true;
     }
 
+    // If we are stepping out of the onPop handler, we want to use "next" mode
+    // so that the parent frame's handlers behave consistently.
+    if (steppingType === "finish" && this.youngestFrame.reportedPop) {
+      steppingType = "next";
+    }
+
     const generatedLocation = this.sources.getFrameLocation(this.youngestFrame);
     const { onEnterFrame, onPop, onStep } = this._makeSteppingHooks(
       generatedLocation,
       steppingType,
       rewinding
     );
 
     // Make sure there is still a frame on the stack if we are to continue
--- a/devtools/server/actors/utils/TabSources.js
+++ b/devtools/server/actors/utils/TabSources.js
@@ -268,17 +268,17 @@ TabSources.prototype = {
    * @param Debugger.Script script
    *        The script associated with the offset.
    * @param Number offset
    *        Offset within the script of the location.
    * @returns Object
    *          Returns an object of the form { source, line, column }
    */
   getScriptOffsetLocation: function(script, offset) {
-    const {lineNumber, columnNumber} = script.getOffsetLocation(offset);
+    const {lineNumber, columnNumber} = script.getOffsetMetadata(offset);
     return new GeneratedLocation(
       this.createSourceActor(script.source),
       lineNumber,
       columnNumber
     );
   },
 
   /**
--- a/devtools/server/actors/worker/service-worker-registration.js
+++ b/devtools/server/actors/worker/service-worker-registration.js
@@ -60,29 +60,30 @@ protocol.ActorClassWithSpec(serviceWorke
   form() {
     const registration = this._registration;
     const installingWorker = this._installingWorker.form();
     const waitingWorker = this._waitingWorker.form();
     const activeWorker = this._activeWorker.form();
 
     const newestWorker = (activeWorker || waitingWorker || installingWorker);
 
-    const isE10s = Services.appinfo.browserTabsRemoteAutostart;
+    const isNewE10sImplementation = swm.isParentInterceptEnabled();
+    const isMultiE10sWithOldImplementation =
+      Services.appinfo.browserTabsRemoteAutostart && !isNewE10sImplementation;
     return {
       actor: this.actorID,
       scope: registration.scope,
       url: registration.scriptSpec,
       installingWorker,
       waitingWorker,
       activeWorker,
       fetch: newestWorker && newestWorker.fetch,
-      // - In e10s: only active registrations are available.
-      // - In non-e10s: registrations always have at least one worker, if the worker is
-      // active, the registration is active.
-      active: isE10s ? true : !!activeWorker,
+      // - In old multi e10s: only active registrations are available.
+      // - In non-e10s or new implementaion: check if we have an active worker
+      active: isMultiE10sWithOldImplementation ? true : !!activeWorker,
       lastUpdateTime: registration.lastUpdateTime,
     };
   },
 
   destroy() {
     protocol.Actor.prototype.destroy.call(this);
     Services.obs.removeObserver(this, PushService.subscriptionModifiedTopic);
     this._registration.removeListener(this);
--- a/devtools/server/tests/unit/test_breakpoint-01.js
+++ b/devtools/server/tests/unit/test_breakpoint-01.js
@@ -32,18 +32,25 @@ add_task(threadClientTest(async ({ threa
 
     info("Check that the breakpoint worked.");
     Assert.equal(debuggee.a, 1);
     Assert.equal(debuggee.b, undefined);
 
     await threadClient.resume();
   })();
 
-  /* eslint-disable */
-  Cu.evalInSandbox(
-    "var line0 = Error().lineNumber;\n" +
-    "debugger;\n" +   // line0 + 1
-    "var a = 1;\n" +  // line0 + 2
-    "var b = 2;\n",   // line0 + 3
-     debuggee
-  );
-  /* eslint-enable */
+  /*
+   * Be sure to run debuggee code in its own HTML 'task', so that when we call
+   * the onDebuggerStatement hook, the test's own microtasks don't get suspended
+   * along with the debuggee's.
+   */
+  do_timeout(0, () => {
+    /* eslint-disable */
+    Cu.evalInSandbox(
+      "var line0 = Error().lineNumber;\n" +
+        "debugger;\n" +   // line0 + 1
+        "var a = 1;\n" +  // line0 + 2
+        "var b = 2;\n",   // line0 + 3
+        debuggee
+    );
+    /* eslint-enable */
+  });
 }));
--- a/devtools/server/tests/unit/test_breakpoint-10.js
+++ b/devtools/server/tests/unit/test_breakpoint-10.js
@@ -11,36 +11,50 @@
 
 add_task(threadClientTest(({ threadClient, debuggee }) => {
   return new Promise(resolve => {
     threadClient.addOneTimeListener("paused", async function(event, packet) {
       const source = await getSourceById(
         threadClient,
         packet.frame.where.actor
       );
-      const location = { sourceUrl: source.url, line: debuggee.line0 + 3 };
+      const location = {
+        sourceUrl: source.url,
+        line: debuggee.line0 + 3,
+        column: 5,
+      };
 
       threadClient.setBreakpoint(location, {});
 
       threadClient.addOneTimeListener("paused", function(event, packet) {
         // Check the return value.
         Assert.equal(packet.type, "paused");
         Assert.equal(packet.why.type, "breakpoint");
         // Check that the breakpoint worked.
         Assert.equal(debuggee.i, 0);
 
+        // Remove the breakpoint.
+        threadClient.removeBreakpoint(location);
+
+        const location2 = {
+          sourceUrl: source.url,
+          line: debuggee.line0 + 3,
+          column: 12,
+        };
+        threadClient.setBreakpoint(location2, {});
+
         threadClient.addOneTimeListener("paused", function(event, packet) {
           // Check the return value.
           Assert.equal(packet.type, "paused");
           Assert.equal(packet.why.type, "breakpoint");
           // Check that the breakpoint worked.
           Assert.equal(debuggee.i, 1);
 
           // Remove the breakpoint.
-          threadClient.removeBreakpoint(location);
+          threadClient.removeBreakpoint(location2);
 
           threadClient.resume(resolve);
         });
 
         // Continue until the breakpoint is hit again.
         threadClient.resume();
       });
 
--- a/devtools/server/tests/unit/test_breakpoint-11.js
+++ b/devtools/server/tests/unit/test_breakpoint-11.js
@@ -11,37 +11,52 @@
 
 add_task(threadClientTest(({ threadClient, debuggee }) => {
   return new Promise(resolve => {
     threadClient.addOneTimeListener("paused", async function(event, packet) {
       const source = await getSourceById(
         threadClient,
         packet.frame.where.actor
       );
-      const location = { sourceUrl: source.url, line: debuggee.line0 + 2 };
+      const location = {
+        sourceUrl: source.url,
+        line: debuggee.line0 + 2,
+        column: 8,
+      };
 
       threadClient.setBreakpoint(location, {});
 
       threadClient.addOneTimeListener("paused", function(event, packet) {
         // Check the return value.
         Assert.equal(packet.type, "paused");
         Assert.equal(packet.why.type, "breakpoint");
         // Check that the breakpoint worked.
         Assert.equal(debuggee.a, undefined);
 
+        // Remove the breakpoint.
+        threadClient.removeBreakpoint(location);
+
+        const location2 = {
+          sourceUrl: source.url,
+          line: debuggee.line0 + 2,
+          column: 32,
+        };
+
+        threadClient.setBreakpoint(location2, {});
+
         threadClient.addOneTimeListener("paused", function(event, packet) {
           // Check the return value.
           Assert.equal(packet.type, "paused");
           Assert.equal(packet.why.type, "breakpoint");
           // Check that the breakpoint worked.
           Assert.equal(debuggee.a.b, 1);
           Assert.equal(debuggee.res, undefined);
 
           // Remove the breakpoint.
-          threadClient.removeBreakpoint(location);
+          threadClient.removeBreakpoint(location2);
 
           threadClient.resume(resolve);
         });
 
         // Continue until the breakpoint is hit again.
         threadClient.resume();
       });
 
--- a/devtools/server/tests/unit/test_breakpoint-13.js
+++ b/devtools/server/tests/unit/test_breakpoint-13.js
@@ -28,22 +28,16 @@ add_task(threadClientTest(({ threadClien
         },
         function(packet) {
           // Entered the foo function call frame.
           Assert.equal(packet.frame.where.line, location.line);
           Assert.notEqual(packet.why.type, "breakpoint");
           Assert.equal(packet.why.type, "resumeLimit");
         },
         function(packet) {
-          // At the end of the foo function call frame.
-          Assert.equal(packet.frame.where.line, debuggee.line0 + 3);
-          Assert.notEqual(packet.why.type, "breakpoint");
-          Assert.equal(packet.why.type, "resumeLimit");
-        },
-        function(packet) {
           // Check that the breakpoint wasn't the reason for this pause, but
           // that the frame is about to be popped while stepping.
           Assert.equal(packet.frame.where.line, debuggee.line0 + 3);
           Assert.notEqual(packet.why.type, "breakpoint");
           Assert.equal(packet.why.type, "resumeLimit");
           Assert.equal(packet.why.frameFinished.return.type, "undefined");
         },
         function(packet) {
--- a/devtools/server/tests/unit/test_breakpoint-14.js
+++ b/devtools/server/tests/unit/test_breakpoint-14.js
@@ -27,21 +27,16 @@ add_task(threadClientTest(({ threadClien
         },
         function(packet) {
           // Reached the breakpoint.
           Assert.equal(packet.frame.where.line, location.line);
           Assert.equal(packet.why.type, "breakpoint");
           Assert.notEqual(packet.why.type, "resumeLimit");
         },
         function(packet) {
-          // Stepped to the closing brace of the function.
-          Assert.equal(packet.frame.where.line, debuggee.line0 + 3);
-          Assert.equal(packet.why.type, "resumeLimit");
-        },
-        function(packet) {
           // The frame is about to be popped while stepping.
           Assert.equal(packet.frame.where.line, debuggee.line0 + 3);
           Assert.notEqual(packet.why.type, "breakpoint");
           Assert.equal(packet.why.type, "resumeLimit");
           Assert.equal(packet.why.frameFinished.return.type, "undefined");
         },
         function(packet) {
           // Check that the debugger statement wasn't the reason for this pause.
--- a/devtools/server/tests/unit/test_objectgrips-20.js
+++ b/devtools/server/tests/unit/test_objectgrips-20.js
@@ -15,17 +15,17 @@ registerCleanupFunction(() => {
   Services.prefs.clearUserPref("security.allow_eval_with_system_principal");
 });
 
 add_task(threadClientTest(async ({ threadClient, debuggee, client }) => {
   debuggee.eval(function stopMe(arg1) {
     debugger;
   }.toString());
 
-  await Promise.all([{
+  const testCases = [{
     evaledObject: { a: 10 },
     expectedIndexedProperties: [],
     expectedNonIndexedProperties: [["a", 10]],
   }, {
     evaledObject: { length: 10 },
     expectedIndexedProperties: [],
     expectedNonIndexedProperties: [["length", 10]],
   }, {
@@ -161,19 +161,21 @@ add_task(threadClientTest(async ({ threa
   }, {
     evaledObject: `(() => {
       x = new Int32Array([1, 2]);
       Object.setPrototypeOf(x, null);
       return x;
     })()`,
     expectedIndexedProperties: [["0", 1], ["1", 2]],
     expectedNonIndexedProperties: [],
-  }].map(async (testData) => {
-    await test_object_grip(debuggee, client, threadClient, testData);
-  }));
+  }];
+
+  for (const test of testCases) {
+    await test_object_grip(debuggee, client, threadClient, test);
+  }
 }));
 
 async function test_object_grip(debuggee, dbgClient, threadClient, testData = {}) {
   const {
     evaledObject,
     expectedIndexedProperties,
     expectedNonIndexedProperties,
   } = testData;
@@ -199,23 +201,28 @@ async function test_object_grip(debuggee
 
       response = await objClient.enumProperties({ ignoreIndexedProperties: true });
       await check_enum_properties(response, expectedNonIndexedProperties);
 
       await threadClient.resume();
       resolve();
     });
 
-    debuggee.eval(`
-      stopMe(${
-        typeof evaledObject === "string"
-          ? evaledObject
-          : JSON.stringify(evaledObject)
-      });
-    `);
+    // Be sure to run debuggee code in its own HTML 'task', so that when we call
+    // the onDebuggerStatement hook, the test's own microtasks don't get suspended
+    // along with the debuggee's.
+    do_timeout(0, () => {
+      debuggee.eval(`
+        stopMe(${
+          typeof evaledObject === "string"
+            ? evaledObject
+            : JSON.stringify(evaledObject)
+        });
+      `);
+    });
   });
 }
 
 async function check_enum_properties(response, expected = []) {
   ok(response && Object.getOwnPropertyNames(response).includes("iterator"),
     "The response object has an iterator property");
 
   const {iterator} = response;
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/unit/test_promises_run_to_completion.js
@@ -0,0 +1,117 @@
+// Bug 1145201:  Promise then-handlers can still be executed while the debugger is paused.
+//
+// When a promise is resolved, for each of its callbacks, a microtask is queued
+// to run the callback. At various points, the HTML spec says the browser must
+// "perform a microtask checkpoint", which means to draw microtasks from the
+// queue and run them, until the queue is empty.
+//
+// The HTML spec is careful to perform a microtask checkpoint directly after
+// each invocation of an event handler or DOM callback, so that code using
+// promises can trust that its promise callbacks run promptly, in a
+// deterministic order, without DOM events or other outside influences
+// intervening.
+//
+// When the JavaScript debugger interrupts the execution of debuggee content
+// code, it naturally must process events for its own user interface and promise
+// callbacks. However, it must not run any debuggee microtasks. The debuggee has
+// been interrupted in the midst of executing some other code, and the
+// JavaScript spec promises developers: "Once execution of a Job is initiated,
+// the Job always executes to completion. No other Job may be initiated until
+// the currently running Job completes." [1] This promise would be broken if the
+// debugger's own event processing ran debuggee microtasks during the
+// interruption.
+//
+// Looking at things from the other side, a microtask checkpoint must be
+// performed before returning from a debugger callback, rather than being put
+// off until the debuggee performs its next microtask checkpoint, so that
+// debugger microtasks are not interleaved with debuggee microtasks. A debuggee
+// microtask could hit a breakpoint or otherwise re-enter the debugger, which
+// might be quite surprised to see a new debugger callback begin before its
+// previous promise callbacks could finish.
+//
+// [1]: https://www.ecma-international.org/ecma-262/9.0/index.html#sec-jobs-and-job-queues
+
+"use strict";
+
+const Debugger = require("Debugger");
+
+function test_promises_run_to_completion() {
+  const g = testGlobal("test global for test_promises_run_to_completion.js");
+  const dbg = new Debugger(g);
+  g.Assert = Assert;
+  const log = [""];
+  g.log = log;
+
+  dbg.onDebuggerStatement = function handleDebuggerStatement(frame) {
+    dbg.onDebuggerStatement = undefined;
+
+    // Exercise the promise machinery: resolve a promise and perform a microtask
+    // queue. When called from a debugger hook, the debuggee's microtasks should not
+    // run.
+    log[0] += "debug-handler(";
+    Promise.resolve(42).then(v => {
+      Assert.equal(v, 42, "debugger callback promise handler got the right value");
+      log[0] += "debug-react";
+    });
+    log[0] += "(";
+    force_microtask_checkpoint();
+    log[0] += ")";
+
+    Promise.resolve(42).then(v => {
+      // The microtask running this callback should be handled as we leave the
+      // onDebuggerStatement Debugger callback, and should not be interleaved
+      // with debuggee microtasks.
+      log[0] += "(trailing)";
+    });
+
+    log[0] += ")";
+  };
+
+  // Evaluate some debuggee code that resolves a promise, and then enters the debugger.
+  Cu.evalInSandbox(`
+    log[0] += "eval(";
+    Promise.resolve(42).then(function debuggeePromiseCallback(v) {
+      Assert.equal(v, 42, "debuggee promise handler got the right value");
+      // Debugger microtask checkpoints must not run debuggee microtasks, so
+      // this callback should run at the next microtask checkpoint *not*
+      // performed by the debugger.
+      log[0] += "eval-react";
+    });
+
+    log[0] += "debugger(";
+    debugger;
+    log[0] += "))";
+  `, g);
+
+  // Let other microtasks run. This should run the debuggee's promise callback.
+  log[0] += "final(";
+  force_microtask_checkpoint();
+  log[0] += ")";
+
+  Assert.equal(log[0], `\
+eval(\
+debugger(\
+debug-handler(\
+(debug-react)\
+)\
+(trailing)\
+))\
+final(\
+eval-react\
+)`,
+               "microtasks ran as expected");
+
+  run_next_test();
+}
+
+function force_microtask_checkpoint() {
+  // Services.tm.spinEventLoopUntilEmpty only performs a microtask checkpoint if
+  // there is actually an event to run. So make one up.
+  let ran = false;
+  Services.tm.dispatchToMainThread(() => {
+    ran = true;
+  });
+  Services.tm.spinEventLoopUntil(() => ran);
+}
+
+add_test(test_promises_run_to_completion);
--- a/devtools/server/tests/unit/test_setBreakpoint-at-the-beginning-of-a-line.js
+++ b/devtools/server/tests/unit/test_setBreakpoint-at-the-beginning-of-a-line.js
@@ -17,17 +17,17 @@ add_task(threadClientTest(async ({ threa
   Assert.equal(packet.type, "paused");
   const why = packet.why;
   Assert.equal(why.type, "breakpoint");
   Assert.equal(why.actors.length, 1);
 
   const frame = packet.frame;
   Assert.equal(frame.where.actor, source.actor);
   Assert.equal(frame.where.line, location.line);
-  Assert.equal(frame.where.column, 6);
+  Assert.equal(frame.where.column, 10);
 
   const variables = frame.environment.bindings.variables;
   Assert.equal(variables.a.value.type, "undefined");
   Assert.equal(variables.b.value.type, "undefined");
   Assert.equal(variables.c.value.type, "undefined");
 
   await resume(threadClient);
 }, { doNotRunWorker: true }));
--- a/devtools/server/tests/unit/test_setBreakpoint-at-the-beginning-of-a-minified-fn.js
+++ b/devtools/server/tests/unit/test_setBreakpoint-at-the-beginning-of-a-minified-fn.js
@@ -20,17 +20,17 @@ add_task(threadClientTest(async ({ threa
   const why = packet.why;
   Assert.equal(why.type, "breakpoint");
   Assert.equal(why.actors.length, 1);
 
   const frame = packet.frame;
   const where = frame.where;
   Assert.equal(where.actor, source.actor);
   Assert.equal(where.line, location.line);
-  Assert.equal(where.column, 52);
+  Assert.equal(where.column, 56);
 
   const variables = frame.environment.bindings.variables;
   Assert.equal(variables.a.value.type, "undefined");
   Assert.equal(variables.b.value.type, "undefined");
   Assert.equal(variables.c.value.type, "undefined");
 
   await resume(threadClient);
 }, { doNotRunWorker: true }));
--- a/devtools/server/tests/unit/test_setBreakpoint-at-the-end-of-a-line.js
+++ b/devtools/server/tests/unit/test_setBreakpoint-at-the-end-of-a-line.js
@@ -18,17 +18,17 @@ add_task(threadClientTest(async ({ threa
   const why = packet.why;
   Assert.equal(why.type, "breakpoint");
   Assert.equal(why.actors.length, 1);
 
   const frame = packet.frame;
   const where = frame.where;
   Assert.equal(where.actor, source.actor);
   Assert.equal(where.line, location.line);
-  Assert.equal(where.column, 28);
+  Assert.equal(where.column, 32);
 
   const variables = frame.environment.bindings.variables;
   Assert.equal(variables.a.value, 1);
   Assert.equal(variables.b.value, 2);
   Assert.equal(variables.c.value.type, "undefined");
 
   await resume(threadClient);
 }, { doNotRunWorker: true }));
--- a/devtools/server/tests/unit/test_setBreakpoint-on-column-in-gcd-script.js
+++ b/devtools/server/tests/unit/test_setBreakpoint-on-column-in-gcd-script.js
@@ -4,17 +4,17 @@ const SOURCE_URL = getFileUrl("setBreakp
 
 add_task(threadClientTest(async ({ threadClient, debuggee, client, targetFront }) => {
   const promise = waitForNewSource(threadClient, SOURCE_URL);
   loadSubScriptWithOptions(SOURCE_URL, {target: debuggee, ignoreCache: true});
   Cu.forceGC(); Cu.forceGC(); Cu.forceGC();
 
   const { source } = await promise;
 
-  const location = { sourceUrl: source.url, line: 6, column: 17 };
+  const location = { sourceUrl: source.url, line: 6, column: 21 };
   setBreakpoint(threadClient, location);
 
   const packet = await executeOnNextTickAndWaitForPause(function() {
     reload(targetFront).then(function() {
       loadSubScriptWithOptions(SOURCE_URL, {target: debuggee, ignoreCache: true});
     });
   }, client);
   Assert.equal(packet.type, "paused");
--- a/devtools/server/tests/unit/test_setBreakpoint-on-column.js
+++ b/devtools/server/tests/unit/test_setBreakpoint-on-column.js
@@ -2,17 +2,17 @@
 
 const SOURCE_URL = getFileUrl("setBreakpoint-on-column.js");
 
 add_task(threadClientTest(async ({ threadClient, debuggee, client }) => {
   const promise = waitForNewSource(threadClient, SOURCE_URL);
   loadSubScript(SOURCE_URL, debuggee);
   const { source } = await promise;
 
-  const location = { sourceUrl: source.url, line: 4, column: 17 };
+  const location = { sourceUrl: source.url, line: 4, column: 21 };
   setBreakpoint(threadClient, location);
 
   const packet = await executeOnNextTickAndWaitForPause(function() {
     Cu.evalInSandbox("f()", debuggee);
   }, client);
   Assert.equal(packet.type, "paused");
   const why = packet.why;
   Assert.equal(why.type, "breakpoint");
--- a/devtools/server/tests/unit/test_setBreakpoint-on-line-with-multiple-offsets.js
+++ b/devtools/server/tests/unit/test_setBreakpoint-on-line-with-multiple-offsets.js
@@ -20,25 +20,28 @@ add_task(threadClientTest(async ({ threa
   Assert.equal(why.actors.length, 1);
   let frame = packet.frame;
   let where = frame.where;
   Assert.equal(where.actor, source.actor);
   Assert.equal(where.line, location.line);
   let variables = frame.environment.bindings.variables;
   Assert.equal(variables.i.value.type, "undefined");
 
+  const location2 = { sourceUrl: sourceClient.url, line: 7 };
+  setBreakpoint(threadClient, location2);
+
   packet = await executeOnNextTickAndWaitForPause(
     () => resume(threadClient),
     client
   );
   Assert.equal(packet.type, "paused");
   why = packet.why;
   Assert.equal(why.type, "breakpoint");
   Assert.equal(why.actors.length, 1);
   frame = packet.frame;
   where = frame.where;
   Assert.equal(where.actor, source.actor);
-  Assert.equal(where.line, location.line);
+  Assert.equal(where.line, location2.line);
   variables = frame.environment.bindings.variables;
-  Assert.equal(variables.i.value, 0);
+  Assert.equal(variables.i.value, 1);
 
   await resume(threadClient);
 }, { doNotRunWorker: true }));
--- a/devtools/server/tests/unit/test_source-02.js
+++ b/devtools/server/tests/unit/test_source-02.js
@@ -32,17 +32,22 @@ function run_test() {
                              gThreadClient = threadClient;
                              test_source();
                            });
   });
   do_test_pending();
 }
 
 const SOURCE_URL = "http://example.com/foobar.js";
-const SOURCE_CONTENT = "stopMe()";
+const SOURCE_CONTENT = `
+  stopMe();
+  for(var i = 0; i < 2; i++) {
+    debugger;
+  }
+`;
 
 function test_source() {
   DebuggerServer.LONG_STRING_LENGTH = 200;
 
   gThreadClient.addOneTimeListener("paused", function(event, packet) {
     gThreadClient.getSources(async function(response) {
       Assert.ok(!!response);
       Assert.ok(!!response.sources);
@@ -55,31 +60,46 @@ function test_source() {
 
       const sourceClient = gThreadClient.source(source);
       response = await sourceClient.getBreakpointPositions();
       Assert.ok(!!response);
       Assert.ok(!!response.positions);
       Assert.deepEqual(
         response.positions,
         [{
-          line: 1,
-          column: 0,
+          line: 2,
+          column: 2,
+        }, {
+          line: 3,
+          column: 14,
+        }, {
+          line: 3,
+          column: 17,
         }, {
-          line: 1,
-          column: 8,
+          line: 3,
+          column: 24,
+        }, {
+          line: 4,
+          column: 4,
+        }, {
+          line: 6,
+          column: 0,
         }]
       );
 
       response = await sourceClient.getBreakpointPositionsCompressed();
       Assert.ok(!!response);
       Assert.ok(!!response.positions);
       Assert.deepEqual(
         response.positions,
         {
-          1: [0, 8],
+          2: [2],
+          3: [14, 17, 24],
+          4: [4],
+          6: [0],
         }
       );
 
       await gThreadClient.resume();
       finishClient(gClient);
     });
   });
 
--- a/devtools/server/tests/unit/test_stepping-01.js
+++ b/devtools/server/tests/unit/test_stepping-01.js
@@ -56,17 +56,17 @@ async function stepOutOfA(dbg, func, exp
 
   deepEqual(getPauseLocation(packet), expectedLocation, `step out location in ${func}`);
 
   await resume(dbg.threadClient);
 }
 
 async function stepOverInA(dbg, func, expectedLocation) {
   await invokeAndPause(dbg, `${func}()`);
-  await steps(dbg, [stepOver, stepIn, stepOver]);
+  await steps(dbg, [stepOver, stepIn]);
 
   let packet = await step(dbg, stepOver);
   dump(`>> stepOverInA hi\n`);
   equal(getPauseReturn(packet).ownPropertyLength, 1, "a() is returning obj");
 
   packet = await step(dbg, stepOver);
   deepEqual(getPauseLocation(packet), expectedLocation, `step out location in ${func}`);
 
@@ -77,15 +77,15 @@ async function testStep(dbg, func, expec
   await stepOverInA(dbg, func, expectedLocation);
   await stepOutOfA(dbg, func, expectedLocation);
 }
 
 function run_test() {
   return (async function() {
     const dbg = await setupTestFromUrl("stepping.js");
 
-    await testStep(dbg, "arithmetic", {line: 16, column: 8});
-    await testStep(dbg, "composition", {line: 21, column: 2});
-    await testStep(dbg, "chaining", {line: 26, column: 6});
+    await testStep(dbg, "arithmetic", {line: 17, column: 0});
+    await testStep(dbg, "composition", {line: 22, column: 0});
+    await testStep(dbg, "chaining", {line: 27, column: 0});
 
     await testFinish(dbg);
   })();
 }
--- a/devtools/server/tests/unit/test_stepping-06.js
+++ b/devtools/server/tests/unit/test_stepping-06.js
@@ -44,17 +44,17 @@ async function testFinish({threadClient,
   do_test_finished();
 }
 
 async function testRet(dbg) {
   let packet;
 
   info(`1. Test returning from doRet via stepping over`);
   await invokeAndPause(dbg, `doRet()`);
-  await steps(dbg, [stepOver, stepIn, stepOver]);
+  await steps(dbg, [stepOver, stepIn]);
   packet = await step(dbg, stepOver);
 
   deepEqual(
     getPauseLocation(packet),
     {line: 6, column: 0},
     `completion location in doRet`
   );
   deepEqual(
@@ -84,17 +84,17 @@ async function testRet(dbg) {
   await resume(dbg.threadClient);
 }
 
 async function testThrow(dbg) {
   let packet;
 
   info(`3. Test leaving from doThrow via stepping over`);
   await invokeAndPause(dbg, `doThrow()`);
-  await steps(dbg, [stepOver, stepOver, stepIn]);
+  await steps(dbg, [stepOver, stepIn]);
   packet = await step(dbg, stepOver);
 
   deepEqual(
     getPauseLocation(packet),
     {line: 9, column: 8},
     `completion location in doThrow`
   );
 
@@ -108,34 +108,29 @@ async function testThrow(dbg) {
     "yo",
     `completion value preview`
   );
 
   await resume(dbg.threadClient);
 
   info(`4. Test leaving from doThrow via stepping out`);
   await invokeAndPause(dbg, `doThrow()`);
-  await steps(dbg, [stepOver, stepOver, stepIn]);
+  await steps(dbg, [stepOver, stepIn]);
 
   packet = await step(dbg, stepOut);
   deepEqual(
     getPauseLocation(packet),
-    {line: 22, column: 14},
-    `completion location in doThrow`
+    {line: 24, column: 0},
+    `stepOut location in doThrow`
   );
 
   deepEqual(
-    getFrameFinished(packet).throw.class,
-    "Error",
-    `completion completion value class`
-  );
-  deepEqual(
-    getFrameFinished(packet).throw.preview.message,
-    "yo",
-    `completion completion value preview`
+    getFrameFinished(packet),
+    {return: {type: "undefined"}},
+    `completion type`
   );
   await resume(dbg.threadClient);
 }
 
 function run_test() {
   return (async function() {
     const dbg = await setupTestFromUrl("completions.js");
 
--- a/devtools/server/tests/unit/test_stepping-07.js
+++ b/devtools/server/tests/unit/test_stepping-07.js
@@ -24,27 +24,24 @@ add_task(threadClientTest(async ({ threa
   // step at the end of this function to get the frameFinished.
   // See bug 923975.
   //
   // ok(step2.why.frameFinished, "This should be the implicit function return");
 
   dumpn("Continuing and waiting for second debugger statement");
   const dbgStmt2 = await resumeAndWaitForPause(client, threadClient);
   equal(dbgStmt2.frame.where.line, 12,
-        "Should be at debugger statement on line 3");
+        "Should be at debugger statement on line 12");
 
   dumpn("Testing stepping with explicit return");
   const step3 = await stepOver(client, threadClient);
   equal(step3.frame.where.line, 13, "Should step to line 13");
   const step4 = await stepOver(client, threadClient);
   equal(step4.frame.where.line, 15, "Should step out of the function from line 15");
-  // This step is a bit funny, see bug 1013219 for details.
-  const step5 = await stepOver(client, threadClient);
-  equal(step5.frame.where.line, 15, "Should step out of the function from line 15");
-  ok(step5.why.frameFinished, "This should be the explicit function return");
+  ok(step4.why.frameFinished, "This should be the explicit function return");
 }));
 
 function evaluateTestCode(debuggee) {
   /* eslint-disable */
   Cu.evalInSandbox(
     `                                   //  1
     function implicitReturn() {         //  2
       debugger;                         //  3
--- a/devtools/server/tests/unit/test_stepping-08.js
+++ b/devtools/server/tests/unit/test_stepping-08.js
@@ -16,23 +16,27 @@ add_task(threadClientTest(async ({ threa
   dumpn("Setting breakpoint in innerFunction");
   const source = await getSourceById(
     threadClient,
     dbgStmt.frame.where.actor
   );
   await threadClient.setBreakpoint({ sourceUrl: source.url, line: 7 }, {});
 
   dumpn("Step in to innerFunction");
-  const step1 = await stepIn(client, threadClient);
-  equal(step1.frame.where.line, 7);
+  const step1 = await stepOver(client, threadClient);
+  equal(step1.frame.where.line, 3);
+
+  dumpn("Step in to innerFunction");
+  const step2 = await stepIn(client, threadClient);
+  equal(step2.frame.where.line, 7);
 
   dumpn("Step out of innerFunction");
-  const step2 = await stepOut(client, threadClient);
+  const step3 = await stepOut(client, threadClient);
   // The bug was that we'd stop again at the breakpoint on line 7.
-  equal(step2.frame.where.line, 4);
+  equal(step3.frame.where.line, 4);
 }));
 
 function evaluateTestCode(debuggee) {
   /* eslint-disable */
   Cu.evalInSandbox(
     `                                   //  1
     function outerFunction() {          //  2
       debugger; innerFunction();        //  3
--- a/devtools/server/tests/unit/test_stepping-09.js
+++ b/devtools/server/tests/unit/test_stepping-09.js
@@ -13,17 +13,17 @@ add_task(threadClientTest(async ({ threa
   const dbgStmt = await executeOnNextTickAndWaitForPause(
     () => evaluateTestCode(debuggee), client);
   equal(dbgStmt.frame.where.line, 2, "Should be at debugger statement on line 2");
 
   dumpn("Step out of inner and into outer");
   const step2 = await stepOut(client, threadClient);
   // The bug was that we'd step right past the end of the function and never pause.
   equal(step2.frame.where.line, 2);
-  equal(step2.why.frameFinished.return, 42);
+  deepEqual(step2.why.frameFinished.return, { type: "undefined"});
 }));
 
 function evaluateTestCode(debuggee) {
   // By placing the inner and outer on the same line, this triggers the server's
   // logic to skip steps for these functions, meaning that onPop is the only
   // thing that will cause it to pop.
   Cu.evalInSandbox(
     `
deleted file mode 100644
--- a/devtools/server/tests/unit/test_stepping-with-pause-points.js
+++ /dev/null
@@ -1,76 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-/* eslint-disable no-shadow, max-nested-callbacks */
-
-"use strict";
-
-/**
- * Check basic step-over functionality with pause points
- * for the first statement and end of the last statement.
- */
-
-add_task(threadClientTest(async ({ threadClient, debuggee, client }) => {
-  dumpn("Evaluating test code and waiting for first debugger statement");
-  const dbgStmt = await executeOnNextTickAndWaitForPause(
-    () => evaluateTestCode(debuggee), client);
-  equal(dbgStmt.frame.where.line, 2, "Should be at debugger statement on line 2");
-  equal(debuggee.a, undefined);
-  equal(debuggee.b, undefined);
-
-  const source = await getSource(threadClient, "test_stepping-01-test-code.js");
-
-  // Add pause points for the first and end of the last statement.
-  // Note: we intentionally ignore the second statement.
-  source.setPausePoints([{
-    location: {line: 3, column: 8},
-    types: {breakpoint: true, stepOver: true},
-  },
-  {
-    location: {line: 4, column: 14},
-    types: {breakpoint: true, stepOver: true},
-  }]);
-
-  dumpn("Step Over to line 3");
-  const step1 = await stepOver(client, threadClient);
-  equal(step1.type, "paused");
-  equal(step1.why.type, "resumeLimit");
-  equal(step1.frame.where.line, 3);
-  equal(step1.frame.where.column, 8);
-
-  equal(debuggee.a, undefined);
-  equal(debuggee.b, undefined);
-
-  dumpn("Step Over to line 4");
-  const step2 = await stepOver(client, threadClient);
-  equal(step2.type, "paused");
-  equal(step2.why.type, "resumeLimit");
-  equal(step2.frame.where.line, 4);
-  equal(step2.frame.where.column, 8);
-
-  equal(debuggee.a, 1);
-  equal(debuggee.b, undefined);
-
-  dumpn("Step Over to the end of line 4");
-  const step3 = await stepOver(client, threadClient);
-  equal(step3.type, "paused");
-  equal(step3.why.type, "resumeLimit");
-  equal(step3.frame.where.line, 4);
-  equal(step3.frame.where.column, 14);
-  equal(debuggee.a, 1);
-  equal(debuggee.b, 2);
-}));
-
-function evaluateTestCode(debuggee) {
-  /* eslint-disable */
-  Cu.evalInSandbox(
-    `                                   // 1
-    debugger;                           // 2
-    var a = 1;                          // 3
-    var b = 2;`,                        // 4
-    debuggee,
-    "1.8",
-    "test_stepping-01-test-code.js",
-    1
-  );
-  /* eslint-disable */
-}
--- a/devtools/server/tests/unit/test_stepping-with-skip-breakpoints.js
+++ b/devtools/server/tests/unit/test_stepping-with-skip-breakpoints.js
@@ -30,27 +30,27 @@ add_task(threadClientTest(async ({ threa
     types: {breakpoint: true, stepOver: true},
   }]);
 
   dumpn("Step Over to line 3");
   const step1 = await stepOver(client, threadClient);
   equal(step1.type, "paused");
   equal(step1.why.type, "resumeLimit");
   equal(step1.frame.where.line, 3);
-  equal(step1.frame.where.column, 8);
+  equal(step1.frame.where.column, 12);
 
   equal(debuggee.a, undefined);
   equal(debuggee.b, undefined);
 
   dumpn("Step Over to line 4");
   const step2 = await stepOver(client, threadClient);
   equal(step2.type, "paused");
   equal(step2.why.type, "resumeLimit");
   equal(step2.frame.where.line, 4);
-  equal(step2.frame.where.column, 8);
+  equal(step2.frame.where.column, 12);
 
   equal(debuggee.a, 1);
   equal(debuggee.b, undefined);
 
   dumpn("Step Over to the end of line 4");
   const step3 = await stepOver(client, threadClient);
   equal(step3.type, "paused");
   equal(step3.why.type, "resumeLimit");
@@ -68,9 +68,9 @@ function evaluateTestCode(debuggee) {
     var a = 1;                          // 3
     var b = 2;`,                        // 4
     debuggee,
     "1.8",
     "test_stepping-01-test-code.js",
     1
   );
   /* eslint-disable */
-}
+}
\ No newline at end of file
--- a/devtools/server/tests/unit/xpcshell.ini
+++ b/devtools/server/tests/unit/xpcshell.ini
@@ -93,16 +93,17 @@ support-files =
 [test_promises_actor_list_promises.js]
 skip-if = coverage # bug 1336670
 [test_promises_actor_onnewpromise.js]
 [test_promises_actor_onpromisesettled.js]
 [test_promises_client_getdependentpromises.js]
 [test_promises_object_creationtimestamp.js]
 [test_promises_object_timetosettle-01.js]
 [test_promises_object_timetosettle-02.js]
+[test_promises_run_to_completion.js]
 [test_protocol_abort.js]
 [test_protocol_async.js]
 [test_protocol_children.js]
 [test_protocol_longstring.js]
 [test_protocol_onFront.js]
 [test_protocol_simple.js]
 [test_protocol_stack.js]
 [test_protocol_unregister.js]
@@ -187,17 +188,16 @@ skip-if = true # breakpoint sliding is n
 [test_stepping-02.js]
 [test_stepping-03.js]
 [test_stepping-04.js]
 [test_stepping-05.js]
 [test_stepping-06.js]
 [test_stepping-07.js]
 [test_stepping-08.js]
 [test_stepping-09.js]
-[test_stepping-with-pause-points.js]
 [test_stepping-with-skip-breakpoints.js]
 [test_framebindings-01.js]
 [test_framebindings-02.js]
 [test_framebindings-03.js]
 [test_framebindings-04.js]
 [test_framebindings-05.js]
 [test_framebindings-06.js]
 [test_framebindings-07.js]
--- a/devtools/shared/client/debugger-client.js
+++ b/devtools/shared/client/debugger-client.js
@@ -1,17 +1,15 @@
 /* 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 Services = require("Services");
 const promise = require("devtools/shared/deprecated-sync-thenables");
-const {AppConstants} = require("resource://gre/modules/AppConstants.jsm");
 
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const { getStack, callFunctionWithAsyncStack } = require("devtools/shared/platform/stack");
 const eventSource = require("devtools/shared/client/event-source");
 const {
   ThreadStateTypes,
   UnsolicitedNotifications,
   UnsolicitedPauses,
@@ -24,27 +22,16 @@ loader.lazyRequireGetter(this, "EventEmi
 loader.lazyRequireGetter(this, "WebConsoleClient", "devtools/shared/webconsole/client", true);
 loader.lazyRequireGetter(this, "RootFront", "devtools/shared/fronts/root", true);
 loader.lazyRequireGetter(this, "BrowsingContextTargetFront", "devtools/shared/fronts/targets/browsing-context", true);
 loader.lazyRequireGetter(this, "ThreadClient", "devtools/shared/client/thread-client");
 loader.lazyRequireGetter(this, "ObjectClient", "devtools/shared/client/object-client");
 loader.lazyRequireGetter(this, "Pool", "devtools/shared/protocol", true);
 loader.lazyRequireGetter(this, "Front", "devtools/shared/protocol", true);
 
-// Retrieve the major platform version, i.e. if we are on Firefox 64.0a1, it will be 64.
-const PLATFORM_MAJOR_VERSION = AppConstants.MOZ_APP_VERSION.match(/\d+/)[0];
-
-// Define the minimum officially supported version of Firefox when connecting to a remote
-// runtime. (Use ".0a1" to support the very first nightly version)
-// This matches the release channel's version when we are on nightly,
-// or 2 versions before when we are on other channels.
-const MIN_SUPPORTED_PLATFORM_VERSION = (PLATFORM_MAJOR_VERSION - 2) + ".0a1";
-
-const MS_PER_DAY = 86400000;
-
 /**
  * Creates a client for the remote debugging protocol server. This client
  * provides the means to communicate with the server and exchange the messages
  * required by the protocol in a traditional JavaScript API.
  */
 function DebuggerClient(transport) {
   this._transport = transport;
   this._transport.hooks = this;
@@ -197,90 +184,16 @@ DebuggerClient.prototype = {
       deferred.resolve([applicationType, traits]);
     });
 
     this._transport.ready();
     return deferred.promise;
   },
 
   /**
-   * Tells if the remote device is using a supported version of Firefox.
-   *
-   * @return Object with the following attributes:
-   *   * String incompatible
-   *            null if the runtime is compatible,
-   *            "too-recent" if the runtime uses a too recent version,
-   *            "too-old" if the runtime uses a too old version.
-   *   * String minVersion
-   *            The minimum supported version.
-   *   * String runtimeVersion
-   *            The remote runtime version.
-   *   * String localID
-   *            Build ID of local runtime. A date with like this: YYYYMMDD.
-   *   * String deviceID
-   *            Build ID of remote runtime. A date with like this: YYYYMMDD.
-   */
-  async checkRuntimeVersion() {
-    const localID = Services.appinfo.appBuildID.substr(0, 8);
-
-    let deviceFront;
-    try {
-      deviceFront = await this.mainRoot.getFront("device");
-    } catch (e) {
-      // On <FF55, getFront is going to call RootActor.getRoot and fail
-      // because this method doesn't exists.
-      if (e.error == "unrecognizedPacketType") {
-        return {
-          incompatible: "too-old",
-          minVersion: MIN_SUPPORTED_PLATFORM_VERSION,
-          runtimeVersion: "<55",
-          localID,
-          runtimeID: "?",
-        };
-      }
-      throw e;
-    }
-    const desc = await deviceFront.getDescription();
-    let incompatible = null;
-
-    // 1) Check for Firefox too recent on device.
-    // Compare device and firefox build IDs
-    // and only compare by day (strip hours/minutes) to prevent
-    // warning against builds of the same day.
-    const runtimeID = desc.appbuildid.substr(0, 8);
-    function buildIDToDate(buildID) {
-      const fields = buildID.match(/(\d{4})(\d{2})(\d{2})/);
-      // Date expects 0 - 11 for months
-      return new Date(fields[1], Number.parseInt(fields[2], 10) - 1, fields[3]);
-    }
-    const runtimeDate = buildIDToDate(runtimeID);
-    const localDate = buildIDToDate(localID);
-    // Allow device to be newer by up to a week.  This accommodates those with
-    // local device builds, since their devices will almost always be newer
-    // than the client.
-    if (runtimeDate - localDate > 7 * MS_PER_DAY) {
-      incompatible = "too-recent";
-    }
-
-    // 2) Check for too old Firefox on device
-    const platformversion = desc.platformversion;
-    if (Services.vc.compare(platformversion, MIN_SUPPORTED_PLATFORM_VERSION) < 0) {
-      incompatible = "too-old";
-    }
-
-    return {
-      incompatible,
-      minVersion: MIN_SUPPORTED_PLATFORM_VERSION,
-      runtimeVersion: platformversion,
-      localID,
-      runtimeID,
-    };
-  },
-
-  /**
    * Shut down communication with the debugging server.
    *
    * @param onClosed function
    *        If specified, will be called when the debugging connection
    *        has been closed. This parameter is deprecated - please use
    *        the returned Promise.
    * @return Promise
    *         Resolves after the underlying transport is closed.
--- a/dom/interfaces/base/nsIServiceWorkerManager.idl
+++ b/dom/interfaces/base/nsIServiceWorkerManager.idl
@@ -175,13 +175,15 @@ interface nsIServiceWorkerManager : nsIS
                                      [optional] in uint32_t aDataLength,
                                      [optional, array, size_is(aDataLength)] in uint8_t aDataBytes);
   void sendPushSubscriptionChangeEvent(in ACString aOriginAttributes,
                                        in ACString scope);
 
   void addListener(in nsIServiceWorkerManagerListener aListener);
 
   void removeListener(in nsIServiceWorkerManagerListener aListener);
+
+  bool isParentInterceptEnabled();
 };
 
 %{ C++
 #define SERVICEWORKERMANAGER_CONTRACTID "@mozilla.org/serviceworkers/manager;1"
 %}
--- a/dom/media/ipc/RemoteVideoDecoderChild.h
+++ b/dom/media/ipc/RemoteVideoDecoderChild.h
@@ -15,33 +15,34 @@ namespace layers {
 class BufferRecycleBin;
 }
 }  // namespace mozilla
 
 namespace mozilla {
 
 class RemoteDecoderManagerChild;
 using mozilla::MediaDataDecoder;
+using mozilla::ipc::IPCResult;
 
 class RemoteVideoDecoderChild final : public PRemoteVideoDecoderChild,
                                       public IRemoteDecoderChild {
+  friend class PRemoteVideoDecoderChild;
+
  public:
   explicit RemoteVideoDecoderChild();
 
   // PRemoteVideoDecoderChild
-  mozilla::ipc::IPCResult RecvVideoOutput(
-      const RemoteVideoDataIPDL& aData) override;
-  mozilla::ipc::IPCResult RecvInputExhausted() override;
-  mozilla::ipc::IPCResult RecvDrainComplete() override;
-  mozilla::ipc::IPCResult RecvError(const nsresult& aError) override;
-  mozilla::ipc::IPCResult RecvInitComplete(
-      const nsCString& aDecoderDescription,
-      const ConversionRequired& aConversion) override;
-  mozilla::ipc::IPCResult RecvInitFailed(const nsresult& aReason) override;
-  mozilla::ipc::IPCResult RecvFlushComplete() override;
+  IPCResult RecvVideoOutput(const RemoteVideoDataIPDL& aData);
+  IPCResult RecvInputExhausted();
+  IPCResult RecvDrainComplete();
+  IPCResult RecvError(const nsresult& aError);
+  IPCResult RecvInitComplete(const nsCString& aDecoderDescription,
+                             const ConversionRequired& aConversion);
+  IPCResult RecvInitFailed(const nsresult& aReason);
+  IPCResult RecvFlushComplete();
 
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
   // IRemoteDecoderChild
   RefPtr<MediaDataDecoder::InitPromise> Init() override;
   RefPtr<MediaDataDecoder::DecodePromise> Decode(
       MediaRawData* aSample) override;
   RefPtr<MediaDataDecoder::DecodePromise> Drain() override;
--- a/dom/media/ipc/RemoteVideoDecoderParent.h
+++ b/dom/media/ipc/RemoteVideoDecoderParent.h
@@ -5,39 +5,42 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #ifndef include_dom_media_ipc_RemoteVideoDecoderParent_h
 #define include_dom_media_ipc_RemoteVideoDecoderParent_h
 #include "mozilla/PRemoteVideoDecoderParent.h"
 
 namespace mozilla {
 
 class RemoteDecoderManagerParent;
+using mozilla::ipc::IPCResult;
 
 class RemoteVideoDecoderParent final : public PRemoteVideoDecoderParent {
+  friend class PRemoteVideoDecoderParent;
+
  public:
   // We refcount this class since the task queue can have runnables
   // that reference us.
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RemoteVideoDecoderParent)
 
   RemoteVideoDecoderParent(RemoteDecoderManagerParent* aParent,
                            const VideoInfo& aVideoInfo, float aFramerate,
                            const CreateDecoderParams::OptionSet& aOptions,
                            TaskQueue* aManagerTaskQueue,
                            TaskQueue* aDecodeTaskQueue, bool* aSuccess,
                            nsCString* aErrorDescription);
 
   void Destroy();
 
   // PRemoteVideoDecoderParent
-  mozilla::ipc::IPCResult RecvInit() override;
-  mozilla::ipc::IPCResult RecvInput(const MediaRawDataIPDL& aData) override;
-  mozilla::ipc::IPCResult RecvFlush() override;
-  mozilla::ipc::IPCResult RecvDrain() override;
-  mozilla::ipc::IPCResult RecvShutdown() override;
-  mozilla::ipc::IPCResult RecvSetSeekThreshold(const int64_t& aTime) override;
+  IPCResult RecvInit();
+  IPCResult RecvInput(const MediaRawDataIPDL& aData);
+  IPCResult RecvFlush();
+  IPCResult RecvDrain();
+  IPCResult RecvShutdown();
+  IPCResult RecvSetSeekThreshold(const int64_t& aTime);
 
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
  private:
   bool OnManagerThread();
   void Error(const MediaResult& aError);
 
   ~RemoteVideoDecoderParent();
--- a/dom/media/ipc/VideoDecoderChild.h
+++ b/dom/media/ipc/VideoDecoderChild.h
@@ -11,32 +11,36 @@
 #include "mozilla/PVideoDecoderChild.h"
 #include "IRemoteDecoderChild.h"
 
 namespace mozilla {
 
 class RemoteVideoDecoder;
 class RemoteDecoderModule;
 class VideoDecoderManagerChild;
+using mozilla::ipc::IPCResult;
 
 class VideoDecoderChild final : public PVideoDecoderChild,
                                 public IRemoteDecoderChild {
+  friend class PVideoDecoderChild;
+
  public:
   explicit VideoDecoderChild();
 
   // PVideoDecoderChild
-  mozilla::ipc::IPCResult RecvOutput(const VideoDataIPDL& aData) override;
-  mozilla::ipc::IPCResult RecvInputExhausted() override;
-  mozilla::ipc::IPCResult RecvDrainComplete() override;
-  mozilla::ipc::IPCResult RecvError(const nsresult& aError) override;
-  mozilla::ipc::IPCResult RecvInitComplete(
-      const nsCString& aDecoderDescription, const bool& aHardware,
-      const nsCString& aHardwareReason, const uint32_t& aConversion) override;
-  mozilla::ipc::IPCResult RecvInitFailed(const nsresult& aReason) override;
-  mozilla::ipc::IPCResult RecvFlushComplete() override;
+  IPCResult RecvOutput(const VideoDataIPDL& aData);
+  IPCResult RecvInputExhausted();
+  IPCResult RecvDrainComplete();
+  IPCResult RecvError(const nsresult& aError);
+  IPCResult RecvInitComplete(const nsCString& aDecoderDescription,
+                             const bool& aHardware,
+                             const nsCString& aHardwareReason,
+                             const uint32_t& aConversion);
+  IPCResult RecvInitFailed(const nsresult& aReason);
+  IPCResult RecvFlushComplete();
 
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
   RefPtr<MediaDataDecoder::InitPromise> Init() override;
   RefPtr<MediaDataDecoder::DecodePromise> Decode(
       MediaRawData* aSample) override;
   RefPtr<MediaDataDecoder::DecodePromise> Drain() override;
   RefPtr<MediaDataDecoder::FlushPromise> Flush() override;
--- a/dom/media/ipc/VideoDecoderParent.h
+++ b/dom/media/ipc/VideoDecoderParent.h
@@ -12,39 +12,42 @@
 #include "VideoDecoderManagerParent.h"
 #include "mozilla/MozPromise.h"
 #include "mozilla/PVideoDecoderParent.h"
 #include "mozilla/layers/TextureForwarder.h"
 
 namespace mozilla {
 
 class KnowsCompositorVideo;
+using mozilla::ipc::IPCResult;
 
 class VideoDecoderParent final : public PVideoDecoderParent {
+  friend class PVideoDecoderParent;
+
  public:
   // We refcount this class since the task queue can have runnables
   // that reference us.
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VideoDecoderParent)
 
   VideoDecoderParent(VideoDecoderManagerParent* aParent,
                      const VideoInfo& aVideoInfo, float aFramerate,
                      const CreateDecoderParams::OptionSet& aOptions,
                      const layers::TextureFactoryIdentifier& aIdentifier,
                      TaskQueue* aManagerTaskQueue, TaskQueue* aDecodeTaskQueue,
                      bool* aSuccess, nsCString* aErrorDescription);
 
   void Destroy();
 
   // PVideoDecoderParent
-  mozilla::ipc::IPCResult RecvInit() override;
-  mozilla::ipc::IPCResult RecvInput(const MediaRawDataIPDL& aData) override;
-  mozilla::ipc::IPCResult RecvFlush() override;
-  mozilla::ipc::IPCResult RecvDrain() override;
-  mozilla::ipc::IPCResult RecvShutdown() override;
-  mozilla::ipc::IPCResult RecvSetSeekThreshold(const int64_t& aTime) override;
+  IPCResult RecvInit();
+  IPCResult RecvInput(const MediaRawDataIPDL& aData);
+  IPCResult RecvFlush();
+  IPCResult RecvDrain();
+  IPCResult RecvShutdown();
+  IPCResult RecvSetSeekThreshold(const int64_t& aTime);
 
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
  private:
   bool OnManagerThread();
   void Error(const MediaResult& aError);
 
   ~VideoDecoderParent();
--- a/dom/media/ipc/moz.build
+++ b/dom/media/ipc/moz.build
@@ -11,27 +11,32 @@ IPDL_SOURCES += [
     'PRemoteDecoderManager.ipdl',
     'PRemoteVideoDecoder.ipdl',
     'PVideoDecoder.ipdl',
     'PVideoDecoderManager.ipdl',
 ]
 
 EXPORTS.mozilla += [
     'GpuDecoderModule.h',
+    'IRemoteDecoderChild.h',
     'RDDChild.h',
     'RDDParent.h',
     'RDDProcessHost.h',
     'RDDProcessImpl.h',
     'RDDProcessManager.h',
     'RemoteDecoderManagerChild.h',
     'RemoteDecoderManagerParent.h',
     'RemoteDecoderModule.h',
     'RemoteMediaDataDecoder.h',
+    'RemoteVideoDecoderChild.h',
+    'RemoteVideoDecoderParent.h',
+    'VideoDecoderChild.h',
     'VideoDecoderManagerChild.h',
     'VideoDecoderManagerParent.h',
+    'VideoDecoderParent.h',
 ]
 
 EXPORTS.mozilla.dom += [
     'MediaIPCUtils.h',
 ]
 
 SOURCES += [
     'GpuDecoderModule.cpp',
--- a/dom/serviceworkers/ServiceWorkerManager.cpp
+++ b/dom/serviceworkers/ServiceWorkerManager.cpp
@@ -3005,10 +3005,17 @@ void ServiceWorkerManager::MaybeSendUnre
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return;
   }
 
   Unused << mActor->SendUnregister(principalInfo,
                                    NS_ConvertUTF8toUTF16(aScope));
 }
 
+NS_IMETHODIMP
+ServiceWorkerManager::IsParentInterceptEnabled(bool* aIsEnabled) {
+  MOZ_ASSERT(NS_IsMainThread());
+  *aIsEnabled = ServiceWorkerParentInterceptEnabled();
+  return NS_OK;
+}
+
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/u2f/moz.build
+++ b/dom/u2f/moz.build
@@ -1,16 +1,16 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 with Files("**"):
-    BUG_COMPONENT = ("Core", "DOM: Device Interfaces")
+    BUG_COMPONENT = ("Core", "DOM: Web Authentication")
 
 EXPORTS.mozilla.dom += [
     'U2F.h',
     'U2FAuthenticator.h',
 ]
 
 UNIFIED_SOURCES += [
     'U2F.cpp',
--- a/dom/webauthn/moz.build
+++ b/dom/webauthn/moz.build
@@ -1,16 +1,16 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 with Files("**"):
-    BUG_COMPONENT = ("Core", "DOM: Device Interfaces")
+    BUG_COMPONENT = ("Core", "DOM: Web Authentication")
 
 IPDL_SOURCES += [
     'PWebAuthnTransaction.ipdl'
 ]
 
 XPIDL_SOURCES += [
     'nsIU2FTokenManager.idl'
 ]
--- a/gfx/layers/wr/WebRenderCommandBuilder.cpp
+++ b/gfx/layers/wr/WebRenderCommandBuilder.cpp
@@ -2043,17 +2043,17 @@ WebRenderCommandBuilder::GenerateFallbac
       aItem->Frame()->PresContext()->AppUnitsPerDevPixel();
   auto bounds =
       LayoutDeviceRect::FromAppUnits(paintBounds, appUnitsPerDevPixel);
   if (bounds.IsEmpty()) {
     return nullptr;
   }
 
   gfx::Size scale = aSc.GetInheritedScale();
-  gfx::Size oldScale = fallbackData->GetScale();
+  gfx::Size oldScale = fallbackData->mScale;
   // We tolerate slight changes in scale so that we don't, for example,
   // rerasterize on MotionMark
   bool differentScale = gfx::FuzzyEqual(scale.width, oldScale.width, 1e-6f) &&
                         gfx::FuzzyEqual(scale.height, oldScale.height, 1e-6f);
 
   LayoutDeviceToLayerScale2D layerScale(scale.width, scale.height);
 
   auto trans =
@@ -2077,17 +2077,17 @@ WebRenderCommandBuilder::GenerateFallbac
   if (dtSize.IsEmpty()) {
     return nullptr;
   }
 
   aImageRect = dtRect / layerScale;
 
   auto offset = aImageRect.TopLeft();
 
-  nsDisplayItemGeometry* geometry = fallbackData->GetGeometry();
+  nsDisplayItemGeometry* geometry = fallbackData->mGeometry;
 
   bool needPaint = true;
 
   // nsDisplayFilters is rendered via BasicLayerManager which means the
   // invalidate region is unknown until we traverse the displaylist contained by
   // it.
   if (geometry && !fallbackData->IsInvalid() &&
       aItem->GetType() != DisplayItemType::TYPE_FILTER &&
@@ -2098,31 +2098,31 @@ WebRenderCommandBuilder::GenerateFallbac
     if (aItem->IsInvalid(invalid)) {
       invalidRegion.OrWith(paintBounds);
     } else {
       nsPoint shift = itemBounds.TopLeft() - geometry->mBounds.TopLeft();
       geometry->MoveBy(shift);
       aItem->ComputeInvalidationRegion(aDisplayListBuilder, geometry,
                                        &invalidRegion);
 
-      nsRect lastBounds = fallbackData->GetBounds();
+      nsRect lastBounds = fallbackData->mBounds;
       lastBounds.MoveBy(shift);
 
       if (!lastBounds.IsEqualInterior(paintBounds)) {
         invalidRegion.OrWith(lastBounds);
         invalidRegion.OrWith(paintBounds);
       }
     }
     needPaint = !invalidRegion.IsEmpty();
   }
 
   if (needPaint || !fallbackData->GetImageKey()) {
     nsAutoPtr<nsDisplayItemGeometry> newGeometry;
     newGeometry = aItem->AllocateGeometry(aDisplayListBuilder);
-    fallbackData->SetGeometry(std::move(newGeometry));
+    fallbackData->mGeometry = std::move(newGeometry);
 
     gfx::SurfaceFormat format = aItem->GetType() == DisplayItemType::TYPE_MASK
                                     ? gfx::SurfaceFormat::A8
                                     : gfx::SurfaceFormat::B8G8R8A8;
     if (useBlobImage) {
       bool snapped;
       wr::OpacityType opacity =
           aItem->GetOpaqueRegion(aDisplayListBuilder, &snapped)
@@ -2190,18 +2190,20 @@ WebRenderCommandBuilder::GenerateFallbac
           return nullptr;
         }
       }
       aResources.SetBlobImageVisibleArea(
           fallbackData->GetBlobImageKey().value(),
           ViewAs<ImagePixel>(visibleRect,
                              PixelCastJustification::LayerIsImage));
     } else {
-      fallbackData->CreateImageClientIfNeeded();
-      RefPtr<ImageClient> imageClient = fallbackData->GetImageClient();
+      WebRenderImageData* imageData = fallbackData->PaintIntoImage();
+
+      imageData->CreateImageClientIfNeeded();
+      RefPtr<ImageClient> imageClient = imageData->GetImageClient();
       RefPtr<ImageContainer> imageContainer =
           LayerManager::CreateImageContainer();
       bool isInvalidated = false;
 
       {
         UpdateImageHelper helper(imageContainer, imageClient,
                                  dtSize.ToUnknownSize(), format);
         {
@@ -2221,38 +2223,38 @@ WebRenderCommandBuilder::GenerateFallbac
         if (isInvalidated) {
           // Update image if there it's invalidated.
           if (!helper.UpdateImage()) {
             return nullptr;
           }
         } else {
           // If there is no invalidation region and we don't have a image key,
           // it means we don't need to push image for the item.
-          if (!fallbackData->GetImageKey().isSome()) {
+          if (!imageData->GetImageKey().isSome()) {
             return nullptr;
           }
         }
       }
 
       // Force update the key in fallback data since we repaint the image in
       // this path. If not force update, fallbackData may reuse the original key
       // because it doesn't know UpdateImageHelper already updated the image
       // container.
       if (isInvalidated &&
-          !fallbackData->UpdateImageKey(imageContainer, aResources, true)) {
+          !imageData->UpdateImageKey(imageContainer, aResources, true)) {
         return nullptr;
       }
     }
 
-    fallbackData->SetScale(scale);
+    fallbackData->mScale = scale;
     fallbackData->SetInvalid(false);
   }
 
   // Update current bounds to fallback data
-  fallbackData->SetBounds(paintBounds);
+  fallbackData->mBounds = paintBounds;
 
   MOZ_ASSERT(fallbackData->GetImageKey());
 
   return fallbackData.forget();
 }
 
 class WebRenderMaskData : public WebRenderUserData {
  public:
--- a/gfx/layers/wr/WebRenderUserData.cpp
+++ b/gfx/layers/wr/WebRenderUserData.cpp
@@ -61,16 +61,25 @@ void WebRenderBackgroundData::AddWebRend
     return true;
   }
 
   aFrame->SchedulePaint();
   return false;
 }
 
 WebRenderUserData::WebRenderUserData(RenderRootStateManager* aManager,
+                                     uint32_t aDisplayItemKey,
+                                     nsIFrame* aFrame)
+    : mManager(aManager),
+      mFrame(aFrame),
+      mDisplayItemKey(aDisplayItemKey),
+      mTable(aManager->GetWebRenderUserDataTable()),
+      mUsed(false) {}
+
+WebRenderUserData::WebRenderUserData(RenderRootStateManager* aManager,
                                      nsDisplayItem* aItem)
     : mManager(aManager),
       mFrame(aItem->Frame()),
       mDisplayItemKey(aItem->GetPerFrameKey()),
       mTable(aManager->GetWebRenderUserDataTable()),
       mUsed(false) {}
 
 WebRenderUserData::~WebRenderUserData() {}
@@ -80,16 +89,21 @@ void WebRenderUserData::RemoveFromTable(
 WebRenderBridgeChild* WebRenderUserData::WrBridge() const {
   return mManager->WrBridge();
 }
 
 WebRenderImageData::WebRenderImageData(RenderRootStateManager* aManager,
                                        nsDisplayItem* aItem)
     : WebRenderUserData(aManager, aItem), mOwnsKey(false) {}
 
+WebRenderImageData::WebRenderImageData(RenderRootStateManager* aManager,
+                                       uint32_t aDisplayItemKey,
+                                       nsIFrame* aFrame)
+    : WebRenderUserData(aManager, aDisplayItemKey, aFrame), mOwnsKey(false) {}
+
 WebRenderImageData::~WebRenderImageData() {
   ClearImageKey();
 
   if (mPipelineId) {
     mManager->RemovePipelineIdForCompositable(mPipelineId.ref());
   }
 }
 
@@ -261,52 +275,65 @@ void WebRenderImageData::CreateImageClie
     }
 
     mImageClient->Connect();
   }
 }
 
 WebRenderFallbackData::WebRenderFallbackData(RenderRootStateManager* aManager,
                                              nsDisplayItem* aItem)
-    : WebRenderImageData(aManager, aItem), mInvalid(false) {}
+    : WebRenderUserData(aManager, aItem), mInvalid(false) {}
 
 WebRenderFallbackData::~WebRenderFallbackData() { ClearImageKey(); }
 
-nsDisplayItemGeometry* WebRenderFallbackData::GetGeometry() {
-  return mGeometry.get();
-}
-
-void WebRenderFallbackData::SetGeometry(
-    nsAutoPtr<nsDisplayItemGeometry> aGeometry) {
-  mGeometry = aGeometry;
-}
-
 void WebRenderFallbackData::SetBlobImageKey(const wr::BlobImageKey& aKey) {
   ClearImageKey();
   mBlobKey = Some(aKey);
-  mOwnsKey = true;
 }
 
 Maybe<wr::ImageKey> WebRenderFallbackData::GetImageKey() {
   if (mBlobKey) {
     return Some(wr::AsImageKey(mBlobKey.value()));
   }
 
-  return mKey;
+  if (mImageData) {
+    return mImageData->GetImageKey();
+  }
+
+  return Nothing();
 }
 
 void WebRenderFallbackData::ClearImageKey() {
-  if (mBlobKey && mOwnsKey) {
+  if (mImageData) {
+    mImageData->ClearImageKey();
+    mImageData = nullptr;
+  }
+
+  if (mBlobKey) {
     mManager->AddBlobImageKeyForDiscard(mBlobKey.value());
+    mBlobKey.reset();
   }
-  mBlobKey.reset();
+}
+
+WebRenderImageData* WebRenderFallbackData::PaintIntoImage() {
+  if (mBlobKey) {
+    mManager->AddBlobImageKeyForDiscard(mBlobKey.value());
+    mBlobKey.reset();
+  }
 
-  WebRenderImageData::ClearImageKey();
+  if (mImageData) {
+    return mImageData.get();
+  }
+
+  mImageData = MakeAndAddRef<WebRenderImageData>(mManager.get(), mDisplayItemKey, mFrame);
+
+  return mImageData.get();
 }
 
+
 WebRenderAnimationData::WebRenderAnimationData(RenderRootStateManager* aManager,
                                                nsDisplayItem* aItem)
     : WebRenderUserData(aManager, aItem) {}
 
 WebRenderAnimationData::~WebRenderAnimationData() {
   // It may be the case that nsDisplayItem that created this WebRenderUserData
   // gets destroyed without getting a chance to discard the compositor animation
   // id, so we should do it as part of cleanup here.
--- a/gfx/layers/wr/WebRenderUserData.h
+++ b/gfx/layers/wr/WebRenderUserData.h
@@ -43,29 +43,32 @@ class WebRenderBackgroundData {
       : mBounds(aBounds), mColor(aColor) {}
   void AddWebRenderCommands(wr::DisplayListBuilder& aBuilder);
 
  protected:
   wr::LayoutRect mBounds;
   wr::ColorF mColor;
 };
 
+/// Parent class for arbitrary WebRender-specific data that can be associated
+/// to an nsFrame.
 class WebRenderUserData {
  public:
   typedef nsTHashtable<nsRefPtrHashKey<WebRenderUserData>>
       WebRenderUserDataRefTable;
 
   static bool SupportsAsyncUpdate(nsIFrame* aFrame);
 
   static bool ProcessInvalidateForImage(nsIFrame* aFrame,
                                         DisplayItemType aType);
 
   NS_INLINE_DECL_REFCOUNTING(WebRenderUserData)
 
   WebRenderUserData(RenderRootStateManager* aManager, nsDisplayItem* aItem);
+  WebRenderUserData(RenderRootStateManager* aManager, uint32_t mDisplayItemKey, nsIFrame* aFrame);
 
   virtual WebRenderImageData* AsImageData() { return nullptr; }
   virtual WebRenderFallbackData* AsFallbackData() { return nullptr; }
   virtual WebRenderCanvasData* AsCanvasData() { return nullptr; }
   virtual WebRenderGroupData* AsGroupData() { return nullptr; }
 
   enum class UserDataType {
     eImage,
@@ -114,26 +117,27 @@ struct WebRenderUserDataKey {
   WebRenderUserData::UserDataType mType;
 };
 
 typedef nsRefPtrHashtable<
     nsGenericHashKey<mozilla::layers::WebRenderUserDataKey>, WebRenderUserData>
     WebRenderUserDataTable;
 
 /// Holds some data used to share TextureClient/ImageClient with the parent
-/// process except if used with blob images (watch your step).
+/// process.
 class WebRenderImageData : public WebRenderUserData {
  public:
   WebRenderImageData(RenderRootStateManager* aManager, nsDisplayItem* aItem);
+  WebRenderImageData(RenderRootStateManager* aManager, uint32_t aDisplayItemKey, nsIFrame* aFrame);
   virtual ~WebRenderImageData();
 
   virtual WebRenderImageData* AsImageData() override { return this; }
   virtual UserDataType GetType() override { return UserDataType::eImage; }
   static UserDataType Type() { return UserDataType::eImage; }
-  virtual Maybe<wr::ImageKey> GetImageKey() { return mKey; }
+  Maybe<wr::ImageKey> GetImageKey() { return mKey; }
   void SetImageKey(const wr::ImageKey& aKey);
   already_AddRefed<ImageClient> GetImageClient();
 
   Maybe<wr::ImageKey> UpdateImageKey(ImageContainer* aContainer,
                                      wr::IpcResourceUpdateQueue& aResources,
                                      bool aFallback = false);
 
   void CreateAsyncImageWebRenderCommands(
@@ -144,71 +148,71 @@ class WebRenderImageData : public WebRen
       const wr::MixBlendMode& aMixBlendMode, bool aIsBackfaceVisible);
 
   void CreateImageClientIfNeeded();
 
   bool IsAsync() { return mPipelineId.isSome(); }
 
   bool UsingSharedSurface() const;
 
- protected:
-  virtual void ClearImageKey();
+  void ClearImageKey();
 
+ protected:
+  Maybe<wr::ImageKey> mKey;
   RefPtr<TextureClient> mTextureOfImage;
-  Maybe<wr::ImageKey> mKey;
   RefPtr<ImageClient> mImageClient;
   Maybe<wr::PipelineId> mPipelineId;
   RefPtr<ImageContainer> mContainer;
+  // The key can be owned by a shared surface that is used by several elements.
+  // when this is the case the shared surface is responsible for managing the
+  // destruction of the key.
+  // TODO: we surely can come up with a simpler/safer way to model this.
   bool mOwnsKey;
 };
 
 /// Used for fallback rendering.
 ///
 /// In most cases this uses blob images but it can also render on the content
 /// side directly into a texture.
-///
-/// TODO(nical) It would be much better to separate the two use cases into
-/// separate classes and not have the blob image related code inherit from
-/// WebRenderImageData (the current code only works if we carefully use a subset
-/// of the parent code).
-class WebRenderFallbackData : public WebRenderImageData {
+class WebRenderFallbackData : public WebRenderUserData {
  public:
   WebRenderFallbackData(RenderRootStateManager* aManager, nsDisplayItem* aItem);
   virtual ~WebRenderFallbackData();
 
   virtual WebRenderFallbackData* AsFallbackData() override { return this; }
   virtual UserDataType GetType() override { return UserDataType::eFallback; }
   static UserDataType Type() { return UserDataType::eFallback; }
-  nsDisplayItemGeometry* GetGeometry() override;
-  void SetGeometry(nsAutoPtr<nsDisplayItemGeometry> aGeometry);
-  nsRect GetBounds() { return mBounds; }
-  void SetBounds(const nsRect& aRect) { mBounds = aRect; }
+
   void SetInvalid(bool aInvalid) { mInvalid = aInvalid; }
-  void SetScale(gfx::Size aScale) { mScale = aScale; }
-  gfx::Size GetScale() { return mScale; }
   bool IsInvalid() { return mInvalid; }
   void SetFonts(const std::vector<RefPtr<gfx::ScaledFont>>& aFonts) {
     mFonts = aFonts;
   }
   Maybe<wr::BlobImageKey> GetBlobImageKey() { return mBlobKey; }
-  virtual Maybe<wr::ImageKey> GetImageKey() override;
   void SetBlobImageKey(const wr::BlobImageKey& aKey);
+  Maybe<wr::ImageKey> GetImageKey();
+
+  /// Create a WebRenderImageData to manage the image we are about to render into.
+  WebRenderImageData* PaintIntoImage();
 
+  std::vector<RefPtr<gfx::SourceSurface>> mExternalSurfaces;
   RefPtr<BasicLayerManager> mBasicLayerManager;
-  std::vector<RefPtr<gfx::SourceSurface>> mExternalSurfaces;
+  nsAutoPtr<nsDisplayItemGeometry> mGeometry;
+  nsRect mBounds;
+  gfx::Size mScale;
 
  protected:
-  virtual void ClearImageKey() override;
+  void ClearImageKey();
 
+  std::vector<RefPtr<gfx::ScaledFont>> mFonts;
   Maybe<wr::BlobImageKey> mBlobKey;
-  nsAutoPtr<nsDisplayItemGeometry> mGeometry;
-  nsRect mBounds;
+  // When rendering into a blob image, mImageData is null. It is non-null only when
+  // we render directly into a texture on the content side.
+  RefPtr<WebRenderImageData> mImageData;
   bool mInvalid;
-  gfx::Size mScale;
-  std::vector<RefPtr<gfx::ScaledFont>> mFonts;
 };
 
 class WebRenderAnimationData : public WebRenderUserData {
  public:
   WebRenderAnimationData(RenderRootStateManager* aManager,
                          nsDisplayItem* aItem);
   virtual ~WebRenderAnimationData();
 
--- a/ipc/ipdl/ipdl/direct_call.py
+++ b/ipc/ipdl/ipdl/direct_call.py
@@ -249,28 +249,24 @@ VIRTUAL_CALL_CLASSES = set([
     ("PPluginBackgroundDestroyer", "parent"),
     ("PPrintProgressDialog", "child"),
     ("PPrintProgressDialog", "parent"),
     ("PPrintSettingsDialog", "child"),
     ("PPrintSettingsDialog", "parent"),
     ("PQuota", "child"),
     ("PQuotaRequest", "child"),
     ("PQuotaUsageRequest", "child"),
-    ("PRemoteVideoDecoder", "child"),
-    ("PRemoteVideoDecoder", "parent"),
     ("PServiceWorker", "child"),
     ("PServiceWorker", "parent"),
     ("PServiceWorkerContainer", "child"),
     ("PServiceWorkerContainer", "parent"),
     ("PServiceWorkerRegistration", "child"),
     ("PServiceWorkerRegistration", "parent"),
     ("PServiceWorkerUpdater", "child"),
     ("PServiceWorkerUpdater", "parent"),
-    ("PVideoDecoder", "child"),
-    ("PVideoDecoder", "parent"),
     ("PVRLayer", "parent"),
     ("PWebBrowserPersistResources", "child"),
     ("PWebBrowserPersistResources", "parent"),
     ("PWebBrowserPersistSerialize", "child"),
     ("PWebBrowserPersistSerialize", "parent"),
     ("PWebrtcGlobal", "child"),
     ("PWebrtcGlobal", "parent"),
 
--- a/js/moz.configure
+++ b/js/moz.configure
@@ -483,21 +483,19 @@ def enable_pipeline_operator(value):
 set_config('ENABLE_PIPELINE_OPERATOR', enable_pipeline_operator)
 set_define('ENABLE_PIPELINE_OPERATOR', enable_pipeline_operator)
 
 
 
 # Experimental support for BinAST
 # ==============================================================
 
-@depends(target, milestone)
-def enable_build_binast(target, milestone):
-    # For reasons unknown at this time, BinAST causes timeouts on win32
-    # and failures on Android.
-    if milestone.is_nightly and not (target.kernel == 'WINNT' and target.cpu == 'x86') and not (target.os == 'Android'):
+@depends(milestone)
+def enable_build_binast(milestone):
+    if milestone.is_nightly:
         return True
 
 set_define('JS_BUILD_BINAST', enable_build_binast)
 set_config('JS_BUILD_BINAST', enable_build_binast)
 
 
 # Experimental support for wasm code generation with Cranelift
 # ==============================================================
--- a/js/public/Promise.h
+++ b/js/public/Promise.h
@@ -2,60 +2,273 @@
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * 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/. */
 
 #ifndef js_Promise_h
 #define js_Promise_h
 
+#include "mozilla/Attributes.h"
+#include "mozilla/GuardObjects.h"
+
 #include "jspubtd.h"
 #include "js/RootingAPI.h"
 #include "js/TypeDecls.h"
+#include "js/UniquePtr.h"
 
 namespace JS {
 
-typedef JSObject* (*GetIncumbentGlobalCallback)(JSContext* cx);
+class JS_PUBLIC_API AutoDebuggerJobQueueInterruption;
+
+/**
+ * Abstract base class for an ECMAScript Job Queue:
+ * https://www.ecma-international.org/ecma-262/9.0/index.html#sec-jobs-and-job-queues
+ *
+ * SpiderMonkey doesn't schedule Promise resolution jobs itself; instead, the
+ * embedding can provide an instance of this class SpiderMonkey can use to do
+ * that scheduling.
+ *
+ * The JavaScript shell includes a simple implementation adequate for running
+ * tests. Browsers need to augment job handling to meet their own additional
+ * requirements, so they can provide their own implementation.
+ */
+class JS_PUBLIC_API JobQueue {
+ public:
+  virtual ~JobQueue() = default;
+
+  /**
+   * Ask the embedding for the incumbent global.
+   *
+   * SpiderMonkey doesn't itself have a notion of incumbent globals as defined
+   * by the HTML spec, so we need the embedding to provide this. See
+   * dom/script/ScriptSettings.h for details.
+   */
+  virtual JSObject* getIncumbentGlobal(JSContext* cx) = 0;
+
+  /**
+   * Enqueue a reaction job `job` for `promise`, which was allocated at
+   * `allocationSite`. Provide `incumbentGlobal` as the incumbent global for
+   * the reaction job's execution.
+   */
+  virtual bool enqueuePromiseJob(JSContext* cx, JS::HandleObject promise,
+                                 JS::HandleObject job,
+                                 JS::HandleObject allocationSite,
+                                 JS::HandleObject incumbentGlobal) = 0;
+
+  /**
+   * Run all jobs in the queue. Running one job may enqueue others; continue to
+   * run jobs until the queue is empty.
+   *
+   * Calling this method at the wrong time can break the web. The HTML spec
+   * indicates exactly when the job queue should be drained (in HTML jargon,
+   * when it should "perform a microtask checkpoint"), and doing so at other
+   * times can incompatibly change the semantics of programs that use promises
+   * or other microtask-based features.
+   *
+   * This method is called only via AutoDebuggerJobQueueInterruption, used by
+   * the Debugger API implementation to ensure that the debuggee's job queue is
+   * protected from the debugger's own activity. See the comments on
+   * AutoDebuggerJobQueueInterruption.
+   */
+  virtual void runJobs(JSContext* cx) = 0;
+
+  /**
+   * Return true if the job queue is empty, false otherwise.
+   */
+  virtual bool empty() const = 0;
+
+ protected:
+  friend class AutoDebuggerJobQueueInterruption;
+
+  /**
+   * A saved job queue, represented however the JobQueue implementation pleases.
+   * Use AutoDebuggerJobQueueInterruption rather than trying to construct one of
+   * these directly; see documentation there.
+   *
+   * Destructing an instance of this class should assert that the current queue
+   * is empty, and then restore the queue the instance captured.
+   */
+  class SavedJobQueue {
+   public:
+    virtual ~SavedJobQueue() = default;
+  };
+
+  /**
+   * Capture this JobQueue's current job queue as a SavedJobQueue and return it,
+   * leaving the JobQueue's job queue empty. Destroying the returned object
+   * should assert that this JobQueue's current job queue is empty, and restore
+   * the original queue.
+   *
+   * On OOM, this should call JS_ReportOutOfMemory on the given JSContext,
+   * and return a null UniquePtr.
+   */
+  virtual js::UniquePtr<SavedJobQueue> saveJobQueue(JSContext*) = 0;
+};
+
+/**
+ * Tell SpiderMonkey to use `queue` to schedule promise reactions.
+ *
+ * SpiderMonkey does not take ownership of the queue; it is the embedding's
+ * responsibility to clean it up after the runtime is destroyed.
+ */
+extern JS_PUBLIC_API void SetJobQueue(JSContext* cx, JobQueue* queue);
 
-typedef bool (*EnqueuePromiseJobCallback)(JSContext* cx,
-                                          JS::HandleObject promise,
-                                          JS::HandleObject job,
-                                          JS::HandleObject allocationSite,
-                                          JS::HandleObject incumbentGlobal,
-                                          void* data);
+/**
+ * [SMDOC] Protecting the debuggee's job/microtask queue from debugger activity.
+ *
+ * When the JavaScript debugger interrupts the execution of some debuggee code
+ * (for a breakpoint, for example), the debuggee's execution must be paused
+ * while the developer takes time to look at it. During this interruption, other
+ * tabs should remain active and usable. If the debuggee shares a main thread
+ * with non-debuggee tabs, that means that the thread will have to process
+ * non-debuggee HTML tasks and microtasks as usual, even as the debuggee's are
+ * on hold until the debugger lets it continue execution. (Letting debuggee
+ * microtasks run during the interruption would mean that, from the debuggee's
+ * point of view, their side effects would take place wherever the breakpoint
+ * was set - in general, not a place other code should ever run, and a violation
+ * of the run-to-completion rule.)
+ *
+ * This means that, even though the timing and ordering of microtasks is
+ * carefully specified by the standard - and important to preserve for
+ * compatibility and predictability - debugger use may, correctly, have the
+ * effect of reordering microtasks. During the interruption, microtasks enqueued
+ * by non-debuggee tabs must run immediately alongside their HTML tasks as
+ * usual, whereas any debuggee microtasks that were in the queue when the
+ * interruption began must wait for the debuggee to be continued - and thus run
+ * after microtasks enqueued after they were.
+ *
+ * Fortunately, this reordering is visible olny at the global level: when
+ * implemented correctly, it is not detectable by an individual debuggee. Note
+ * that a debuggee should generally be a complete unit of similar-origin related
+ * browsing contexts. Since non-debuggee activity falls outside that unit, it
+ * should never be visible to the debuggee (except via mechanisms that are
+ * already asynchronous, like events), so the debuggee should be unable to
+ * detect non-debuggee microtasks running when they normally would not. As long
+ * as behavior *visible to the debuggee* is unaffected by the interruption, we
+ * have respected the spirit of the rule.
+ *
+ * Of course, even as we accept the general principle that interrupting the
+ * debuggee should have as little detectable effect as possible, we still permit
+ * the developer to do things like evaluate expressions at the console that have
+ * arbitrary effects on the debuggee's state—effects that could never occur
+ * naturally at that point in the program. But since these are explicitly
+ * requested by the developer, who presumably knows what they're doing, we
+ * support this as best we can. If the developer evaluates an expression in the
+ * console that resolves a promise, it seems most natural for the promise's
+ * reaction microtasks to run immediately, within the interruption. This is an
+ * 'unnatural' time for the microtasks to run, but no more unnatural than the
+ * evaluation that triggered them.
+ *
+ * So the overall behavior we need is as follows:
+ *
+ * - When the debugger interrupts a debuggee, the debuggee's microtask queue
+ *   must be saved.
+ *
+ * - When debuggee execution resumes, the debuggee's microtask queue must be
+ *   restored exactly as it was when the interruption occurred.
+ *
+ * - Non-debuggee task and microtask execution must take place normally during
+ *   the interruption.
+ *
+ * Since each HTML task begins with an empty microtask queue, and it should not
+ * be possible for a task to mix debuggee and non-debuggee code, interrupting a
+ * debuggee should always find a microtask queue containing exclusively debuggee
+ * microtasks, if any. So saving and restoring the microtask queue should affect
+ * only the debuggee, not any non-debuggee content.
+ *
+ * AutoDebuggerJobQueueInterruption
+ * --------------------------------
+ *
+ * AutoDebuggerJobQueueInterruption is an RAII class, meant for use by the
+ * Debugger API implementation, that takes care of saving and restoring the
+ * queue.
+ *
+ * Constructing and initializing an instance of AutoDebuggerJobQueueInterruption
+ * sets aside the given JSContext's job queue, leaving the JSContext's queue
+ * empty. When the AutoDebuggerJobQueueInterruption instance is destroyed, it
+ * asserts that the JSContext's current job queue (holding jobs enqueued while
+ * the AutoDebuggerJobQueueInterruption was alive) is empty, and restores the
+ * saved queue to the JSContext.
+ *
+ * Since the Debugger API's behavior is up to us, we can specify that Debugger
+ * hooks begin execution with an empty job queue, and that we drain the queue
+ * after each hook function has run. This drain will be visible to debugger
+ * hooks, and makes hook calls resemble HTML tasks, with their own automatic
+ * microtask checkpoint. But, the drain will be invisible to the debuggee, as
+ * its queue is preserved across the hook invocation.
+ *
+ * To protect the debuggee's job queue, Debugger takes care to invoke callback
+ * functions only within the scope of an AutoDebuggerJobQueueInterruption
+ * instance.
+ *
+ * Why not let the hook functions themselves take care of this?
+ * ------------------------------------------------------------
+ *
+ * Certainly, we could leave responsibility for saving and restoring the job
+ * queue to the Debugger hook functions themselves.
+ *
+ * In fact, early versions of this change tried making the devtools server save
+ * and restore the queue explicitly, but because hooks are set and changed in
+ * numerous places, it was hard to be confident that every case had been
+ * covered, and it seemed that future changes could easily introduce new holes.
+ *
+ * Later versions of this change modified the accessor properties on the
+ * Debugger objects' prototypes to automatically protect the job queue when
+ * calling hooks, but the effect was essentially a monkeypatch applied to an API
+ * we defined and control, which doesn't make sense.
+ *
+ * In the end, since promises have become such a pervasive part of JavaScript
+ * programming, almost any imaginable use of Debugger would need to provide some
+ * kind of protection for the debuggee's job queue, so it makes sense to simply
+ * handle it once, carefully, in the implementation of Debugger itself.
+ */
+class MOZ_RAII JS_PUBLIC_API AutoDebuggerJobQueueInterruption {
+ public:
+  explicit AutoDebuggerJobQueueInterruption(
+      MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM);
+  ~AutoDebuggerJobQueueInterruption();
+
+  bool init(JSContext* cx);
+  bool initialized() const { return !!saved; }
+
+  /**
+   * Drain the job queue. (In HTML terminology, perform a microtask checkpoint.)
+   *
+   * To make Debugger hook calls more like HTML tasks or ECMAScript jobs,
+   * Debugger promises that each hook begins execution with a clean microtask
+   * queue, and that a microtask checkpoint (queue drain) takes place after each
+   * hook returns, successfully or otherwise.
+   *
+   * To ensure these debugger-introduced microtask checkpoints serve only the
+   * hook's microtasks, and never affect the debuggee's, the Debugger API
+   * implementation uses only this method to perform the checkpoints, thereby
+   * statically ensuring that an AutoDebuggerJobQueueInterruption is in scope to
+   * protect the debuggee.
+   *
+   * SavedJobQueue implementations are required to assert that the queue is
+   * empty before restoring the debuggee's queue. If the Debugger API ever fails
+   * to perform a microtask checkpoint after calling a hook, that assertion will
+   * fail, catching the mistake.
+   */
+  void runJobs();
+
+ private:
+  MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER;
+  JSContext* cx;
+  js::UniquePtr<JobQueue::SavedJobQueue> saved;
+};
 
 enum class PromiseRejectionHandlingState { Unhandled, Handled };
 
 typedef void (*PromiseRejectionTrackerCallback)(
     JSContext* cx, JS::HandleObject promise,
     JS::PromiseRejectionHandlingState state, void* data);
 
 /**
- * Sets the callback that's invoked whenever an incumbent global is required.
- *
- * SpiderMonkey doesn't itself have a notion of incumbent globals as defined
- * by the html spec, so we need the embedding to provide this.
- * See dom/base/ScriptSettings.h for details.
- */
-extern JS_PUBLIC_API void SetGetIncumbentGlobalCallback(
-    JSContext* cx, GetIncumbentGlobalCallback callback);
-
-/**
- * Sets the callback that's invoked whenever a Promise job should be enqeued.
- *
- * SpiderMonkey doesn't schedule Promise resolution jobs itself; instead,
- * using this function the embedding can provide a callback to do that
- * scheduling. The provided `callback` is invoked with the promise job,
- * the corresponding Promise's allocation stack, and the `data` pointer
- * passed here as arguments.
- */
-extern JS_PUBLIC_API void SetEnqueuePromiseJobCallback(
-    JSContext* cx, EnqueuePromiseJobCallback callback, void* data = nullptr);
-
-/**
  * Sets the callback that's invoked whenever a Promise is rejected without
  * a rejection handler, and when a Promise that was previously rejected
  * without a handler gets a handler attached.
  */
 extern JS_PUBLIC_API void SetPromiseRejectionTrackerCallback(
     JSContext* cx, PromiseRejectionTrackerCallback callback,
     void* data = nullptr);
 
--- a/js/src/builtin/Promise.cpp
+++ b/js/src/builtin/Promise.cpp
@@ -6,16 +6,17 @@
 
 #include "builtin/Promise.h"
 
 #include "mozilla/Atomics.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/Move.h"
 #include "mozilla/TimeStamp.h"
 
+#include "jsapi.h"
 #include "jsexn.h"
 #include "jsfriendapi.h"
 
 #include "gc/Heap.h"
 #include "js/Debug.h"
 #include "js/PropertySpec.h"
 #include "vm/AsyncFunction.h"
 #include "vm/AsyncIteration.h"
@@ -5166,16 +5167,38 @@ void OffThreadPromiseRuntimeState::shutd
   numCanceled_ = 0;
 
   // After shutdown, there should be no OffThreadPromiseTask activity in this
   // JSRuntime. Revert to the !initialized() state to catch bugs.
   dispatchToEventLoopCallback_ = nullptr;
   MOZ_ASSERT(!initialized());
 }
 
+JS::AutoDebuggerJobQueueInterruption::AutoDebuggerJobQueueInterruption(
+    MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL)
+    : cx(nullptr) {
+  MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+}
+
+JS::AutoDebuggerJobQueueInterruption::~AutoDebuggerJobQueueInterruption() {
+  MOZ_ASSERT_IF(cx, cx->jobQueue->empty());
+}
+
+bool JS::AutoDebuggerJobQueueInterruption::init(JSContext* cx) {
+  MOZ_ASSERT(cx->jobQueue);
+  this->cx = cx;
+  saved = cx->jobQueue->saveJobQueue(cx);
+  return !!saved;
+}
+
+void JS::AutoDebuggerJobQueueInterruption::runJobs() {
+  JS::AutoSaveExceptionState ases(cx);
+  cx->jobQueue->runJobs(cx);
+}
+
 const JSJitInfo promise_then_info = {
     {(JSJitGetterOp)Promise_then_noRetVal},
     {0}, /* unused */
     {0}, /* unused */
     JSJitInfo::IgnoresReturnValueNative,
     JSJitInfo::AliasEverything,
     JSVAL_TYPE_UNDEFINED,
 };
--- a/js/src/doc/Debugger/Debugger.Script.md
+++ b/js/src/doc/Debugger/Debugger.Script.md
@@ -200,16 +200,164 @@ from its prototype:
     **If the instance refers to WebAssembly code**, `"wasm"`.
 
 ## Function Properties of the Debugger.Script Prototype Object
 
 The functions described below may only be called with a `this` value
 referring to a `Debugger.Script` instance; they may not be used as
 methods of other kinds of objects.
 
+`getChildScripts()`
+:   **If the instance refers to a `JSScript`**, return a new array whose
+    elements are Debugger.Script objects for each function
+    in this script. Only direct children are included; nested
+    children can be reached by walking the tree.
+
+    **If the instance refers to WebAssembly code**, throw a `TypeError`.
+
+<code>getPossibleBreakpoints(<i>query</i>)</code>
+:   Query for the recommended breakpoint locations available in SpiderMonkey.
+    Returns a result array of objects with the following properties:
+      * `offset: number` - The offset the breakpoint.
+      * `lineNumber: number` - The line number of the breakpoint.
+      * `columnNumber: number` - The column number of the breakpoint.
+      * `isStepStart: boolean` - True if SpiderMonkey recommends that the
+        breakpoint be treated as a step location when users of debuggers
+        step to the next item. This _roughly_ translates to the start of
+        each statement, though not entirely.
+
+    The `query` argument can be used to filter the set of breakpoints.
+    The `query` object can contain the following properties:
+
+      * `minOffset: number` - The inclusive lower bound of `offset` values to include.
+      * `maxOffset: number` - The exclusive upper bound of `offset` values to include.
+      * `line: number` - Limit to breakpoints on the given line.
+      * `minLine: number` - The inclusive lower bound of lines to include.
+      * `minColumn: number` - The inclusive lower bound of the line/minLine column to include.
+      * `maxLine: number` - The exclusive upper bound of lines to include.
+      * `maxColumn: number` - The exclusive upper bound of the line/maxLine column to include.
+
+<code>getPossibleBreakpointOffsets(<i>query</i>)</code>
+:   Query for the recommended breakpoint locations available in SpiderMonkey.
+    Identical to getPossibleBreakpoints except this returns an array of `offset`
+    values instead of offset metadata objects.
+
+<code>getOffsetMetadata(<i>offset</i>)</code>
+:   Get metadata about a given bytecode offset.
+    Returns an object with the following properties:
+      * `lineNumber: number` - The line number of the breakpoint.
+      * `columnNumber: number` - The column number of the breakpoint.
+      * `isBreakpoint: boolean` - True if this offset qualifies as a breakpoint,
+        defined using the same semantics used for `getPossibleBreakpoints()`.
+      * `isStepStart: boolean` - True if SpiderMonkey recommends that the
+        breakpoint be treated as a step location when users of debuggers
+        step to the next item. This _roughly_ translates to the start of
+        each statement, though not entirely.
+
+<code>setBreakpoint(<i>offset</i>, <i>handler</i>)</code>
+:   **If the instance refers to a `JSScript`**, set a breakpoint at the
+    bytecode instruction at <i>offset</i> in this script, reporting hits to
+    the `hit` method of <i>handler</i>. If <i>offset</i> is not a valid offset
+    in this script, throw an error.
+
+    When execution reaches the given instruction, SpiderMonkey calls the
+    `hit` method of <i>handler</i>, passing a [`Debugger.Frame`][frame]
+    instance representing the currently executing stack frame. The `hit`
+    method's return value should be a [resumption value][rv], determining
+    how execution should continue.
+
+    Any number of breakpoints may be set at a single location; when control
+    reaches that point, SpiderMonkey calls their handlers in an unspecified
+    order.
+
+    Any number of breakpoints may use the same <i>handler</i> object.
+
+    Breakpoint handler method calls are cross-compartment, intra-thread
+    calls: the call takes place in the same thread that hit the breakpoint,
+    and in the compartment containing the handler function (typically the
+    debugger's compartment).
+
+    The new breakpoint belongs to the [`Debugger`][debugger-object] instance to
+    which this script belongs. Disabling the [`Debugger`][debugger-object]
+    instance disables this breakpoint; and removing a global from the
+    [`Debugger`][debugger-object] instance's set of debuggees clears all the
+    breakpoints belonging to that [`Debugger`][debugger-object] instance in that
+    global's scripts.
+
+<code>getBreakpoints([<i>offset</i>])</code>
+:   **If the instance refers to a `JSScript`**, return an array containing the
+    handler objects for all the breakpoints set at <i>offset</i> in this
+    script. If <i>offset</i> is omitted, return the handlers of all
+    breakpoints set anywhere in this script. If <i>offset</i> is present, but
+    not a valid offset in this script, throw an error.
+
+    **If the instance refers to WebAssembly code**, throw a `TypeError`.
+
+<code>clearBreakpoint(handler, [<i>offset</i>])</code>
+:   **If the instance refers to a `JSScript`**, remove all breakpoints set in
+    this [`Debugger`][debugger-object] instance that use <i>handler</i> as
+    their handler. If <i>offset</i> is given, remove only those breakpoints
+    set at <i>offset</i> that use <i>handler</i>; if <i>offset</i> is not a
+    valid offset in this script, throw an error.
+
+    Note that, if breakpoints using other handler objects are set at the
+    same location(s) as <i>handler</i>, they remain in place.
+
+<code>clearAllBreakpoints([<i>offset</i>])</code>
+:   **If the instance refers to a `JSScript`**, remove all breakpoints set in
+    this script. If <i>offset</i> is present, remove all breakpoints set at
+    that offset in this script; if <i>offset</i> is not a valid bytecode
+    offset in this script, throw an error.
+
+<code>getSuccessorOffsets(<i>offset</i>)</code>
+:   **If the instance refers to a `JSScript`**, return an array
+    containing the offsets of all bytecodes in the script which are
+    immediate successors of <i>offset</i> via non-exceptional control
+    flow paths.
+
+<code>getPredecessorOffsets(<i>offset</i>)</code>
+:   **If the instance refers to a `JSScript`**, return an array
+    containing the offsets of all bytecodes in the script for which
+    <i>offset</i> is an immediate successor via non-exceptional
+    control flow paths.
+
+`getOffsetsCoverage()`:
+:   **If the instance refers to a `JSScript`**, return `null` or an array which
+    contains information about the coverage of all opcodes. The elements of
+    the array are objects, each of which describes a single opcode, and
+    contains the following properties:
+
+    * lineNumber: the line number of the current opcode.
+
+    * columnNumber: the column number of the current opcode.
+
+    * offset: the bytecode instruction offset of the current opcode.
+
+    * count: the number of times the current opcode got executed.
+
+    If this script has no coverage, or if it is not instrumented, then this
+    function will return `null`. To ensure that the debuggee is instrumented,
+    the flag `Debugger.collectCoverageInfo` should be set to `true`.
+
+    **If the instance refers to WebAssembly code**, throw a `TypeError`.
+
+<code>isInCatchScope([<i>offset</i>])</code>
+:   **If the instance refers to a `JSScript`**, this is `true` if this offset
+    falls within the scope of a try block, and `false` otherwise.
+
+    **If the instance refers to WebAssembly code**, throw a `TypeError`.
+
+
+### Deprecated Debugger.Script Prototype Functions
+
+The following functions have all been deprecated in favor of `getOffsetMetadata`,
+`getPossibleBreakpoints`, and `getPossibleBreakpointOffsets`. These functions
+all have an under-defined concept of what offsets are and are not included
+in their results.
+
 `getAllOffsets()`
 :   **If the instance refers to a `JSScript`**, return an array <i>L</i>
     describing the relationship between bytecode instruction offsets and
     source code positions in this script. <i>L</i> is sparse, and indexed by
     source line number. If a source line number <i>line</i> has no code, then
     <i>L</i> has no <i>line</i> property. If there is code for <i>line</i>,
     then <code><i>L</i>[<i>line</i>]</code> is an array of offsets of byte
     code instructions that are entry points to that line.
@@ -291,109 +439,8 @@ methods of other kinds of objects.
     script.  The object has the following properties:
 
     * lineNumber: the line number for which offset is an entry point
 
     * columnNumber: the column number for which offset is an entry point
 
     * isEntryPoint: true if the offset is a column entry point, as
       would be reported by getAllColumnOffsets(); otherwise false.
-
-<code>getSuccessorOffsets(<i>offset</i>)</code>
-:   **If the instance refers to a `JSScript`**, return an array
-    containing the offsets of all bytecodes in the script which are
-    immediate successors of <i>offset</i> via non-exceptional control
-    flow paths.
-
-<code>getPredecessorOffsets(<i>offset</i>)</code>
-:   **If the instance refers to a `JSScript`**, return an array
-    containing the offsets of all bytecodes in the script for which
-    <i>offset</i> is an immediate successor via non-exceptional
-    control flow paths.
-
-`getOffsetsCoverage()`:
-:   **If the instance refers to a `JSScript`**, return `null` or an array which
-    contains information about the coverage of all opcodes. The elements of
-    the array are objects, each of which describes a single opcode, and
-    contains the following properties:
-
-    * lineNumber: the line number of the current opcode.
-
-    * columnNumber: the column number of the current opcode.
-
-    * offset: the bytecode instruction offset of the current opcode.
-
-    * count: the number of times the current opcode got executed.
-
-    If this script has no coverage, or if it is not instrumented, then this
-    function will return `null`. To ensure that the debuggee is instrumented,
-    the flag `Debugger.collectCoverageInfo` should be set to `true`.
-
-    **If the instance refers to WebAssembly code**, throw a `TypeError`.
-
-`getChildScripts()`
-:   **If the instance refers to a `JSScript`**, return a new array whose
-    elements are Debugger.Script objects for each function
-    in this script. Only direct children are included; nested
-    children can be reached by walking the tree.
-
-    **If the instance refers to WebAssembly code**, throw a `TypeError`.
-
-<code>setBreakpoint(<i>offset</i>, <i>handler</i>)</code>
-:   **If the instance refers to a `JSScript`**, set a breakpoint at the
-    bytecode instruction at <i>offset</i> in this script, reporting hits to
-    the `hit` method of <i>handler</i>. If <i>offset</i> is not a valid offset
-    in this script, throw an error.
-
-    When execution reaches the given instruction, SpiderMonkey calls the
-    `hit` method of <i>handler</i>, passing a [`Debugger.Frame`][frame]
-    instance representing the currently executing stack frame. The `hit`
-    method's return value should be a [resumption value][rv], determining
-    how execution should continue.
-
-    Any number of breakpoints may be set at a single location; when control
-    reaches that point, SpiderMonkey calls their handlers in an unspecified
-    order.
-
-    Any number of breakpoints may use the same <i>handler</i> object.
-
-    Breakpoint handler method calls are cross-compartment, intra-thread
-    calls: the call takes place in the same thread that hit the breakpoint,
-    and in the compartment containing the handler function (typically the
-    debugger's compartment).
-
-    The new breakpoint belongs to the [`Debugger`][debugger-object] instance to
-    which this script belongs. Disabling the [`Debugger`][debugger-object]
-    instance disables this breakpoint; and removing a global from the
-    [`Debugger`][debugger-object] instance's set of debuggees clears all the
-    breakpoints belonging to that [`Debugger`][debugger-object] instance in that
-    global's scripts.
-
-<code>getBreakpoints([<i>offset</i>])</code>
-:   **If the instance refers to a `JSScript`**, return an array containing the
-    handler objects for all the breakpoints set at <i>offset</i> in this
-    script. If <i>offset</i> is omitted, return the handlers of all
-    breakpoints set anywhere in this script. If <i>offset</i> is present, but
-    not a valid offset in this script, throw an error.
-
-    **If the instance refers to WebAssembly code**, throw a `TypeError`.
-
-<code>clearBreakpoint(handler, [<i>offset</i>])</code>
-:   **If the instance refers to a `JSScript`**, remove all breakpoints set in
-    this [`Debugger`][debugger-object] instance that use <i>handler</i> as
-    their handler. If <i>offset</i> is given, remove only those breakpoints
-    set at <i>offset</i> that use <i>handler</i>; if <i>offset</i> is not a
-    valid offset in this script, throw an error.
-
-    Note that, if breakpoints using other handler objects are set at the
-    same location(s) as <i>handler</i>, they remain in place.
-
-<code>clearAllBreakpoints([<i>offset</i>])</code>
-:   **If the instance refers to a `JSScript`**, remove all breakpoints set in
-    this script. If <i>offset</i> is present, remove all breakpoints set at
-    that offset in this script; if <i>offset</i> is not a valid bytecode
-    offset in this script, throw an error.
-
-<code>isInCatchScope([<i>offset</i>])</code>
-:   **If the instance refers to a `JSScript`**, this is `true` if this offset
-    falls within the scope of a try block, and `false` otherwise.
-
-    **If the instance refers to WebAssembly code**, throw a `TypeError`.
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -97,16 +97,19 @@ BytecodeEmitter::BytecodeEmitter(Bytecod
       parent(parent),
       script(cx, script),
       lazyScript(cx, lazyScript),
       code_(cx),
       notes_(cx),
       lastNoteOffset_(0),
       currentLine_(lineNum),
       lastColumn_(0),
+      lastSeparatorOffet_(0),
+      lastSeparatorLine_(0),
+      lastSeparatorColumn_(0),
       mainOffset_(),
       lastTarget{-1 - ptrdiff_t(JSOP_JUMPTARGET_LENGTH)},
       parser(nullptr),
       atomIndices(cx->frontendCollectionPool()),
       firstLine(lineNum),
       maxFixedSlots(0),
       maxStackDepth(0),
       stackDepth(0),
@@ -189,16 +192,60 @@ Maybe<NameLocation> BytecodeEmitter::loc
     JSAtom* name, EmitterScope* source) {
   EmitterScope* funScope = source;
   while (!funScope->scope(this)->is<FunctionScope>()) {
     funScope = funScope->enclosingInFrame();
   }
   return source->locationBoundInScope(name, funScope);
 }
 
+bool BytecodeEmitter::markStepBreakpoint() {
+  if (inPrologue()) {
+    return true;
+  }
+
+  if (!newSrcNote(SRC_STEP_SEP)) {
+    return false;
+  }
+
+  if (!newSrcNote(SRC_BREAKPOINT)) {
+    return false;
+  }
+
+  // We track the location of the most recent separator for use in
+  // markSimpleBreakpoint. Note that this means that the position must already
+  // be set before markStepBreakpoint is called.
+  lastSeparatorOffet_ = code().length();
+  lastSeparatorLine_ = currentLine_;
+  lastSeparatorColumn_ = lastColumn_;
+
+  return true;
+}
+
+bool BytecodeEmitter::markSimpleBreakpoint() {
+  if (inPrologue()) {
+    return true;
+  }
+
+  // If a breakable call ends up being the same location as the most recent
+  // expression start, we need to skip marking it breakable in order to avoid
+  // having two breakpoints with the same line/column position.
+  // Note: This assumes that the position for the call has already been set.
+  bool isDuplicateLocation =
+      lastSeparatorLine_ == currentLine_ && lastSeparatorColumn_ == lastColumn_;
+
+  if (!isDuplicateLocation) {
+    if (!newSrcNote(SRC_BREAKPOINT)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
 bool BytecodeEmitter::emitCheck(JSOp op, ptrdiff_t delta, ptrdiff_t* offset) {
   *offset = code().length();
 
   if (!code().growBy(delta)) {
     ReportOutOfMemory(cx);
     return false;
   }
 
@@ -515,16 +562,18 @@ bool BytecodeEmitter::updateLineNumberNo
       }
     } else {
       do {
         if (!newSrcNote(SRC_NEWLINE)) {
           return false;
         }
       } while (--delta != 0);
     }
+
+    updateSeparatorPosition();
   }
   return true;
 }
 
 /* Updates the line number and column number information in the source notes. */
 bool BytecodeEmitter::updateSourceCoordNotes(uint32_t offset) {
   if (!updateLineNumberNotes(offset)) {
     return false;
@@ -545,18 +594,27 @@ bool BytecodeEmitter::updateSourceCoordN
     // but it's better to fail soft here.
     if (!SN_REPRESENTABLE_COLSPAN(colspan)) {
       return true;
     }
     if (!newSrcNote2(SRC_COLSPAN, SN_COLSPAN_TO_OFFSET(colspan))) {
       return false;
     }
     lastColumn_ = columnIndex;
-  }
-  return true;
+    updateSeparatorPosition();
+  }
+  return true;
+}
+
+/* Updates the last separator position, if present */
+void BytecodeEmitter::updateSeparatorPosition() {
+  if (!inPrologue() && lastSeparatorOffet_ == code().length()) {
+    lastSeparatorLine_ = currentLine_;
+    lastSeparatorColumn_ = lastColumn_;
+  }
 }
 
 Maybe<uint32_t> BytecodeEmitter::getOffsetForLoop(ParseNode* nextpn) {
   if (!nextpn) {
     return Nothing();
   }
 
   // Try to give the JSOP_LOOPHEAD and JSOP_LOOPENTRY the same line number as
@@ -1941,17 +1999,17 @@ bool BytecodeEmitter::emitCallIncDec(Una
              incDec->isKind(ParseNodeKind::PostDecrementExpr));
 
   ParseNode* call = incDec->kid();
   MOZ_ASSERT(call->isKind(ParseNodeKind::CallExpr));
   if (!emitTree(call)) {
     //              [stack] CALLRESULT
     return false;
   }
-  if (!emit1(JSOP_POS)) {
+  if (!emit1(JSOP_TONUMERIC)) {
     //              [stack] N
     return false;
   }
 
   // The increment/decrement has no side effects, so proceed to throw for
   // invalid assignment target.
   return emitUint16Operand(JSOP_THROWMSG, JSMSG_BAD_LEFTSIDE_OF_ASS);
 }
@@ -2004,17 +2062,21 @@ bool BytecodeEmitter::emitNumberOp(doubl
  */
 MOZ_NEVER_INLINE bool BytecodeEmitter::emitSwitch(SwitchStatement* switchStmt) {
   LexicalScopeNode& lexical = switchStmt->lexicalForCaseList();
   MOZ_ASSERT(lexical.isKind(ParseNodeKind::LexicalScope));
   ListNode* cases = &lexical.scopeBody()->as<ListNode>();
   MOZ_ASSERT(cases->isKind(ParseNodeKind::StatementList));
 
   SwitchEmitter se(this);
-  if (!se.emitDiscriminant(Some(switchStmt->pn_pos.begin))) {
+  if (!se.emitDiscriminant(Some(switchStmt->discriminant().pn_pos.begin))) {
+    return false;
+  }
+
+  if (!markStepBreakpoint()) {
     return false;
   }
   if (!emitTree(&switchStmt->discriminant())) {
     return false;
   }
 
   // Enter the scope before pushing the switch BreakableControl since all
   // breaks are under this scope.
@@ -2387,16 +2449,19 @@ bool BytecodeEmitter::emitScript(ParseNo
     if (!emitTree(body)) {
       return false;
     }
 
     if (!updateSourceCoordNotes(body->pn_pos.end)) {
       return false;
     }
   }
+  if (!markSimpleBreakpoint()) {
+    return false;
+  }
 
   if (!emit1(JSOP_RETRVAL)) {
     return false;
   }
 
   if (!emitterScope.leave(this)) {
     return false;
   }
@@ -2449,16 +2514,19 @@ bool BytecodeEmitter::emitFunctionScript
   setFunctionBodyEndPos(body->pn_pos);
   if (!emitTree(body)) {
     return false;
   }
 
   if (!updateSourceCoordNotes(body->pn_pos.end)) {
     return false;
   }
+  if (!markSimpleBreakpoint()) {
+    return false;
+  }
 
   // Always end the script with a JSOP_RETRVAL. Some other parts of the
   // codebase depend on this opcode,
   // e.g. InterpreterRegs::setToEndOfScript.
   if (!emit1(JSOP_RETRVAL)) {
     return false;
   }
 
@@ -3925,28 +3993,30 @@ bool BytecodeEmitter::emitTemplateString
 
   return true;
 }
 
 bool BytecodeEmitter::emitDeclarationList(ListNode* declList) {
   MOZ_ASSERT(declList->isOp(JSOP_NOP));
 
   for (ParseNode* decl : declList->contents()) {
-    if (!updateSourceCoordNotes(decl->pn_pos.begin)) {
-      return false;
-    }
-
     if (decl->isKind(ParseNodeKind::AssignExpr)) {
       MOZ_ASSERT(decl->isOp(JSOP_NOP));
 
       AssignmentNode* assignNode = &decl->as<AssignmentNode>();
       ListNode* pattern = &assignNode->left()->as<ListNode>();
       MOZ_ASSERT(pattern->isKind(ParseNodeKind::ArrayExpr) ||
                  pattern->isKind(ParseNodeKind::ObjectExpr));
 
+      if (!updateSourceCoordNotes(assignNode->right()->pn_pos.begin)) {
+        return false;
+      }
+      if (!markStepBreakpoint()) {
+        return false;
+      }
       if (!emitTree(assignNode->right())) {
         return false;
       }
 
       if (!emitDestructuringOps(pattern, DestructuringDeclaration)) {
         return false;
       }
 
@@ -3984,16 +4054,23 @@ bool BytecodeEmitter::emitSingleDeclarat
                "var declarations without initializers handled above, "
                "and const declarations must have initializers");
     if (!emit1(JSOP_UNDEFINED)) {
       //            [stack] ENV? UNDEF
       return false;
     }
   } else {
     MOZ_ASSERT(initializer);
+
+    if (!updateSourceCoordNotes(initializer->pn_pos.begin)) {
+      return false;
+    }
+    if (!markStepBreakpoint()) {
+      return false;
+    }
     if (!emitInitializer(initializer, decl)) {
       //            [stack] ENV? V
       return false;
     }
   }
   if (!noe.emitAssignment()) {
     //              [stack] V
     return false;
@@ -4638,21 +4715,25 @@ MOZ_MUST_USE bool BytecodeEmitter::emitG
 
   SET_RESUMEINDEX(code(off), resumeIndex);
   return true;
 }
 
 bool BytecodeEmitter::emitIf(TernaryNode* ifNode) {
   IfEmitter ifThenElse(this);
 
-  if (!ifThenElse.emitIf(Some(ifNode->pn_pos.begin))) {
+  if (!ifThenElse.emitIf(Some(ifNode->kid1()->pn_pos.begin))) {
     return false;
   }
 
 if_again:
+  if (!markStepBreakpoint()) {
+    return false;
+  }
+
   /* Emit code for the condition before pushing stmtInfo. */
   if (!emitTree(ifNode->kid1())) {
     return false;
   }
 
   ParseNode* elseNode = ifNode->kid3();
   if (elseNode) {
     if (!ifThenElse.emitThenElse()) {
@@ -4668,17 +4749,17 @@ if_again:
   if (!emitTree(ifNode->kid2())) {
     return false;
   }
 
   if (elseNode) {
     if (elseNode->isKind(ParseNodeKind::IfStmt)) {
       ifNode = &elseNode->as<TernaryNode>();
 
-      if (!ifThenElse.emitElseIf(Some(ifNode->pn_pos.begin))) {
+      if (!ifThenElse.emitElseIf(Some(ifNode->kid1()->pn_pos.begin))) {
         return false;
       }
 
       goto if_again;
     }
 
     if (!ifThenElse.emitElse()) {
       return false;
@@ -4807,17 +4888,21 @@ MOZ_NEVER_INLINE bool BytecodeEmitter::e
   if (!lse.emitEnd()) {
     return false;
   }
   return true;
 }
 
 bool BytecodeEmitter::emitWith(BinaryNode* withNode) {
   // Ensure that the column of the 'with' is set properly.
-  if (!updateSourceCoordNotes(withNode->pn_pos.begin)) {
+  if (!updateSourceCoordNotes(withNode->left()->pn_pos.begin)) {
+    return false;
+  }
+
+  if (!markStepBreakpoint()) {
     return false;
   }
 
   if (!emitTree(withNode->left())) {
     return false;
   }
 
   EmitterScope emitterScope(this);
@@ -5265,16 +5350,22 @@ bool BytecodeEmitter::emitForOf(ForNode*
   ForOfEmitter forOf(this, headLexicalEmitterScope, allowSelfHostedIter,
                      iterKind);
 
   if (!forOf.emitIterated()) {
     //              [stack]
     return false;
   }
 
+  if (!updateSourceCoordNotes(forHeadExpr->pn_pos.begin)) {
+    return false;
+  }
+  if (!markStepBreakpoint()) {
+    return false;
+  }
   if (!emitTree(forHeadExpr)) {
     //              [stack] ITERABLE
     return false;
   }
 
   if (headLexicalEmitterScope) {
     DebugOnly<ParseNode*> forOfTarget = forOfHead->kid1();
     MOZ_ASSERT(forOfTarget->isKind(ParseNodeKind::LetDecl) ||
@@ -5359,16 +5450,23 @@ bool BytecodeEmitter::emitForIn(ForNode*
 
   if (!forIn.emitIterated()) {
     //              [stack]
     return false;
   }
 
   // Evaluate the expression being iterated.
   ParseNode* expr = forInHead->kid3();
+
+  if (!updateSourceCoordNotes(expr->pn_pos.begin)) {
+    return false;
+  }
+  if (!markStepBreakpoint()) {
+    return false;
+  }
   if (!emitTree(expr)) {
     //              [stack] EXPR
     return false;
   }
 
   MOZ_ASSERT(forInLoop->iflags() == 0);
 
   MOZ_ASSERT_IF(headLexicalEmitterScope,
@@ -5431,16 +5529,23 @@ bool BytecodeEmitter::emitCStyleFor(
     // declaration. (The loop variables were hoisted into an enclosing
     // scope, but we still need to emit code for the initializers.)
     if (init->isForLoopDeclaration()) {
       if (!emitTree(init)) {
         //          [stack]
         return false;
       }
     } else {
+      if (!updateSourceCoordNotes(init->pn_pos.begin)) {
+        return false;
+      }
+      if (!markStepBreakpoint()) {
+        return false;
+      }
+
       // 'init' is an expression, not a declaration. emitTree left its
       // value on the stack.
       if (!emitTree(init, ValueUsage::IgnoreValue)) {
         //          [stack] VAL
         return false;
       }
       if (!emit1(JSOP_POP)) {
         //          [stack]
@@ -5465,30 +5570,42 @@ bool BytecodeEmitter::emitCStyleFor(
           update ? CForEmitter::Update::Present : CForEmitter::Update::Missing,
           update ? Some(update->pn_pos.begin) : Nothing())) {
     //              [stack]
     return false;
   }
 
   // Check for update code to do before the condition (if any).
   if (update) {
+    if (!updateSourceCoordNotes(update->pn_pos.begin)) {
+      return false;
+    }
+    if (!markStepBreakpoint()) {
+      return false;
+    }
     if (!emitTree(update, ValueUsage::IgnoreValue)) {
       //            [stack] VAL
       return false;
     }
   }
 
   if (!cfor.emitCond(Some(forNode->pn_pos.begin),
                      cond ? Some(cond->pn_pos.begin) : Nothing(),
                      Some(forNode->pn_pos.end))) {
     //              [stack]
     return false;
   }
 
   if (cond) {
+    if (!updateSourceCoordNotes(cond->pn_pos.begin)) {
+      return false;
+    }
+    if (!markStepBreakpoint()) {
+      return false;
+    }
     if (!emitTree(cond)) {
       //            [stack] VAL
       return false;
     }
   }
 
   if (!cfor.emitEnd()) {
     //              [stack]
@@ -5840,16 +5957,22 @@ bool BytecodeEmitter::emitDo(BinaryNode*
     return false;
   }
 
   if (!doWhile.emitCond()) {
     return false;
   }
 
   ParseNode* condNode = doNode->right();
+  if (!updateSourceCoordNotes(condNode->pn_pos.begin)) {
+    return false;
+  }
+  if (!markStepBreakpoint()) {
+    return false;
+  }
   if (!emitTree(condNode)) {
     return false;
   }
 
   if (!doWhile.emitEnd()) {
     return false;
   }
 
@@ -5869,16 +5992,22 @@ bool BytecodeEmitter::emitWhile(BinaryNo
     return false;
   }
 
   ParseNode* condNode = whileNode->left();
   if (!wh.emitCond(getOffsetForLoop(condNode))) {
     return false;
   }
 
+  if (!updateSourceCoordNotes(condNode->pn_pos.begin)) {
+    return false;
+  }
+  if (!markStepBreakpoint()) {
+    return false;
+  }
   if (!emitTree(condNode)) {
     return false;
   }
 
   if (!wh.emitEnd()) {
     return false;
   }
 
@@ -5997,16 +6126,23 @@ bool BytecodeEmitter::emitReturn(UnaryNo
   bool needsIteratorResult =
       sc->isFunctionBox() && sc->asFunctionBox()->needsIteratorResult();
   if (needsIteratorResult) {
     if (!emitPrepareIteratorResult()) {
       return false;
     }
   }
 
+  if (!updateSourceCoordNotes(returnNode->pn_pos.begin)) {
+    return false;
+  }
+  if (!markStepBreakpoint()) {
+    return false;
+  }
+
   /* Push a return value */
   if (ParseNode* expr = returnNode->kid()) {
     if (!emitTree(expr)) {
       return false;
     }
 
     bool isAsyncGenerator =
         sc->asFunctionBox()->isAsync() && sc->asFunctionBox()->isGenerator();
@@ -6708,16 +6844,19 @@ bool BytecodeEmitter::emitExpressionStat
     MOZ_ASSERT_IF(expr->isKind(ParseNodeKind::AssignExpr),
                   expr->isOp(JSOP_NOP));
     ValueUsage valueUsage =
         wantval ? ValueUsage::WantValue : ValueUsage::IgnoreValue;
     ExpressionStatementEmitter ese(this, valueUsage);
     if (!ese.prepareForExpr(Some(exprStmt->pn_pos.begin))) {
       return false;
     }
+    if (!markStepBreakpoint()) {
+      return false;
+    }
     if (!emitTree(expr, valueUsage)) {
       return false;
     }
     if (!ese.emitEnd()) {
       return false;
     }
   } else if (exprStmt->isDirectivePrologueMember()) {
     // Don't complain about directive prologue members; just don't emit
@@ -8552,16 +8691,22 @@ bool BytecodeEmitter::emitClass(
 
   // This is kind of silly. In order to the get the home object defined on
   // the constructor, we have to make it second, but we want the prototype
   // on top for EmitPropertyList, because we expect static properties to be
   // rarer. The result is a few more swaps than we would like. Such is life.
   bool isDerived = !!heritageExpression;
   bool hasNameOnStack = nameKind == ClassNameKind::ComputedName;
   if (isDerived) {
+    if (!updateSourceCoordNotes(classNode->pn_pos.begin)) {
+      return false;
+    }
+    if (!markStepBreakpoint()) {
+      return false;
+    }
     if (!emitTree(heritageExpression)) {
       //            [stack] HERITAGE
       return false;
     }
     if (!ce.emitDerivedClass(innerName, nameForAnonymousClass,
                              hasNameOnStack)) {
       //            [stack] HERITAGE HOMEOBJ
       return false;
@@ -8704,27 +8849,33 @@ bool BytecodeEmitter::emitTree(
       }
       break;
 
     case ParseNodeKind::BreakStmt:
       // Ensure that the column of the 'break' is set properly.
       if (!updateSourceCoordNotes(pn->pn_pos.begin)) {
         return false;
       }
+      if (!markStepBreakpoint()) {
+        return false;
+      }
 
       if (!emitBreak(pn->as<BreakStatement>().label())) {
         return false;
       }
       break;
 
     case ParseNodeKind::ContinueStmt:
       // Ensure that the column of the 'continue' is set properly.
       if (!updateSourceCoordNotes(pn->pn_pos.begin)) {
         return false;
       }
+      if (!markStepBreakpoint()) {
+        return false;
+      }
 
       if (!emitContinue(pn->as<ContinueStatement>().label())) {
         return false;
       }
       break;
 
     case ParseNodeKind::WithStmt:
       if (!emitWith(&pn->as<BinaryNode>())) {
@@ -8896,16 +9047,23 @@ bool BytecodeEmitter::emitTree(
 
     case ParseNodeKind::TypeOfExpr:
       if (!emitTypeof(&pn->as<UnaryNode>(), JSOP_TYPEOFEXPR)) {
         return false;
       }
       break;
 
     case ParseNodeKind::ThrowStmt:
+      if (!updateSourceCoordNotes(pn->pn_pos.begin)) {
+        return false;
+      }
+      if (!markStepBreakpoint()) {
+        return false;
+      }
+      MOZ_FALLTHROUGH;
     case ParseNodeKind::VoidExpr:
     case ParseNodeKind::NotExpr:
     case ParseNodeKind::BitNotExpr:
     case ParseNodeKind::PosExpr:
     case ParseNodeKind::NegExpr:
       if (!emitUnary(&pn->as<UnaryNode>())) {
         return false;
       }
@@ -9114,16 +9272,19 @@ bool BytecodeEmitter::emitTree(
         return false;
       }
       break;
 
     case ParseNodeKind::DebuggerStmt:
       if (!updateSourceCoordNotes(pn->pn_pos.begin)) {
         return false;
       }
+      if (!markStepBreakpoint()) {
+        return false;
+      }
       if (!emit1(JSOP_DEBUGGER)) {
         return false;
       }
       break;
 
     case ParseNodeKind::ClassDecl:
       if (!emitClass(&pn->as<ClassNode>())) {
         return false;
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -132,16 +132,20 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
 
   // Zero-based column index on currentLine of last SRC_COLSPAN-annotated
   // opcode.
   //
   // WARNING: If this becomes out of sync with already-emitted srcnotes,
   // we can get undefined behavior.
   uint32_t lastColumn_;
 
+  uint32_t lastSeparatorOffet_;
+  uint32_t lastSeparatorLine_;
+  uint32_t lastSeparatorColumn_;
+
   // switchToMain sets this to the bytecode offset of the main section.
   mozilla::Maybe<uint32_t> mainOffset_;
 
  public:
   JumpTarget lastTarget;  // Last jump target emitted.
 
   // Private storage for parser wrapper. DO NOT REFERENCE INTERNALLY. May not be
   // initialized. Use |parser| instead.
@@ -483,18 +487,21 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
   MOZ_MUST_USE bool emitScript(ParseNode* body);
 
   // Emit function code for the tree rooted at body.
   enum class TopLevelFunction { No, Yes };
   MOZ_MUST_USE bool emitFunctionScript(FunctionNode* funNode,
                                        TopLevelFunction isTopLevel);
 
   void updateDepth(ptrdiff_t target);
+  MOZ_MUST_USE bool markStepBreakpoint();
+  MOZ_MUST_USE bool markSimpleBreakpoint();
   MOZ_MUST_USE bool updateLineNumberNotes(uint32_t offset);
   MOZ_MUST_USE bool updateSourceCoordNotes(uint32_t offset);
+  void updateSeparatorPosition();
 
   JSOp strictifySetNameOp(JSOp op);
 
   MOZ_MUST_USE bool emitCheck(JSOp op, ptrdiff_t delta, ptrdiff_t* offset);
 
   // Emit one bytecode.
   MOZ_MUST_USE bool emit1(JSOp op);
 
--- a/js/src/frontend/CallOrNewEmitter.cpp
+++ b/js/src/frontend/CallOrNewEmitter.cpp
@@ -266,27 +266,30 @@ bool CallOrNewEmitter::emitEnd(uint32_t 
       // Repush the callee as new.target
       uint32_t effectiveArgc = isSpread() ? 1 : argc;
       if (!bce_->emitDupAt(effectiveArgc + 1)) {
         //          [stack] CALLEE THIS ARR CALLEE
         return false;
       }
     }
   }
+  if (beginPos) {
+    if (!bce_->updateSourceCoordNotes(*beginPos)) {
+      return false;
+    }
+  }
+  if (!bce_->markSimpleBreakpoint()) {
+    return false;
+  }
   if (!isSpread()) {
-    if (!bce_->emitCall(op_, argc, beginPos)) {
+    if (!bce_->emitCall(op_, argc)) {
       //            [stack] RVAL
       return false;
     }
   } else {
-    if (beginPos) {
-      if (!bce_->updateSourceCoordNotes(*beginPos)) {
-        return false;
-      }
-    }
     if (!bce_->emit1(op_)) {
       //            [stack] RVAL
       return false;
     }
   }
 
   if (isEval() && beginPos) {
     uint32_t lineNum = bce_->parser->errorReporter().lineAt(*beginPos);
--- a/js/src/frontend/ElemOpEmitter.cpp
+++ b/js/src/frontend/ElemOpEmitter.cpp
@@ -221,17 +221,17 @@ bool ElemOpEmitter::emitIncDec() {
   if (!emitGet()) {
     //              [stack] ... ELEM
     return false;
   }
 
   MOZ_ASSERT(state_ == State::Get);
 
   JSOp incOp = isInc() ? JSOP_INC : JSOP_DEC;
-  if (!bce_->emit1(JSOP_POS)) {
+  if (!bce_->emit1(JSOP_TONUMERIC)) {
     //              [stack] ... N
     return false;
   }
   if (isPostIncDec()) {
     if (!bce_->emit1(JSOP_DUP)) {
       //            [stack] ... N? N
       return false;
     }
--- a/js/src/frontend/NameOpEmitter.cpp
+++ b/js/src/frontend/NameOpEmitter.cpp
@@ -352,17 +352,17 @@ bool NameOpEmitter::emitAssignment() {
 bool NameOpEmitter::emitIncDec() {
   MOZ_ASSERT(state_ == State::Start);
 
   JSOp incOp = isInc() ? JSOP_INC : JSOP_DEC;
   if (!prepareForRhs()) {
     //              [stack] ENV? V
     return false;
   }
-  if (!bce_->emit1(JSOP_POS)) {
+  if (!bce_->emit1(JSOP_TONUMERIC)) {
     //              [stack] ENV? N
     return false;
   }
   if (isPostIncDec()) {
     if (!bce_->emit1(JSOP_DUP)) {
       //            [stack] ENV? N? N
       return false;
     }
--- a/js/src/frontend/PropOpEmitter.cpp
+++ b/js/src/frontend/PropOpEmitter.cpp
@@ -205,17 +205,17 @@ bool PropOpEmitter::emitIncDec(JSAtom* p
   if (!emitGet(prop)) {
     return false;
   }
 
   MOZ_ASSERT(state_ == State::Get);
 
   JSOp incOp = isInc() ? JSOP_INC : JSOP_DEC;
 
-  if (!bce_->emit1(JSOP_POS)) {
+  if (!bce_->emit1(JSOP_TONUMERIC)) {
     //              [stack] ... N
     return false;
   }
   if (isPostIncDec()) {
     if (!bce_->emit1(JSOP_DUP)) {
       //            [stack] .. N N
       return false;
     }
--- a/js/src/frontend/SourceNotes.h
+++ b/js/src/frontend/SourceNotes.h
@@ -182,18 +182,18 @@ class SrcNote {
     M(SRC_ASSIGNOP,     "assignop",    0)  /* += or another assign-op follows. */                  \
     M(SRC_CLASS_SPAN,   "class",       2)  /* The starting and ending offsets for the class, used  \
                                               for toString correctness for default ctors. */       \
     M(SRC_TRY,          "try",         SrcNote::Try::Count) \
     /* All notes above here are "gettable".  See SN_IS_GETTABLE below. */                          \
     M(SRC_COLSPAN,      "colspan",     SrcNote::ColSpan::Count) \
     M(SRC_NEWLINE,      "newline",     0)  /* Bytecode follows a source newline. */                \
     M(SRC_SETLINE,      "setline",     SrcNote::SetLine::Count) \
-    M(SRC_UNUSED22,     "unused22",    0)  /* Unused. */                                           \
-    M(SRC_UNUSED23,     "unused23",    0)  /* Unused. */                                           \
+    M(SRC_BREAKPOINT,   "breakpoint",  0)  /* Bytecode is a recommended breakpoint. */             \
+    M(SRC_STEP_SEP,     "step-sep",    0)  /* Bytecode is the first in a new steppable area. */    \
     M(SRC_XDELTA,       "xdelta",      0)  /* 24-31 are for extended delta notes. */
 // clang-format on
 
 enum SrcNoteType {
 #define DEFINE_SRC_NOTE_TYPE(sym, name, arity) sym,
   FOR_EACH_SRC_NOTE_TYPE(DEFINE_SRC_NOTE_TYPE)
 #undef DEFINE_SRC_NOTE_TYPE
 
--- a/js/src/frontend/moz.build
+++ b/js/src/frontend/moz.build
@@ -1,18 +1,16 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 FINAL_LIBRARY = 'js'
 
-FILES_PER_UNIFIED_FILE = 6
-
 # Includes should be relative to parent path
 LOCAL_INCLUDES += [
     '!..',
     '..'
 ]
 
 include('../js-config.mozbuild')
 include('../js-cxxflags.mozbuild')
--- a/js/src/gc/PublicIterators.cpp
+++ b/js/src/gc/PublicIterators.cpp
@@ -215,17 +215,33 @@ void js::IterateGrayObjectsUnderCC(Zone*
 }
 
 JS_PUBLIC_API void JS_IterateCompartments(
     JSContext* cx, void* data,
     JSIterateCompartmentCallback compartmentCallback) {
   AutoTraceSession session(cx->runtime());
 
   for (CompartmentsIter c(cx->runtime()); !c.done(); c.next()) {
-    (*compartmentCallback)(cx, data, c);
+    if ((*compartmentCallback)(cx, data, c) ==
+        JS::CompartmentIterResult::Stop) {
+      break;
+    }
+  }
+}
+
+JS_PUBLIC_API void JS_IterateCompartmentsInZone(
+    JSContext* cx, JS::Zone* zone, void* data,
+    JSIterateCompartmentCallback compartmentCallback) {
+  AutoTraceSession session(cx->runtime());
+
+  for (CompartmentsInZoneIter c(zone); !c.done(); c.next()) {
+    if ((*compartmentCallback)(cx, data, c) ==
+        JS::CompartmentIterResult::Stop) {
+      break;
+    }
   }
 }
 
 JS_PUBLIC_API void JS::IterateRealms(JSContext* cx, void* data,
                                      JS::IterateRealmCallback realmCallback) {
   AutoTraceSession session(cx->runtime());
 
   Rooted<Realm*> realm(cx);
--- a/js/src/gc/moz.build
+++ b/js/src/gc/moz.build
@@ -1,18 +1,16 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 FINAL_LIBRARY = 'js'
 
-FILES_PER_UNIFIED_FILE = 6
-
 # Includes should be relative to parent path
 LOCAL_INCLUDES += [
     '!..',
     '..'
 ]
 
 include('../js-config.mozbuild')
 include('../js-cxxflags.mozbuild')
--- a/js/src/jit-test/tests/debug/Script-getAllColumnOffsets.js
+++ b/js/src/jit-test/tests/debug/Script-getAllColumnOffsets.js
@@ -1,92 +1,93 @@
 load(libdir + "assert-offset-columns.js");
 
 // getColumnOffsets correctly places the various parts of a ForStatement.
 assertOffsetColumns(
     "function f(n) { for (var i = 0; i < n; ++i) hits.push('.'); hits.push('!'); }",
-    "                         ^      ^      ^    ^    ^          ^    ^          ^",
+    "                             ^  ^      ^    ^    ^          ^    ^          ^",
     "0 1 3 4 . 2 1 3 4 . 2 1 3 4 . 2 1 5 6 ! 7",
 );
 
 // getColumnOffsets correctly places multiple variable declarations.
 assertOffsetColumns(
     "function f(n){var w0,x1=3,y2=4,z3=9}",
-    "                     ^    ^    ^   ^",
+    "                        ^    ^    ^^",
 );
 
 // getColumnOffsets correctly places comma separated expressions.
 assertOffsetColumns(
     "function f(n){print(n),print(n),print(n)}",
     "              ^        ^        ^       ^",
 );
 
 // getColumnOffsets correctly places object properties.
 assertOffsetColumns(
     // Should hit each property in the object.
     "function f(n){var o={a:1,b:2,c:3}}",
-    "                  ^  ^   ^   ^   ^",
+    "                    ^^   ^   ^   ^",
 );
 
 // getColumnOffsets correctly places array properties.
 assertOffsetColumns(
     // Should hit each item in the array.
     "function f(n){var a=[1,2,n]}",
-    "                  ^  ^ ^ ^ ^",
+    "                    ^^ ^ ^ ^",
 );
 
 // getColumnOffsets correctly places function calls.
 assertOffsetColumns(
     "function ppppp() { return 1; }\n" +
     "function f(){ 1 && ppppp(ppppp()) && new Error() }",
     "              ^    ^     ^           ^           ^",
     "0 2 1 3 4",
 );
 
 // getColumnOffsets correctly places the various parts of a SwitchStatement.
 assertOffsetColumns(
     "function f(n) { switch(n) { default: print(n); } }",
-    "                ^                    ^           ^",
+    "                       ^             ^           ^",
 );
 
 // getColumnOffsets correctly places the various parts of a BreakStatement.
 assertOffsetColumns(
     "function f(n) { do { print(n); break; } while(false); }",
-    "                ^    ^         ^                      ^",
+    "                ^    ^         ^              ^       ^",
+    "0 1 2 4"
 );
 
 // getColumnOffsets correctly places the various parts of a ContinueStatement.
 assertOffsetColumns(
     "function f(n) { do { print(n); continue; } while(false); }",
-    "                ^    ^         ^                         ^",
+    "                ^    ^         ^                 ^       ^",
 );
 
 // getColumnOffsets correctly places the various parts of a WithStatement.
 assertOffsetColumns(
     "function f(n) { with({}) { print(n); } }",
-    "                ^          ^           ^",
+    "                     ^     ^           ^",
 );
 
 // getColumnOffsets correctly places the various parts of a IfStatement.
 assertOffsetColumns(
     "function f(n) { if (n == 3) print(n); }",
-    "                ^           ^         ^",
+    "                    ^       ^         ^",
 );
 
 // getColumnOffsets correctly places the various parts of a IfStatement
 // with an if/else
 assertOffsetColumns(
     "function f(n) { if (n == 2); else if (n === 3) print(n); }",
-    "                ^                 ^            ^         ^",
+    "                    ^                 ^        ^         ^",
 );
 
 // getColumnOffsets correctly places the various parts of a DoWhileStatement.
 assertOffsetColumns(
     "function f(n) { do { print(n); } while(false); }",
-    "                ^    ^                         ^",
+    "                ^    ^                 ^       ^",
 );
 
 // getColumnOffsets correctly places the part of normal ::Dot node with identifier root.
 assertOffsetColumns(
     "var args = [];\n" +
     "var obj = { base: { a(){ return { b(){} }; } } };\n" +
     "function f(n) { obj.base.a().b(...args); }",
     "                ^        ^   ^ ^         ^",
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getOffsetMetadata.js
@@ -0,0 +1,36 @@
+var global = newGlobal({newCompartment: true});
+var dbg = Debugger(global);
+dbg.onDebuggerStatement = function(frame) {
+  const bps = frame.script.getPossibleBreakpoints();
+
+  const stepBps = [];
+  frame.onStep = function() {
+    assertOffset(this);
+  };
+
+  assertOffset(frame);
+
+  function assertOffset(frame) {
+    const meta = frame.script.getOffsetMetadata(frame.offset);
+
+    if (meta.isBreakpoint) {
+      assertEq(frame.offset, bps[0].offset);
+      const expectedData = bps.shift();
+
+      assertEq(meta.lineNumber, expectedData.lineNumber);
+      assertEq(meta.columnNumber, expectedData.columnNumber);
+      assertEq(meta.isStepStart, expectedData.isStepStart);
+    } else {
+      assertEq(meta.isStepStart, false);
+    }
+  };
+};
+
+global.eval(`
+  function a() { return "str"; }
+  debugger;
+
+  console.log("42" + a());
+  a();
+  a() + a();
+`);
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getPossibleBreakpoints-02.js
@@ -0,0 +1,63 @@
+
+var global = newGlobal({newCompartment: true});
+var dbg = Debugger(global);
+dbg.onDebuggerStatement = onDebuggerStatement;
+
+global.eval(`
+  debugger;
+  function f() {
+    var o = {};         // 4
+
+    o.a; o.a; o.a; o.a; // 6
+    o.a; o.a;           // 7
+    o.a; o.a; o.a;      // 8
+    o.a;                // 9
+  }                     // 10
+`);
+
+function onDebuggerStatement(frame) {
+  const fScript = frame.script.getChildScripts()[0];
+
+  const allBreakpoints = fScript.getPossibleBreakpoints();
+  assertEq(allBreakpoints.length, 12);
+
+  assertBPCount({ line: 4 }, 1);
+  assertBPCount({ line: 5 }, 0);
+  assertBPCount({ line: 6 }, 4);
+  assertBPCount({ line: 7 }, 2);
+  assertBPCount({ line: 8 }, 3);
+  assertBPCount({ line: 9 }, 1);
+  assertBPCount({ line: 10 }, 1);
+
+  assertBPCount({ line: 6, minColumn: 7 }, 3);
+  assertBPCount({ line: 6, maxColumn: 16 }, 3);
+  assertBPCount({ line: 6, minColumn: 7, maxColumn: 16 }, 2);
+
+  assertBPCount({ minLine: 9 }, 2);
+  assertBPCount({ minLine: 9, minColumn: 0 }, 2);
+  assertBPCount({ minLine: 9, minColumn: 8 }, 1);
+
+  assertBPCount({ maxLine: 7 }, 5);
+  assertBPCount({ maxLine: 7, maxColumn: 0 }, 5);
+  assertBPCount({ maxLine: 7, maxColumn: 8 }, 6);
+
+  assertBPCount({ minLine: 6, maxLine: 8 }, 6);
+  assertBPCount({ minLine: 6, minColumn: 8, maxLine: 8 }, 5);
+  assertBPCount({ minLine: 6, maxLine: 8, maxColumn: 8 }, 7);
+  assertBPCount({ minLine: 6, minColumn: 8, maxLine: 8, maxColumn: 8 }, 6);
+
+  assertBPCount({
+    minOffset: fScript.getPossibleBreakpoints({ line: 6 })[3].offset,
+  }, 8);
+  assertBPCount({
+    maxOffset: fScript.getPossibleBreakpoints({ line: 6 })[3].offset,
+  }, 4);
+  assertBPCount({
+    minOffset: fScript.getPossibleBreakpoints({ line: 6 })[2].offset,
+    maxOffset: fScript.getPossibleBreakpoints({ line: 7 })[1].offset,
+  }, 3);
+
+  function assertBPCount(query, count) {
+    assertEq(fScript.getPossibleBreakpoints(query).length, count);
+  }
+};
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getPossibleBreakpoints.js
@@ -0,0 +1,391 @@
+// simple ExpressionStatement
+assertBreakpoints(`
+  /*S*/a;
+  /*S*/obj.prop;
+`);
+
+// ExpressionStatement with calls
+assertBreakpoints(`
+  /*S*/a();
+  /*S*/obj./*B*/prop();
+`);
+
+// ExpressionStatement with nested expression calls.
+assertBreakpoints(`
+  "45";
+  /*S*/"45" + /*B*/a();
+  /*S*/b() + "45";
+  /*S*/"45" + /*B*/a() + /*B*/b();
+  /*S*/b() + "45" + /*B*/a();
+  /*S*/b() + /*B*/a() + "45";
+
+  /*S*/"45" + o./*B*/a();
+  /*S*/o./*B*/b() + "45";
+  /*S*/"45" + o./*B*/a() + o./*B*/b();
+  /*S*/o./*B*/b() + "45" + o./*B*/a();
+  /*S*/o./*B*/b() + o./*B*/a() + "45";
+`);
+
+// var VariableStatement initializers
+assertBreakpoints(`
+  var foo1 = /*S*/"" + o.a + "" + /*B*/b(),
+      foo2 = /*S*/"45",
+      foo3 = /*S*/"45" + /*B*/a(),
+      foo4 = /*S*/b() + "45",
+      foo5 = /*S*/"45" + /*B*/a() + /*B*/b(),
+      foo6 = /*S*/b() + "45" + /*B*/a(),
+      foo7 = /*S*/b() + /*B*/a() + "45",
+      foo8 = /*S*/"45" + o./*B*/a(),
+      foo9 = /*S*/o./*B*/b() + "45",
+      foo10 = /*S*/"45" + o./*B*/a() + o./*B*/b(),
+      foo11 = /*S*/o./*B*/b() + "45" + o./*B*/a(),
+      foo12 = /*S*/o./*B*/b() + o./*B*/a() + "45";
+`);
+
+// let VariableStatement initializers
+assertBreakpoints(`
+  let foo1 = /*S*/"" + o.a + "" + /*B*/b(),
+      foo2 = /*S*/"45",
+      foo3 = /*S*/"45" + /*B*/a(),
+      foo4 = /*S*/b() + "45",
+      foo5 = /*S*/"45" + /*B*/a() + /*B*/b(),
+      foo6 = /*S*/b() + "45" + /*B*/a(),
+      foo7 = /*S*/b() + /*B*/a() + "45",
+      foo8 = /*S*/"45" + o./*B*/a(),
+      foo9 = /*S*/o./*B*/b() + "45",
+      foo10 = /*S*/"45" + o./*B*/a() + o./*B*/b(),
+      foo11 = /*S*/o./*B*/b() + "45" + o./*B*/a(),
+      foo12 = /*S*/o./*B*/b() + o./*B*/a() + "45";
+`);
+
+// const VariableStatement initializers
+assertBreakpoints(`
+  const foo1 = /*S*/"" + o.a + "" + /*B*/b(),
+        foo2 = /*S*/"45",
+        foo3 = /*S*/"45" + /*B*/a(),
+        foo4 = /*S*/b() + "45",
+        foo5 = /*S*/"45" + /*B*/a() + /*B*/b(),
+        foo6 = /*S*/b() + "45" + /*B*/a(),
+        foo7 = /*S*/b() + /*B*/a() + "45",
+        foo8 = /*S*/"45" + o./*B*/a(),
+        foo9 = /*S*/o./*B*/b() + "45",
+        foo10 = /*S*/"45" + o./*B*/a() + o./*B*/b(),
+        foo11 = /*S*/o./*B*/b() + "45" + o./*B*/a(),
+        foo12 = /*S*/o./*B*/b() + o./*B*/a() + "45";
+`);
+
+// EmptyStatement
+assertBreakpoints(`
+  ;
+  ;
+  ;
+  /*S*/a();
+`);
+
+// IfStatement
+assertBreakpoints(`
+  if (/*S*/a) {}
+  if (/*S*/a()) {}
+  if (/*S*/obj.prop) {}
+  if (/*S*/obj./*B*/prop()) {}
+  if (/*S*/"42" + a) {}
+  if (/*S*/"42" + /*B*/a()) {}
+  if (/*S*/"42" + obj.prop) {}
+  if (/*S*/"42" + obj./*B*/prop()) {}
+`);
+
+// DoWhile
+assertBreakpoints(`
+  do {
+    /*S*/fn();
+  } while(/*S*/a)
+  do {
+    /*S*/fn();
+  } while(/*S*/"42" + /*B*/a());
+`);
+
+// While
+assertBreakpoints(`
+  while(/*S*/a) {
+    /*S*/fn();
+  }
+  while(/*S*/"42" + /*B*/a()) {
+    /*S*/fn();
+  }
+`);
+
+// ForExpr
+assertBreakpoints(`
+  for (/*S*/b = 42; /*S*/c; /*S*/d) /*S*/fn();
+`);
+
+// ForVar
+assertBreakpoints(`
+  for (var b = /*S*/42; /*S*/c; /*S*/d) /*S*/fn();
+`);
+
+// ForLet
+assertBreakpoints(`
+  for (let b = /*S*/42; /*S*/c; /*S*/d) /*S*/fn();
+`);
+
+// ForConst
+assertBreakpoints(`
+  for (const b = /*S*/42; /*S*/c; /*S*/d) /*S*/fn();
+`);
+
+// ForInExpr
+assertBreakpoints(`
+  for (b in /*S*/d) /*S*/fn();
+`);
+// ForInVar
+assertBreakpoints(`
+  for (var b in /*S*/d) /*S*/fn();
+`);
+// ForInLet
+assertBreakpoints(`
+  for (let b in /*S*/d) /*S*/fn();
+`);
+// ForInConst
+assertBreakpoints(`
+  for (const b in /*S*/d) /*S*/fn();
+`);
+
+// ForOfExpr
+assertBreakpoints(`
+  for (b of /*S*/d) /*S*/fn();
+`);
+// ForOfVar
+assertBreakpoints(`
+  for (var b of /*S*/d) /*S*/fn();
+`);
+// ForOfLet
+assertBreakpoints(`
+  for (let b of /*S*/d) /*S*/fn();
+`);
+// ForOfConst
+assertBreakpoints(`
+  for (const b of /*S*/d) /*S*/fn();
+`);
+
+// SwitchStatement
+assertBreakpoints(`
+  switch (/*S*/d) {
+    case 42:
+      /*S*/fn();
+  }
+`);
+
+// ContinueStatement
+assertBreakpoints(`
+  while (/*S*/a) {
+    /*S*/continue;
+  }
+`);
+
+// BreakStatement
+assertBreakpoints(`
+  while (/*S*/a) {
+    /*S*/break;
+  }
+`);
+
+// ReturnStatement
+assertBreakpoints(`
+  /*S*/return a + /*B*/b();
+`);
+
+// WithStatement
+assertBreakpoints(`
+  with (/*S*/a) {
+    /*S*/fn();
+  }
+`);
+
+// ThrowStatement
+assertBreakpoints(`
+  /*S*/throw /*B*/fn();
+  /*S*/throw "42" + /*B*/fn();
+`);
+
+// DebuggerStatement
+assertBreakpoints(`
+  /*S*/debugger;
+  /*S*/debugger;
+`);
+
+// BlockStatent wrapper
+assertBreakpoints(`
+  {
+    /*S*/a();
+  }
+`);
+
+// ClassDeclaration
+assertBreakpoints(`
+  class Foo2 {}
+  /*S*/class Foo extends ("" + o.a + /*B*/a() + /*B*/b()) { }
+`);
+
+// Misc examples
+assertBreakpoints(`
+  /*S*/void /*B*/a();
+`);
+assertBreakpoints(`
+  /*S*/a() + /*B*/b();
+`);
+assertBreakpoints(`
+  for (
+    var i = /*S*/0;
+    /*S*/i < n;  // 4
+    /*S*/++i
+  ) {
+    /*S*/console./*B*/log("omg");
+  }
+`);
+assertBreakpoints(`
+  function * gen(){
+    var foo = (
+      (/*S*/console./*B*/log('before', /*B*/a())),
+      (yield console./*B*/log('mid', /*B*/b())),
+      (console./*B*/log('after', /*B*/a()))
+    );
+    var foo2 = /*S*/a() + /*B*/b();
+    /*S*/console./*B*/log(foo);
+  /*B*/}
+  var i = /*S*/0;
+  for (var foo of /*S*/gen()) {
+    /*S*/console./*B*/log(i++);
+  }
+`);
+assertBreakpoints(`
+  var fn /*S*/= () => {
+    /*S*/console./*B*/log("fn");
+    /*S*/return /*B*/new Proxy({ prop: 42 }, {
+      deleteProperty() {
+        /*S*/console./*B*/log("delete");
+      /*B*/}
+    });
+  /*B*/};
+`);
+assertBreakpoints(`
+  if ((/*S*/delete /*B*/fn().prop) + /*B*/b()) {
+    /*S*/console./*B*/log("foo");
+  }
+`);
+assertBreakpoints(`
+  for (var j = /*S*/0; (/*S*/o.a) < 3; (/*S*/j++, /*B*/a(), /*B*/b())) {
+    /*S*/console./*B*/log(i);
+  }
+`);
+assertBreakpoints(`
+  function fn2(
+    [a, b] = (/*B*/a(), /*B*/b())
+  ) {
+    /*S*/a();
+    /*S*/b();
+  /*B*/}
+
+  ({ a, b } = (/*S*/a(), /*B*/b()));
+`);
+assertBreakpoints(`
+  /*S*/o.a + "42" + /*B*/a() + /*B*/b();
+`);
+assertBreakpoints(`
+  /*S*/a();
+  /*S*/o./*B*/a(/*B*/b());
+`);
+assertBreakpoints(`
+  (/*S*/{}[obj.a] = 42 + /*B*/a());
+`);
+assertBreakpoints(`
+  var {
+    foo = o.a
+  } = /*S*/{};
+`);
+assertBreakpoints(`
+  var ack = /*S*/[
+    o.a,
+    o.b,
+    /*B*/a(),
+    /*B*/a(),
+    /*B*/a(),
+    /*B*/a(),
+    /*B*/a(),
+    /*B*/a(),
+    /*B*/a(),
+  ];
+`);
+
+function assertBreakpoints(expected) {
+  const input = expected.replace(/\/\*[BS]\*\//g, "");
+
+  var global = newGlobal({newCompartment: true});
+  var dbg = Debugger(global);
+  dbg.onDebuggerStatement = function(frame) {
+    const fScript = frame.environment.parent.getVariable("f").script;
+
+    let positions = [];
+    (function recurse(script) {
+      const bps = script.getPossibleBreakpoints();
+      const offsets = script.getPossibleBreakpointOffsets();
+
+      assertEq(offsets.length, bps.length);
+      for (let i = 0; i < bps.length; i++) {
+        assertEq(offsets[i], bps[i].offset);
+      }
+
+      positions = positions.concat(bps);
+      script.getChildScripts().forEach(recurse);
+    })(fScript);
+
+    const result = annotateOffsets(input, positions);
+    assertEq(result, expected + "/*B*/");
+  };
+
+  global.eval(`function f(){${input}} debugger;`);
+}
+
+function annotateOffsets(code, positions) {
+  const offsetLookup = createOffsetLookup(code);
+
+  positions = positions.slice();
+  positions.sort((a, b) => {
+    const lineDiff = a.lineNumber - b.lineNumber;
+    return lineDiff === 0 ? a.columnNumber - b.columnNumber : lineDiff;
+  });
+  positions.reverse();
+
+  let output = "";
+  let last = code.length;
+  for (const { lineNumber, columnNumber, isStepStart } of positions) {
+    const offset = offsetLookup(lineNumber, columnNumber);
+
+    output = "/*" + (isStepStart ? "S" : "B") + "*/" + code.slice(offset, last) + output;
+    last = offset;
+  }
+  return code.slice(0, last) + output;
+}
+
+function createOffsetLookup(code) {
+  const lines = code.split(/(\r?\n|\r|\u2028|\u2029)/g);
+  const lineOffsets = [];
+
+  let count = 0;
+  for (const [i, str] of lines.entries()) {
+    if (i % 2 === 0) {
+      lineOffsets[i / 2] = count;
+    }
+    count += str.length;
+  }
+
+  return function(line, column) {
+    // Lines from getAllColumnOffsets are 1-based.
+    line = line - 1;
+
+    if (!lineOffsets.hasOwnProperty(line)) {
+      throw new Error("Unknown line " + line + " column " + column);
+    }
+    return lineOffsets[line] + column;
+  };
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/job-queue-01.js
@@ -0,0 +1,122 @@
+// Debuggee promise reaction jobs should not run from debugger callbacks.
+// This covers:
+// - onDebuggerStatement
+// - onStep
+// - onEnterFrame
+// - onPop
+// - onExceptionUnwind
+// - breakpoint handlers
+// - uncaughtExceptionHook
+
+var g = newGlobal({ newCompartment: true });
+g.parent = this;
+var dbg = new Debugger;
+var gDO = dbg.addDebuggee(g);
+var log = '';
+
+// Exercise the promise machinery: resolve a promise and drain the job queue (or
+// in HTML terms, perform a microtask checkpoint). When called from a debugger
+// hook, the debuggee's microtasks should not run.
+function exercise(name) {
+  log += `${name}-handler`;
+  Promise.resolve(42).then(v => {
+    assertEq(v, 42);
+    log += `${name}-react`;
+  });
+  log += `(`;
+  drainJobQueue();
+  log += `)`;
+
+  // This should be run by the implicit microtask checkpoint after each Debugger
+  // hook call.
+  Promise.resolve(42).then(v => {
+    assertEq(v, 42);
+    log += `(${name}-tail)`;
+  });
+}
+
+dbg.onDebuggerStatement = function (frame) {
+  exercise('debugger');
+
+  frame.onStep = function () {
+    this.onStep = undefined;
+    exercise('step');
+  };
+
+  dbg.onEnterFrame = function (frame) {
+    dbg.onEnterFrame = undefined;
+    frame.onPop = function(completion) {
+      assertEq(completion.return, 'recompense');
+      exercise('pop');
+    }
+
+    exercise('enter');
+  }
+
+  dbg.onExceptionUnwind = function(frame, value) {
+    dbg.onExceptionUnwind = undefined;
+    assertEq(value, 'recidivism');
+    exercise('exception');
+    return { return: 'recompense' };
+  };
+
+  // Set a breakpoint on entry to g.breakpoint_here.
+  const script = gDO.getOwnPropertyDescriptor('breakpoint_here').value.script;
+  const handler = {
+    hit(frame) {
+      script.clearAllBreakpoints();
+      exercise('bp');
+    }
+  };
+  script.setBreakpoint(0, handler);
+
+  dbg.uncaughtExceptionHook = function (ex) {
+    assertEq(ex, 'turncoat');
+    exercise('uncaught');
+  };
+
+  // Throw an uncaught exception from the Debugger handler. This should reach
+  // uncaughtExceptionHook, but shouldn't affect the debuggee.
+  throw 'turncoat';
+};
+
+g.eval(`
+  function breakpoint_here() {
+    throw 'recidivism';
+  }
+
+  parent.log += 'eval(';
+
+  // DebuggeeWouldRun detection may prevent this callback from running at all if
+  // bug 1145201 is present. SpiderMonkey will try to run the promise reaction
+  // job from the Debugger hook's microtask checkpoint, triggering
+  // DebuggeeWouldRun. This is a little difficult to observe, since the callback
+  // never even begins execution. But it should cause the 'then' promise to be
+  // rejected, which the shell will report (if the assertEq(log, ...) doesn't
+  // kill the test first).
+
+  Promise.resolve(84).then(function(v) {
+    assertEq(v, 84);
+    parent.log += 'eval-react';
+  });
+  debugger;
+  parent.log += '...';
+  breakpoint_here();
+  parent.log += ')';
+`);
+
+log += 'main-drain('
+drainJobQueue();
+log += ')';
+
+assertEq(log, `eval(\
+debugger-handler(debugger-react)\
+uncaught-handler((debugger-tail)uncaught-react)(uncaught-tail)\
+step-handler(step-react)(step-tail)\
+...\
+enter-handler(enter-react)(enter-tail)\
+bp-handler(bp-react)(bp-tail)\
+exception-handler(exception-react)(exception-tail)\
+pop-handler(pop-react)(pop-tail)\
+)\
+main-drain(eval-react)`);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/job-queue-02.js
@@ -0,0 +1,82 @@
+// |jit-test| error: "async tests completed successfully"
+// Test that the shell's job queue doesn't skip calls to JS::JobQueueMayNotBeEmpty.
+
+// For expressions like `await 1`, or `await P` for some already-resolved
+// promise P, there's no need to suspend the async call to determine the
+// expression's value. Suspension and resumption are expensive, so it would be
+// nice if we could avoid them.
+//
+// But of course, even when the value is known, the act of suspension itself is
+// visible: an async call's first suspension returns to its (synchronous)
+// caller; subsequent suspensions let other jobs run. So in general, we can't
+// short-circuit such `await` expressions.
+//
+// However, if an async call has been resumed from the job queue (that is, this
+// isn't the initial execution, with a synchronous caller expecting a promise of
+// the call's final return value), and there are no other jobs following that,
+// then the `await`'s reaction job would run immediately following this job ---
+// which *is* indistinguishable from skipping the suspension altogether.
+//
+// A JS::JobQueue implementation may call JS::JobQueueIsEmpty to indicate to the
+// engine that the currently running job is the last job in the queue, so this
+// optimization may be considered (there are further conditions that must be met
+// as well). If the JobQueue calls JobQueueIsEmpty, then it must also call
+// JS::JobQueueMayNotBeEmpty when jobs are enqueued, to indicate when the
+// opportunity has passed.
+
+var log = '';
+async function f(label, k) {
+  log += label + '1';
+  await 1;
+  log += label + '2';
+  await 1;
+  log += label + '3';
+
+  return k();
+}
+
+// Call `f` with `label` and `k`. If `skippable` is true, exercise the path that
+// skips the suspension and resumption; otherwise exercise the
+// non-short-circuited path.
+function test(skippable, label, k) {
+  var resolve;
+  (new Promise(r => { resolve = r; }))
+    .then(v => { log += v + 't'; });
+  assertEq(log, '');
+  f(label, k);
+  // job queue now: f(label)'s first await's continuation
+  assertEq(log, label + '1');
+
+  if (!skippable) {
+    resolve('p');
+    assertEq(log, label + '1');
+    // job queue now: f(label)'s first await's continuation, explicit promise's reaction
+  }
+
+  // Resuming f(label) will reach the second await, which should skip suspension
+  // or not, depending on whether we resolved that promise.
+}
+
+// SpiderMonkey's internal 'queue is empty' flag is initially false, even though
+// the queue is initially empty, because we don't yet know whether the embedding
+// is going to participate in the optimization by calling
+// JS::JobQueueMayNotBeEmpty and JS::JobQueueIsEmpty. But since the shell uses
+// SpiderMonkey's internal job queue implementation, this call to
+// `drainJobQueue` calls `JS::JobQueueIsEmpty`, and we are ready to play.
+Promise.resolve(42).then(v => assertEq(v, 42));
+drainJobQueue();
+
+log = '';
+test(true, 'b', continuation1);
+
+function continuation1() {
+  assertEq(log, 'b1b2b3');
+
+  log = '';
+  test(false, 'c', continuation2);
+}
+
+function continuation2() {
+  assertEq(log, 'c1c2ptc3');
+  throw "async tests completed successfully"; // proof that we actually finished
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/job-queue-03.js
@@ -0,0 +1,173 @@
+// Multiple debuggers get their job queues drained after each hook.
+// This covers:
+// - onDebuggerStatement
+// - onStep
+// - onEnterFrame
+// - onPop
+// - onExceptionUnwind
+// - breakpoint handlers
+// - uncaughtExceptionHook
+
+const g = newGlobal({ newCompartment: true });
+g.parent = this;
+
+var log = '';
+let expected_throws = 0;
+
+function setup(global, label) {
+  const dbg = new Debugger;
+  dbg.gDO = dbg.addDebuggee(global);
+  dbg.log = '';
+
+  dbg.onDebuggerStatement = function (frame) {
+    // Exercise the promise machinery: resolve a promise and perform a microtask
+    // checkpoint. When called from a debugger hook, the debuggee's microtasks
+    // should not run.
+    function exercise(name) {
+      dbg.log += name + ',';
+      log += `${label}-${name}-handler\n`;
+      Promise.resolve(42).then(v => {
+        assertEq(v, 42);
+        log += `${label}-${name}-tail\n`;
+      });
+    }
+
+    exercise('debugger');
+
+    frame.onStep = function () {
+      this.onStep = undefined;
+      exercise('step');
+    };
+
+    dbg.onEnterFrame = function (frame) {
+      dbg.onEnterFrame = undefined;
+      frame.onPop = function(completion) {
+        assertEq(completion.return, 'escutcheon');
+        exercise('pop');
+      }
+
+      exercise('enter');
+    }
+
+    expected_throws++;
+    dbg.onExceptionUnwind = function(frame, value) {
+      dbg.onExceptionUnwind = undefined;
+      assertEq(value, 'myrmidon');
+      exercise('exception');
+      if (--expected_throws > 0) {
+        return undefined;
+      } else {
+        return { return: 'escutcheon' };
+      }
+    };
+
+    // Set a breakpoint on entry to g.breakpoint_here.
+    const script = dbg.gDO.getOwnPropertyDescriptor('breakpoint_here').value.script;
+    const handler = {
+      hit(frame) {
+        script.clearAllBreakpoints();
+        exercise('bp');
+      }
+    };
+    script.setBreakpoint(0, handler);
+
+    dbg.uncaughtExceptionHook = function (ex) {
+      assertEq(ex, 'turncoat');
+      exercise('uncaught');
+    };
+
+    // Throw an uncaught exception from the Debugger handler. This should reach
+    // uncaughtExceptionHook, but shouldn't affect the debuggee.
+    throw 'turncoat';
+  };
+
+  return dbg;
+}
+
+const dbg1 = setup(g, '1');
+const dbg2 = setup(g, '2');
+const dbg3 = setup(g, '3');
+
+g.eval(`
+  function breakpoint_here() {
+    throw 'myrmidon';
+  }
+
+  parent.log += 'eval-start\\n';
+
+  // DebuggeeWouldRun detection may prevent this callback from running at all if
+  // bug 1145201 is present. SpiderMonkey will try to run the promise reaction
+  // job from the Debugger hook's microtask checkpoint, triggering
+  // DebuggeeWouldRun. This is a little difficult to observe, since the callback
+  // never even begins execution. But it should cause the 'then' promise to be
+  // rejected, which the shell will report (if the assertEq(log, ...) doesn't
+  // kill the test first).
+
+  Promise.resolve(84).then(function(v) {
+    assertEq(v, 84);
+    parent.log += 'eval-react';
+  });
+  debugger;
+  parent.log += 'stuff to step over\\n';
+  breakpoint_here();
+  parent.log += 'eval-end\\n';
+`);
+
+log += 'main-drain\n'
+drainJobQueue();
+log += 'main-drain-done\n';
+
+const regex = new RegExp(`eval-start
+.-debugger-handler
+.-uncaught-handler
+.-debugger-tail
+.-uncaught-tail
+.-debugger-handler
+.-uncaught-handler
+.-debugger-tail
+.-uncaught-tail
+.-debugger-handler
+.-uncaught-handler
+.-debugger-tail
+.-uncaught-tail
+.-step-handler
+.-step-tail
+.-step-handler
+.-step-tail
+.-step-handler
+.-step-tail
+stuff to step over
+.-enter-handler
+.-enter-tail
+.-enter-handler
+.-enter-tail
+.-enter-handler
+.-enter-tail
+.-bp-handler
+.-bp-tail
+.-bp-handler
+.-bp-tail
+.-bp-handler
+.-bp-tail
+.-exception-handler
+.-exception-tail
+.-exception-handler
+.-exception-tail
+.-exception-handler
+.-exception-tail
+.-pop-handler
+.-pop-tail
+.-pop-handler
+.-pop-tail
+.-pop-handler
+.-pop-tail
+eval-end
+main-drain
+eval-reactmain-drain-done
+`);
+
+assertEq(!!log.match(regex), true)
+
+assertEq(dbg1.log, 'debugger,uncaught,step,enter,bp,exception,pop,');
+assertEq(dbg2.log, 'debugger,uncaught,step,enter,bp,exception,pop,');
+assertEq(dbg3.log, 'debugger,uncaught,step,enter,bp,exception,pop,');
--- a/js/src/jit-test/tests/debug/onNewScript-wasm-01.js
+++ b/js/src/jit-test/tests/debug/onNewScript-wasm-01.js
@@ -1,16 +1,20 @@
 // |jit-test| skip-if: !wasmDebuggingIsSupported()
 // Draining the job queue from an onNewScript hook reporting a streamed wasm
 // module should not deadlock.
 
 ignoreUnhandledRejections();
 
 try {
     WebAssembly.compileStreaming();
+    // Avoid mixing the test's jobs with the debuggee's, so that
+    // automated checks can make sure AutoSaveJobQueue only
+    // suspends debuggee work.
+    drainJobQueue();
 } catch (err) {
     assertEq(String(err).indexOf("not supported with --no-threads") !== -1, true);
     quit();
 }
 
 load(libdir + "asserts.js");
 
 var g = newGlobal({newCompartment: true});
--- a/js/src/jit-test/tests/debug/onNewScript-wasm-02.js
+++ b/js/src/jit-test/tests/debug/onNewScript-wasm-02.js
@@ -1,16 +1,20 @@
 // |jit-test| skip-if: !wasmDebuggingIsSupported()
 // Draining the job queue from an onNewScript hook reporting a streamed wasm
 // module should not deadlock.
 
 ignoreUnhandledRejections();
 
 try {
     WebAssembly.compileStreaming();
+    // Avoid mixing the test's jobs with the debuggee's, so that
+    // automated checks can make sure AutoSaveJobQueue only
+    // suspends debuggee work.
+    drainJobQueue();
 } catch (err) {
     assertEq(String(err).indexOf("not supported with --no-threads") !== -1, true);
     quit();
 }
 
 var g = newGlobal({newCompartment: true});
 
 var source = new g.Uint8Array(wasmTextToBinary('(module (func unreachable))'));
--- a/js/src/jit-test/tests/debug/wasm-responseurls.js
+++ b/js/src/jit-test/tests/debug/wasm-responseurls.js
@@ -1,16 +1,20 @@
 // |jit-test| test-also=--wasm-compiler=ion; skip-if: !wasmDebuggingIsSupported()
 // Tests that wasm module can accept URL and sourceMapURL from response
 // when instantiateStreaming is used.
 
 ignoreUnhandledRejections();
 
 try {
     WebAssembly.compileStreaming();
+    // Avoid mixing the test's jobs with the debuggee's, so that
+    // automated checks can make sure AutoSaveJobQueue only
+    // suspends debuggee work.
+    drainJobQueue();
 } catch (err) {
     assertEq(String(err).indexOf("not supported with --no-threads") !== -1, true);
     quit();
 }
 
 load(libdir + "asserts.js");
 
 var g = newGlobal({newCompartment: true});
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -1659,22 +1659,43 @@ bool BaselineCodeGen<Handler>::emit_JSOP
   return true;
 }
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_POS() {
   // Keep top stack value in R0.
   frame.popRegsAndSync(1);
 
-  // Inline path for int32 and double.
+  // Inline path for int32 and double; otherwise call VM.
   Label done;
   masm.branchTestNumber(Assembler::Equal, R0, &done);
 
-  // Call IC.
-  if (!emitNextIC()) {
+  prepareVMCall();
+  pushArg(R0);
+  if (!callVM(ToNumberInfo)) {
+    return false;
+  }
+
+  masm.bind(&done);
+  frame.push(R0);
+  return true;
+}
+
+template <typename Handler>
+bool BaselineCodeGen<Handler>::emit_JSOP_TONUMERIC() {
+  // Keep top stack value in R0.
+  frame.popRegsAndSync(1);
+
+  // Inline path for int32 and double; otherwise call VM.
+  Label done;
+  masm.branchTestNumber(Assembler::Equal, R0, &done);
+
+  prepareVMCall();
+  pushArg(R0);
+  if (!callVM(ToNumericInfo)) {
     return false;
   }
 
   masm.bind(&done);
   frame.push(R0);
   return true;
 }
 
--- a/js/src/jit/BaselineCompiler.h
+++ b/js/src/jit/BaselineCompiler.h
@@ -33,16 +33,17 @@ namespace jit {
   _(JSOP_UNPICK)                \
   _(JSOP_GOTO)                  \
   _(JSOP_IFEQ)                  \
   _(JSOP_IFNE)                  \
   _(JSOP_AND)                   \
   _(JSOP_OR)                    \
   _(JSOP_NOT)                   \
   _(JSOP_POS)                   \
+  _(JSOP_TONUMERIC)             \
   _(JSOP_LOOPHEAD)              \
   _(JSOP_LOOPENTRY)             \
   _(JSOP_VOID)                  \
   _(JSOP_UNDEFINED)             \
   _(JSOP_HOLE)                  \
   _(JSOP_NULL)                  \
   _(JSOP_TRUE)                  \
   _(JSOP_FALSE)                 \
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -209,23 +209,16 @@ void ICEntry::trace(JSTracer* trc) {
       case JSOP_STRICTEQ:
       case JSOP_STRICTNE: {
         ICCompare_Fallback::Compiler stubCompiler(cx);
         if (!addIC(pc, stubCompiler.getStub(&stubSpace))) {
           return nullptr;
         }
         break;
       }
-      case JSOP_POS: {
-        ICToNumber_Fallback::Compiler stubCompiler(cx);
-        if (!addIC(pc, stubCompiler.getStub(&stubSpace))) {
-          return nullptr;
-        }
-        break;
-      }
       case JSOP_LOOPENTRY: {
         ICWarmUpCounter_Fallback::Compiler stubCompiler(cx);
         if (!addIC(pc, stubCompiler.getStub(&stubSpace))) {
           return nullptr;
         }
         break;
       }
       case JSOP_NEWARRAY: {
@@ -1924,50 +1917,16 @@ bool ICToBool_Fallback::Compiler::genera
   // Push arguments.
   masm.pushValue(R0);
   masm.push(ICStubReg);
   pushStubPayload(masm, R0.scratchReg());
 
   return tailCallVM(fun, masm);
 }
 
-//
-// ToNumber_Fallback
-//
-
-static bool DoToNumberFallback(JSContext* cx, ICToNumber_Fallback* stub,
-                               HandleValue arg, MutableHandleValue ret) {
-  stub->incrementEnteredCount();
-  FallbackICSpew(cx, stub, "ToNumber");
-  ret.set(arg);
-  return ToNumber(cx, ret);
-}
-
-typedef bool (*DoToNumberFallbackFn)(JSContext*, ICToNumber_Fallback*,
-                                     HandleValue, MutableHandleValue);
-static const VMFunction DoToNumberFallbackInfo =
-    FunctionInfo<DoToNumberFallbackFn>(DoToNumberFallback, "DoToNumberFallback",
-                                       TailCall, PopValues(1));
-
-bool ICToNumber_Fallback::Compiler::generateStubCode(MacroAssembler& masm) {
-  MOZ_ASSERT(R0 == JSReturnOperand);
-
-  // Restore the tail call register.
-  EmitRestoreTailCallReg(masm);
-
-  // Ensure stack is fully synced for the expression decompiler.
-  masm.pushValue(R0);
-
-  // Push arguments.
-  masm.pushValue(R0);
-  masm.push(ICStubReg);
-
-  return tailCallVM(DoToNumberFallbackInfo, masm);
-}
-
 static void StripPreliminaryObjectStubs(JSContext* cx, ICFallbackStub* stub) {
   // Before the new script properties analysis has been performed on a type,
   // all instances of that type have the maximum number of fixed slots.
   // Afterwards, the objects (even the preliminary ones) might be changed
   // to reduce the number of fixed slots they have. If we generate stubs for
   // both the old and new number of fixed slots, the stub will look
   // polymorphic to IonBuilder when it is actually monomorphic. To avoid
   // this, strip out any stubs for preliminary objects before attaching a new
--- a/js/src/jit/BaselineIC.h
+++ b/js/src/jit/BaselineIC.h
@@ -1645,41 +1645,16 @@ class ICToBool_Fallback : public ICFallb
         : ICStubCompiler(cx, ICStub::ToBool_Fallback) {}
 
     ICStub* getStub(ICStubSpace* space) override {
       return newStub<ICToBool_Fallback>(space, getStubCode());
     }
   };
 };
 
-// ToNumber
-//     JSOP_POS
-
-class ICToNumber_Fallback : public ICFallbackStub {
-  friend class ICStubSpace;
-
-  explicit ICToNumber_Fallback(JitCode* stubCode)
-      : ICFallbackStub(ICStub::ToNumber_Fallback, stubCode) {}
-
- public:
-  // Compiler for this stub kind.
-  class Compiler : public ICStubCompiler {
-   protected:
-    MOZ_MUST_USE bool generateStubCode(MacroAssembler& masm) override;
-
-   public:
-    explicit Compiler(JSContext* cx)
-        : ICStubCompiler(cx, ICStub::ToNumber_Fallback) {}
-
-    ICStub* getStub(ICStubSpace* space) override {
-      return newStub<ICToNumber_Fallback>(space, getStubCode());
-    }
-  };
-};
-
 // GetElem
 //      JSOP_GETELEM
 //      JSOP_GETELEM_SUPER
 
 class ICGetElem_Fallback : public ICMonitoredFallbackStub {
   friend class ICStubSpace;
 
   explicit ICGetElem_Fallback(JitCode* stubCode)
--- a/js/src/jit/BaselineICList.h
+++ b/js/src/jit/BaselineICList.h
@@ -27,18 +27,16 @@ namespace jit {
   _(TypeUpdate_AnyValue)              \
                                       \
   _(NewArray_Fallback)                \
   _(NewObject_Fallback)               \
   _(NewObject_WithTemplate)           \
                                       \
   _(ToBool_Fallback)                  \
                                       \
-  _(ToNumber_Fallback)                \
-                                      \
   _(UnaryArith_Fallback)              \
                                       \
   _(Call_Fallback)                    \
   _(Call_Scripted)                    \
   _(Call_AnyScripted)                 \
   _(Call_Native)                      \
   _(Call_ClassHook)                   \
   _(Call_ScriptedApplyArray)          \
--- a/js/src/jit/BaselineInspector.cpp
+++ b/js/src/jit/BaselineInspector.cpp
@@ -691,16 +691,21 @@ static bool TryToSpecializeBinaryArithOp
 MIRType BaselineInspector::expectedBinaryArithSpecialization(jsbytecode* pc) {
   if (!hasICScript()) {
     return MIRType::None;
   }
 
   MIRType result;
   ICStub* stubs[2];
 
+  if (JSOp(*pc) == JSOP_POS) {
+    // +x expanding to x*1, but no corresponding IC.
+    return MIRType::None;
+  }
+
   const ICEntry& entry = icEntryFromPC(pc);
   ICFallbackStub* stub = entry.fallbackStub();
   if (stub->state().hasFailures()) {
     return MIRType::None;
   }
 
   stubs[0] = monomorphicStub(pc);
   if (stubs[0]) {
--- a/js/src/jit/CacheIRSpewer.cpp
+++ b/js/src/jit/CacheIRSpewer.cpp
@@ -26,33 +26,33 @@
 #  include "vm/Realm-inl.h"
 
 using namespace js;
 using namespace js::jit;
 
 CacheIRSpewer CacheIRSpewer::cacheIRspewer;
 
 CacheIRSpewer::CacheIRSpewer()
-    : outputLock(mutexid::CacheIRSpewer), guardCount_(0) {
+    : outputLock_(mutexid::CacheIRSpewer), guardCount_(0) {
   spewInterval_ =
       getenv("CACHEIR_LOG_FLUSH") ? atoi(getenv("CACHEIR_LOG_FLUSH")) : 10000;
 
   if (spewInterval_ < 1) {
     spewInterval_ = 1;
   }
 }
 
 CacheIRSpewer::~CacheIRSpewer() {
   if (!enabled()) {
     return;
   }
 
-  json.ref().endList();
-  output.flush();
-  output.finish();
+  json_.ref().endList();
+  output_.flush();
+  output_.finish();
 }
 
 #  ifndef JIT_SPEW_DIR
 #    if defined(_WIN32)
 #      define JIT_SPEW_DIR "."
 #    elif defined(__ANDROID__)
 #      define JIT_SPEW_DIR "/data/local/tmp"
 #    else
@@ -69,28 +69,28 @@ bool CacheIRSpewer::init(const char* fil
   uint32_t pid = getpid();
   // Default to JIT_SPEW_DIR/cacheir${pid}.json
   if (filename[0] == '1') {
     SprintfLiteral(name, JIT_SPEW_DIR "/cacheir%" PRIu32 ".json", pid);
   } else {
     SprintfLiteral(name, "%s%" PRIu32 ".json", filename, pid);
   }
 
-  if (!output.init(name)) {
+  if (!output_.init(name)) {
     return false;
   }
-  output.put("[");
+  output_.put("[");
 
-  json.emplace(output);
+  json_.emplace(output_);
   return true;
 }
 
 void CacheIRSpewer::beginCache(const IRGenerator& gen) {
   MOZ_ASSERT(enabled());
-  JSONPrinter& j = json.ref();
+  JSONPrinter& j = json_.ref();
   const char* filename = gen.script_->filename();
   j.beginObject();
   j.property("name", CacheKindNames[uint8_t(gen.cacheKind_)]);
   j.property("file", filename ? filename : "null");
   j.property("mode", int(gen.mode_));
   if (jsbytecode* pc = gen.pc_) {
     unsigned column;
     j.property("line", PCToLineNumber(gen.script_, pc, &column));
@@ -123,17 +123,17 @@ static void QuoteString(GenericPrinter& 
     QuoteString(out, str->latin1Chars(nogc), str->length());
   } else {
     QuoteString(out, str->twoByteChars(nogc), str->length());
   }
 }
 
 void CacheIRSpewer::valueProperty(const char* name, const Value& v) {
   MOZ_ASSERT(enabled());
-  JSONPrinter& j = json.ref();
+  JSONPrinter& j = json_.ref();
 
   j.beginObjectProperty(name);
 
   const char* type = InformalValueTypeName(v);
   if (v.isInt32()) {
     type = "int32";
   }
   j.property("type", type);
@@ -141,17 +141,17 @@ void CacheIRSpewer::valueProperty(const 
   if (v.isInt32()) {
     j.property("value", v.toInt32());
   } else if (v.isDouble()) {
     j.floatProperty("value", v.toDouble(), 3);
   } else if (v.isString() || v.isSymbol()) {
     JSString* str = v.isString() ? v.toString() : v.toSymbol()->description();
     if (str && str->isLinear()) {
       j.beginStringProperty("value");
-      QuoteString(output, &str->asLinear());
+      QuoteString(output_, &str->asLinear());
       j.endStringProperty();
     }
   } else if (v.isObject()) {
     JSObject& object = v.toObject();
     j.formatProperty("value", "%p (shape: %p)", &object, object.maybeShape());
     if (NativeObject* nobj =
             object.isNative() ? &object.as<NativeObject>() : nullptr) {
       j.beginListProperty("flags");
@@ -180,26 +180,26 @@ void CacheIRSpewer::valueProperty(const 
     }
   }
 
   j.endObject();
 }
 
 void CacheIRSpewer::opcodeProperty(const char* name, const JSOp op) {
   MOZ_ASSERT(enabled());
-  JSONPrinter& j = json.ref();
+  JSONPrinter& j = json_.ref();
 
   j.beginStringProperty(name);
-  output.put(CodeName[op]);
+  output_.put(CodeName[op]);
   j.endStringProperty();
 }
 
 void CacheIRSpewer::attached(const char* name) {
   MOZ_ASSERT(enabled());
-  json.ref().property("attached", name);
+  json_.ref().property("attached", name);
 }
 
 void CacheIRSpewer::endCache() {
   MOZ_ASSERT(enabled());
-  json.ref().endObject();
+  json_.ref().endObject();
 }
 
 #endif /* JS_CACHEIR_SPEW */
--- a/js/src/jit/CacheIRSpewer.h
+++ b/js/src/jit/CacheIRSpewer.h
@@ -16,40 +16,40 @@
 #  include "threading/LockGuard.h"
 #  include "vm/JSONPrinter.h"
 #  include "vm/MutexIDs.h"
 
 namespace js {
 namespace jit {
 
 class CacheIRSpewer {
-  Mutex outputLock;
-  Fprinter output;
-  mozilla::Maybe<JSONPrinter> json;
+  Mutex outputLock_;
+  Fprinter output_;
+  mozilla::Maybe<JSONPrinter> json_;
   static CacheIRSpewer cacheIRspewer;
 
   // Counter to record how many times Guard class is called. This is used to
   // determine when to flush outputs based on the given interval value.
   // For example, if |spewInterval_ = 2|, outputs will be flushed on
   // guardCount_ values 0,2,4,6,...
   uint32_t guardCount_;
 
   // Interval at which to flush output files. This value can be set with the
   // environment variable |CACHEIR_LOG_FLUSH|.
   uint32_t spewInterval_;
 
   CacheIRSpewer();
   ~CacheIRSpewer();
 
-  bool enabled() { return json.isSome(); }
+  bool enabled() { return json_.isSome(); }
 
   // These methods can only be called when enabled() is true.
   Mutex& lock() {
     MOZ_ASSERT(enabled());
-    return outputLock;
+    return outputLock_;
   }
 
   void beginCache(const IRGenerator& generator);
   void valueProperty(const char* name, const Value& v);
   void opcodeProperty(const char* name, const JSOp op);
   void attached(const char* name);
   void endCache();
 
@@ -73,17 +73,17 @@ class CacheIRSpewer {
 
     ~Guard() {
       if (sp_.enabled()) {
         if (name_ != nullptr) {
           sp_.attached(name_);
         }
         sp_.endCache();
         if (sp_.guardCount_++ % sp_.spewInterval_ == 0) {
-          sp_.output.flush();
+          sp_.output_.flush();
         }
         sp_.lock().unlock();
       }
     }
 
     void valueProperty(const char* name, const Value& v) const {
       sp_.valueProperty(name, v);
     }
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -3998,16 +3998,61 @@ void CodeGenerator::visitGuardUnboxedExp
 
 void CodeGenerator::visitLoadUnboxedExpando(LLoadUnboxedExpando* lir) {
   Register obj = ToRegister(lir->object());
   Register result = ToRegister(lir->getDef(0));
 
   masm.loadPtr(Address(obj, UnboxedPlainObject::offsetOfExpando()), result);
 }
 
+void CodeGenerator::visitToNumeric(LToNumeric* lir) {
+  ValueOperand operand = ToValue(lir, LToNumeric::Input);
+  ValueOperand output = ToOutValue(lir);
+  bool maybeInt32 = lir->mir()->mightBeType(MIRType::Int32);
+  bool maybeDouble = lir->mir()->mightBeType(MIRType::Double);
+  bool maybeNumber = maybeInt32 || maybeDouble;
+#ifdef ENABLE_BIGINT
+  bool maybeBigInt = lir->mir()->mightBeType(MIRType::BigInt);
+#endif
+  int checks = int(maybeNumber) + IF_BIGINT(int(maybeBigInt), 0);
+
+  OutOfLineCode* ool =
+      oolCallVM(ToNumericInfo, lir, ArgList(operand), StoreValueTo(output));
+
+  if (checks == 0) {
+    masm.jump(ool->entry());
+  } else {
+    Label done;
+    using Condition = Assembler::Condition;
+    constexpr Condition Equal = Assembler::Equal;
+    constexpr Condition NotEqual = Assembler::NotEqual;
+
+    if (maybeNumber) {
+      checks--;
+      Condition cond = checks ? Equal : NotEqual;
+      Label* target = checks ? &done : ool->entry();
+      masm.branchTestNumber(cond, operand, target);
+    }
+#ifdef ENABLE_BIGINT
+    if (maybeBigInt) {
+      checks--;
+      Condition cond = checks ? Equal : NotEqual;
+      Label* target = checks ? &done : ool->entry();
+      masm.branchTestBigInt(cond, operand, target);
+    }
+#endif
+
+    MOZ_ASSERT(checks == 0);
+    masm.bind(&done);
+    masm.moveValue(operand, output);
+  }
+
+  masm.bind(ool->rejoin());
+}
+
 void CodeGenerator::visitTypeBarrierV(LTypeBarrierV* lir) {
   ValueOperand operand = ToValue(lir, LTypeBarrierV::Input);
   Register unboxScratch = ToTempRegisterOrInvalid(lir->unboxTemp());
   Register objScratch = ToTempRegisterOrInvalid(lir->objTemp());
 
   // guardObjectType may zero the payload/Value register on speculative paths
   // (we should have a defineReuseInput allocation in this case).
   Register spectreRegToZero = operand.payloadOrValueReg();
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -617,17 +617,17 @@ AbortReasonOr<Ok> IonBuilder::analyzeNew
       continue;
     }
     if (!last) {
       continue;
     }
 
     MPhi* phi = entry->getSlot(slot)->toPhi();
 
-    if (*last == JSOP_POS) {
+    if (*last == JSOP_POS || *last == JSOP_TONUMERIC) {
       last = earlier;
     }
 
     if (CodeSpec[*last].format & JOF_TYPESET) {
       TemporaryTypeSet* typeSet = bytecodeTypes(last);
       if (!typeSet->empty()) {
         MIRType type = typeSet->getKnownMIRType();
         if (!phi->addBackedgeType(alloc(), type, typeSet)) {
@@ -1654,16 +1654,17 @@ AbortReasonOr<Ok> IonBuilder::visitBlock
         case JSOP_SETLOCAL:
         case JSOP_INITLEXICAL:
         case JSOP_SETRVAL:
         case JSOP_VOID:
           // Don't require SSA uses for values popped by these ops.
           break;
 
         case JSOP_POS:
+        case JSOP_TONUMERIC:
         case JSOP_TOID:
         case JSOP_TOSTRING:
           // These ops may leave their input on the stack without setting
           // the ImplicitlyUsed flag. If this value will be popped immediately,
           // we may replace it with |undefined|, but the difference is
           // not observable.
           MOZ_ASSERT(i == 0);
           if (current->peek(-1) == popped[0]) {
@@ -1896,16 +1897,19 @@ AbortReasonOr<Ok> IonBuilder::inspectOpc
       return jsop_binary_arith(op);
 
     case JSOP_POW:
       return jsop_pow();
 
     case JSOP_POS:
       return jsop_pos();
 
+    case JSOP_TONUMERIC:
+      return jsop_tonumeric();