Merge mozilla-central to autoland. CLOSED TREE
authorCsoregi Natalia <ncsoregi@mozilla.com>
Fri, 16 Nov 2018 00:19:45 +0200
changeset 503113 20e31516e9bb4fdab1be492a5105cd26f91cc5e7
parent 503112 eed15f1e00944954d89865708a7bf7e759fe3d50 (diff)
parent 503045 0051c8d339a969d80b66e6ce243091a9dbb6a319 (current diff)
child 503114 8f260179cc9fe83cc36b185ed8a03c7a1848ba1a
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone65.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 autoland. CLOSED TREE
build/build-clang/rename_gcov_flush.patch
devtools/client/inspector/flexbox/components/FlexContainerProperties.js
media/mtransport/third_party/nICEr/src/net/nr_proxy_tunnel.c
media/mtransport/third_party/nICEr/src/net/nr_proxy_tunnel.h
netwerk/protocol/http/nsHttpChannel.cpp
--- a/browser/base/content/test/favicons/browser.ini
+++ b/browser/base/content/test/favicons/browser.ini
@@ -57,8 +57,12 @@ support-files =
 support-files =
   file_insecure_favicon.html
   file_favicon.png
 [browser_title_flicker.js]
 support-files =
   file_with_slow_favicon.html
   blank.html
   file_favicon.png
+[browser_favicon_cache.js]
+support-files =
+  cookie_favicon.sjs
+  cookie_favicon.html
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/favicons/browser_favicon_cache.js
@@ -0,0 +1,40 @@
+add_task(async () => {
+  const testPath = "http://example.com/browser/browser/base/content/test/favicons/cookie_favicon.html";
+  const resetPath = "http://example.com/browser/browser/base/content/test/favicons/cookie_favicon.sjs?reset";
+
+  let tab = BrowserTestUtils.addTab(gBrowser, testPath);
+  gBrowser.selectedTab = tab;
+  let browser = tab.linkedBrowser;
+
+  let faviconPromise = waitForLinkAvailable(browser);
+  await BrowserTestUtils.browserLoaded(browser);
+  await faviconPromise;
+  let cookies = Services.cookies.getCookiesFromHost("example.com", browser.contentPrincipal.originAttributes);
+  let seenCookie = false;
+  for (let cookie of cookies) {
+    if (cookie.name == "faviconCookie") {
+      seenCookie = true;
+      is(cookie.value, 1, "Should have seen the right initial cookie.");
+    }
+  }
+  ok(seenCookie, "Should have seen the cookie.");
+
+  faviconPromise = waitForLinkAvailable(browser);
+  BrowserTestUtils.loadURI(browser, testPath);
+  await BrowserTestUtils.browserLoaded(browser);
+  await faviconPromise;
+  cookies = Services.cookies.getCookiesFromHost("example.com", browser.contentPrincipal.originAttributes);
+  seenCookie = false;
+  for (let cookie of cookies) {
+    if (cookie.name == "faviconCookie") {
+      seenCookie = true;
+      is(cookie.value, 1, "Should have seen the cached cookie.");
+    }
+  }
+  ok(seenCookie, "Should have seen the cookie.");
+
+  // Reset the cookie so if this test is run again it will still pass.
+  await fetch(resetPath);
+
+  BrowserTestUtils.removeTab(tab);
+});
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/favicons/cookie_favicon.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <meta charset='utf-8'>
+    <title>Favicon Test for caching</title>
+    <link rel="icon" type="image/png" href="cookie_favicon.sjs" />
+  </head>
+  <body>
+    Favicon!!
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/favicons/cookie_favicon.sjs
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function handleRequest(request, response) {
+  if (request.queryString == "reset") {
+    setState("cache_cookie", "0");
+    response.setStatusLine(request.httpVersion, 200, "Ok");
+    response.write("Reset");
+    return;
+  }
+
+  let state = getState("cache_cookie");
+  if (!state) {
+    state = 0;
+  }
+
+  response.setStatusLine(request.httpVersion, 302, "Moved Temporarily");
+  response.setHeader("Set-Cookie", `faviconCookie=${++state}`);
+  response.setHeader("Location", "http://example.com/browser/browser/base/content/test/favicons/moz.png");
+  setState("cache_cookie", `${state}`);
+}
--- a/browser/base/content/test/general/browser_alltabslistener.js
+++ b/browser/base/content/test/general/browser_alltabslistener.js
@@ -114,52 +114,56 @@ function runTest(browser, url, next) {
 
 function startTest1() {
   info("\nTest 1");
   gBrowser.addProgressListener(gFrontProgressListener);
   gBrowser.addTabsProgressListener(gAllProgressListener);
 
   gAllNotifications = [
     "onStateChange",
+    "onSecurityChange",
     "onLocationChange",
     "onSecurityChange",
     "onStateChange",
   ];
   gFrontNotifications = gAllNotifications;
   runTest(gForegroundBrowser, "http://example.org" + gTestPage, startTest2);
 }
 
 function startTest2() {
   info("\nTest 2");
   gAllNotifications = [
     "onStateChange",
+    "onSecurityChange",
     "onLocationChange",
     "onSecurityChange",
     "onStateChange",
   ];
   gFrontNotifications = gAllNotifications;
   runTest(gForegroundBrowser, "https://example.com" + gTestPage, startTest3);
 }
 
 function startTest3() {
   info("\nTest 3");
   gAllNotifications = [
     "onStateChange",
+    "onSecurityChange",
     "onLocationChange",
     "onSecurityChange",
     "onStateChange",
   ];
   gFrontNotifications = [];
   runTest(gBackgroundBrowser, "http://example.org" + gTestPage, startTest4);
 }
 
 function startTest4() {
   info("\nTest 4");
   gAllNotifications = [
     "onStateChange",
+    "onSecurityChange",
     "onLocationChange",
     "onSecurityChange",
     "onStateChange",
   ];
   gFrontNotifications = [];
   runTest(gBackgroundBrowser, "https://example.com" + gTestPage, startTest5);
 }
 
@@ -170,28 +174,30 @@ function startTest5() {
   [gForegroundTab, gBackgroundTab] = [gBackgroundTab, gForegroundTab];
   // Avoid the onLocationChange this will fire
   gBrowser.removeProgressListener(gFrontProgressListener);
   gBrowser.selectedTab = gForegroundTab;
   gBrowser.addProgressListener(gFrontProgressListener);
 
   gAllNotifications = [
     "onStateChange",
+    "onSecurityChange",
     "onLocationChange",
     "onSecurityChange",
     "onStateChange",
   ];
   gFrontNotifications = gAllNotifications;
   runTest(gForegroundBrowser, "http://example.org" + gTestPage, startTest6);
 }
 
 function startTest6() {
   info("\nTest 6");
   gAllNotifications = [
     "onStateChange",
+    "onSecurityChange",
     "onLocationChange",
     "onSecurityChange",
     "onStateChange",
   ];
   gFrontNotifications = [];
   runTest(gBackgroundBrowser, "http://example.org" + gTestPage, finishTest);
 }
 
--- a/browser/base/content/test/performance/browser_preferences_usage.js
+++ b/browser/base/content/test/performance/browser_preferences_usage.js
@@ -184,29 +184,29 @@ add_task(async function navigate_around(
       min: 100,
       max: 110,
     },
     "network.loadinfo.skip_type_assertion": {
       // This is accessed in debug only.
     },
     "security.insecure_connection_icon.pbmode.enabled": {
       min: 20,
-      max: 30,
+      max: 60,
     },
     "security.insecure_connection_icon.enabled": {
       min: 20,
-      max: 30,
+      max: 60,
     },
     "security.insecure_connection_text.enabled": {
       min: 20,
-      max: 30,
+      max: 60,
     },
     "security.insecure_connection_text.pbmode.enabled": {
       min: 20,
-      max: 30,
+      max: 60,
     },
     "toolkit.cosmeticAnimations.enabled": {
       min: 45,
       max: 55,
     },
   };
 
   Services.prefs.resetStats();
--- a/browser/base/content/test/siteIdentity/browser_ignore_same_page_navigation.js
+++ b/browser/base/content/test/siteIdentity/browser_ignore_same_page_navigation.js
@@ -26,16 +26,16 @@ add_task(async function() {
     };
     browser.addProgressListener(progressListener, Ci.nsIWebProgress.NOTIFY_ALL);
 
     let uri = getRootDirectory(gTestPath).replace("chrome://mochitests/content",
                                                   "https://example.com") + "dummy_page.html";
     BrowserTestUtils.loadURI(browser, uri);
     await BrowserTestUtils.browserLoaded(browser, false, uri);
     is(onLocationChangeCount, 1, "should have 1 onLocationChange event");
-    is(onSecurityChangeCount, 1, "should have 1 onSecurityChange event");
+    is(onSecurityChangeCount, 2, "should have 2 onSecurityChange event");
     await ContentTask.spawn(browser, null, async () => {
       content.history.pushState({}, "", "https://example.com");
     });
     is(onLocationChangeCount, 2, "should have 2 onLocationChange events");
-    is(onSecurityChangeCount, 1, "should still have only 1 onSecurityChange event");
+    is(onSecurityChangeCount, 2, "should still have only 2 onSecurityChange event");
   });
 });
--- a/browser/base/content/test/trackingUI/browser_trackingUI_animation_2.js
+++ b/browser/base/content/test/trackingUI/browser_trackingUI_animation_2.js
@@ -6,16 +6,18 @@
 
 const TP_PREF = "privacy.trackingprotection.enabled";
 const TP_PB_PREF = "privacy.trackingprotection.enabled";
 const NCB_PREF = "network.cookie.cookieBehavior";
 const BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/benignPage.html";
 const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/trackingPage.html";
 const COOKIE_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/cookiePage.html";
 
+requestLongerTimeout(2);
+
 registerCleanupFunction(function() {
   UrlClassifierTestUtils.cleanupTestTrackers();
   Services.prefs.clearUserPref(TP_PREF);
   Services.prefs.clearUserPref(TP_PB_PREF);
   Services.prefs.clearUserPref(NCB_PREF);
   Services.prefs.clearUserPref(ContentBlocking.prefIntroCount);
 });
 
@@ -78,36 +80,36 @@ async function testTrackingProtectionAni
   securityChanged = waitForSecurityChange(tabbrowser);
   tabbrowser.selectedTab = trackingCookiesTab;
   await securityChanged;
 
   ok(ContentBlocking.iconBox.hasAttribute("active"), "iconBox active");
   ok(!ContentBlocking.iconBox.hasAttribute("animate"), "iconBox not animating");
 
   info("Reload tracking cookies tab");
-  securityChanged = waitForSecurityChange(tabbrowser, 2);
+  securityChanged = waitForSecurityChange(tabbrowser, 4);
   tabbrowser.reload();
   await securityChanged;
 
   ok(ContentBlocking.iconBox.hasAttribute("active"), "iconBox active");
   ok(ContentBlocking.iconBox.hasAttribute("animate"), "iconBox animating");
   await BrowserTestUtils.waitForEvent(ContentBlocking.animatedIcon, "animationend");
 
   info("Reload tracking tab");
-  securityChanged = waitForSecurityChange(tabbrowser, 3);
+  securityChanged = waitForSecurityChange(tabbrowser, 5);
   tabbrowser.selectedTab = trackingTab;
   tabbrowser.reload();
   await securityChanged;
 
   ok(ContentBlocking.iconBox.hasAttribute("active"), "iconBox active");
   ok(ContentBlocking.iconBox.hasAttribute("animate"), "iconBox animating");
   await BrowserTestUtils.waitForEvent(ContentBlocking.animatedIcon, "animationend");
 
   info("Inject tracking cookie inside tracking tab");
-  securityChanged = waitForSecurityChange(tabbrowser);
+  securityChanged = waitForSecurityChange(tabbrowser, 2);
   await ContentTask.spawn(tabbrowser.selectedBrowser, {},
                           function() {
     content.postMessage("cookie", "*");
   });
   await securityChanged;
 
   ok(ContentBlocking.iconBox.hasAttribute("active"), "iconBox active");
   ok(!ContentBlocking.iconBox.hasAttribute("animate"), "iconBox not animating");
@@ -121,28 +123,28 @@ async function testTrackingProtectionAni
   await securityChanged;
 
   ok(ContentBlocking.iconBox.hasAttribute("active"), "iconBox active");
   ok(!ContentBlocking.iconBox.hasAttribute("animate"), "iconBox not animating");
 
   tabbrowser.selectedTab = trackingCookiesTab;
 
   info("Inject tracking cookie inside tracking cookies tab");
-  securityChanged = waitForSecurityChange(tabbrowser);
+  securityChanged = waitForSecurityChange(tabbrowser, 2);
   await ContentTask.spawn(tabbrowser.selectedBrowser, {},
                           function() {
     content.postMessage("cookie", "*");
   });
   await securityChanged;
 
   ok(ContentBlocking.iconBox.hasAttribute("active"), "iconBox active");
   ok(!ContentBlocking.iconBox.hasAttribute("animate"), "iconBox not animating");
 
   info("Inject tracking element inside tracking cookies tab");
-  securityChanged = waitForSecurityChange(tabbrowser);
+  securityChanged = waitForSecurityChange(tabbrowser, 2);
   await ContentTask.spawn(tabbrowser.selectedBrowser, {},
                           function() {
     content.postMessage("tracking", "*");
   });
   await securityChanged;
 
   ok(ContentBlocking.iconBox.hasAttribute("active"), "iconBox active");
   ok(!ContentBlocking.iconBox.hasAttribute("animate"), "iconBox not animating");
--- a/browser/base/content/test/trackingUI/browser_trackingUI_telemetry.js
+++ b/browser/base/content/test/trackingUI/browser_trackingUI_telemetry.js
@@ -46,17 +46,17 @@ add_task(async function setup() {
 add_task(async function testShieldHistogram() {
   Services.prefs.setBoolPref(PREF, true);
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
 
   // Reset these to make counting easier
   getShieldHistogram().clear();
 
   await promiseTabLoadEvent(tab, BENIGN_PAGE);
-  is(getShieldCounts()[0], 1, "Page loads without tracking");
+  is(getShieldCounts()[0], 2, "Page loads without tracking");
 
   await promiseTabLoadEvent(tab, TRACKING_PAGE);
   // Note that right now the shield histogram is not measuring what
   // you might think.  Since onSecurityChange fires twice for a tracking page,
   // the total page loads count is double counting, and the shield count
   // (which is meant to measure times when the shield wasn't shown) fires even
   // when tracking elements exist on the page.
   todo_is(getShieldCounts()[0], 1, "FIXME: TOTAL PAGE LOADS WITHOUT TRACKING IS DOUBLE COUNTING");
--- a/browser/components/enterprisepolicies/EnterprisePolicies.js
+++ b/browser/components/enterprisepolicies/EnterprisePolicies.js
@@ -402,17 +402,19 @@ class JSONPoliciesProvider {
 class WindowsGPOPoliciesProvider {
   constructor() {
     this._policies = null;
 
     let wrk = Cc["@mozilla.org/windows-registry-key;1"].createInstance(Ci.nsIWindowsRegKey);
 
     // Machine policies override user policies, so we read
     // user policies first and then replace them if necessary.
+    log.debug("root = HKEY_CURRENT_USER");
     this._readData(wrk, wrk.ROOT_KEY_CURRENT_USER);
+    log.debug("root = HKEY_LOCAL_MACHINE");
     this._readData(wrk, wrk.ROOT_KEY_LOCAL_MACHINE);
   }
 
   get hasPolicies() {
     return this._policies !== null;
   }
 
   get policies() {
@@ -421,18 +423,17 @@ class WindowsGPOPoliciesProvider {
 
   get failed() {
     return this._failed;
   }
 
   _readData(wrk, root) {
     wrk.open(root, "SOFTWARE\\Policies", wrk.ACCESS_READ);
     if (wrk.hasChild("Mozilla\\Firefox")) {
-      let isMachineRoot = (root == wrk.ROOT_KEY_LOCAL_MACHINE);
-      this._policies = WindowsGPOParser.readPolicies(wrk, this._policies, isMachineRoot);
+      this._policies = WindowsGPOParser.readPolicies(wrk, this._policies);
     }
     wrk.close();
   }
 }
 
 class macOSPoliciesProvider {
   constructor() {
     this._policies = null;
--- a/browser/components/enterprisepolicies/WindowsGPOParser.jsm
+++ b/browser/components/enterprisepolicies/WindowsGPOParser.jsm
@@ -14,89 +14,75 @@ XPCOMUtils.defineLazyGetter(this, "log",
     prefix: "GPOParser.jsm",
     // tip: set maxLogLevel to "debug" and use log.debug() to create detailed
     // messages during development. See LOG_LEVELS in Console.jsm for details.
     maxLogLevel: "error",
     maxLogLevelPref: PREF_LOGLEVEL,
   });
 });
 
-XPCOMUtils.defineLazyModuleGetters(this, {
-  schema: "resource:///modules/policies/schema.jsm",
-});
-
 var EXPORTED_SYMBOLS = ["WindowsGPOParser"];
 
 var WindowsGPOParser = {
-  readPolicies(wrk, policies, isMachineRoot) {
+  readPolicies(wrk, policies) {
     let childWrk = wrk.openChild("Mozilla\\Firefox", wrk.ACCESS_READ);
     if (!policies) {
       policies = {};
     }
     try {
-      policies = registryToObject(childWrk, policies, isMachineRoot);
+      policies = registryToObject(childWrk, policies);
     } catch (e) {
       log.error(e);
     } finally {
       childWrk.close();
     }
     // Need an extra check here so we don't
     // JSON.stringify if we aren't in debug mode
     if (log._maxLogLevel == "debug") {
-      log.debug("root = " + isMachineRoot ? "HKEY_LOCAL_MACHINE" : "HKEY_CURRENT_USER");
       log.debug(JSON.stringify(policies, null, 2));
     }
     return policies;
   },
 };
 
-function registryToObject(wrk, policies, isMachineRoot) {
+function registryToObject(wrk, policies) {
   if (!policies) {
     policies = {};
   }
   if (wrk.valueCount > 0) {
     if (wrk.getValueName(0) == "1") {
       // If the first item is 1, just assume it is an array
       let array = [];
       for (let i = 0; i < wrk.valueCount; i++) {
         array.push(readRegistryValue(wrk, wrk.getValueName(i)));
       }
       // If it's an array, it shouldn't have any children
       return array;
     }
     for (let i = 0; i < wrk.valueCount; i++) {
       let name = wrk.getValueName(i);
-      if (!isMachineRoot && isMachineOnlyPolicy(name)) {
-        continue;
-      }
       let value = readRegistryValue(wrk, name);
       policies[name] = value;
     }
   }
   if (wrk.childCount > 0) {
     if (wrk.getChildName(0) == "1") {
       // If the first item is 1, it's an array of objects
       let array = [];
       for (let i = 0; i < wrk.childCount; i++) {
         let name = wrk.getChildName(i);
-        if (!isMachineRoot && isMachineOnlyPolicy(name)) {
-          continue;
-        }
         let childWrk = wrk.openChild(name, wrk.ACCESS_READ);
         array.push(registryToObject(childWrk));
         childWrk.close();
       }
       // If it's an array, it shouldn't have any children
       return array;
     }
     for (let i = 0; i < wrk.childCount; i++) {
       let name = wrk.getChildName(i);
-        if (!isMachineRoot && isMachineOnlyPolicy(name)) {
-        continue;
-      }
       let childWrk = wrk.openChild(name, wrk.ACCESS_READ);
       policies[name] = registryToObject(childWrk);
       childWrk.close();
     }
   }
   return policies;
 }
 
@@ -109,17 +95,8 @@ function readRegistryValue(wrk, value) {
     case wrk.TYPE_INT:
       return wrk.readIntValue(value);
     case wrk.TYPE_INT64:
       return wrk.readInt64Value(value);
   }
   // unknown type
   return null;
 }
-
-function isMachineOnlyPolicy(name) {
-  if (schema.properties[name] &&
-      schema.properties[name].machine_only) {
-    log.error(`Policy ${name} is only allowed under the HKEY_LOCAL_MACHINE root`);
-    return true;
-  }
-  return false;
-}
--- a/browser/components/enterprisepolicies/content/aboutPolicies.css
+++ b/browser/components/enterprisepolicies/content/aboutPolicies.css
@@ -109,20 +109,16 @@ tbody:nth-child(4n + 1) {
   fill: var(--newtab-icon-primary-color);
   height: 14px;
   vertical-align: middle;
   width: 14px;
   margin-top: -.125rem;
   margin-left: .5rem;
 }
 
-.icon.machine-only {
-  background-image: url("chrome://browser/skin/developer.svg");
-}
-
 .collapsible {
   cursor: pointer;
   border: none;
   outline: none;
 }
 
 .content {
   display: none;
--- a/browser/components/enterprisepolicies/content/aboutPolicies.js
+++ b/browser/components/enterprisepolicies/content/aboutPolicies.js
@@ -17,28 +17,16 @@ function col(text, className) {
   if (className) {
     column.classList.add(className);
   }
   let content = document.createTextNode(text);
   column.appendChild(content);
   return column;
 }
 
-function machine_only_col(text) {
-  let icon = document.createElement("span");
-  icon.classList.add("icon");
-  icon.classList.add("machine-only");
-  icon.setAttribute("data-l10n-id", "gpo-machine-only");
-  let column = document.createElement("td");
-  let content = document.createTextNode(text);
-  column.appendChild(content);
-  column.appendChild(icon);
-  return column;
-}
-
 function addMissingColumns() {
   const table = document.getElementById("activeContent");
   let maxColumns = 0;
 
   // count the number of columns per row and set the max number of columns
   for (let i = 0, length = table.rows.length; i < length; i++) {
     if (maxColumns < table.rows[i].cells.length) {
       maxColumns = table.rows[i].cells.length;
@@ -241,22 +229,17 @@ function generateDocumentation() {
   for (let policyName in schema.properties) {
     let main_tbody = document.createElement("tbody");
     main_tbody.classList.add("collapsible");
     main_tbody.addEventListener("click", function() {
       let content = this.nextElementSibling;
       content.classList.toggle("content");
     });
     let row = document.createElement("tr");
-    if (AppConstants.platform == "win" &&
-        schema.properties[policyName].machine_only) {
-      row.appendChild(machine_only_col(policyName));
-    } else {
-      row.appendChild(col(policyName));
-    }
+    row.appendChild(col(policyName));
     let descriptionColumn = col("");
     let stringID = string_mapping[policyName] || policyName;
     descriptionColumn.setAttribute("data-l10n-id", `policy-${stringID}`);
     row.appendChild(descriptionColumn);
     main_tbody.appendChild(row);
     let sec_tbody = document.createElement("tbody");
     sec_tbody.classList.add("content");
     sec_tbody.classList.add("content-style");
--- a/browser/components/enterprisepolicies/schemas/policies-schema.json
+++ b/browser/components/enterprisepolicies/schemas/policies-schema.json
@@ -1,15 +1,13 @@
 {
   "$schema": "http://json-schema.org/draft-04/schema#",
   "type": "object",
   "properties": {
     "AppUpdateURL": {
-      "machine_only": true,
-
       "type": "URL"
     },
 
     "Authentication": {
       "type": "object",
       "properties": {
         "SPNEGO" : {
           "type": "array",
@@ -158,18 +156,16 @@
         },
         "Locked": {
           "type": "boolean"
         }
       }
     },
 
     "DisableAppUpdate": {
-      "machine_only": true,
-
       "type": "boolean"
     },
 
     "DisableBuiltinPDFViewer": {
       "type": "boolean"
     },
 
     "DisableDeveloperTools": {
@@ -237,24 +233,20 @@
       }
     },
 
     "DisableSetDesktopBackground": {
       "type": "boolean"
     },
 
     "DisableSystemAddonUpdate": {
-      "machine_only": true,
-
       "type": "boolean"
     },
 
     "DisableTelemetry": {
-      "machine_only": true,
-
       "type": "boolean"
     },
 
     "DisplayBookmarksToolbar": {
       "type": "boolean"
     },
 
     "DisplayMenuBar": {
@@ -274,18 +266,16 @@
         "Locked": {
           "type": "boolean"
         }
       },
       "required": ["Value"]
     },
 
     "Extensions": {
-      "machine_only": true,
-
       "type": "object",
       "properties": {
         "Install" : {
           "type": "array",
           "items": {
             "type": "string"
           }
         },
@@ -333,18 +323,16 @@
       }
     },
 
     "HardwareAcceleration": {
       "type": "boolean"
     },
 
     "Homepage": {
-      "machine_only": true,
-
       "type": "object",
       "properties": {
         "URL": {
           "type": "URL"
         },
         "Locked": {
           "type": "boolean"
         },
@@ -382,24 +370,20 @@
       "type": "boolean"
     },
 
     "OfferToSaveLogins": {
       "type": "boolean"
     },
 
     "OverrideFirstRunPage": {
-      "machine_only": true,
-
       "type": "URLorEmpty"
     },
 
     "OverridePostUpdatePage": {
-      "machine_only": true,
-
       "type": "URLorEmpty"
     },
 
     "Permissions": {
       "type": "object",
       "properties": {
         "Camera": {
           "type": "object",
@@ -666,18 +650,16 @@
     "SecurityDevices": {
       "type": "object",
       "patternProperties": {
         "^.*$": { "type": "string" }
       }
     },
 
     "WebsiteFilter": {
-      "machine_only": "true",
-
       "type": "object",
       "properties": {
         "Block": {
           "type": "array",
           "items": {
             "type": "string"
           }
         },
deleted file mode 100644
--- a/browser/components/payments/content/paymentDialogWrapper.css
+++ /dev/null
@@ -1,11 +0,0 @@
-/* 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/. */
-
-body {
-  margin: 0;
-}
-
-#paymentRequestFrame {
-  border: none;
-}
--- a/browser/components/payments/content/paymentDialogWrapper.js
+++ b/browser/components/payments/content/paymentDialogWrapper.js
@@ -2,25 +2,26 @@
  * 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/. */
 
 /**
  * Runs in the privileged outer dialog. Each dialog loads this script in its
  * own scope.
  */
 
+/* exported paymentDialogWrapper */
+
 "use strict";
 
 const paymentSrv = Cc["@mozilla.org/dom/payments/payment-request-service;1"]
                      .getService(Ci.nsIPaymentRequestService);
 
 const paymentUISrv = Cc["@mozilla.org/dom/payments/payment-ui-service;1"]
                      .getService(Ci.nsIPaymentUIService);
 
-ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 ChromeUtils.defineModuleGetter(this, "BrowserWindowTracker",
                                "resource:///modules/BrowserWindowTracker.jsm");
 ChromeUtils.defineModuleGetter(this, "OSKeyStore",
                                "resource://formautofill/OSKeyStore.jsm");
 ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
@@ -85,17 +86,17 @@ class TempCollection {
 
   getAll() {
     return this._data;
   }
 }
 
 var paymentDialogWrapper = {
   componentsLoaded: new Map(),
-  frame: null,
+  frameWeakRef: null,
   mm: null,
   request: null,
   temporaryStore: null,
 
   QueryInterface: ChromeUtils.generateQI([
     Ci.nsIObserver,
     Ci.nsISupportsWeakReference,
   ]),
@@ -197,44 +198,74 @@ var paymentDialogWrapper = {
     return methodData;
   },
 
   init(requestId, frame) {
     if (!requestId || typeof(requestId) != "string") {
       throw new Error("Invalid PaymentRequest ID");
     }
 
-    window.addEventListener("unload", this);
-
     // The Request object returned by the Payment Service is live and
     // will automatically get updated if event.updateWith is used.
     this.request = paymentSrv.getPaymentRequestById(requestId);
 
     if (!this.request) {
       throw new Error(`PaymentRequest not found: ${requestId}`);
     }
 
-    this.frame = frame;
-    this.mm = frame.frameLoader.messageManager;
-    this.mm.addMessageListener("paymentContentToChrome", this);
+    this._attachToFrame(frame);
     this.mm.loadFrameScript("chrome://payments/content/paymentDialogFrameScript.js", true);
     // Until we have bug 1446164 and bug 1407418 we use form autofill's temporary
     // shim for data-localization* attributes.
     this.mm.loadFrameScript("chrome://formautofill/content/l10n.js", true);
-    if (AppConstants.platform == "win") {
-      this.frame.setAttribute("selectmenulist", "ContentSelectDropdown-windows");
-    }
-    this.frame.setAttribute("src", "resource://payments/paymentRequest.xhtml");
+    frame.setAttribute("src", "resource://payments/paymentRequest.xhtml");
 
     this.temporaryStore = {
       addresses: new TempCollection("addresses"),
       creditCards: new TempCollection("creditCards"),
     };
   },
 
+  uninit() {
+    try {
+      Services.obs.removeObserver(this, "message-manager-close");
+      Services.obs.removeObserver(this, "formautofill-storage-changed");
+    } catch (ex) {
+      // Observers may not have been added yet
+    }
+  },
+
+  /**
+   * Code here will be re-run at various times, e.g. initial show and
+   * when a tab is detached to a different window.
+   *
+   * Code that should only run once belongs in `init`.
+   * Code to only run upon detaching should be in `changeAttachedFrame`.
+   *
+   * @param {Element} frame
+   */
+  _attachToFrame(frame) {
+    this.frameWeakRef = Cu.getWeakReference(frame);
+    this.mm = frame.frameLoader.messageManager;
+    this.mm.addMessageListener("paymentContentToChrome", this);
+    Services.obs.addObserver(this, "message-manager-close", true);
+  },
+
+  /**
+   * Called only when a frame is changed from one to another.
+   *
+   * @param {Element} frame
+   */
+  changeAttachedFrame(frame) {
+    this.mm.removeMessageListener("paymentContentToChrome", this);
+    this._attachToFrame(frame);
+    // This isn't in `attachToFrame` because we only want to do it once we've sent records.
+    Services.obs.addObserver(this, "formautofill-storage-changed", true);
+  },
+
   createShowResponse({
     acceptStatus,
     methodName = "",
     methodData = null,
     payerName = "",
     payerEmail = "",
     payerPhone = "",
   }) {
@@ -450,20 +481,22 @@ var paymentDialogWrapper = {
       if (result !== undefined) {
         obj[key] = result;
       }
     }
     return obj;
   },
 
   async initializeFrame() {
+    // We don't do this earlier as it's only necessary once this function sends
+    // the initial saved records.
     Services.obs.addObserver(this, "formautofill-storage-changed", true);
 
     let requestSerialized = this._serializeRequest(this.request);
-    let chromeWindow = window.frameElement.ownerGlobal;
+    let chromeWindow = this.frameWeakRef.get().ownerGlobal;
     let isPrivate = PrivateBrowsingUtils.isWindowPrivate(chromeWindow);
 
     let [savedAddresses, savedBasicCards] =
       await Promise.all([this.fetchSavedAddresses(), this.fetchSavedPaymentCards()]);
 
     this.sendMessageToContent("showPaymentRequest", {
       request: requestSerialized,
       savedAddresses,
@@ -479,17 +512,17 @@ var paymentDialogWrapper = {
     if (!Services.prefs.getBoolPref("devtools.chrome.enabled", false)) {
       Cu.reportError("devtools.chrome.enabled must be enabled to debug the frame");
       return;
     }
     let {
       gDevToolsBrowser,
     } = ChromeUtils.import("resource://devtools/client/framework/gDevTools.jsm", {});
     gDevToolsBrowser.openContentProcessToolbox({
-      selectedBrowser: document.getElementById("paymentRequestFrame").frameLoader,
+      selectedBrowser: this.frameWeakRef.get(),
     });
   },
 
   onOpenPreferences() {
     BrowserWindowTracker.getTopWindow().openPreferences("privacy-form-autofill");
   },
 
   onPaymentCancel() {
@@ -630,54 +663,45 @@ var paymentDialogWrapper = {
         // there will be no formautofill-storage-changed event to update state
         // so add updated collection here
         Object.assign(responseMessage.stateChange, {
           tempBasicCards: this.temporaryStore.creditCards.getAll(),
         });
       }
     } catch (ex) {
       responseMessage.error = true;
+      Cu.reportError(ex);
     } finally {
       this.sendMessageToContent("updateAutofillRecord:Response", responseMessage);
     }
   },
 
   /**
-   * @implement {nsIDOMEventListener}
-   * @param {Event} event
-   */
-  handleEvent(event) {
-    switch (event.type) {
-      case "unload": {
-        // Remove the observer to avoid message manager errors while the dialog
-        // is closing and tests are cleaning up autofill storage.
-        Services.obs.removeObserver(this, "formautofill-storage-changed");
-        break;
-      }
-      default: {
-        throw new Error("Unexpected event handled");
-      }
-    }
-  },
-
-  /**
    * @implements {nsIObserver}
    * @param {nsISupports} subject
    * @param {string} topic
    * @param {string} data
    */
   observe(subject, topic, data) {
     switch (topic) {
       case "formautofill-storage-changed": {
         if (data == "notifyUsed") {
           break;
         }
         this.onAutofillStorageChange();
         break;
       }
+      case "message-manager-close": {
+        if (this.mm && subject == this.mm) {
+          // Remove the observer to avoid message manager errors while the dialog
+          // is closing and tests are cleaning up autofill storage.
+          Services.obs.removeObserver(this, "formautofill-storage-changed");
+        }
+        break;
+      }
     }
   },
 
   receiveMessage({data}) {
     let {messageType} = data;
 
     switch (messageType) {
       case "debugFrame": {
@@ -704,17 +728,17 @@ var paymentDialogWrapper = {
         this.onOpenPreferences();
         break;
       }
       case "paymentCancel": {
         this.onPaymentCancel();
         break;
       }
       case "paymentDialogReady": {
-        window.dispatchEvent(new Event("tabmodaldialogready", {
+        this.frameWeakRef.get().dispatchEvent(new Event("tabmodaldialogready", {
           bubbles: true,
         }));
         break;
       }
       case "pay": {
         this.onPay(data);
         break;
       }
@@ -723,15 +747,8 @@ var paymentDialogWrapper = {
         break;
       }
       default: {
         throw new Error(`paymentDialogWrapper: Unexpected messageType: ${messageType}`);
       }
     }
   },
 };
-
-if ("document" in this) {
-  // Running in a browser, not a unit test
-  let frame = document.getElementById("paymentRequestFrame");
-  let requestId = (new URLSearchParams(window.location.search)).get("requestId");
-  paymentDialogWrapper.init(requestId, frame);
-}
deleted file mode 100644
--- a/browser/components/payments/content/paymentDialogWrapper.xul
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 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/. -->
-<?xml-stylesheet href="chrome://global/skin/global.css"?>
-<?xml-stylesheet href="chrome://payments/content/paymentDialogWrapper.css"?>
-
-<!DOCTYPE window>
-<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-        xmlns:html="http://www.w3.org/1999/xhtml"
-        xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
-
-  <popupset>
-    <!-- for select dropdowns. The menupopup is what shows the list of options,
-         and the popuponly menulist makes things like the menuactive attributes
-         work correctly on the menupopup. ContentSelectDropdown expects the
-         popuponly menulist to be its immediate parent. -->
-    <menulist popuponly="true" id="ContentSelectDropdown" hidden="true">
-      <menupopup rolluponmousewheel="true"
-                 activateontab="true" position="after_start"
-                 level="parent"
-                 />
-    </menulist>
-
-    <!-- same as above but with additional attributes for Windows -->
-    <menulist popuponly="true" id="ContentSelectDropdown-windows" hidden="true">
-      <menupopup rolluponmousewheel="true"
-                 activateontab="true" position="after_start"
-                 level="parent"
-                 consumeoutsideclicks="false" ignorekeys="shortcuts"
-                 />
-    </menulist>
-  </popupset>
-
-  <browser type="content"
-           id="paymentRequestFrame"
-           disablehistory="true"
-           nodefaultsrc="true"
-           remote="true"
-           selectmenulist="ContentSelectDropdown"
-           style="height:100vh;width:100vw"
-           transparent="true"></browser>
-  <script type="application/javascript" src="chrome://payments/content/paymentDialogWrapper.js"></script>
-</window>
--- a/browser/components/payments/jar.mn
+++ b/browser/components/payments/jar.mn
@@ -1,18 +1,16 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 browser.jar:
 %   content payments %content/payments/
     content/payments/paymentDialogFrameScript.js      (content/paymentDialogFrameScript.js)
-    content/payments/paymentDialogWrapper.css         (content/paymentDialogWrapper.css)
     content/payments/paymentDialogWrapper.js          (content/paymentDialogWrapper.js)
-    content/payments/paymentDialogWrapper.xul         (content/paymentDialogWrapper.xul)
 
 %   resource payments %res/payments/
     res/payments                                      (res/paymentRequest.*)
     res/payments/components/                          (res/components/*.css)
     res/payments/components/                          (res/components/*.js)
     res/payments/components/                          (res/components/*.svg)
     res/payments/containers/                          (res/containers/*.js)
     res/payments/containers/                          (res/containers/*.css)
--- a/browser/components/payments/paymentUIService.js
+++ b/browser/components/payments/paymentUIService.js
@@ -12,16 +12,17 @@
  * For now the UI is shown in a native dialog but that is likely to change.
  * Tests should try to avoid relying on that implementation detail.
  */
 
 "use strict";
 
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
 
+ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 ChromeUtils.defineModuleGetter(this, "BrowserWindowTracker",
                                "resource:///modules/BrowserWindowTracker.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this,
                                    "paymentSrv",
                                    "@mozilla.org/dom/payments/payment-request-service;1",
@@ -37,59 +38,61 @@ function PaymentUIService() {
     });
   });
   this.log.debug("constructor");
 }
 
 PaymentUIService.prototype = {
   classID: Components.ID("{01f8bd55-9017-438b-85ec-7c15d2b35cdc}"),
   QueryInterface: ChromeUtils.generateQI([Ci.nsIPaymentUIService]),
-  DIALOG_URL: "chrome://payments/content/paymentDialogWrapper.xul",
-  REQUEST_ID_PREFIX: "paymentRequest-",
 
   // nsIPaymentUIService implementation:
 
   showPayment(requestId) {
     this.log.debug("showPayment:", requestId);
     let request = paymentSrv.getPaymentRequestById(requestId);
     let merchantBrowser = this.findBrowserByOuterWindowId(request.topOuterWindowId);
     let chromeWindow = merchantBrowser.ownerGlobal;
     let {gBrowser} = chromeWindow;
     let browserContainer = gBrowser.getBrowserContainer(merchantBrowser);
     let container = chromeWindow.document.createElementNS(XHTML_NS, "div");
     container.dataset.requestId = requestId;
     container.classList.add("paymentDialogContainer");
     container.hidden = true;
-    let paymentsBrowser = chromeWindow.document.createElementNS(XHTML_NS, "iframe");
-    paymentsBrowser.classList.add("paymentDialogContainerFrame");
-    paymentsBrowser.setAttribute("type", "content");
-    paymentsBrowser.setAttribute("remote", "true");
-    paymentsBrowser.setAttribute("src", `${this.DIALOG_URL}?requestId=${requestId}`);
+    let paymentsBrowser = this._createPaymentFrame(chromeWindow.document, requestId);
+
+    let pdwGlobal = {};
+    Services.scriptloader.loadSubScript("chrome://payments/content/paymentDialogWrapper.js",
+                                        pdwGlobal);
+
+    paymentsBrowser.paymentDialogWrapper = pdwGlobal.paymentDialogWrapper;
+
+    // Create an <html:div> wrapper to absolutely position the <xul:browser>
+    // because XUL elements don't support position:absolute.
+    let absDiv = chromeWindow.document.createElementNS(XHTML_NS, "div");
+    container.appendChild(absDiv);
+
     // append the frame to start the loading
-    container.appendChild(paymentsBrowser);
+    absDiv.appendChild(paymentsBrowser);
     browserContainer.prepend(container);
 
+    // Initialize the wrapper once the <browser> is connected.
+    paymentsBrowser.paymentDialogWrapper.init(requestId, paymentsBrowser);
+
+    this._attachBrowserEventListeners(merchantBrowser);
+
     // Only show the frame and change the UI when the dialog is ready to show.
     paymentsBrowser.addEventListener("tabmodaldialogready", function readyToShow() {
       if (!container) {
         // The dialog was closed by the DOM code before it was ready to be shown.
         return;
       }
       container.hidden = false;
-
-      // Prevent focusing or interacting with the <browser>.
-      merchantBrowser.setAttribute("tabmodalPromptShowing", "true");
-
-      // Darken the merchant content area.
-      let tabModalBackground = chromeWindow.document.createXULElement("box");
-      tabModalBackground.classList.add("tabModalBackground", "paymentDialogBackground");
-      // Insert the same way as <tabmodalprompt>.
-      merchantBrowser.parentNode.insertBefore(tabModalBackground,
-                                              merchantBrowser.nextElementSibling);
-    }, {
+      this._showDialog(merchantBrowser);
+    }.bind(this), {
       once: true,
     });
   },
 
   abortPayment(requestId) {
     this.log.debug("abortPayment:", requestId);
     let abortResponse = Cc["@mozilla.org/dom/payments/payment-abort-action-response;1"]
                           .createInstance(Ci.nsIPaymentAbortActionResponse);
@@ -115,89 +118,129 @@ PaymentUIService.prototype = {
       case "fail":
       case "timeout":
         break;
       default:
         closed = this.closeDialog(requestId);
         break;
     }
 
-    let dialogContainer;
+    let paymentFrame;
     if (!closed) {
       // We need to call findDialog before we respond below as getPaymentRequestById
       // may fail due to the request being removed upon completion.
-      dialogContainer = this.findDialog(requestId).dialogContainer;
-      if (!dialogContainer) {
+      paymentFrame = this.findDialog(requestId).paymentFrame;
+      if (!paymentFrame) {
         this.log.error("completePayment: no dialog found");
         return;
       }
     }
 
     let responseCode = closed ?
         Ci.nsIPaymentActionResponse.COMPLETE_SUCCEEDED :
         Ci.nsIPaymentActionResponse.COMPLETE_FAILED;
     let completeResponse = Cc["@mozilla.org/dom/payments/payment-complete-action-response;1"]
                              .createInstance(Ci.nsIPaymentCompleteActionResponse);
     completeResponse.init(requestId, responseCode);
     paymentSrv.respondPayment(completeResponse.QueryInterface(Ci.nsIPaymentActionResponse));
 
     if (!closed) {
-      dialogContainer.querySelector("iframe").contentWindow.paymentDialogWrapper.updateRequest();
+      paymentFrame.paymentDialogWrapper.updateRequest();
     }
   },
 
   updatePayment(requestId) {
-    let {dialogContainer} = this.findDialog(requestId);
+    let {paymentFrame} = this.findDialog(requestId);
     this.log.debug("updatePayment:", requestId);
-    if (!dialogContainer) {
+    if (!paymentFrame) {
       this.log.error("updatePayment: no dialog found");
       return;
     }
-    dialogContainer.querySelector("iframe").contentWindow.paymentDialogWrapper.updateRequest();
+    paymentFrame.paymentDialogWrapper.updateRequest();
   },
 
   closePayment(requestId) {
     this.closeDialog(requestId);
   },
 
   // other helper methods
 
+  _createPaymentFrame(doc, requestId) {
+    let frame = doc.createXULElement("browser");
+    frame.classList.add("paymentDialogContainerFrame");
+    frame.setAttribute("type", "content");
+    frame.setAttribute("remote", "true");
+    frame.setAttribute("disablehistory", "true");
+    frame.setAttribute("nodefaultsrc", "true");
+    frame.setAttribute("transparent", "true");
+    frame.setAttribute("selectmenulist", "ContentSelectDropdown");
+    frame.setAttribute("autocompletepopup", "PopupAutoComplete");
+    return frame;
+  },
+
+  _attachBrowserEventListeners(merchantBrowser) {
+    merchantBrowser.addEventListener("SwapDocShells", this);
+  },
+
+  _showDialog(merchantBrowser) {
+    let chromeWindow = merchantBrowser.ownerGlobal;
+    // Prevent focusing or interacting with the <browser>.
+    merchantBrowser.setAttribute("tabmodalPromptShowing", "true");
+
+    // Darken the merchant content area.
+    let tabModalBackground = chromeWindow.document.createXULElement("box");
+    tabModalBackground.classList.add("tabModalBackground", "paymentDialogBackground");
+    // Insert the same way as <tabmodalprompt>.
+    merchantBrowser.parentNode.insertBefore(tabModalBackground,
+                                            merchantBrowser.nextElementSibling);
+  },
+
   /**
    * @param {string} requestId - Payment Request ID of the dialog to close.
    * @returns {boolean} whether the specified dialog was closed.
    */
   closeDialog(requestId) {
     let {
       browser,
       dialogContainer,
+      paymentFrame,
     } = this.findDialog(requestId);
     if (!dialogContainer) {
       return false;
     }
     this.log.debug(`closing: ${requestId}`);
+    paymentFrame.paymentDialogWrapper.uninit();
     dialogContainer.remove();
+    browser.removeEventListener("SwapDocShells", this);
+
     if (!dialogContainer.hidden) {
       // If the container is no longer hidden then the background was added after
       // `tabmodaldialogready` so remove it.
       browser.parentElement.querySelector(".paymentDialogBackground").remove();
 
       if (!browser.tabModalPromptBox || browser.tabModalPromptBox.listPrompts().length == 0) {
         browser.removeAttribute("tabmodalPromptShowing");
       }
     }
     return true;
   },
 
+  getDialogContainerForMerchantBrowser(merchantBrowser) {
+    return merchantBrowser.ownerGlobal.gBrowser.getBrowserContainer(merchantBrowser)
+                          .querySelector(".paymentDialogContainer");
+  },
+
   findDialog(requestId) {
     for (let win of BrowserWindowTracker.orderedWindows) {
       for (let dialogContainer of win.document.querySelectorAll(".paymentDialogContainer")) {
         if (dialogContainer.dataset.requestId == requestId) {
           return {
             dialogContainer,
-            browser: dialogContainer.parentElement.querySelector("browser"),
+            paymentFrame: dialogContainer.querySelector(".paymentDialogContainerFrame"),
+            browser: dialogContainer.parentElement.querySelector(".browserStack > browser"),
           };
         }
       }
     }
     return {};
   },
 
   findBrowserByOuterWindowId(outerWindowId) {
@@ -208,11 +251,54 @@ PaymentUIService.prototype = {
       }
       return browser;
     }
 
     this.log.error("findBrowserByOuterWindowId: No browser found for outerWindowId:",
                    outerWindowId);
     return null;
   },
+
+  _moveDialogToNewBrowser(oldBrowser, newBrowser) {
+    // Re-attach event listeners to the new browser.
+    newBrowser.addEventListener("SwapDocShells", this);
+
+    let dialogContainer = this.getDialogContainerForMerchantBrowser(oldBrowser);
+    let newBrowserContainer = newBrowser.ownerGlobal.gBrowser.getBrowserContainer(newBrowser);
+
+    // Clone the container tree
+    let newDialogContainer = newBrowserContainer.ownerDocument.importNode(dialogContainer, true);
+
+    let oldFrame = dialogContainer.querySelector(".paymentDialogContainerFrame");
+    let newFrame = newDialogContainer.querySelector(".paymentDialogContainerFrame");
+
+    // We need a document to be synchronously loaded in order to do the swap and
+    // there's no point in wasting resources loading a dialog we're going to replace.
+    newFrame.setAttribute("src", "about:blank");
+    newFrame.setAttribute("nodefaultsrc", "true");
+
+    newBrowserContainer.prepend(newDialogContainer);
+
+    // Force the <browser> to be created so that it'll have a document loaded and frame created.
+    // See `ourChildDocument` and `ourFrame` checks in nsFrameLoader::SwapWithOtherLoader.
+    /* eslint-disable-next-line no-unused-expressions */
+    newFrame.clientTop;
+
+    // Swap the frameLoaders to preserve the frame state
+    newFrame.swapFrameLoaders(oldFrame);
+    newFrame.paymentDialogWrapper = oldFrame.paymentDialogWrapper;
+    newFrame.paymentDialogWrapper.changeAttachedFrame(newFrame);
+    dialogContainer.remove();
+
+    this._showDialog(newBrowser);
+  },
+
+  handleEvent(event) {
+    switch (event.type) {
+      case "SwapDocShells": {
+        this._moveDialogToNewBrowser(event.target, event.detail);
+        break;
+      }
+    }
+  },
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PaymentUIService]);
--- a/browser/components/payments/res/paymentRequest.js
+++ b/browser/components/payments/res/paymentRequest.js
@@ -120,29 +120,33 @@ var paymentRequest = {
     // Handle getting called before the DOM is ready.
     log.debug("onShowPaymentRequest:", detail);
     await this.domReadyPromise;
 
     log.debug("onShowPaymentRequest: domReadyPromise resolved");
     log.debug("onShowPaymentRequest, isPrivate?", detail.isPrivate);
 
     let paymentDialog = document.querySelector("payment-dialog");
-    let hasSavedAddresses = Object.keys(detail.savedAddresses).length != 0;
-    let hasSavedCards = Object.keys(detail.savedBasicCards).length != 0;
-    let shippingRequested = detail.request.paymentOptions.requestShipping;
     let state = {
       request: detail.request,
       savedAddresses: detail.savedAddresses,
       savedBasicCards: detail.savedBasicCards,
+      // Temp records can exist upon a reload during development.
+      tempAddresses: detail.tempAddresses,
+      tempBasicCards: detail.tempBasicCards,
       isPrivate: detail.isPrivate,
       page: {
         id: "payment-summary",
       },
     };
 
+    let hasSavedAddresses = Object.keys(this.getAddresses(state)).length != 0;
+    let hasSavedCards = Object.keys(this.getBasicCards(state)).length != 0;
+    let shippingRequested = state.request.paymentOptions.requestShipping;
+
     // Onboarding wizard flow.
     if (!hasSavedAddresses && (shippingRequested || !hasSavedCards)) {
       state.page = {
         id: "address-page",
         onboardingWizard: true,
       };
 
       state["address-page"] = {
--- a/browser/components/payments/test/browser/browser.ini
+++ b/browser/components/payments/test/browser/browser.ini
@@ -21,9 +21,10 @@ skip-if = debug && (os == 'mac' || os ==
 [browser_payment_completion.js]
 [browser_profile_storage.js]
 [browser_request_serialization.js]
 [browser_request_shipping.js]
 [browser_retry.js]
 [browser_shippingaddresschange_error.js]
 [browser_show_dialog.js]
 skip-if = os == 'win' && debug # bug 1418385
+[browser_tab_modal.js]
 [browser_total.js]
--- a/browser/components/payments/test/browser/browser_dropdowns.js
+++ b/browser/components/payments/test/browser/browser_dropdowns.js
@@ -35,19 +35,16 @@ add_task(async function test_dropdown() 
       content.document.querySelector("#country").scrollIntoView();
     });
 
     info("going to open the country <select>");
     await BrowserTestUtils.synthesizeMouseAtCenter("#country", {}, frame);
 
     let event = await popupshownPromise;
     let expectedPopupID = "ContentSelectDropdown";
-    if (AppConstants.platform == "win") {
-      expectedPopupID = "ContentSelectDropdown-windows";
-    }
     is(event.target.parentElement.id, expectedPopupID, "Checked menulist of opened popup");
 
     event.target.hidePopup(true);
 
     info("clicking cancel");
     spawnPaymentDialogTask(frame, PTU.DialogContentTasks.manuallyClickCancel);
 
     await BrowserTestUtils.waitForCondition(() => win.closed, "dialog should be closed");
--- a/browser/components/payments/test/browser/browser_show_dialog.js
+++ b/browser/components/payments/test/browser/browser_show_dialog.js
@@ -255,80 +255,8 @@ add_task(async function test_supportedNe
     await spawnPaymentDialogTask(frame, async () => {
       ok(!content.document.getElementById("pay").disabled, "pay button should not be disabled");
     });
 
     spawnPaymentDialogTask(frame, PTU.DialogContentTasks.manuallyClickCancel);
     await BrowserTestUtils.waitForCondition(() => win.closed, "dialog should be closed");
   });
 });
-
-add_task(async function test_tab_modal() {
-  await BrowserTestUtils.withNewTab({
-    gBrowser,
-    url: BLANK_PAGE_URL,
-  }, async browser => {
-    let {win, frame} = await setupPaymentDialog(browser, {
-      methodData,
-      details,
-      merchantTaskFn: PTU.ContentTasks.createAndShowRequest,
-    });
-
-    await TestUtils.waitForCondition(() => {
-      return !document.querySelector(".paymentDialogContainer").hidden;
-    }, "Waiting for container to be visible after the dialog's ready");
-
-    ok(!EventUtils.isHidden(win.frameElement), "Frame should be visible");
-
-    let {
-      bottom: toolboxBottom,
-    } = document.getElementById("navigator-toolbox").getBoundingClientRect();
-
-    let {x, y} = win.frameElement.getBoundingClientRect();
-    ok(y > 0, "Frame should have y > 0");
-    // Inset by 10px since the corner point doesn't return the frame due to the
-    // border-radius.
-    is(document.elementFromPoint(x + 10, y + 10), win.frameElement,
-       "Check .paymentDialogContainerFrame is visible");
-
-    info("Click to the left of the dialog over the content area");
-    isnot(document.elementFromPoint(x - 10, y + 50), browser,
-          "Check clicks on the merchant content area don't go to the browser");
-    is(document.elementFromPoint(x - 10, y + 50),
-       document.querySelector(".paymentDialogBackground"),
-       "Check clicks on the merchant content area go to the payment dialog background");
-
-    ok(y < toolboxBottom - 2, "Dialog should overlap the toolbox by at least 2px");
-
-    ok(browser.hasAttribute("tabmodalPromptShowing"), "Check browser has @tabmodalPromptShowing");
-
-    await BrowserTestUtils.withNewTab({
-      gBrowser,
-      url: BLANK_PAGE_URL,
-    }, async newBrowser => {
-      let {
-        x: x2,
-        y: y2,
-      } = win.frameElement.getBoundingClientRect();
-      is(x2, x, "Check x-coordinate is the same");
-      is(y2, y, "Check y-coordinate is the same");
-      isnot(document.elementFromPoint(x + 10, y + 10), win.frameElement,
-            "Check .paymentDialogContainerFrame is hidden");
-      ok(!newBrowser.hasAttribute("tabmodalPromptShowing"),
-         "Check second browser doesn't have @tabmodalPromptShowing");
-    });
-
-    let {
-      x: x3,
-      y: y3,
-    } = win.frameElement.getBoundingClientRect();
-    is(x3, x, "Check x-coordinate is the same again");
-    is(y3, y, "Check y-coordinate is the same again");
-    is(document.elementFromPoint(x + 10, y + 10), win.frameElement,
-       "Check .paymentDialogContainerFrame is visible again");
-
-    spawnPaymentDialogTask(frame, PTU.DialogContentTasks.manuallyClickCancel);
-    await BrowserTestUtils.waitForCondition(() => win.closed, "dialog should be closed");
-
-    await BrowserTestUtils.waitForCondition(() => !browser.hasAttribute("tabmodalPromptShowing"),
-                                            "Check @tabmodalPromptShowing was removed");
-  });
-});
copy from browser/components/payments/test/browser/browser_show_dialog.js
copy to browser/components/payments/test/browser/browser_tab_modal.js
--- a/browser/components/payments/test/browser/browser_show_dialog.js
+++ b/browser/components/payments/test/browser/browser_tab_modal.js
@@ -1,309 +1,63 @@
 "use strict";
 
 const methodData = [PTU.MethodData.basicCard];
 const details = Object.assign({}, PTU.Details.twoShippingOptions, PTU.Details.total2USD);
 
-add_task(async function test_show_abort_dialog() {
-  await BrowserTestUtils.withNewTab({
-    gBrowser,
-    url: BLANK_PAGE_URL,
-  }, async browser => {
-    let {win} =
-      await setupPaymentDialog(browser, {
-        methodData,
-        details,
-        merchantTaskFn: PTU.ContentTasks.createAndShowRequest,
-      }
-    );
-
-    // abort the payment request
-    ContentTask.spawn(browser, null, async () => content.rq.abort());
-    await BrowserTestUtils.waitForCondition(() => win.closed, "dialog should be closed");
-  });
-});
-
-add_task(async function test_show_manualAbort_dialog() {
-  await BrowserTestUtils.withNewTab({
-    gBrowser,
-    url: BLANK_PAGE_URL,
-  }, async browser => {
-    let {win, frame} =
-      await setupPaymentDialog(browser, {
-        methodData,
-        details,
-        merchantTaskFn: PTU.ContentTasks.createAndShowRequest,
-      }
-    );
-
-    spawnPaymentDialogTask(frame, PTU.DialogContentTasks.manuallyClickCancel);
-    await BrowserTestUtils.waitForCondition(() => win.closed, "dialog should be closed");
-  });
-});
-
-add_task(async function test_show_completePayment() {
-  if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
-    todo(false, "Cannot test OS key store login on official builds.");
-    return;
-  }
-  let {address1GUID, card1GUID} = await addSampleAddressesAndBasicCard();
-
-  let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
-                                          (subject, data) => data == "update");
-  info("associating the card with the billing address");
-  await formAutofillStorage.creditCards.update(card1GUID, {
-    billingAddressGUID: address1GUID,
-  }, true);
-  await onChanged;
+async function checkTabModal(browser, win, msg) {
+  info(`checkTabModal: ${msg}`);
+  let doc = browser.ownerDocument;
+  await TestUtils.waitForCondition(() => {
+    return !doc.querySelector(".paymentDialogContainer").hidden;
+  }, "Waiting for container to be visible after the dialog's ready");
+  is(doc.querySelectorAll(".paymentDialogContainer").length, 1,
+     "Only 1 paymentDialogContainer");
+  ok(!EventUtils.isHidden(win.frameElement), "Frame should be visible");
 
-  await BrowserTestUtils.withNewTab({
-    gBrowser,
-    url: BLANK_PAGE_URL,
-  }, async browser => {
-    let {win, frame} =
-      await setupPaymentDialog(browser, {
-        methodData,
-        details,
-        options: PTU.Options.requestShippingOption,
-        merchantTaskFn: PTU.ContentTasks.createAndShowRequest,
-      }
-    );
-
-    info("select the shipping address");
-    await selectPaymentDialogShippingAddressByCountry(frame, "US");
-
-    info("entering CSC");
-    await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.setSecurityCode, {
-      securityCode: "999",
-    });
-    info("clicking pay");
-    await loginAndCompletePayment(frame);
-
-    // Add a handler to complete the payment above.
-    info("acknowledging the completion from the merchant page");
-    let result = await ContentTask.spawn(browser, {}, PTU.ContentTasks.addCompletionHandler);
-
-    let {shippingAddress} = result.response;
-    checkPaymentAddressMatchesStorageAddress(shippingAddress, PTU.Addresses.TimBL, "Shipping");
-
-    is(result.response.methodName, "basic-card", "Check methodName");
-    let {methodDetails} = result;
-    checkPaymentMethodDetailsMatchesCard(methodDetails, PTU.BasicCards.JohnDoe, "Payment method");
-    is(methodDetails.cardSecurityCode, "999", "Check cardSecurityCode");
-    is(typeof methodDetails.methodName, "undefined", "Check methodName wasn't included");
-
-    checkPaymentAddressMatchesStorageAddress(methodDetails.billingAddress, PTU.Addresses.TimBL,
-                                             "Billing address");
-
-    is(result.response.shippingOption, "2", "Check shipping option");
-
-    await BrowserTestUtils.waitForCondition(() => win.closed, "dialog should be closed");
-  });
-});
-
-add_task(async function test_show_completePayment2() {
-  if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
-    todo(false, "Cannot test OS key store login on official builds.");
-    return;
-  }
-
-  await BrowserTestUtils.withNewTab({
-    gBrowser,
-    url: BLANK_PAGE_URL,
-  }, async browser => {
-    let {win, frame} =
-      await setupPaymentDialog(browser, {
-        methodData,
-        details,
-        options: PTU.Options.requestShippingOption,
-        merchantTaskFn: PTU.ContentTasks.createAndShowRequest,
-      }
-    );
-
-    await ContentTask.spawn(browser, {
-      eventName: "shippingoptionchange",
-    }, PTU.ContentTasks.promisePaymentRequestEvent);
-
-    info("changing shipping option to '1' from default selected option of '2'");
-    await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.selectShippingOptionById, "1");
-
-    await ContentTask.spawn(browser, {
-      eventName: "shippingoptionchange",
-    }, PTU.ContentTasks.awaitPaymentRequestEventPromise);
-    info("got shippingoptionchange event");
+  let {
+    bottom: toolboxBottom,
+  } = doc.getElementById("navigator-toolbox").getBoundingClientRect();
 
-    info("select the shipping address");
-    await selectPaymentDialogShippingAddressByCountry(frame, "US");
-
-    info("entering CSC");
-    await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.setSecurityCode, {
-      securityCode: "123",
-    });
-
-    info("clicking pay");
-    await loginAndCompletePayment(frame);
-
-    // Add a handler to complete the payment above.
-    info("acknowledging the completion from the merchant page");
-    let result = await ContentTask.spawn(browser, {}, PTU.ContentTasks.addCompletionHandler);
-
-    is(result.response.shippingOption, "1", "Check shipping option");
-
-    await BrowserTestUtils.waitForCondition(() => win.closed, "dialog should be closed");
-  });
-});
-
-add_task(async function test_localized() {
-  await BrowserTestUtils.withNewTab({
-    gBrowser,
-    url: BLANK_PAGE_URL,
-  }, async browser => {
-    let {win, frame} =
-      await setupPaymentDialog(browser, {
-        methodData,
-        details,
-        merchantTaskFn: PTU.ContentTasks.createAndShowRequest,
-      }
-    );
-
-    await spawnPaymentDialogTask(frame, async function check_l10n() {
-      await ContentTaskUtils.waitForCondition(() => {
-        let telephoneLabel = content.document.querySelector("#tel-container > .label-text");
-        return telephoneLabel && telephoneLabel.textContent.includes("Phone");
-      }, "Check that the telephone number label is localized");
-
-      await ContentTaskUtils.waitForCondition(() => {
-        let ccNumberField = content.document.querySelector("#cc-number");
-        if (!ccNumberField) {
-          return false;
-        }
-        let ccNumberLabel = ccNumberField.parentElement.querySelector(".label-text");
-        return ccNumberLabel.textContent.includes("Number");
-      }, "Check that the cc-number label is localized");
-
-      const L10N_ATTRIBUTE_SELECTOR = "[data-localization], [data-localization-region]";
-      await ContentTaskUtils.waitForCondition(() => {
-        return content.document.querySelectorAll(L10N_ATTRIBUTE_SELECTOR).length === 0;
-      }, "Check that there are no unlocalized strings");
-    });
-
-    // abort the payment request
-    ContentTask.spawn(browser, null, async () => content.rq.abort());
-    await BrowserTestUtils.waitForCondition(() => win.closed, "dialog should be closed");
-  });
-});
-
-add_task(async function test_supportedNetworks() {
-  await setupFormAutofillStorage();
-  await cleanupFormAutofillStorage();
+  let {x, y} = win.frameElement.getBoundingClientRect();
+  ok(y > 0, "Frame should have y > 0");
+  // Inset by 10px since the corner point doesn't return the frame due to the
+  // border-radius.
+  is(doc.elementFromPoint(x + 10, y + 10), win.frameElement,
+     "Check .paymentDialogContainerFrame is visible");
 
-  let address1GUID = await addAddressRecord(PTU.Addresses.TimBL);
-  let visaCardGUID = await addCardRecord(Object.assign({}, PTU.BasicCards.JohnDoe, {
-    billingAddressGUID: address1GUID,
-  }));
-  let masterCardGUID = await addCardRecord(Object.assign({}, PTU.BasicCards.JaneMasterCard, {
-    billingAddressGUID: address1GUID,
-  }));
-
-  let cardMethod = {
-    supportedMethods: "basic-card",
-    data: {
-      supportedNetworks: ["visa"],
-    },
-  };
-
-  await BrowserTestUtils.withNewTab({
-    gBrowser,
-    url: BLANK_PAGE_URL,
-  }, async browser => {
-    let {win, frame} =
-      await setupPaymentDialog(browser, {
-        methodData: [cardMethod],
-        details,
-        merchantTaskFn: PTU.ContentTasks.createAndShowRequest,
-      }
-    );
-
-    info("entering CSC");
-    await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.setSecurityCode, {
-      securityCode: "789",
-    });
+  info("Click to the left of the dialog over the content area");
+  isnot(doc.elementFromPoint(x - 10, y + 50), browser,
+        "Check clicks on the merchant content area don't go to the browser");
+  is(doc.elementFromPoint(x - 10, y + 50),
+     doc.querySelector(".paymentDialogBackground"),
+     "Check clicks on the merchant content area go to the payment dialog background");
 
-    await spawnPaymentDialogTask(frame, () => {
-      let acceptedCards = content.document.querySelector("accepted-cards");
-      ok(acceptedCards && !content.isHidden(acceptedCards),
-         "accepted-cards element is present and visible");
-      is(Cu.waiveXrays(acceptedCards).acceptedItems.length, 1,
-         "accepted-cards element has 1 item");
-    });
+  ok(y < toolboxBottom - 2, "Dialog should overlap the toolbox by at least 2px");
 
-    info("select the mastercard using guid: " + masterCardGUID);
-    await spawnPaymentDialogTask(frame,
-                                 PTU.DialogContentTasks.selectPaymentOptionByGuid,
-                                 masterCardGUID);
+  ok(browser.hasAttribute("tabmodalPromptShowing"), "Check browser has @tabmodalPromptShowing");
 
-    info("spawn task to check pay button with mastercard selected");
-    await spawnPaymentDialogTask(frame, async () => {
-      ok(content.document.getElementById("pay").disabled, "pay button should be disabled");
-    });
-
-    info("select the visa using guid: " + visaCardGUID);
-    await spawnPaymentDialogTask(frame,
-                                 PTU.DialogContentTasks.selectPaymentOptionByGuid,
-                                 visaCardGUID);
-
-    info("spawn task to check pay button");
-    await spawnPaymentDialogTask(frame, async () => {
-      ok(!content.document.getElementById("pay").disabled, "pay button should not be disabled");
-    });
-
-    spawnPaymentDialogTask(frame, PTU.DialogContentTasks.manuallyClickCancel);
-    await BrowserTestUtils.waitForCondition(() => win.closed, "dialog should be closed");
-  });
-});
+  return {
+    x,
+    y,
+  };
+}
 
 add_task(async function test_tab_modal() {
   await BrowserTestUtils.withNewTab({
     gBrowser,
     url: BLANK_PAGE_URL,
   }, async browser => {
     let {win, frame} = await setupPaymentDialog(browser, {
       methodData,
       details,
       merchantTaskFn: PTU.ContentTasks.createAndShowRequest,
     });
 
-    await TestUtils.waitForCondition(() => {
-      return !document.querySelector(".paymentDialogContainer").hidden;
-    }, "Waiting for container to be visible after the dialog's ready");
-
-    ok(!EventUtils.isHidden(win.frameElement), "Frame should be visible");
-
-    let {
-      bottom: toolboxBottom,
-    } = document.getElementById("navigator-toolbox").getBoundingClientRect();
-
-    let {x, y} = win.frameElement.getBoundingClientRect();
-    ok(y > 0, "Frame should have y > 0");
-    // Inset by 10px since the corner point doesn't return the frame due to the
-    // border-radius.
-    is(document.elementFromPoint(x + 10, y + 10), win.frameElement,
-       "Check .paymentDialogContainerFrame is visible");
-
-    info("Click to the left of the dialog over the content area");
-    isnot(document.elementFromPoint(x - 10, y + 50), browser,
-          "Check clicks on the merchant content area don't go to the browser");
-    is(document.elementFromPoint(x - 10, y + 50),
-       document.querySelector(".paymentDialogBackground"),
-       "Check clicks on the merchant content area go to the payment dialog background");
-
-    ok(y < toolboxBottom - 2, "Dialog should overlap the toolbox by at least 2px");
-
-    ok(browser.hasAttribute("tabmodalPromptShowing"), "Check browser has @tabmodalPromptShowing");
+    let {x, y} = await checkTabModal(browser, win, "initial dialog");
 
     await BrowserTestUtils.withNewTab({
       gBrowser,
       url: BLANK_PAGE_URL,
     }, async newBrowser => {
       let {
         x: x2,
         y: y2,
@@ -314,21 +68,130 @@ add_task(async function test_tab_modal()
             "Check .paymentDialogContainerFrame is hidden");
       ok(!newBrowser.hasAttribute("tabmodalPromptShowing"),
          "Check second browser doesn't have @tabmodalPromptShowing");
     });
 
     let {
       x: x3,
       y: y3,
-    } = win.frameElement.getBoundingClientRect();
+    } = await checkTabModal(browser, win, "after tab switch back");
     is(x3, x, "Check x-coordinate is the same again");
     is(y3, y, "Check y-coordinate is the same again");
-    is(document.elementFromPoint(x + 10, y + 10), win.frameElement,
-       "Check .paymentDialogContainerFrame is visible again");
 
     spawnPaymentDialogTask(frame, PTU.DialogContentTasks.manuallyClickCancel);
     await BrowserTestUtils.waitForCondition(() => win.closed, "dialog should be closed");
 
     await BrowserTestUtils.waitForCondition(() => !browser.hasAttribute("tabmodalPromptShowing"),
                                             "Check @tabmodalPromptShowing was removed");
   });
 });
+
+add_task(async function test_detachToNewWindow() {
+  let tab = await BrowserTestUtils.openNewForegroundTab({
+    gBrowser,
+    url: BLANK_PAGE_URL,
+  });
+  let browser = tab.linkedBrowser;
+
+  let {
+    frame,
+    requestId,
+  } = await setupPaymentDialog(browser, {
+    methodData,
+    details,
+    merchantTaskFn: PTU.ContentTasks.createAndShowRequest,
+  });
+
+  is(Object.values(frame.paymentDialogWrapper.temporaryStore.addresses.getAll()).length, 0,
+     "Check initial temp. address store");
+  is(Object.values(frame.paymentDialogWrapper.temporaryStore.creditCards.getAll()).length, 0,
+     "Check initial temp. card store");
+
+  info("Create some temp. records so we can later check if they are preserved");
+  let address1 = {...PTU.Addresses.Temp};
+  let card1 = {...PTU.BasicCards.JaneMasterCard, ...{"cc-csc": "123"}};
+
+  await fillInBillingAddressForm(frame, address1, {
+    setPersistCheckedValue: false,
+  });
+
+  await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.clickPrimaryButton);
+
+  await spawnPaymentDialogTask(frame, async function waitForPageChange() {
+    let {
+      PaymentTestUtils: PTU,
+    } = ChromeUtils.import("resource://testing-common/PaymentTestUtils.jsm", {});
+
+    await PTU.DialogContentUtils.waitForState(content, (state) => {
+      return state.page.id == "basic-card-page";
+    }, "Wait for basic-card-page");
+  });
+
+  await fillInCardForm(frame, card1, {
+    checkboxSelector: "basic-card-form .persist-checkbox",
+    setPersistCheckedValue: false,
+  });
+
+  await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.clickPrimaryButton);
+
+  let {temporaryStore} = frame.paymentDialogWrapper;
+  TestUtils.waitForCondition(() => {
+    return Object.values(temporaryStore.addresses.getAll()).length == 1;
+  }, "Check address store");
+  TestUtils.waitForCondition(() => {
+    return Object.values(temporaryStore.creditCards.getAll()).length == 1;
+  }, "Check card store");
+
+  let windowLoadedPromise = BrowserTestUtils.waitForNewWindow();
+  let newWin = gBrowser.replaceTabWithWindow(tab);
+  await windowLoadedPromise;
+
+  info("tab was detached");
+  let newBrowser = newWin.gBrowser.selectedBrowser;
+  ok(newBrowser, "Found new <browser>");
+
+  let widget = await TestUtils.waitForCondition(async () => getPaymentWidget(requestId));
+  await checkTabModal(newBrowser, widget, "after detach");
+
+  let state = await spawnPaymentDialogTask(widget.frameElement, async function checkAfterDetach() {
+    let {
+      PaymentTestUtils: PTU,
+    } = ChromeUtils.import("resource://testing-common/PaymentTestUtils.jsm", {});
+
+    return PTU.DialogContentUtils.getCurrentState(content);
+  });
+
+  is(Object.values(state.tempAddresses).length, 1, "Check still 1 temp. address in state");
+  is(Object.values(state.tempBasicCards).length, 1, "Check still 1 temp. basic card in state");
+
+  temporaryStore = widget.frameElement.paymentDialogWrapper.temporaryStore;
+  is(Object.values(temporaryStore.addresses.getAll()).length, 1, "Check address store in wrapper");
+  is(Object.values(temporaryStore.creditCards.getAll()).length, 1, "Check card store in wrapper");
+
+  info("Check that the message manager and formautofill-storage-changed observer are connected");
+  is(Object.values(state.savedAddresses).length, 0, "Check 0 saved addresses");
+  await addAddressRecord(PTU.Addresses.TimBL2);
+  await spawnPaymentDialogTask(widget.frameElement, async function waitForSavedAddress() {
+    let {
+      PaymentTestUtils: PTU,
+    } = ChromeUtils.import("resource://testing-common/PaymentTestUtils.jsm", {});
+
+    await PTU.DialogContentUtils.waitForState(content, function checkSavedAddresses(s) {
+      return Object.values(s.savedAddresses).length == 1;
+    }, "Check 1 saved address in state");
+  });
+
+  info("re-attach the tab back in the original window to test the event listeners were added");
+
+  let tab3 = gBrowser.adoptTab(newWin.gBrowser.selectedTab, 1, true);
+  widget = await TestUtils.waitForCondition(async () => getPaymentWidget(requestId));
+  is(widget.frameElement.ownerGlobal, window, "Check widget is back in first window");
+  await checkTabModal(tab3.linkedBrowser, widget, "after re-attaching");
+
+  temporaryStore = widget.frameElement.paymentDialogWrapper.temporaryStore;
+  is(Object.values(temporaryStore.addresses.getAll()).length, 1, "Check temp addresses in wrapper");
+  is(Object.values(temporaryStore.creditCards.getAll()).length, 1, "Check temp cards in wrapper");
+
+  spawnPaymentDialogTask(widget.frameElement, PTU.DialogContentTasks.manuallyClickCancel);
+  await BrowserTestUtils.waitForCondition(() => widget.closed, "dialog should be closed");
+  await BrowserTestUtils.removeTab(tab3);
+});
--- a/browser/components/payments/test/browser/head.js
+++ b/browser/components/payments/test/browser/head.js
@@ -40,26 +40,31 @@ function getPaymentRequests() {
  * @returns {Promise}
  */
 async function getPaymentWidget(requestId) {
   return BrowserTestUtils.waitForCondition(() => {
     let {dialogContainer} = paymentUISrv.findDialog(requestId);
     if (!dialogContainer) {
       return false;
     }
-    let browserIFrame = dialogContainer.querySelector("iframe");
-    if (!browserIFrame) {
+    let paymentFrame = dialogContainer.querySelector(".paymentDialogContainerFrame");
+    if (!paymentFrame) {
       return false;
     }
-    return browserIFrame.contentWindow;
+    return {
+      get closed() {
+        return !paymentFrame.isConnected;
+      },
+      frameElement: paymentFrame,
+    };
   }, "payment dialog should be opened");
 }
 
 async function getPaymentFrame(widget) {
-  return widget.document.getElementById("paymentRequestFrame");
+  return widget.frameElement;
 }
 
 function waitForMessageFromWidget(messageType, widget = null) {
   info("waitForMessageFromWidget: " + messageType);
   return new Promise(resolve => {
     Services.mm.addMessageListener("paymentContentToChrome", function onMessage({data, target}) {
       if (data.messageType != messageType) {
         return;
@@ -418,21 +423,22 @@ async function navigateToAddAddressPage(
 
     info("navigateToAddAddressPage: wait for address page");
     await PaymentTestUtils.DialogContentUtils.waitForState(content, (state) => {
       return state.page.id == "address-page" && !state.page.guid;
     }, "Check add page state");
   }, aOptions);
 }
 
-async function fillInBillingAddressForm(frame, aAddress) {
+async function fillInBillingAddressForm(frame, aAddress, aOptions) {
   // For now billing and shipping address forms have the same fields but that may
   // change so use separarate helpers.
   return fillInShippingAddressForm(frame, aAddress, {
     expectedSelectedStateKey: ["basic-card-page", "billingAddressGUID"],
+    ...aOptions,
   });
 }
 
 async function fillInShippingAddressForm(frame, aAddress, aOptions) {
   let address = Object.assign({}, aAddress);
   // Email isn't used on address forms, only payer/contact ones.
   delete address.email;
   return fillInAddressForm(frame, address, {
new file mode 100644
--- /dev/null
+++ b/browser/components/search/SearchTelemetry.jsm
@@ -0,0 +1,142 @@
+/* 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";
+
+var EXPORTED_SYMBOLS = ["SearchTelemetry"];
+
+const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm", null);
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+  Services: "resource://gre/modules/Services.jsm",
+});
+
+const SEARCH_COUNTS_HISTOGRAM_KEY = "SEARCH_COUNTS";
+
+// Used to identify various parameters (query, code, etc.) in search URLS.
+const SEARCH_PROVIDER_INFO = {
+  "google": {
+    "regexp": /^https:\/\/www\.(google)\.(?:.+)\/search/,
+    "queryParam": "q",
+    "codeParam": "client",
+    "codePrefixes": ["firefox"],
+    "followonParams": ["oq", "ved", "ei"],
+  },
+  "duckduckgo": {
+    "regexp": /^https:\/\/(duckduckgo)\.com\//,
+    "queryParam": "q",
+    "codeParam": "t",
+    "codePrefixes": ["ff"],
+  },
+  "yahoo": {
+    "regexp": /^https:\/\/(?:.*)search\.(yahoo)\.com\/search/,
+    "queryParam": "p",
+  },
+  "baidu": {
+    "regexp": /^https:\/\/www\.(baidu)\.com\/(?:s|baidu)/,
+    "queryParam": "wd",
+    "codeParam": "tn",
+    "codePrefixes": ["monline_dg"],
+    "followonParams": ["oq"],
+  },
+  "bing": {
+    "regexp": /^https:\/\/www\.(bing)\.com\/search/,
+    "queryParam": "q",
+    "codeParam": "pc",
+    "codePrefixes": ["MOZ", "MZ"],
+  },
+};
+
+const BROWSER_SEARCH_PREF = "browser.search.";
+
+XPCOMUtils.defineLazyPreferenceGetter(this, "loggingEnabled", BROWSER_SEARCH_PREF + "log", false);
+
+class TelemetryHandler {
+  constructor() {
+    this.__searchProviderInfo = null;
+  }
+
+  overrideSearchTelemetryForTests(infoByProvider) {
+    if (infoByProvider) {
+      for (let info of Object.values(infoByProvider)) {
+        info.regexp = new RegExp(info.regexp);
+      }
+      this.__searchProviderInfo = infoByProvider;
+    } else {
+      this.__searchProviderInfo = SEARCH_PROVIDER_INFO;
+    }
+  }
+
+  recordSearchURLTelemetry(url) {
+    let entry = Object.entries(this._searchProviderInfo).find(
+      ([_, info]) => info.regexp.test(url)
+    );
+    if (!entry) {
+      return;
+    }
+    let [provider, searchProviderInfo] = entry;
+    let queries = new URLSearchParams(url.split("#")[0].split("?")[1]);
+    if (!queries.get(searchProviderInfo.queryParam)) {
+      return;
+    }
+    // Default to organic to simplify things.
+    // We override type in the sap cases.
+    let type = "organic";
+    let code;
+    if (searchProviderInfo.codeParam) {
+      code = queries.get(searchProviderInfo.codeParam);
+      if (code &&
+          searchProviderInfo.codePrefixes.some(p => code.startsWith(p))) {
+        if (searchProviderInfo.followonParams &&
+           searchProviderInfo.followonParams.some(p => queries.has(p))) {
+          type = "sap-follow-on";
+        } else {
+          type = "sap";
+        }
+      } else if (provider == "bing") {
+        // Bing requires lots of extra work related to cookies.
+        let secondaryCode = queries.get("form");
+        // This code is used for all Bing follow-on searches.
+        if (secondaryCode == "QBRE") {
+          for (let cookie of Services.cookies.getCookiesFromHost("www.bing.com", {})) {
+            if (cookie.name == "SRCHS") {
+              // If this cookie is present, it's probably an SAP follow-on.
+              // This might be an organic follow-on in the same session,
+              // but there is no way to tell the difference.
+              if (searchProviderInfo.codePrefixes.some(p => cookie.value.startsWith("PC=" + p))) {
+                type = "sap-follow-on";
+                code = cookie.value.split("=")[1];
+                break;
+              }
+            }
+          }
+        }
+      }
+    }
+
+    let payload = `${provider}.in-content:${type}:${code || "none"}`;
+    let histogram = Services.telemetry.getKeyedHistogramById(SEARCH_COUNTS_HISTOGRAM_KEY);
+    histogram.add(payload);
+    LOG("recordSearchURLTelemetry: " + payload);
+  }
+
+  get _searchProviderInfo() {
+    if (!this.__searchProviderInfo) {
+      this.__searchProviderInfo = SEARCH_PROVIDER_INFO;
+    }
+    return this.__searchProviderInfo;
+  }
+}
+
+/**
+ * Outputs aText to the JavaScript console as well as to stdout.
+ */
+function LOG(aText) {
+  if (loggingEnabled) {
+    dump(`*** SearchTelemetry: ${aText}\n"`);
+    Services.console.logStringMessage(aText);
+  }
+}
+
+var SearchTelemetry = new TelemetryHandler();
--- a/browser/components/search/moz.build
+++ b/browser/components/search/moz.build
@@ -1,19 +1,25 @@
 # -*- 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/google_codes/browser.ini',
+EXTRA_JS_MODULES += [
+    'SearchTelemetry.jsm',
 ]
 
+BROWSER_CHROME_MANIFESTS += [
+    'test/browser/browser.ini',
+    'test/browser/google_codes/browser.ini',
+]
+
+XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
+
 TESTING_JS_MODULES += [
-    'test/SearchTestUtils.jsm',
+    'test/browser/SearchTestUtils.jsm',
 ]
 
 JAR_MANIFESTS += ['jar.mn']
 
 with Files('**'):
     BUG_COMPONENT = ('Firefox', 'Search')
rename from browser/components/search/test/.eslintrc.js
rename to browser/components/search/test/browser/.eslintrc.js
rename from browser/components/search/test/426329.xml
rename to browser/components/search/test/browser/426329.xml
--- a/browser/components/search/test/426329.xml
+++ b/browser/components/search/test/browser/426329.xml
@@ -1,11 +1,11 @@
 <OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"
                        xmlns:moz="http://www.mozilla.org/2006/browser/search/">
   <ShortName>Bug 426329</ShortName>
   <Description>426329 Search</Description>
   <InputEncoding>utf-8</InputEncoding>
   <Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABGklEQVQoz2NgGB6AnZ1dUlJSXl4eSDIyMhLW4Ovr%2B%2Fr168uXL69Zs4YoG%2BLi4i5dusTExMTGxsbNzd3f37937976%2BnpmZmagbHR09J49e5YvX66kpATVEBYW9ubNm2nTphkbG7e2tp44cQLIuHfvXm5urpaWFlDKysqqu7v73LlzECMYIiIiHj58mJCQoKKicvXq1bS0NKBgW1vbjh074uPjgeqAXE1NzSdPnvDz84M0AEUvXLgAsW379u1z5swBen3jxo2zZ892cHB4%2BvQp0KlAfwI1cHJyghQFBwfv2rULokFXV%2FfixYu7d%2B8GGqGgoMDKyrpu3br9%2B%2FcDuXl5eVA%2FAEWBfoWHAdAYoNuAYQ0XAeoUERFhGDYAAPoUaT2dfWJuAAAAAElFTkSuQmCC</Image>
-  <Url type="text/html" method="GET" template="http://mochi.test:8888/browser/browser/components/search/test/test.html">
+  <Url type="text/html" method="GET" template="http://mochi.test:8888/browser/browser/components/search/test/browser/test.html">
     <Param name="test" value="{searchTerms}"/>
   </Url>
-  <moz:SearchForm>http://mochi.test:8888/browser/browser/components/search/test/test.html</moz:SearchForm>
+  <moz:SearchForm>http://mochi.test:8888/browser/browser/components/search/test/browser/test.html</moz:SearchForm>
 </OpenSearchDescription>
rename from browser/components/search/test/483086-1.xml
rename to browser/components/search/test/browser/483086-1.xml
--- a/browser/components/search/test/483086-1.xml
+++ b/browser/components/search/test/browser/483086-1.xml
@@ -1,10 +1,10 @@
 <OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"
                        xmlns:moz="http://www.mozilla.org/2006/browser/search/">
   <ShortName>483086a</ShortName>
   <Description>Bug 483086 Test 1</Description>
   <Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABGklEQVQoz2NgGB6AnZ1dUlJSXl4eSDIyMhLW4Ovr%2B%2Fr168uXL69Zs4YoG%2BLi4i5dusTExMTGxsbNzd3f37937976%2BnpmZmagbHR09J49e5YvX66kpATVEBYW9ubNm2nTphkbG7e2tp44cQLIuHfvXm5urpaWFlDKysqqu7v73LlzECMYIiIiHj58mJCQoKKicvXq1bS0NKBgW1vbjh074uPjgeqAXE1NzSdPnvDz84M0AEUvXLgAsW379u1z5swBen3jxo2zZ892cHB4%2BvQp0KlAfwI1cHJyghQFBwfv2rULokFXV%2FfixYu7d%2B8GGqGgoMDKyrpu3br9%2B%2FcDuXl5eVA%2FAEWBfoWHAdAYoNuAYQ0XAeoUERFhGDYAAPoUaT2dfWJuAAAAAElFTkSuQmCC</Image>
-  <Url type="text/html" method="GET" template="http://mochi.test:8888/browser/browser/components/search/test/?search">
+  <Url type="text/html" method="GET" template="http://mochi.test:8888/browser/browser/components/search/test/browser/?search">
     <Param name="test" value="{searchTerms}"/>
   </Url>
   <moz:SearchForm>foo://example.com</moz:SearchForm>
 </OpenSearchDescription>
rename from browser/components/search/test/483086-2.xml
rename to browser/components/search/test/browser/483086-2.xml
--- a/browser/components/search/test/483086-2.xml
+++ b/browser/components/search/test/browser/483086-2.xml
@@ -1,10 +1,10 @@
 <OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"
                        xmlns:moz="http://www.mozilla.org/2006/browser/search/">
   <ShortName>483086b</ShortName>
   <Description>Bug 483086 Test 2</Description>
   <Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABGklEQVQoz2NgGB6AnZ1dUlJSXl4eSDIyMhLW4Ovr%2B%2Fr168uXL69Zs4YoG%2BLi4i5dusTExMTGxsbNzd3f37937976%2BnpmZmagbHR09J49e5YvX66kpATVEBYW9ubNm2nTphkbG7e2tp44cQLIuHfvXm5urpaWFlDKysqqu7v73LlzECMYIiIiHj58mJCQoKKicvXq1bS0NKBgW1vbjh074uPjgeqAXE1NzSdPnvDz84M0AEUvXLgAsW379u1z5swBen3jxo2zZ892cHB4%2BvQp0KlAfwI1cHJyghQFBwfv2rULokFXV%2FfixYu7d%2B8GGqGgoMDKyrpu3br9%2B%2FcDuXl5eVA%2FAEWBfoWHAdAYoNuAYQ0XAeoUERFhGDYAAPoUaT2dfWJuAAAAAElFTkSuQmCC</Image>
-  <Url type="text/html" method="GET" template="http://mochi.test:8888/browser/browser/components/search/test/?search">
+  <Url type="text/html" method="GET" template="http://mochi.test:8888/browser/browser/components/search/test/browser/?search">
     <Param name="test" value="{searchTerms}"/>
   </Url>
   <moz:SearchForm>http://example.com</moz:SearchForm>
 </OpenSearchDescription>
rename from browser/components/search/test/SearchTestUtils.jsm
rename to browser/components/search/test/browser/SearchTestUtils.jsm
rename from browser/components/search/test/browser.ini
rename to browser/components/search/test/browser/browser.ini
rename from browser/components/search/test/browser_426329.js
rename to browser/components/search/test/browser/browser_426329.js
--- a/browser/components/search/test/browser_426329.js
+++ b/browser/components/search/test/browser/browser_426329.js
@@ -1,14 +1,14 @@
 /* eslint-disable mozilla/no-arbitrary-setTimeout */
 ChromeUtils.defineModuleGetter(this, "FormHistory",
   "resource://gre/modules/FormHistory.jsm");
 
 function expectedURL(aSearchTerms) {
-  const ENGINE_HTML_BASE = "http://mochi.test:8888/browser/browser/components/search/test/test.html";
+  const ENGINE_HTML_BASE = "http://mochi.test:8888/browser/browser/components/search/test/browser/test.html";
   var searchArg = Services.textToSubURI.ConvertAndEscape("utf-8", aSearchTerms);
   return ENGINE_HTML_BASE + "?test=" + searchArg;
 }
 
 function simulateClick(aEvent, aTarget) {
   var event = document.createEvent("MouseEvent");
   var ctrlKeyArg  = aEvent.ctrlKey || false;
   var altKeyArg   = aEvent.altKey || false;
@@ -76,17 +76,17 @@ function promiseSetEngine() {
 
           Services.obs.removeObserver(observer, "browser-search-engine-modified");
           resolve();
           break;
       }
     }
 
     Services.obs.addObserver(observer, "browser-search-engine-modified");
-    ss.addEngine("http://mochi.test:8888/browser/browser/components/search/test/426329.xml",
+    ss.addEngine("http://mochi.test:8888/browser/browser/components/search/test/browser/426329.xml",
                  "data:image/x-icon,%00", false);
   });
 }
 
 function promiseRemoveEngine() {
   return new Promise(resolve => {
     var ss = Services.search;
 
rename from browser/components/search/test/browser_483086.js
rename to browser/components/search/test/browser/browser_483086.js
--- a/browser/components/search/test/browser_483086.js
+++ b/browser/components/search/test/browser/browser_483086.js
@@ -18,17 +18,17 @@ function test() {
       case "engine-removed":
         Services.obs.removeObserver(observer, "browser-search-engine-modified");
         test2();
         break;
     }
   }
 
   Services.obs.addObserver(observer, "browser-search-engine-modified");
-  gSS.addEngine("http://mochi.test:8888/browser/browser/components/search/test/483086-1.xml",
+  gSS.addEngine("http://mochi.test:8888/browser/browser/components/search/test/browser/483086-1.xml",
                 "data:image/x-icon;%00", false);
 }
 
 function test2() {
   function observer(aSubject, aTopic, aData) {
     switch (aData) {
       case "engine-added":
         let engine = gSS.getEngineByName("483086b");
@@ -39,11 +39,11 @@ function test2() {
       case "engine-removed":
         Services.obs.removeObserver(observer, "browser-search-engine-modified");
         finish();
         break;
     }
   }
 
   Services.obs.addObserver(observer, "browser-search-engine-modified");
-  gSS.addEngine("http://mochi.test:8888/browser/browser/components/search/test/483086-2.xml",
+  gSS.addEngine("http://mochi.test:8888/browser/browser/components/search/test/browser/483086-2.xml",
                 "data:image/x-icon;%00", false);
 }
rename from browser/components/search/test/browser_aboutSearchReset.js
rename to browser/components/search/test/browser/browser_aboutSearchReset.js
rename from browser/components/search/test/browser_addEngine.js
rename to browser/components/search/test/browser/browser_addEngine.js
--- a/browser/components/search/test/browser_addEngine.js
+++ b/browser/components/search/test/browser/browser_addEngine.js
@@ -39,22 +39,22 @@ function checkEngine(checkObj, engineObj
 
 var gTests = [
   {
     name: "opensearch install",
     engine: {
       name: "Foo",
       alias: null,
       description: "Foo Search",
-      searchForm: "http://mochi.test:8888/browser/browser/components/search/test/",
+      searchForm: "http://mochi.test:8888/browser/browser/components/search/test/browser/",
     },
     run() {
       Services.obs.addObserver(observer, "browser-search-engine-modified");
 
-      gSS.addEngine("http://mochi.test:8888/browser/browser/components/search/test/testEngine.xml",
+      gSS.addEngine("http://mochi.test:8888/browser/browser/components/search/test/browser/testEngine.xml",
                     "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABGklEQVQoz2NgGB6AnZ1dUlJSXl4eSDIyMhLW4Ovr%2B%2Fr168uXL69Zs4YoG%2BLi4i5dusTExMTGxsbNzd3f37937976%2BnpmZmagbHR09J49e5YvX66kpATVEBYW9ubNm2nTphkbG7e2tp44cQLIuHfvXm5urpaWFlDKysqqu7v73LlzECMYIiIiHj58mJCQoKKicvXq1bS0NKBgW1vbjh074uPjgeqAXE1NzSdPnvDz84M0AEUvXLgAsW379u1z5swBen3jxo2zZ892cHB4%2BvQp0KlAfwI1cHJyghQFBwfv2rULokFXV%2FfixYu7d%2B8GGqGgoMDKyrpu3br9%2B%2FcDuXl5eVA%2FAEWBfoWHAdAYoNuAYQ0XAeoUERFhGDYAAPoUaT2dfWJuAAAAAElFTkSuQmCC",
                     false);
     },
     added(engine) {
       ok(engine, "engine was added.");
 
       checkEngine(this.engine, engine);
 
rename from browser/components/search/test/browser_amazon.js
rename to browser/components/search/test/browser/browser_amazon.js
rename from browser/components/search/test/browser_bing.js
rename to browser/components/search/test/browser/browser_bing.js
rename from browser/components/search/test/browser_contextSearchTabPosition.js
rename to browser/components/search/test/browser/browser_contextSearchTabPosition.js
rename from browser/components/search/test/browser_contextmenu.js
rename to browser/components/search/test/browser/browser_contextmenu.js
--- a/browser/components/search/test/browser_contextmenu.js
+++ b/browser/components/search/test/browser/browser_contextmenu.js
@@ -11,17 +11,17 @@ add_task(async function() {
 
   // We want select events to be fired.
   await SpecialPowers.pushPrefEnv({set: [["dom.select_events.enabled", true]]});
 
   let envService = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
   let originalValue = envService.get("XPCSHELL_TEST_PROFILE_DIR");
   envService.set("XPCSHELL_TEST_PROFILE_DIR", "1");
 
-  let url = "chrome://mochitests/content/browser/browser/components/search/test/";
+  let url = "chrome://mochitests/content/browser/browser/components/search/test/browser/";
   let resProt = Services.io.getProtocolHandler("resource")
                         .QueryInterface(Ci.nsIResProtocolHandler);
   let originalSubstitution = resProt.getSubstitution("search-plugins");
   resProt.setSubstitution("search-plugins",
                           Services.io.newURI(url));
 
   let searchDonePromise;
   await new Promise(resolve => {
@@ -78,17 +78,17 @@ add_task(async function() {
   is(searchItem.label, "Search " + ENGINE_NAME + " for \u201ctest search\u201d", "Check context menu label");
   is(searchItem.disabled, false, "Check that search context menu item is enabled");
 
   await BrowserTestUtils.openNewForegroundTab(gBrowser, () => {
     searchItem.click();
   });
 
   is(gBrowser.currentURI.spec,
-     "http://mochi.test:8888/browser/browser/components/search/test/?test=test+search&ie=utf-8&channel=contextsearch",
+     "http://mochi.test:8888/browser/browser/components/search/test/browser/?test=test+search&ie=utf-8&channel=contextsearch",
      "Checking context menu search URL");
 
   contextMenu.hidePopup();
 
   // Remove the tab opened by the search
   gBrowser.removeCurrentTab();
 
   await new Promise(resolve => {
rename from browser/components/search/test/browser_ddg.js
rename to browser/components/search/test/browser/browser_ddg.js
rename from browser/components/search/test/browser_eBay.js
rename to browser/components/search/test/browser/browser_eBay.js
rename from browser/components/search/test/browser_google.js
rename to browser/components/search/test/browser/browser_google.js
rename from browser/components/search/test/browser_google_behavior.js
rename to browser/components/search/test/browser/browser_google_behavior.js
rename from browser/components/search/test/browser_healthreport.js
rename to browser/components/search/test/browser/browser_healthreport.js
--- a/browser/components/search/test/browser_healthreport.js
+++ b/browser/components/search/test/browser/browser_healthreport.js
@@ -72,17 +72,17 @@ function test() {
         gCUITestUtils.removeSearchBar();
         finish();
         break;
     }
   }
 
   Services.obs.addObserver(observer, "browser-search-engine-modified");
   gCUITestUtils.addSearchBar().then(function() {
-    Services.search.addEngine("http://mochi.test:8888/browser/browser/components/search/test/testEngine.xml",
+    Services.search.addEngine("http://mochi.test:8888/browser/browser/components/search/test/browser/testEngine.xml",
                               "data:image/x-icon,%00", false);
   });
 }
 
 function resetPreferences() {
   Preferences.resetBranch("datareporting.policy.");
   Preferences.set("datareporting.policy.dataSubmissionPolicyBypassNotification", true);
 }
rename from browser/components/search/test/browser_hiddenOneOffs_cleanup.js
rename to browser/components/search/test/browser/browser_hiddenOneOffs_cleanup.js
rename from browser/components/search/test/browser_hiddenOneOffs_diacritics.js
rename to browser/components/search/test/browser/browser_hiddenOneOffs_diacritics.js
rename from browser/components/search/test/browser_oneOffContextMenu.js
rename to browser/components/search/test/browser/browser_oneOffContextMenu.js
--- a/browser/components/search/test/browser_oneOffContextMenu.js
+++ b/browser/components/search/test/browser/browser_oneOffContextMenu.js
@@ -60,16 +60,16 @@ add_task(async function telemetry() {
   // By default the search will open in the background and the popup will stay open:
   promise = promiseEvent(searchPopup, "popuphidden");
   info("Closing search panel");
   EventUtils.synthesizeKey("KEY_Escape");
   await promise;
 
   // Check the loaded tab.
   Assert.equal(tab.linkedBrowser.currentURI.spec,
-               "http://mochi.test:8888/browser/browser/components/search/test/",
+               "http://mochi.test:8888/browser/browser/components/search/test/browser/",
                "Expected search tab should have loaded");
 
   BrowserTestUtils.removeTab(tab);
 
   // Move the cursor out of the panel area to avoid messing with other tests.
   await EventUtils.synthesizeNativeMouseMove(searchbar);
 });
rename from browser/components/search/test/browser_oneOffContextMenu_setDefault.js
rename to browser/components/search/test/browser/browser_oneOffContextMenu_setDefault.js
rename from browser/components/search/test/browser_oneOffHeader.js
rename to browser/components/search/test/browser/browser_oneOffHeader.js
rename from browser/components/search/test/browser_private_search_perwindowpb.js
rename to browser/components/search/test/browser/browser_private_search_perwindowpb.js
rename from browser/components/search/test/browser_searchEngine_behaviors.js
rename to browser/components/search/test/browser/browser_searchEngine_behaviors.js
rename from browser/components/search/test/browser_searchbar_keyboard_navigation.js
rename to browser/components/search/test/browser/browser_searchbar_keyboard_navigation.js
rename from browser/components/search/test/browser_searchbar_openpopup.js
rename to browser/components/search/test/browser/browser_searchbar_openpopup.js
rename from browser/components/search/test/browser_searchbar_smallpanel_keyboard_navigation.js
rename to browser/components/search/test/browser/browser_searchbar_smallpanel_keyboard_navigation.js
rename from browser/components/search/test/browser_tooManyEnginesOffered.js
rename to browser/components/search/test/browser/browser_tooManyEnginesOffered.js
rename from browser/components/search/test/browser_webapi.js
rename to browser/components/search/test/browser/browser_webapi.js
rename from browser/components/search/test/google_codes/browser.ini
rename to browser/components/search/test/browser/google_codes/browser.ini
rename from browser/components/search/test/head.js
rename to browser/components/search/test/browser/head.js
rename from browser/components/search/test/opensearch.html
rename to browser/components/search/test/browser/opensearch.html
--- a/browser/components/search/test/opensearch.html
+++ b/browser/components/search/test/browser/opensearch.html
@@ -1,9 +1,9 @@
 <!DOCTYPE html>
 <html>
 <head>
 <meta charset="UTF-8">
-<link rel="search" type="application/opensearchdescription+xml" title="engine1" href="http://mochi.test:8888/browser/browser/components/search/test/testEngine.xml">
-<link rel="search" type="application/opensearchdescription+xml" title="engine2" href="http://mochi.test:8888/browser/browser/components/search/test/testEngine_mozsearch.xml">
+<link rel="search" type="application/opensearchdescription+xml" title="engine1" href="http://mochi.test:8888/browser/browser/components/search/test/browser/testEngine.xml">
+<link rel="search" type="application/opensearchdescription+xml" title="engine2" href="http://mochi.test:8888/browser/browser/components/search/test/browser/testEngine_mozsearch.xml">
 </head>
 <body></body>
 </html>
rename from browser/components/search/test/test.html
rename to browser/components/search/test/browser/test.html
rename from browser/components/search/test/testEngine.xml
rename to browser/components/search/test/browser/testEngine.xml
--- a/browser/components/search/test/testEngine.xml
+++ b/browser/components/search/test/browser/testEngine.xml
@@ -1,12 +1,12 @@
 <OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"
                        xmlns:moz="http://www.mozilla.org/2006/browser/search/">
   <ShortName>Foo</ShortName>
   <Description>Foo Search</Description>
   <InputEncoding>utf-8</InputEncoding>
   <Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABGklEQVQoz2NgGB6AnZ1dUlJSXl4eSDIyMhLW4Ovr%2B%2Fr168uXL69Zs4YoG%2BLi4i5dusTExMTGxsbNzd3f37937976%2BnpmZmagbHR09J49e5YvX66kpATVEBYW9ubNm2nTphkbG7e2tp44cQLIuHfvXm5urpaWFlDKysqqu7v73LlzECMYIiIiHj58mJCQoKKicvXq1bS0NKBgW1vbjh074uPjgeqAXE1NzSdPnvDz84M0AEUvXLgAsW379u1z5swBen3jxo2zZ892cHB4%2BvQp0KlAfwI1cHJyghQFBwfv2rULokFXV%2FfixYu7d%2B8GGqGgoMDKyrpu3br9%2B%2FcDuXl5eVA%2FAEWBfoWHAdAYoNuAYQ0XAeoUERFhGDYAAPoUaT2dfWJuAAAAAElFTkSuQmCC</Image>
-  <Url type="text/html" method="GET" template="http://mochi.test:8888/browser/browser/components/search/test/?search">
+  <Url type="text/html" method="GET" template="http://mochi.test:8888/browser/browser/components/search/test/browser/?search">
     <Param name="test" value="{searchTerms}"/>
   </Url>
-  <moz:SearchForm>http://mochi.test:8888/browser/browser/components/search/test/</moz:SearchForm>
+  <moz:SearchForm>http://mochi.test:8888/browser/browser/components/search/test/browser/</moz:SearchForm>
   <moz:Alias>fooalias</moz:Alias>
 </OpenSearchDescription>
rename from browser/components/search/test/testEngine_diacritics.xml
rename to browser/components/search/test/browser/testEngine_diacritics.xml
--- a/browser/components/search/test/testEngine_diacritics.xml
+++ b/browser/components/search/test/browser/testEngine_diacritics.xml
@@ -1,12 +1,12 @@
 <OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"
                        xmlns:moz="http://www.mozilla.org/2006/browser/search/">
   <ShortName>Foo &#9825;</ShortName>
   <Description>Engine whose ShortName contains non-BMP Unicode characters</Description>
   <InputEncoding>utf-8</InputEncoding>
   <Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABGklEQVQoz2NgGB6AnZ1dUlJSXl4eSDIyMhLW4Ovr%2B%2Fr168uXL69Zs4YoG%2BLi4i5dusTExMTGxsbNzd3f37937976%2BnpmZmagbHR09J49e5YvX66kpATVEBYW9ubNm2nTphkbG7e2tp44cQLIuHfvXm5urpaWFlDKysqqu7v73LlzECMYIiIiHj58mJCQoKKicvXq1bS0NKBgW1vbjh074uPjgeqAXE1NzSdPnvDz84M0AEUvXLgAsW379u1z5swBen3jxo2zZ892cHB4%2BvQp0KlAfwI1cHJyghQFBwfv2rULokFXV%2FfixYu7d%2B8GGqGgoMDKyrpu3br9%2B%2FcDuXl5eVA%2FAEWBfoWHAdAYoNuAYQ0XAeoUERFhGDYAAPoUaT2dfWJuAAAAAElFTkSuQmCC</Image>
-  <Url type="text/html" method="GET" template="http://mochi.test:8888/browser/browser/components/search/test/?search">
+  <Url type="text/html" method="GET" template="http://mochi.test:8888/browser/browser/components/search/test/browser/?search">
     <Param name="test" value="{searchTerms}"/>
   </Url>
-  <moz:SearchForm>http://mochi.test:8888/browser/browser/components/search/test/</moz:SearchForm>
+  <moz:SearchForm>http://mochi.test:8888/browser/browser/components/search/test/browser/</moz:SearchForm>
   <moz:Alias>diacriticalias</moz:Alias>
 </OpenSearchDescription>
rename from browser/components/search/test/testEngine_dupe.xml
rename to browser/components/search/test/browser/testEngine_dupe.xml
--- a/browser/components/search/test/testEngine_dupe.xml
+++ b/browser/components/search/test/browser/testEngine_dupe.xml
@@ -1,12 +1,12 @@
 <OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"
                        xmlns:moz="http://www.mozilla.org/2006/browser/search/">
   <ShortName>FooDupe</ShortName>
   <Description>Second Engine Search</Description>
   <InputEncoding>utf-8</InputEncoding>
   <Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABGklEQVQoz2NgGB6AnZ1dUlJSXl4eSDIyMhLW4Ovr%2B%2Fr168uXL69Zs4YoG%2BLi4i5dusTExMTGxsbNzd3f37937976%2BnpmZmagbHR09J49e5YvX66kpATVEBYW9ubNm2nTphkbG7e2tp44cQLIuHfvXm5urpaWFlDKysqqu7v73LlzECMYIiIiHj58mJCQoKKicvXq1bS0NKBgW1vbjh074uPjgeqAXE1NzSdPnvDz84M0AEUvXLgAsW379u1z5swBen3jxo2zZ892cHB4%2BvQp0KlAfwI1cHJyghQFBwfv2rULokFXV%2FfixYu7d%2B8GGqGgoMDKyrpu3br9%2B%2FcDuXl5eVA%2FAEWBfoWHAdAYoNuAYQ0XAeoUERFhGDYAAPoUaT2dfWJuAAAAAElFTkSuQmCC</Image>
-  <Url type="text/html" method="GET" template="http://mochi.test:8888/browser/browser/components/search/test/?search">
+  <Url type="text/html" method="GET" template="http://mochi.test:8888/browser/browser/components/search/test/browser/?search">
     <Param name="test" value="{searchTerms}"/>
   </Url>
-  <moz:SearchForm>http://mochi.test:8888/browser/browser/components/search/test/</moz:SearchForm>
+  <moz:SearchForm>http://mochi.test:8888/browser/browser/components/search/test/browser/</moz:SearchForm>
   <moz:Alias>secondalias</moz:Alias>
 </OpenSearchDescription>
rename from browser/components/search/test/testEngine_missing_namespace.xml
rename to browser/components/search/test/browser/testEngine_missing_namespace.xml
--- a/browser/components/search/test/testEngine_missing_namespace.xml
+++ b/browser/components/search/test/browser/testEngine_missing_namespace.xml
@@ -1,11 +1,11 @@
 <OpenSearchDescription>
   <ShortName>Foo</ShortName>
   <Description>Foo Search</Description>
   <InputEncoding>utf-8</InputEncoding>
   <Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABGklEQVQoz2NgGB6AnZ1dUlJSXl4eSDIyMhLW4Ovr%2B%2Fr168uXL69Zs4YoG%2BLi4i5dusTExMTGxsbNzd3f37937976%2BnpmZmagbHR09J49e5YvX66kpATVEBYW9ubNm2nTphkbG7e2tp44cQLIuHfvXm5urpaWFlDKysqqu7v73LlzECMYIiIiHj58mJCQoKKicvXq1bS0NKBgW1vbjh074uPjgeqAXE1NzSdPnvDz84M0AEUvXLgAsW379u1z5swBen3jxo2zZ892cHB4%2BvQp0KlAfwI1cHJyghQFBwfv2rULokFXV%2FfixYu7d%2B8GGqGgoMDKyrpu3br9%2B%2FcDuXl5eVA%2FAEWBfoWHAdAYoNuAYQ0XAeoUERFhGDYAAPoUaT2dfWJuAAAAAElFTkSuQmCC</Image>
-  <Url type="text/html" method="GET" template="http://mochi.test:8888/browser/browser/components/search/test/?search">
+  <Url type="text/html" method="GET" template="http://mochi.test:8888/browser/browser/components/search/test/browser/?search">
     <Param name="test" value="{searchTerms}"/>
   </Url>
-  <moz:SearchForm>http://mochi.test:8888/browser/browser/components/search/test/</moz:SearchForm>
+  <moz:SearchForm>http://mochi.test:8888/browser/browser/components/search/test/browser/</moz:SearchForm>
   <moz:Alias>fooalias</moz:Alias>
 </OpenSearchDescription>
rename from browser/components/search/test/testEngine_mozsearch.xml
rename to browser/components/search/test/browser/testEngine_mozsearch.xml
--- a/browser/components/search/test/testEngine_mozsearch.xml
+++ b/browser/components/search/test/browser/testEngine_mozsearch.xml
@@ -1,14 +1,14 @@
 <SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
   <ShortName>Foo</ShortName>
   <Description>Foo Search</Description>
   <InputEncoding>utf-8</InputEncoding>
   <Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABGklEQVQoz2NgGB6AnZ1dUlJSXl4eSDIyMhLW4Ovr%2B%2Fr168uXL69Zs4YoG%2BLi4i5dusTExMTGxsbNzd3f37937976%2BnpmZmagbHR09J49e5YvX66kpATVEBYW9ubNm2nTphkbG7e2tp44cQLIuHfvXm5urpaWFlDKysqqu7v73LlzECMYIiIiHj58mJCQoKKicvXq1bS0NKBgW1vbjh074uPjgeqAXE1NzSdPnvDz84M0AEUvXLgAsW379u1z5swBen3jxo2zZ892cHB4%2BvQp0KlAfwI1cHJyghQFBwfv2rULokFXV%2FfixYu7d%2B8GGqGgoMDKyrpu3br9%2B%2FcDuXl5eVA%2FAEWBfoWHAdAYoNuAYQ0XAeoUERFhGDYAAPoUaT2dfWJuAAAAAElFTkSuQmCC</Image>
-  <Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/components/search/test/?suggestions&amp;locale={moz:locale}&amp;test={searchTerms}"/>
-  <Url type="text/html" method="GET" template="http://mochi.test:8888/browser/browser/components/search/test/">
+  <Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/components/search/test/browser/?suggestions&amp;locale={moz:locale}&amp;test={searchTerms}"/>
+  <Url type="text/html" method="GET" template="http://mochi.test:8888/browser/browser/components/search/test/browser/">
     <Param name="test" value="{searchTerms}"/>
     <Param name="ie" value="utf-8"/>
     <MozParam name="channel" condition="purpose" purpose="keyword" value="keywordsearch"/>
     <MozParam name="channel" condition="purpose" purpose="contextmenu" value="contextsearch"/>
   </Url>
-  <SearchForm>http://mochi.test:8888/browser/browser/components/search/test/</SearchForm>
+  <SearchForm>http://mochi.test:8888/browser/browser/components/search/test/browser/</SearchForm>
 </SearchPlugin>
rename from browser/components/search/test/tooManyEnginesOffered.html
rename to browser/components/search/test/browser/tooManyEnginesOffered.html
--- a/browser/components/search/test/tooManyEnginesOffered.html
+++ b/browser/components/search/test/browser/tooManyEnginesOffered.html
@@ -1,13 +1,13 @@
 <!DOCTYPE html>
 <html>
 <head>
 <meta charset="UTF-8">
-<link rel="search" type="application/opensearchdescription+xml" title="engine1" href="http://mochi.test:8888/browser/browser/components/search/test/engine1.xml">
-<link rel="search" type="application/opensearchdescription+xml" title="engine2" href="http://mochi.test:8888/browser/browser/components/search/test/engine2.xml">
-<link rel="search" type="application/opensearchdescription+xml" title="engine3" href="http://mochi.test:8888/browser/browser/components/search/test/engine3.xml">
-<link rel="search" type="application/opensearchdescription+xml" title="engine4" href="http://mochi.test:8888/browser/browser/components/search/test/engine4.xml">
-<link rel="search" type="application/opensearchdescription+xml" title="engine5" href="http://mochi.test:8888/browser/browser/components/search/test/engine5.xml">
-<link rel="search" type="application/opensearchdescription+xml" title="engine6" href="http://mochi.test:8888/browser/browser/components/search/test/engine6.xml">
+<link rel="search" type="application/opensearchdescription+xml" title="engine1" href="http://mochi.test:8888/browser/browser/components/search/test/browser/engine1.xml">
+<link rel="search" type="application/opensearchdescription+xml" title="engine2" href="http://mochi.test:8888/browser/browser/components/search/test/browser/engine2.xml">
+<link rel="search" type="application/opensearchdescription+xml" title="engine3" href="http://mochi.test:8888/browser/browser/components/search/test/browser/engine3.xml">
+<link rel="search" type="application/opensearchdescription+xml" title="engine4" href="http://mochi.test:8888/browser/browser/components/search/test/browser/engine4.xml">
+<link rel="search" type="application/opensearchdescription+xml" title="engine5" href="http://mochi.test:8888/browser/browser/components/search/test/browser/engine5.xml">
+<link rel="search" type="application/opensearchdescription+xml" title="engine6" href="http://mochi.test:8888/browser/browser/components/search/test/browser/engine6.xml">
 </head>
 <body></body>
 </html>
rename from browser/components/search/test/webapi.html
rename to browser/components/search/test/browser/webapi.html
new file mode 100644
--- /dev/null
+++ b/browser/components/search/test/unit/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+  "extends": [
+    "plugin:mozilla/xpcshell-test"
+  ]
+};
rename from toolkit/components/search/tests/xpcshell/test_urltelemetry.js
rename to browser/components/search/test/unit/test_urlTelemetry.js
--- a/toolkit/components/search/tests/xpcshell/test_urltelemetry.js
+++ b/browser/components/search/test/unit/test_urlTelemetry.js
@@ -1,83 +1,86 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+ChromeUtils.import("resource:///modules/SearchTelemetry.jsm");
+
 add_task(async function test_parsing_search_urls() {
   let hs;
   // Google search access point.
-  Services.search.recordSearchURLTelemetry("https://www.google.com/search?q=test&ie=utf-8&oe=utf-8&client=firefox-b-1-ab");
+  SearchTelemetry.recordSearchURLTelemetry("https://www.google.com/search?q=test&ie=utf-8&oe=utf-8&client=firefox-b-1-ab");
   hs = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").snapshot();
   Assert.ok(hs);
   Assert.ok("google.in-content:sap:firefox-b-1-ab" in hs, "The histogram must contain the correct key");
 
   // Google search access point follow-on.
-  Services.search.recordSearchURLTelemetry("https://www.google.com/search?client=firefox-b-1-ab&ei=EI_VALUE&q=test2&oq=test2&gs_l=GS_L_VALUE");
+  SearchTelemetry.recordSearchURLTelemetry("https://www.google.com/search?client=firefox-b-1-ab&ei=EI_VALUE&q=test2&oq=test2&gs_l=GS_L_VALUE");
   hs = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").snapshot();
   Assert.ok(hs);
   Assert.ok("google.in-content:sap-follow-on:firefox-b-1-ab" in hs, "The histogram must contain the correct key");
 
   // Google organic.
-  Services.search.recordSearchURLTelemetry("https://www.google.com/search?source=hp&ei=EI_VALUE&q=test&oq=test&gs_l=GS_L_VALUE");
+  SearchTelemetry.recordSearchURLTelemetry("https://www.google.com/search?source=hp&ei=EI_VALUE&q=test&oq=test&gs_l=GS_L_VALUE");
   hs = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").snapshot();
   Assert.ok(hs);
   Assert.ok("google.in-content:organic:none" in hs, "The histogram must contain the correct key");
 
   // Google organic UK.
-  Services.search.recordSearchURLTelemetry("https://www.google.co.uk/search?source=hp&ei=EI_VALUE&q=test&oq=test&gs_l=GS_L_VALUE");
+  SearchTelemetry.recordSearchURLTelemetry("https://www.google.co.uk/search?source=hp&ei=EI_VALUE&q=test&oq=test&gs_l=GS_L_VALUE");
   hs = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").snapshot();
   Assert.ok(hs);
   Assert.ok("google.in-content:organic:none" in hs, "The histogram must contain the correct key");
 
   // Yahoo organic.
-  Services.search.recordSearchURLTelemetry("https://search.yahoo.com/search?p=test&fr=yfp-t&fp=1&toggle=1&cop=mss&ei=UTF-8");
+  SearchTelemetry.recordSearchURLTelemetry("https://search.yahoo.com/search?p=test&fr=yfp-t&fp=1&toggle=1&cop=mss&ei=UTF-8");
   hs = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").snapshot();
   Assert.ok(hs);
   Assert.ok("yahoo.in-content:organic:none" in hs, "The histogram must contain the correct key");
 
   // Yahoo organic UK.
-  Services.search.recordSearchURLTelemetry("https://uk.search.yahoo.com/search?p=test&fr=yfp-t&fp=1&toggle=1&cop=mss&ei=UTF-8");
+  SearchTelemetry.recordSearchURLTelemetry("https://uk.search.yahoo.com/search?p=test&fr=yfp-t&fp=1&toggle=1&cop=mss&ei=UTF-8");
   hs = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").snapshot();
   Assert.ok(hs);
   Assert.ok("yahoo.in-content:organic:none" in hs, "The histogram must contain the correct key");
 
   // Bing search access point.
-  Services.search.recordSearchURLTelemetry("https://www.bing.com/search?q=test&pc=MOZI&form=MOZLBR");
+  SearchTelemetry.recordSearchURLTelemetry("https://www.bing.com/search?q=test&pc=MOZI&form=MOZLBR");
   hs = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").snapshot();
   Assert.ok(hs);
   Assert.ok("bing.in-content:sap:MOZI" in hs, "The histogram must contain the correct key");
 
   // Bing organic.
-  Services.search.recordSearchURLTelemetry("https://www.bing.com/search?q=test&qs=n&form=QBLH&sp=-1&pq=&sc=0-0&sk=&cvid=CVID_VALUE");
+  SearchTelemetry.recordSearchURLTelemetry("https://www.bing.com/search?q=test&qs=n&form=QBLH&sp=-1&pq=&sc=0-0&sk=&cvid=CVID_VALUE");
   hs = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").snapshot();
   Assert.ok(hs);
   Assert.ok("bing.in-content:organic:none" in hs, "The histogram must contain the correct key");
 
   // DuckDuckGo search access point.
-  Services.search.recordSearchURLTelemetry("https://duckduckgo.com/?q=test&t=ffab");
+  SearchTelemetry.recordSearchURLTelemetry("https://duckduckgo.com/?q=test&t=ffab");
   hs = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").snapshot();
   Assert.ok(hs);
   Assert.ok("duckduckgo.in-content:sap:ffab" in hs, "The histogram must contain the correct key");
 
   // DuckDuckGo organic.
-  Services.search.recordSearchURLTelemetry("https://duckduckgo.com/?q=test&t=hi&ia=news");
+  SearchTelemetry.recordSearchURLTelemetry("https://duckduckgo.com/?q=test&t=hi&ia=news");
   hs = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").snapshot();
   Assert.ok(hs);
   Assert.ok("duckduckgo.in-content:organic:hi" in hs, "The histogram must contain the correct key");
 
   // Baidu search access point.
-  Services.search.recordSearchURLTelemetry("https://www.baidu.com/baidu?wd=test&tn=monline_dg&ie=utf-8");
+  SearchTelemetry.recordSearchURLTelemetry("https://www.baidu.com/baidu?wd=test&tn=monline_dg&ie=utf-8");
   hs = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").snapshot();
   Assert.ok(hs);
   Assert.ok("baidu.in-content:sap:monline_dg" in hs, "The histogram must contain the correct key");
 
   // Baidu search access point follow-on.
-  Services.search.recordSearchURLTelemetry("https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&tn=monline_dg&wd=test2&oq=test&rsv_pq=RSV_PQ_VALUE&rsv_t=RSV_T_VALUE&rqlang=cn&rsv_enter=1&rsv_sug3=2&rsv_sug2=0&inputT=227&rsv_sug4=397");
+  SearchTelemetry.recordSearchURLTelemetry("https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&tn=monline_dg&wd=test2&oq=test&rsv_pq=RSV_PQ_VALUE&rsv_t=RSV_T_VALUE&rqlang=cn&rsv_enter=1&rsv_sug3=2&rsv_sug2=0&inputT=227&rsv_sug4=397");
   hs = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").snapshot();
   Assert.ok(hs);
   Assert.ok("baidu.in-content:sap-follow-on:monline_dg" in hs, "The histogram must contain the correct key");
 
   // Baidu organic.
-  Services.search.recordSearchURLTelemetry("https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&ch=&tn=baidu&bar=&wd=test&rn=&oq=&rsv_pq=RSV_PQ_VALUE&rsv_t=RSV_T_VALUE&rqlang=cn");
+  SearchTelemetry.recordSearchURLTelemetry("https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&ch=&tn=baidu&bar=&wd=test&rn=&oq=&rsv_pq=RSV_PQ_VALUE&rsv_t=RSV_T_VALUE&rqlang=cn");
   hs = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").snapshot();
   Assert.ok(hs);
   Assert.ok("baidu.in-content:organic:baidu" in hs, "The histogram must contain the correct key");
 });
new file mode 100644
--- /dev/null
+++ b/browser/components/search/test/unit/xpcshell.ini
@@ -0,0 +1,4 @@
+[DEFAULT]
+firefox-appdir = browser
+
+[test_urlTelemetry.js]
--- a/browser/components/shell/HeadlessShell.jsm
+++ b/browser/components/shell/HeadlessShell.jsm
@@ -25,16 +25,20 @@ function loadContentWindow(webNavigation
         // Ignore inner-frame events
         if (progress != webProgress) {
           return;
         }
         // Ignore events that don't change the document
         if (flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) {
           return;
         }
+        // Ignore the initial about:blank
+        if (uri != location.spec) {
+          return;
+        }
         let contentWindow = docShell.domWindow;
         progressListeners.delete(progressListener);
         webProgress.removeProgressListener(progressListener);
         contentWindow.addEventListener("load", (event) => {
           resolve(contentWindow);
         }, { once: true });
       },
       QueryInterface: ChromeUtils.generateQI(["nsIWebProgressListener",
--- a/browser/components/shell/test/test_headless_screenshot.html
+++ b/browser/components/shell/test/test_headless_screenshot.html
@@ -71,17 +71,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   }
 
   async function testWindowSizePositive(width, height) {
     let size = width + "";
     if (height) {
       size += "," + height;
     }
 
-    await runFirefox(["-url", "http://mochi.test:8888/headless.html", "-screenshot", screenshotPath, "-window-size", size]);
+    await runFirefox(["-url", "http://mochi.test:8888/chrome/browser/components/shell/test/headless.html", "-screenshot", screenshotPath, "-window-size", size]);
 
     let saved = await OS.File.exists(screenshotPath);
     ok(saved, "A screenshot should be saved in the tmp directory");
     if (!saved) {
       return;
     }
 
     let data = await OS.File.read(screenshotPath);
@@ -104,43 +104,43 @@ https://bugzilla.mozilla.org/show_bug.cg
     await OS.File.remove(screenshotPath);
   }
 
   (async function() {
     SimpleTest.waitForExplicitFinish();
 
     // Test all four basic variations of the "screenshot" argument
     // when a file path is specified.
-    await testFileCreationPositive(["-url", "http://mochi.test:8888/headless.html", "-screenshot", screenshotPath], screenshotPath);
-    await testFileCreationPositive(["-url", "http://mochi.test:8888/headless.html", `-screenshot=${screenshotPath}`], screenshotPath);
-    await testFileCreationPositive(["-url", "http://mochi.test:8888/headless.html", "--screenshot", screenshotPath], screenshotPath);
-    await testFileCreationPositive(["-url", "http://mochi.test:8888/headless.html", `--screenshot=${screenshotPath}`], screenshotPath);
+    await testFileCreationPositive(["-url", "http://mochi.test:8888/chrome/browser/components/shell/test/headless.html", "-screenshot", screenshotPath], screenshotPath);
+    await testFileCreationPositive(["-url", "http://mochi.test:8888/chrome/browser/components/shell/test/headless.html", `-screenshot=${screenshotPath}`], screenshotPath);
+    await testFileCreationPositive(["-url", "http://mochi.test:8888/chrome/browser/components/shell/test/headless.html", "--screenshot", screenshotPath], screenshotPath);
+    await testFileCreationPositive(["-url", "http://mochi.test:8888/chrome/browser/components/shell/test/headless.html", `--screenshot=${screenshotPath}`], screenshotPath);
 
     // Test variations of the "screenshot" argument when a file path
     // isn't specified.
-    await testFileCreationPositive(["-screenshot", "http://mochi.test:8888/headless.html"], "screenshot.png");
-    await testFileCreationPositive(["http://mochi.test:8888/headless.html", "-screenshot"], "screenshot.png");
-    await testFileCreationPositive(["--screenshot", "http://mochi.test:8888/headless.html"], "screenshot.png");
-    await testFileCreationPositive(["http://mochi.test:8888/headless.html", "--screenshot"], "screenshot.png");
+    await testFileCreationPositive(["-screenshot", "http://mochi.test:8888/chrome/browser/components/shell/test/headless.html"], "screenshot.png");
+    await testFileCreationPositive(["http://mochi.test:8888/chrome/browser/components/shell/test/headless.html", "-screenshot"], "screenshot.png");
+    await testFileCreationPositive(["--screenshot", "http://mochi.test:8888/chrome/browser/components/shell/test/headless.html"], "screenshot.png");
+    await testFileCreationPositive(["http://mochi.test:8888/chrome/browser/components/shell/test/headless.html", "--screenshot"], "screenshot.png");
 
     // Test invalid URL arguments (either no argument or too many arguments).
     await testFileCreationNegative(["-screenshot"], "screenshot.png");
-    await testFileCreationNegative(["http://mochi.test:8888/headless.html", "http://mochi.test:8888/headless.html", "-screenshot"], "screenshot.png");
+    await testFileCreationNegative(["http://mochi.test:8888/chrome/browser/components/shell/test/headless.html", "http://mochi.test:8888/headless.html", "-screenshot"], "screenshot.png");
 
     // Test all four basic variations of the "window-size" argument.
-    await testFileCreationPositive(["-url", "http://mochi.test:8888/headless.html", "-screenshot", "-window-size", "800"], "screenshot.png");
-    await testFileCreationPositive(["-url", "http://mochi.test:8888/headless.html", "-screenshot", "-window-size=800"], "screenshot.png");
-    await testFileCreationPositive(["-url", "http://mochi.test:8888/headless.html", "-screenshot", "--window-size", "800"], "screenshot.png");
-    await testFileCreationPositive(["-url", "http://mochi.test:8888/headless.html", "-screenshot", "--window-size=800"], "screenshot.png");
+    await testFileCreationPositive(["-url", "http://mochi.test:8888/chrome/browser/components/shell/test/headless.html", "-screenshot", "-window-size", "800"], "screenshot.png");
+    await testFileCreationPositive(["-url", "http://mochi.test:8888/chrome/browser/components/shell/test/headless.html", "-screenshot", "-window-size=800"], "screenshot.png");
+    await testFileCreationPositive(["-url", "http://mochi.test:8888/chrome/browser/components/shell/test/headless.html", "-screenshot", "--window-size", "800"], "screenshot.png");
+    await testFileCreationPositive(["-url", "http://mochi.test:8888/chrome/browser/components/shell/test/headless.html", "-screenshot", "--window-size=800"], "screenshot.png");
 
     // Test other variations of the "window-size" argument.
     await testWindowSizePositive(800, 600);
     await testWindowSizePositive(1234);
-    await testFileCreationNegative(["-url", "http://mochi.test:8888/headless.html", "-screenshot", "-window-size", "hello"], "screenshot.png");
-    await testFileCreationNegative(["-url", "http://mochi.test:8888/headless.html", "-screenshot", "-window-size", "800,"], "screenshot.png");
+    await testFileCreationNegative(["-url", "http://mochi.test:8888/chrome/browser/components/shell/test/headless.html", "-screenshot", "-window-size", "hello"], "screenshot.png");
+    await testFileCreationNegative(["-url", "http://mochi.test:8888/chrome/browser/components/shell/test/headless.html", "-screenshot", "-window-size", "800,"], "screenshot.png");
 
     SimpleTest.finish();
   })();
   </script>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1378010">Mozilla Bug 1378010</a>
 <p id="display"></p>
--- a/browser/components/urlbar/tests/browser/browser_UrlbarInput_overflow.js
+++ b/browser/components/urlbar/tests/browser/browser_UrlbarInput_overflow.js
@@ -45,17 +45,17 @@ add_task(async function() {
   // is complete before starting, otherwise onLocationChange for this tab could
   // override the value we set with an empty value.
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
   registerCleanupFunction(function() {
     URLBarSetURI();
     BrowserTestUtils.removeTab(tab);
   });
 
-  let lotsOfSpaces = new Array(200).fill("%20").join("");
+  let lotsOfSpaces = "%20".repeat(200);
 
   // اسماء.شبكة
   let rtlDomain = "\u0627\u0633\u0645\u0627\u0621\u002e\u0634\u0628\u0643\u0629";
 
   // Mix the direction of the tests to cover more cases, and to ensure the
   // textoverflow attribute changes every time, because tewtVal waits for that.
   await testVal(`https://mozilla.org/${lotsOfSpaces}/test/`, "end");
   await testVal(`https://mozilla.org/`);
--- a/browser/locales/en-US/browser/aboutPolicies.ftl
+++ b/browser/locales/en-US/browser/aboutPolicies.ftl
@@ -7,16 +7,8 @@ about-policies-title = Enterprise Polici
 # 'Active' is used to describe the policies that are currently active
 active-policies-tab = Active
 errors-tab = Errors
 documentation-tab = Documentation
 
 policy-name = Policy Name
 policy-value = Policy Value
 policy-errors = Policy Errors
-
-# 'gpo-machine-only' policies are related to the Group Policy features
-# on Windows. Please use the same terminology that is used on Windows
-# to describe Group Policy.
-# These policies can only be set at the computer-level settings, while
-# the other policies can also be set at the user-level.
-gpo-machine-only =
-  .title = When using Group Policy, this policy can only be set at the computer level.
--- a/browser/locales/en-US/browser/policies/policies-descriptions.ftl
+++ b/browser/locales/en-US/browser/policies/policies-descriptions.ftl
@@ -74,18 +74,17 @@ policy-DNSOverHTTPS = Configure DNS over
 
 policy-DontCheckDefaultBrowser = Disable check for default browser on startup.
 
 # “lock” means that the user won’t be able to change this setting
 policy-EnableTrackingProtection = Enable or disable Content Blocking and optionally lock it.
 
 # A “locked” extension can’t be disabled or removed by the user. This policy
 # takes 3 keys (“Install”, ”Uninstall”, ”Locked”), you can either keep them in
-# English or translate them as verbs. See also:
-# https://github.com/mozilla/policy-templates/blob/master/README.md#extensions-machine-only
+# English or translate them as verbs.
 policy-Extensions = Install, uninstall or lock extensions. The Install option takes URLs or paths as parameters. The Uninstall and Locked options take extension IDs.
 
 policy-FlashPlugin = Allow or deny usage of the Flash plugin.
 
 policy-HardwareAcceleration = If false, turn off hardware acceleration.
 
 # “lock” means that the user won’t be able to change this setting
 policy-Homepage = Set and optionally lock the homepage.
@@ -112,11 +111,10 @@ policy-SanitizeOnShutdown = Clear all na
 
 policy-SearchBar = Set the default location of the search bar. The user is still allowed to customize it.
 
 policy-SearchEngines = Configure search engine settings. This policy is only available on the Extended Support Release (ESR) version.
 
 # For more information, see https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Installation
 policy-SecurityDevices = Install PKCS #11 modules.
 
-# “format” refers to the format used for the value of this policy. See also:
-# https://github.com/mozilla/policy-templates/blob/master/README.md#websitefilter-machine-only
+# “format” refers to the format used for the value of this policy.
 policy-WebsiteFilter = Block websites from being visited. See documentation for more details on the format.
--- a/browser/modules/BrowserUsageTelemetry.jsm
+++ b/browser/modules/BrowserUsageTelemetry.jsm
@@ -7,20 +7,23 @@
 
 var EXPORTED_SYMBOLS = [
   "BrowserUsageTelemetry",
   "URLBAR_SELECTED_RESULT_TYPES",
   "URLBAR_SELECTED_RESULT_METHODS",
   "MINIMUM_TAB_COUNT_INTERVAL_MS",
  ];
 
-ChromeUtils.import("resource://gre/modules/Services.jsm");
+const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm", null);
 
-ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
-                               "resource://gre/modules/PrivateBrowsingUtils.jsm");
+XPCOMUtils.defineLazyModuleGetters(this, {
+  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
+  SearchTelemetry: "resource:///modules/SearchTelemetry.jsm",
+  Services: "resource://gre/modules/Services.jsm",
+});
 
 // The upper bound for the count of the visited unique domain names.
 const MAX_UNIQUE_VISITED_DOMAINS = 100;
 
 // Observed topic names.
 const TAB_RESTORING_TOPIC = "SSTabRestoring";
 const TELEMETRY_SUBSESSIONSPLIT_TOPIC = "internal-telemetry-after-subsession-split";
 const DOMWINDOW_OPENED_TOPIC = "domwindowopened";
@@ -199,19 +202,20 @@ let URICountListener = {
     // probe.
     if (shouldCountURI) {
       Services.telemetry.scalarAdd(UNFILTERED_URI_COUNT_SCALAR_NAME, 1);
     }
 
     if (!this.isHttpURI(uri)) {
       return;
     }
+
     if (shouldRecordSearchCount(browser.getTabBrowser()) &&
         !(flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)) {
-      Services.search.recordSearchURLTelemetry(uriSpec);
+      SearchTelemetry.recordSearchURLTelemetry(uriSpec);
     }
 
     if (!shouldCountURI) {
       return;
     }
 
     // Update the URI counts.
     Services.telemetry.scalarAdd(TOTAL_URI_COUNT_SCALAR_NAME, 1);
--- a/browser/modules/FaviconLoader.jsm
+++ b/browser/modules/FaviconLoader.jsm
@@ -73,17 +73,19 @@ class FaviconLoad {
       iconInfo.node,
       iconInfo.node.nodePrincipal,
       iconInfo.node.nodePrincipal,
       (Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS |
        Ci.nsILoadInfo.SEC_ALLOW_CHROME |
        Ci.nsILoadInfo.SEC_DISALLOW_SCRIPT),
       Ci.nsIContentPolicy.TYPE_INTERNAL_IMAGE_FAVICON);
 
-    this.channel.loadFlags |= Ci.nsIRequest.LOAD_BACKGROUND;
+    this.channel.loadFlags |= Ci.nsIRequest.LOAD_BACKGROUND |
+                              Ci.nsIRequest.VALIDATE_NEVER |
+                              Ci.nsIRequest.LOAD_FROM_CACHE;
     // Sometimes node is a document and sometimes it is an element. This is
     // the easiest single way to get to the load group in both those cases.
     this.channel.loadGroup = iconInfo.node.ownerGlobal.document.documentLoadGroup;
     this.channel.notificationCallbacks = this;
 
     if (Services.prefs.getBoolPref("network.http.tailing.enabled", true) &&
         this.channel instanceof Ci.nsIClassOfService) {
       this.channel.addClassFlags(Ci.nsIClassOfService.Tail | Ci.nsIClassOfService.Throttleable);
--- a/browser/modules/test/browser/browser.ini
+++ b/browser/modules/test/browser/browser.ini
@@ -13,18 +13,18 @@ support-files =
   browser_BrowserErrorReporter.html
 [browser_BrowserWindowTracker.js]
 [browser_ContentSearch.js]
 support-files =
   contentSearch.js
   contentSearchBadImage.xml
   contentSearchSuggestions.sjs
   contentSearchSuggestions.xml
-  !/browser/components/search/test/head.js
-  !/browser/components/search/test/testEngine.xml
+  !/browser/components/search/test/browser/head.js
+  !/browser/components/search/test/browser/testEngine.xml
 [browser_LiveBookmarkMigrator.js]
 [browser_PageActions.js]
 [browser_PermissionUI.js]
 [browser_PermissionUI_prompts.js]
 [browser_ProcessHangNotifications.js]
 skip-if = !e10s
 [browser_SitePermissions.js]
 [browser_SitePermissions_combinations.js]
--- a/browser/modules/test/browser/browser_ContentSearch.js
+++ b/browser/modules/test/browser/browser_ContentSearch.js
@@ -1,31 +1,31 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const TEST_MSG = "ContentSearchTest";
 const CONTENT_SEARCH_MSG = "ContentSearch";
 const TEST_CONTENT_SCRIPT_BASENAME = "contentSearch.js";
 
-/* import-globals-from ../../../components/search/test/head.js */
+/* import-globals-from ../../../components/search/test/browser/head.js */
 Services.scriptloader.loadSubScript(
-  "chrome://mochitests/content/browser/browser/components/search/test/head.js",
+  "chrome://mochitests/content/browser/browser/components/search/test/browser/head.js",
   this);
 
 let originalEngine = Services.search.defaultEngine;
 
 add_task(async function setup() {
   await SpecialPowers.pushPrefEnv({
     set: [["browser.newtab.preload", false]],
   });
 
   await promiseNewEngine("testEngine.xml", {
     setAsCurrent: true,
-    testPath: "chrome://mochitests/content/browser/browser/components/search/test/",
+    testPath: "chrome://mochitests/content/browser/browser/components/search/test/browser/",
   });
 
   registerCleanupFunction(() => {
     Services.search.defaultEngine = originalEngine;
   });
 });
 
 add_task(async function GetState() {
--- a/browser/modules/test/browser/browser_UsageTelemetry_urlbar.js
+++ b/browser/modules/test/browser/browser_UsageTelemetry_urlbar.js
@@ -9,16 +9,19 @@ const SUGGESTION_ENGINE_NAME = "browser_
 const ONEOFF_URLBAR_PREF = "browser.urlbar.oneOffSearches";
 
 ChromeUtils.defineModuleGetter(this, "URLBAR_SELECTED_RESULT_TYPES",
                                "resource:///modules/BrowserUsageTelemetry.jsm");
 
 ChromeUtils.defineModuleGetter(this, "URLBAR_SELECTED_RESULT_METHODS",
                                "resource:///modules/BrowserUsageTelemetry.jsm");
 
+ChromeUtils.defineModuleGetter(this, "SearchTelemetry",
+                              "resource:///modules/SearchTelemetry.jsm");
+
 function checkHistogramResults(resultIndexes, expected, histogram) {
   for (let [i, val] of Object.entries(resultIndexes.values)) {
     if (i == expected) {
       Assert.equal(val, 1,
         `expected counts should match for ${histogram} index ${i}`);
     } else {
       Assert.equal(!!val, false,
         `unexpected counts should be zero for ${histogram} index ${i}`);
@@ -571,29 +574,25 @@ add_task(async function test_suggestion_
       URLBAR_SELECTED_RESULT_METHODS.rightClickEnter,
       "FX_URLBAR_SELECTED_RESULT_METHOD");
 
     BrowserTestUtils.removeTab(tab);
   });
 });
 
 add_task(async function test_privateWindow() {
-  // Mock the search service's search provider info so that its
+  // Mock the search telemetry search provider info so that its
   // recordSearchURLTelemetry() function adds the in-content SEARCH_COUNTS
   // telemetry for our test engine.
-  Services.search.QueryInterface(Ci.nsIObserver).observe(
-    null,
-    "test:setSearchProviderInfo",
-    JSON.stringify({
-      "example": {
-        "regexp": "^http://example\\.com/",
-        "queryParam": "q",
-      },
-    })
-  );
+  SearchTelemetry.overrideSearchTelemetryForTests({
+    "example": {
+      "regexp": "^http://example\\.com/",
+      "queryParam": "q",
+    },
+  });
 
   let search_hist = getAndClearKeyedHistogram("SEARCH_COUNTS");
 
   // First, do a bunch of searches in a private window.
   let win = await BrowserTestUtils.openNewBrowserWindow({ private: true });
 
   info("Search in a private window and the pref does not exist");
   let p = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
@@ -685,11 +684,10 @@ add_task(async function test_privateWind
 
   // SEARCH_COUNTS should be incremented.
   checkKeyedHistogram(search_hist, "other-MozSearch.urlbar", 7);
   checkKeyedHistogram(search_hist, "example.in-content:organic:none", 7);
 
   await BrowserTestUtils.closeWindow(win);
 
   // Reset the search provider info.
-  Services.search.QueryInterface(Ci.nsIObserver)
-    .observe(null, "test:setSearchProviderInfo", "");
+  SearchTelemetry.overrideSearchTelemetryForTests();
 });
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -651,27 +651,21 @@ notification[value="translation"] menuli
   :root[tabsintitlebar] #toolbar-menubar[autohide="true"] {
     height: var(--tab-min-height);
   }
   :root[tabsintitlebar][sizemode="normal"] #toolbar-menubar[autohide="true"] {
     height: calc(var(--tab-min-height) + var(--space-above-tabbar));
   }
 
   /* Add extra space to titlebar for dragging */
-  :root[sizemode="normal"][chromehidden~="menubar"] #TabsToolbar,
-  :root[sizemode="normal"] #toolbar-menubar[autohide="true"][inactive] + #TabsToolbar {
+  :root[sizemode="normal"][chromehidden~="menubar"] #TabsToolbar > .toolbar-items,
+  :root[sizemode="normal"] #toolbar-menubar[autohide="true"][inactive] + #TabsToolbar > .toolbar-items {
     padding-top: var(--space-above-tabbar);
   }
 
-  /* Center items (window caption buttons, private browsing indicator,
-   * accessibility indicator, etc) vertically. */
-  :root[sizemode="normal"] #TabsToolbar > .titlebar-item {
-    margin-top: calc(-1 * var(--space-above-tabbar));
-  }
-
   /* Make #TabsToolbar transparent as we style underlying #titlebar with
    * -moz-window-titlebar (Gtk+ theme). */
   :root[tabsintitlebar][sizemode="normal"]:not([inFullscreen]) #TabsToolbar,
   :root[tabsintitlebar][sizemode="maximized"] #TabsToolbar,
   :root[tabsintitlebar] #toolbar-menubar {
     -moz-appearance: none;
   }
 
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -101,17 +101,16 @@
 .titlebar-buttonbox-container {
   -moz-box-align: center;
 }
 
 /* These would be margin-inline-start/end if it wasn't for the fact that OS X
  * doesn't reverse the order of the items in the titlebar in RTL mode. */
 .titlebar-buttonbox {
   margin-left: 12px;
-  margin-top: calc(-1 * var(--space-above-tabbar));
 }
 
 /* The fullscreen button doesnt show on Yosemite(10.10) or above so dont give it a
    border there */
 @media (-moz-mac-yosemite-theme: 0) {
   .titlebar-spacer[type="fullscreen-button"] {
     margin-right: 4px;
   }
@@ -620,17 +619,17 @@ html|input.urlbar-input {
 }
 
 :-moz-any(.keyboard-focused-tab, .tabbrowser-tab:focus:not([aria-activedescendant])) > .tab-stack > .tab-content > .tab-label-container:not([pinned]),
 :-moz-any(.keyboard-focused-tab, .tabbrowser-tab:focus:not([aria-activedescendant])) > .tab-stack > .tab-content > .tab-icon-image[pinned],
 :-moz-any(.keyboard-focused-tab, .tabbrowser-tab:focus:not([aria-activedescendant])) > .tab-stack > .tab-content > .tab-throbber[pinned] {
   box-shadow: var(--focus-ring-box-shadow);
 }
 
-#TabsToolbar {
+#TabsToolbar > .toolbar-items {
   padding-top: var(--space-above-tabbar);
 }
 
 #TabsToolbar:not(:-moz-lwtheme) {
   color: #333;
   text-shadow: @loweredShadow@;
 }
 
--- a/browser/themes/windows/browser-aero.css
+++ b/browser/themes/windows/browser-aero.css
@@ -1,29 +1,29 @@
 /* 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/. */
 
 %filter substitution
 %define glassActiveBorderColor rgb(37, 44, 51)
 %define glassInactiveBorderColor rgb(102, 102, 102)
 
-@media (-moz-os-version: windows-win7) {
+@media (-moz-os-version: windows-win7),
+       (-moz-os-version: windows-win8) {
   @media (-moz-windows-classic: 0) {
     #main-window[sizemode="normal"] > #navigator-toolbox > #titlebar > #toolbar-menubar:not([autohide="true"]) > #menubar-items,
     #main-window[sizemode="normal"] > #navigator-toolbox > #titlebar > #toolbar-menubar[autohide="true"][inactive] + #TabsToolbar > .toolbar-items {
       margin-top: 1px;
     }
     /**
-     * For all Windows configurations except for Windows Aero and
-     * Windows Aero Basic, the -moz-window-button-box appearance on
-     * the .titlebar-buttonbox adds an unwanted margin at the top of
-     * the button box.
+     * Except for Windows 8, Windows 7 Aero and Windows 7 Aero Basic, the
+     * -moz-window-button-box appearance on the .titlebar-buttonbox adds an
+     * unwanted margin at the top of the button box.
      *
-     * For Windows Aero:
+     * For Windows 8 and Windows Aero (which both use the compositor):
      *   We want the -moz-window-button-box applied in the restored case,
      *   and -moz-window-button-box-maximized in the maximized case.
      *
      * For Windows Aero Basic:
      *   The margin is also unwanted in the maximized case, but we want
      *   it in the restored window case.
      */
     #main-window[sizemode="normal"] .titlebar-buttonbox {
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -96,25 +96,25 @@
 @media (-moz-windows-default-theme) {
   @media not all and (-moz-os-version: windows-win7) {
     #toolbar-menubar:not(:-moz-lwtheme):-moz-window-inactive {
       color: ThreeDShadow;
     }
   }
 }
 
-:root[sizemode="normal"][chromehidden~="menubar"] #TabsToolbar,
-:root[sizemode="normal"] #toolbar-menubar[autohide="true"][inactive] + #TabsToolbar {
+:root[sizemode="normal"][chromehidden~="menubar"] #TabsToolbar > .toolbar-items,
+:root[sizemode="normal"] #toolbar-menubar[autohide="true"][inactive] + #TabsToolbar > .toolbar-items {
   padding-top: var(--space-above-tabbar);
 }
 
 /* Add 4px extra margin on top of the tabs toolbar on Windows 7. */
 @media (-moz-os-version: windows-win7) {
-  :root[sizemode="normal"][chromehidden~="menubar"] #TabsToolbar,
-  :root[sizemode="normal"] #toolbar-menubar[autohide="true"][inactive] + #TabsToolbar {
+  :root[sizemode="normal"][chromehidden~="menubar"] #TabsToolbar > .toolbar-items,
+  :root[sizemode="normal"] #toolbar-menubar[autohide="true"][inactive] + #TabsToolbar > .toolbar-items {
     padding-top: calc(var(--space-above-tabbar) + 4px);
   }
 }
 
 #navigator-toolbox,
 .browser-toolbar {
   -moz-appearance: none;
 }
@@ -324,17 +324,18 @@
    */
   z-index: 1;
 }
 
 .titlebar-buttonbox-container {
   -moz-box-align: stretch;
 }
 
-@media (-moz-os-version: windows-win7) {
+@media (-moz-os-version: windows-win7),
+       (-moz-os-version: windows-win8) {
   /* Preserve window control buttons position at the top of the button box. */
   .titlebar-buttonbox-container {
     -moz-box-align: start;
   }
 }
 
 /* titlebar command buttons */
 
@@ -956,28 +957,16 @@ notification[value="translation"] {
 
 /* Prevent titlebar items (window caption buttons, private browsing indicator,
  * accessibility indicator, etc) from overlapping the nav bar's shadow on the
  * tab bar. */
 #TabsToolbar > .titlebar-item {
   margin-bottom: @navbarTabsShadowSize@;
 }
 
-/* Center titlebar items vertically. */
-:root[sizemode="normal"] #TabsToolbar > .titlebar-item {
-  margin-top: calc(-1 * var(--space-above-tabbar));
-}
-
-/* Compensate for 4px extra margin on top of the tabs toolbar on Windows 7. */
-@media (-moz-os-version: windows-win7) {
-  :root[sizemode="normal"] #TabsToolbar > .titlebar-item {
-    margin-top: calc(-1 * (var(--space-above-tabbar) + 4px));
-  }
-}
-
 :root:not([privatebrowsingmode=temporary]) .accessibility-indicator,
 .private-browsing-indicator {
   margin-inline-end: 12px;
 }
 
 :root:not([accessibilitymode]) .private-browsing-indicator,
 .accessibility-indicator {
   margin-inline-start: 12px;
--- a/browser/themes/windows/share.svg
+++ b/browser/themes/windows/share.svg
@@ -1,7 +1,7 @@
 <!-- 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/. -->
 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
-  <path fill="context-fill" d="M 15.707 4.293 l -4 -4 a 1 1 0 0 0 -1.414 1.414 L 12.585 4 H 11 a 7.008 7.008 0 0 0 -7 7 a 1 1 0 0 0 2 0 a 5.006 5.006 0 0 1 5 -5 h 1.585 l -2.293 2.293 a 1 1 0 1 0 1.414 1.414 l 4 -4 a 1 1 0 0 0 0.001 -1.414 Z" />
-  <path fill="context-fill" d="M 13 11 a 1 1 0 0 0 -1 1 v 1 a 1 1 0 0 1 -1 1 H 3 a 1 1 0 0 1 -1 -1 V 6 a 1 1 0 0 1 1 -1 h 1 a 1 1 0 0 0 0 -2 H 3 a 3 3 0 0 0 -3 3 v 7 a 3 3 0 0 0 3 3 h 8 a 3 3 0 0 0 3 -3 v -1 a 1 1 0 0 0 -1 -1 Z" />
+  <path fill="context-fill" fill-opacity="context-fill-opacity" d="M 15.707 4.293 l -4 -4 a 1 1 0 0 0 -1.414 1.414 L 12.585 4 H 11 a 7.008 7.008 0 0 0 -7 7 a 1 1 0 0 0 2 0 a 5.006 5.006 0 0 1 5 -5 h 1.585 l -2.293 2.293 a 1 1 0 1 0 1.414 1.414 l 4 -4 a 1 1 0 0 0 0.001 -1.414 Z" />
+  <path fill="context-fill" fill-opacity="context-fill-opacity" d="M 13 11 a 1 1 0 0 0 -1 1 v 1 a 1 1 0 0 1 -1 1 H 3 a 1 1 0 0 1 -1 -1 V 6 a 1 1 0 0 1 1 -1 h 1 a 1 1 0 0 0 0 -2 H 3 a 3 3 0 0 0 -3 3 v 7 a 3 3 0 0 0 3 3 h 8 a 3 3 0 0 0 3 -3 v -1 a 1 1 0 0 0 -1 -1 Z" />
 </svg>
--- a/devtools/client/aboutdebugging-new/src/actions/debug-targets.js
+++ b/devtools/client/aboutdebugging-new/src/actions/debug-targets.js
@@ -30,17 +30,17 @@ const {
   REQUEST_TABS_START,
   REQUEST_TABS_SUCCESS,
   REQUEST_WORKERS_FAILURE,
   REQUEST_WORKERS_START,
   REQUEST_WORKERS_SUCCESS,
   RUNTIMES,
 } = require("../constants");
 
-function inspectDebugTarget(type, id) {
+function inspectDebugTarget({ type, id, front }) {
   return async (_, getState) => {
     const runtime = getCurrentRuntime(getState().runtimes);
     const { runtimeDetails, type: runtimeType } = runtime;
 
     switch (type) {
       case DEBUG_TARGETS.TAB: {
         // Open tab debugger in new window.
         if (runtimeType === RUNTIMES.NETWORK || runtimeType === RUNTIMES.USB) {
@@ -60,17 +60,17 @@ function inspectDebugTarget(type, id) {
           await debugRemoteAddon(id, devtoolsClient);
         } else if (runtimeType === RUNTIMES.THIS_FIREFOX) {
           debugLocalAddon(id);
         }
         break;
       }
       case DEBUG_TARGETS.WORKER: {
         // Open worker toolbox in new window.
-        gDevToolsBrowser.openWorkerToolbox(runtimeDetails.client, id);
+        gDevToolsBrowser.openWorkerToolbox(front);
         break;
       }
 
       default: {
         console.error("Failed to inspect the debug target of " +
                       `type: ${ type } id: ${ id }`);
       }
     }
--- a/devtools/client/aboutdebugging-new/src/components/debugtarget/InspectAction.js
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/InspectAction.js
@@ -21,17 +21,17 @@ class InspectAction extends PureComponen
     return {
       dispatch: PropTypes.func.isRequired,
       target: PropTypes.object.isRequired,
     };
   }
 
   inspect() {
     const { dispatch, target } = this.props;
-    dispatch(Actions.inspectDebugTarget(target.type, target.id));
+    dispatch(Actions.inspectDebugTarget(target));
   }
 
   render() {
     return Localized(
       {
         id: "about-debugging-debug-target-inspect-button",
       },
       dom.button(
--- a/devtools/client/aboutdebugging-new/src/middleware/worker-component-data.js
+++ b/devtools/client/aboutdebugging-new/src/middleware/worker-component-data.js
@@ -37,36 +37,37 @@ function getServiceWorkerStatus(isActive
   // 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.
   return SERVICE_WORKER_STATUSES.REGISTERING;
 }
 
 function toComponentData(workers, isServiceWorker) {
   return workers.map(worker => {
+    // Here `worker` is the worker object created by RootFront.listAllWorkers
     const type = DEBUG_TARGETS.WORKER;
-    const id = worker.workerTargetActor;
+    const front = worker.workerTargetFront;
     const icon = "chrome://devtools/skin/images/debugging-workers.svg";
     let { fetch, name, registrationActor, scope } = worker;
     let isActive = false;
     let isRunning = false;
     let status = null;
 
     if (isServiceWorker) {
       fetch = fetch ? SERVICE_WORKER_FETCH_STATES.LISTENING
                     : SERVICE_WORKER_FETCH_STATES.NOT_LISTENING;
       isActive = worker.active;
-      isRunning = !!worker.workerTargetActor;
+      isRunning = !!worker.workerTargetFront;
       status = getServiceWorkerStatus(isActive, isRunning);
     }
 
     return {
       name,
       icon,
-      id,
+      front,
       type,
       details: {
         fetch,
         isActive,
         isRunning,
         registrationActor,
         scope,
         status,
--- a/devtools/client/aboutdebugging/components/workers/Panel.js
+++ b/devtools/client/aboutdebugging/components/workers/Panel.js
@@ -51,20 +51,20 @@ class WorkersPanel extends Component {
 
     this.state = this.initialState;
   }
 
   componentDidMount() {
     const client = this.props.client;
     // When calling RootFront.listAllWorkers, ContentProcessTargetActor are created
     // for each content process, which sends `workerListChanged` events.
-    // Until we create a Front for ContentProcessTargetActor, we should listen for these
-    // event on DebuggerClient. After that, we have to listen on the related fronts
-    // directly.
-    client.addListener("workerListChanged", this.updateWorkers);
+    client.mainRoot.onFront("contentProcessTarget", front => {
+      front.on("workerListChanged", this.updateWorkers);
+      this.state.contentProcessFronts.push(front);
+    });
     client.mainRoot.on("workerListChanged", this.updateWorkers);
 
     client.mainRoot.on("serviceWorkerRegistrationListChanged", this.updateWorkers);
     client.mainRoot.on("processListChanged", this.updateWorkers);
     client.addListener("registration-changed", this.updateWorkers);
 
     // Some notes about these observers:
     // - nsIPrefBranch.addObserver observes prefixes. In reality, watching
@@ -85,31 +85,37 @@ class WorkersPanel extends Component {
     this.updateWorkers();
   }
 
   componentWillUnmount() {
     const client = this.props.client;
     client.mainRoot.off("processListChanged", this.updateWorkers);
     client.mainRoot.off("serviceWorkerRegistrationListChanged", this.updateWorkers);
     client.mainRoot.off("workerListChanged", this.updateWorkers);
-    client.removeListener("workerListChanged", this.updateWorkers);
+    for (const front of this.state.contentProcessFronts) {
+      front.off("workerListChanged", this.updateWorkers);
+    }
     client.removeListener("registration-changed", this.updateWorkers);
 
     Services.prefs.removeObserver(PROCESS_COUNT_PREF, this.updateMultiE10S);
     Services.prefs.removeObserver(MULTI_OPTOUT_PREF, this.updateMultiE10S);
   }
 
   get initialState() {
     return {
       workers: {
         service: [],
         shared: [],
         other: [],
       },
       processCount: 1,
+
+      // List of ContentProcessTargetFront registered from componentWillMount
+      // from which we listen for worker list changes
+      contentProcessFronts: [],
     };
   }
 
   updateMultiE10S() {
     // We watch the pref but set the state based on
     // nsIXULRuntime.maxWebProcessCount.
     const processCount = Services.appinfo.maxWebProcessCount;
     this.setState({ processCount });
--- a/devtools/client/aboutdebugging/components/workers/ServiceWorkerTarget.js
+++ b/devtools/client/aboutdebugging/components/workers/ServiceWorkerTarget.js
@@ -28,17 +28,17 @@ class ServiceWorkerTarget extends Compon
         active: PropTypes.bool,
         fetch: PropTypes.bool.isRequired,
         icon: PropTypes.string,
         name: PropTypes.string.isRequired,
         url: PropTypes.string,
         scope: PropTypes.string.isRequired,
         // registrationActor can be missing in e10s.
         registrationActor: PropTypes.string,
-        workerTargetActor: PropTypes.string,
+        workerTargetFront: PropTypes.object,
       }).isRequired,
     };
   }
 
   constructor(props) {
     super(props);
 
     this.state = {
@@ -80,33 +80,30 @@ class ServiceWorkerTarget extends Compon
   }
 
   debug() {
     if (!this.isRunning()) {
       // If the worker is not running, we can't debug it.
       return;
     }
 
-    const { client, target } = this.props;
-    gDevToolsBrowser.openWorkerToolbox(client, target.workerTargetActor);
+    const { workerTargetFront } = this.props.target;
+    gDevToolsBrowser.openWorkerToolbox(workerTargetFront);
   }
 
   push() {
     if (!this.isActive() || !this.isRunning()) {
       // If the worker is not running, we can't push to it.
       // If the worker is not active, the registration might be unavailable and the
       // push will not succeed.
       return;
     }
 
-    const { client, target } = this.props;
-    client.request({
-      to: target.workerTargetActor,
-      type: "push",
-    });
+    const { workerTargetFront } = this.props.target;
+    workerTargetFront.push();
   }
 
   start() {
     if (!this.isActive() || this.isRunning()) {
       // If the worker is not active or if it is already running, we can't start it.
       return;
     }
 
@@ -144,17 +141,17 @@ class ServiceWorkerTarget extends Compon
       type: "getPushSubscription",
     }, ({ subscription }) => {
       this.setState({ pushSubscription: subscription });
     });
   }
 
   isRunning() {
     // We know the target is running if it has a worker actor.
-    return !!this.props.target.workerTargetActor;
+    return !!this.props.target.workerTargetFront;
   }
 
   isActive() {
     return this.props.target.active;
   }
 
   getServiceWorkerStatus() {
     if (this.isActive() && this.isRunning()) {
--- a/devtools/client/aboutdebugging/components/workers/Target.js
+++ b/devtools/client/aboutdebugging/components/workers/Target.js
@@ -22,29 +22,29 @@ const Strings = Services.strings.createB
 class WorkerTarget extends Component {
   static get propTypes() {
     return {
       client: PropTypes.instanceOf(DebuggerClient).isRequired,
       debugDisabled: PropTypes.bool,
       target: PropTypes.shape({
         icon: PropTypes.string,
         name: PropTypes.string.isRequired,
-        workerTargetActor: PropTypes.string,
+        workerTargetFront: PropTypes.object,
       }).isRequired,
     };
   }
 
   constructor(props) {
     super(props);
     this.debug = this.debug.bind(this);
   }
 
   debug() {
-    const { client, target } = this.props;
-    gDevToolsBrowser.openWorkerToolbox(client, target.workerTargetActor);
+    const { workerTargetFront } = this.props.target;
+    gDevToolsBrowser.openWorkerToolbox(workerTargetFront);
   }
 
   render() {
     const { target, debugDisabled } = this.props;
 
     return dom.li({ className: "target-container" },
       dom.img({
         className: "target-icon",
--- a/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_nobg.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_nobg.js
@@ -40,30 +40,38 @@ add_task(async function testWebExtension
   } = await setupTestAboutDebuggingWebExtension(ADDON_NOBG_NAME, addonFile);
 
   // Be careful, this JS function is going to be executed in the addon toolbox,
   // which lives in another process. So do not try to use any scope variable!
   const env = Cc["@mozilla.org/process/environment;1"]
         .getService(Ci.nsIEnvironment);
   const testScript = function() {
     /* eslint-disable no-undef */
-    toolbox.selectTool("inspector").then(async inspector => {
-      const nodeActor = await inspector.walker.querySelector(
-        inspector.walker.rootNode, "body");
+    // This is webextension toolbox process. So we can't access mochitest framework.
+    const waitUntil = async function(predicate, interval = 10) {
+      if (await predicate()) {
+        return true;
+      }
+      return new Promise(resolve => {
+        toolbox.win.setTimeout(function() {
+          waitUntil(predicate, interval).then(() => resolve(true));
+        }, interval);
+      });
+    };
 
-      if (!nodeActor) {
-        throw new Error("nodeActor not found");
-      }
+    toolbox.selectTool("inspector").then(async inspector => {
+      let nodeActor;
 
-      if (!(nodeActor.inlineTextChild)) {
-        throw new Error("inlineTextChild not found");
-      }
+      dump(`Wait the fallback window to be fully loaded\n`);
+      await waitUntil(async () => {
+        nodeActor = await inspector.walker.querySelector(inspector.walker.rootNode, "h1");
+        return nodeActor && nodeActor.inlineTextChild;
+      });
 
       dump("Got a nodeActor with an inline text child\n");
-
       const expectedValue = "Your addon does not have any document opened yet.";
       const actualValue = nodeActor.inlineTextChild._form.nodeValue;
 
       if (actualValue !== expectedValue) {
         throw new Error(
           `mismatched inlineTextchild value: "${actualValue}" !== "${expectedValue}"`
         );
       }
--- a/devtools/client/application/src/components/Worker.js
+++ b/devtools/client/application/src/components/Worker.js
@@ -30,17 +30,17 @@ class Worker extends Component {
       client: PropTypes.instanceOf(DebuggerClient).isRequired,
       debugDisabled: PropTypes.bool,
       worker: PropTypes.shape({
         active: PropTypes.bool,
         name: PropTypes.string.isRequired,
         scope: PropTypes.string.isRequired,
         // registrationActor can be missing in e10s.
         registrationActor: PropTypes.string,
-        workerTargetActor: PropTypes.string,
+        workerTargetFront: PropTypes.object,
       }).isRequired,
     };
   }
 
   constructor(props) {
     super(props);
 
     this.debug = this.debug.bind(this);
@@ -49,18 +49,18 @@ class Worker extends Component {
   }
 
   debug() {
     if (!this.isRunning()) {
       console.log("Service workers cannot be debugged if they are not running");
       return;
     }
 
-    const { client, worker } = this.props;
-    gDevToolsBrowser.openWorkerToolbox(client, worker.workerTargetActor);
+    const { workerTargetFront } = this.props.worker;
+    gDevToolsBrowser.openWorkerToolbox(workerTargetFront);
   }
 
   start() {
     if (!this.isActive() || this.isRunning()) {
       console.log("Running or inactive service workers cannot be started");
       return;
     }
 
@@ -76,17 +76,17 @@ class Worker extends Component {
     client.request({
       to: worker.registrationActor,
       type: "unregister",
     });
   }
 
   isRunning() {
     // We know the worker is running if it has a worker actor.
-    return !!this.props.worker.workerTargetActor;
+    return !!this.props.worker.workerTargetFront;
   }
 
   isActive() {
     return this.props.worker.active;
   }
 
   getServiceWorkerStatus() {
     if (this.isActive() && this.isRunning()) {
--- a/devtools/client/debugger/new/panel.js
+++ b/devtools/client/debugger/new/panel.js
@@ -1,16 +1,17 @@
 /* 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 { Task } = require("devtools/shared/task");
 const { LocalizationHelper } = require("devtools/shared/l10n");
 const { gDevTools } = require("devtools/client/framework/devtools");
+const { gDevToolsBrowser } = require("devtools/client/framework/devtools-browser");
 const { TargetFactory } = require("devtools/client/framework/target");
 const { Toolbox } = require("devtools/client/framework/toolbox");
 loader.lazyRequireGetter(this, "openContentLink", "devtools/client/shared/link", true);
 
 const DBG_STRINGS_URI = "devtools/client/locales/debugger.properties";
 const L10N = new LocalizationHelper(DBG_STRINGS_URI);
 
 function DebuggerPanel(iframeWindow, toolbox) {
@@ -62,22 +63,18 @@ DebuggerPanel.prototype = {
   _getState: function() {
     return this._store.getState();
   },
 
   openLink: function(url) {
     openContentLink(url);
   },
 
-  openWorkerToolbox: async function(worker) {
-    const [response, workerTargetFront] =
-      await this.toolbox.target.client.attachWorker(worker.actor);
-    const workerTarget = TargetFactory.forWorker(workerTargetFront);
-    const toolbox = await gDevTools.showToolbox(workerTarget, "jsdebugger", Toolbox.HostType.WINDOW);
-    toolbox.once("destroy", () => workerTargetFront.detach());
+  openWorkerToolbox: function(workerTargetFront) {
+    return gDevToolsBrowser.openWorkerToolbox(workerTargetFront, "jsdebugger");
   },
 
   getFrames: function() {
     let frames = this._selectors.getFrames(this._getState());
 
     // Frames is null when the debugger is not paused.
     if (!frames) {
       return {
--- a/devtools/client/debugger/new/src/components/SecondaryPanes/Workers.js
+++ b/devtools/client/debugger/new/src/components/SecondaryPanes/Workers.js
@@ -11,17 +11,17 @@ import "./Workers.css";
 import actions from "../../actions";
 import { getWorkers } from "../../selectors";
 import { basename } from "../../utils/path";
 import type { Worker } from "../../types";
 
 export class Workers extends PureComponent {
   props: {
     workers: List<Worker>,
-    openWorkerToolbox: string => void
+    openWorkerToolbox: object => void
   };
 
   renderWorkers(workers) {
     const { openWorkerToolbox } = this.props;
     return workers.map(worker => (
       <div
         className="worker"
         key={worker.actor}
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg-chrome-debugging.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-chrome-debugging.js
@@ -23,22 +23,16 @@ function initDebuggerClient() {
   DebuggerServer.init();
   DebuggerServer.registerAllActors();
   DebuggerServer.allowChromeProcess = true;
 
   let transport = DebuggerServer.connectPipe();
   return new DebuggerClient(transport);
 }
 
-async function attachThread(client, actor) {
-  let [response, targetFront] = await client.attachTarget(actor);
-  let [response2, threadClient] = await targetFront.attachThread(null);
-  return threadClient;
-}
-
 function onNewSource(event, packet) {
   if (packet.source.url.startsWith("chrome:")) {
     ok(true, "Received a new chrome source: " + packet.source.url);
     gThreadClient.removeListener("newSource", onNewSource);
     gNewChromeSource.resolve();
   }
 }
 
@@ -58,19 +52,20 @@ registerCleanupFunction(function() {
 });
 
 add_task(async function() {
   gClient = initDebuggerClient();
 
   const [type] = await gClient.connect();
   is(type, "browser", "Root actor should identify itself as a browser.");
 
-  const response = await gClient.mainRoot.getMainProcess();
-  let actor = response.form.actor;
-  gThreadClient = await attachThread(gClient, actor);
+  const front = await gClient.mainRoot.getMainProcess();
+  await front.attach();
+  const [, threadClient] = await front.attachThread();
+  gThreadClient = threadClient;
   gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, "about:mozilla");
 
   // listen for a new source and global
   gThreadClient.addListener("newSource", onNewSource);
   await gNewChromeSource.promise;
 
   await resumeAndCloseConnection();
 });
--- a/devtools/client/debugger/test/mochitest/browser_dbg_promises-chrome-allocation-stack.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_promises-chrome-allocation-stack.js
@@ -24,18 +24,18 @@ function test() {
     requestLongerTimeout(10);
 
     DebuggerServer.init();
     DebuggerServer.registerAllActors();
     DebuggerServer.allowChromeProcess = true;
 
     let client = new DebuggerClient(DebuggerServer.connectPipe());
     yield connect(client);
-    let chrome = yield client.mainRoot.getMainProcess();
-    let [, targetFront] = yield attachTarget(client, chrome.form);
+    let targetFront = yield client.mainRoot.getMainProcess();
+    yield targetFront.attach();
     yield targetFront.attachThread();
 
     yield testGetAllocationStack(client, chrome.form, () => {
       let p = new Promise(() => {});
       p.name = "p";
       let q = p.then();
       q.name = "q";
       let r = p.catch(() => {});
--- a/devtools/client/debugger/test/mochitest/head.js
+++ b/devtools/client/debugger/test/mochitest/head.js
@@ -946,21 +946,16 @@ function findWorker(workers, url) {
   for (let worker of workers) {
     if (worker.url === url) {
       return worker;
     }
   }
   return null;
 }
 
-function attachWorker(targetFront, worker) {
-  info("Attaching to worker with url '" + worker.url + "'.");
-  return targetFront.attachWorker(worker.actor);
-}
-
 function waitForWorkerListChanged(targetFront) {
   info("Waiting for worker list to change.");
   return targetFront.once("workerListChanged");
 }
 
 function attachThread(workerTargetFront, options) {
   info("Attaching to thread.");
   return workerTargetFront.attachThread(options);
@@ -1130,18 +1125,17 @@ async function initWorkerDebugger(TAB_UR
 
   let tab = await addTab(TAB_URL);
   let { tabs } = await listTabs(client);
   let [, targetFront] = await attachTarget(client, findTab(tabs, TAB_URL));
 
   await createWorkerInTab(tab, WORKER_URL);
 
   let { workers } = await listWorkers(targetFront);
-  let [, workerTargetFront] = await attachWorker(targetFront,
-                                             findWorker(workers, WORKER_URL));
+  let workerTargetFront = findWorker(workers, WORKER_URL);
 
   let toolbox = await gDevTools.showToolbox(TargetFactory.forWorker(workerTargetFront),
                                             "jsdebugger",
                                             Toolbox.HostType.WINDOW);
 
   let debuggerPanel = toolbox.getCurrentPanel();
   let gDebugger = debuggerPanel.panelWin;
 
--- a/devtools/client/definitions.js
+++ b/devtools/client/definitions.js
@@ -274,17 +274,18 @@ Tools.memory = {
   icon: "chrome://devtools/skin/images/tool-memory.svg",
   url: "chrome://devtools/content/memory/index.xhtml",
   visibilityswitch: "devtools.memory.enabled",
   label: l10n("memory.label"),
   panelLabel: l10n("memory.panelLabel"),
   tooltip: l10n("memory.tooltip"),
 
   isTargetSupported: function(target) {
-    return target.getTrait("heapSnapshots") && !target.isAddon;
+    return target.getTrait("heapSnapshots") && !target.isAddon
+      && !target.isWorkerTarget;
   },
 
   build: function(frame, target) {
     return new MemoryPanel(frame, target);
   },
 };
 
 Tools.netMonitor = {
@@ -299,17 +300,17 @@ Tools.netMonitor = {
   get tooltip() {
     return l10n("netmonitor.tooltip2",
     (osString == "Darwin" ? "Cmd+Opt+" : "Ctrl+Shift+") +
     l10n("netmonitor.commandkey"));
   },
   inMenu: true,
 
   isTargetSupported: function(target) {
-    return target.getTrait("networkMonitor");
+    return target.getTrait("networkMonitor") && !target.isWorkerTarget;
   },
 
   build: function(iframeWindow, toolbox) {
     return new NetMonitorPanel(iframeWindow, toolbox);
   },
 };
 
 Tools.storage = {
--- a/devtools/client/framework/connect/connect.js
+++ b/devtools/client/framework/connect/connect.js
@@ -124,18 +124,18 @@ var onConnectionReady = async function([
   // Build the Remote Process button
   // If Fx<39, chrome target actors were used to be exposed on RootActor
   // but in Fx>=39, chrome is debuggable via getProcess() and ParentProcessTargetActor
   if (globals.consoleActor || gClient.mainRoot.traits.allowChromeProcess) {
     const a = document.createElement("a");
     a.onclick = function() {
       if (gClient.mainRoot.traits.allowChromeProcess) {
         gClient.mainRoot.getMainProcess()
-               .then(aResponse => {
-                 openToolbox(aResponse.form, true);
+               .then(front => {
+                 openToolbox(null, true, null, front);
                });
       } else if (globals.consoleActor) {
         openToolbox(globals, true, "webconsole", false);
       }
     };
     a.title = a.textContent = L10N.getStr("mainProcess");
     a.className = "remote-process";
     a.href = "#";
@@ -217,21 +217,22 @@ function showError(type) {
 function handleConnectionTimeout() {
   showError("timeout");
 }
 
 /**
  * The user clicked on one of the buttons.
  * Opens the toolbox.
  */
-function openToolbox(form, chrome = false, tool = "webconsole") {
+function openToolbox(form, chrome = false, tool = "webconsole", activeTab = null) {
   const options = {
-    form: form,
+    form,
+    activeTab,
     client: gClient,
-    chrome: chrome,
+    chrome,
   };
   TargetFactory.forRemoteTab(options).then((target) => {
     const hostType = Toolbox.HostType.WINDOW;
     gDevTools.showToolbox(target, tool, hostType).then((toolbox) => {
       toolbox.once("destroyed", function() {
         gClient.close();
       });
     }, console.error);
--- a/devtools/client/framework/devtools-browser.js
+++ b/devtools/client/framework/devtools-browser.js
@@ -313,20 +313,18 @@ var gDevToolsBrowser = exports.gDevTools
     DebuggerServer.init();
     DebuggerServer.registerAllActors();
     DebuggerServer.allowChromeProcess = true;
 
     const transport = DebuggerServer.connectPipe();
     const client = new DebuggerClient(transport);
 
     await client.connect();
-    const { form } = await client.mainRoot.getProcess(processId);
-    const front = await client.mainRoot.attachContentProcessTarget(form);
+    const front = await client.mainRoot.getProcess(processId);
     const options = {
-      form,
       activeTab: front,
       client,
       chrome: true,
     };
     const target = await TargetFactory.forRemoteTab(options);
     // Ensure closing the connection in order to cleanup
     // the debugger client and also the server created in the
     // content process
@@ -371,24 +369,24 @@ var gDevToolsBrowser = exports.gDevTools
     Services.prompt.alert(null, "", msg);
     return Promise.reject(msg);
   },
 
   /**
    * Open a window-hosted toolbox to debug the worker associated to the provided
    * worker actor.
    *
-   * @param  {DebuggerClient} client
-   * @param  {Object} workerTargetActor
-   *         worker actor form to debug
+   * @param  {WorkerTargetFront} workerTargetFront
+   *         worker actor front to debug
+   * @param  {String} toolId (optional)
+   *        The id of the default tool to show
    */
-  async openWorkerToolbox(client, workerTargetActor) {
-    const [, workerTargetFront] = await client.attachWorker(workerTargetActor);
+  async openWorkerToolbox(workerTargetFront, toolId) {
     const workerTarget = TargetFactory.forWorker(workerTargetFront);
-    const toolbox = await gDevTools.showToolbox(workerTarget, null, Toolbox.HostType.WINDOW);
+    const toolbox = await gDevTools.showToolbox(workerTarget, toolId, Toolbox.HostType.WINDOW);
     toolbox.once("destroy", () => workerTargetFront.detach());
   },
 
   /**
    * Install WebIDE widget
    */
   // Used by itself
   installWebIDEWidget() {
--- a/devtools/client/framework/target-from-url.js
+++ b/devtools/client/framework/target-from-url.js
@@ -46,17 +46,17 @@ exports.targetFromURL = async function t
   if (!type) {
     throw new Error("targetFromURL, missing type parameter");
   }
   let id = params.get("id");
   // Allows to spawn a chrome enabled target for any context
   // (handy to debug chrome stuff in a content process)
   let chrome = params.has("chrome");
 
-  let form;
+  let form, front;
   if (type === "tab") {
     // Fetch target for a remote tab
     id = parseInt(id, 10);
     if (isNaN(id)) {
       throw new Error(`targetFromURL, wrong tab id '${id}', should be a number`);
     }
     try {
       const response = await client.getTab({ outerWindowID: id });
@@ -70,18 +70,17 @@ exports.targetFromURL = async function t
   } else if (type == "process") {
     // Fetch target for a remote chrome actor
     DebuggerServer.allowChromeProcess = true;
     try {
       id = parseInt(id, 10);
       if (isNaN(id)) {
         id = 0;
       }
-      const response = await client.mainRoot.getProcess(id);
-      form = response.form;
+      front = await client.mainRoot.getProcess(id);
       chrome = true;
     } catch (ex) {
       if (ex.error == "noProcess") {
         throw new Error(`targetFromURL, process with id '${id}' doesn't exist`);
       }
       throw ex;
     }
   } else if (type == "window") {
@@ -102,17 +101,17 @@ exports.targetFromURL = async function t
         throw new Error(`targetFromURL, window with id '${id}' doesn't exist`);
       }
       throw ex;
     }
   } else {
     throw new Error(`targetFromURL, unsupported type '${type}' parameter`);
   }
 
-  return TargetFactory.forRemoteTab({ client, form, chrome });
+  return TargetFactory.forRemoteTab({ client, form, activeTab: front, chrome });
 };
 
 /**
  * Create a DebuggerClient for a given URL object having various query parameters:
  *
  * host:
  *    {String} The hostname or IP address to connect to.
  * port:
--- a/devtools/client/framework/target.js
+++ b/devtools/client/framework/target.js
@@ -123,28 +123,16 @@ const TargetFactory = exports.TargetFact
     return targetPromise;
   },
 
   forWorker: function(workerTargetFront) {
     let target = targets.get(workerTargetFront);
     if (target == null) {
       target = new Target({
         client: workerTargetFront.client,
-        // Fake a form attribute until all Target is merged with the Front itself
-        // and will receive form attribute natively.
-        get form() {
-          return {
-            actor: workerTargetFront.actorID,
-            traits: {},
-            // /!\ This depends on WorkerTargetFront.attach being called before this.
-            // It happens that WorkerTargetFront is instantiated from attachWorker,
-            // which instiate this class *and* calls `attach`.
-            consoleActor: workerTargetFront.consoleActor,
-          };
-        },
         activeTab: workerTargetFront,
         chrome: false,
       });
       targets.set(workerTargetFront, target);
     }
     return target;
   },
 
@@ -195,38 +183,39 @@ const TargetFactory = exports.TargetFact
  * a remote device, like a tab on Firefox for Android. But it can also be an add-on,
  * as well as firefox parent process, or just one of its content process.
  * A Target is related to a given TargetActor, for which we pass the form as
  * argument.
  *
  * For now, only workers are having a distinct Target class called WorkerTarget.
  *
  * @param {Object} form
- *                 The TargetActor's form to be connected to.
+ *                  The TargetActor's form to be connected to. Null if front is passed.
+ * @param {Front} activeTab
+ *                  If we already have a front for this target, pass it here. Null if
+ *                  form is passed.
  * @param {DebuggerClient} client
- *                 The DebuggerClient instance to be used to debug this target.
+ *                  The DebuggerClient instance to be used to debug this target.
  * @param {Boolean} chrome
  *                  True, if we allow to see privileged resources like JSM, xpcom,
  *                  frame scripts...
- * @param {Front}   activeTab (optional)
- *                  If we already have a front for this target, pass it here.
  * @param {xul:tab} tab (optional)
  *                  If the target is a local Firefox tab, a reference to the firefox
  *                  frontend tab object.
  */
 function Target({ form, client, chrome, activeTab = null, tab = null }) {
   EventEmitter.decorate(this);
   this.destroy = this.destroy.bind(this);
   this._onTabNavigated = this._onTabNavigated.bind(this);
   this.activeConsole = null;
   this.activeTab = activeTab;
 
   this._form = form;
-  this._url = form.url;
-  this._title = form.title;
+  this._url = this.form.url;
+  this._title = this.form.title;
 
   this._client = client;
   this._chrome = chrome;
 
   // When debugging local tabs, we also have a reference to the Firefox tab
   // This is used to:
   // * distinguish local tabs from remote (see target.isLocalTab)
   // * being able to hookup into Firefox UI (see Hosts)
@@ -242,18 +231,18 @@ function Target({ form, client, chrome, 
   // * xpcshell debugging (it uses ParentProcessTargetActor, which inherits from
   //                       BrowsingContextActor, but doesn't have any valid browsing
   //                       context to attach to.)
   // Starting with FF64, BrowsingContextTargetActor exposes a traits to help identify
   // the target actors inheriting from it. It also help identify the xpcshell debugging
   // target actor that doesn't have any valid browsing context.
   // (Once FF63 is no longer supported, we can remove the `else` branch and only look
   // for the traits)
-  if (this._form.traits && ("isBrowsingContext" in this._form.traits)) {
-    this._isBrowsingContext = this._form.traits.isBrowsingContext;
+  if (this.form.traits && ("isBrowsingContext" in this.form.traits)) {
+    this._isBrowsingContext = this.form.traits.isBrowsingContext;
   } else {
     this._isBrowsingContext = !this.isLegacyAddon && !this.isContentProcess && !this.isWorkerTarget;
   }
 
   // Cache of already created targed-scoped fronts
   // [typeName:string => Front instance]
   this.fronts = new Map();
   // Temporary fix for bug #1493131 - inspector has a different life cycle
@@ -270,18 +259,16 @@ Target.prototype = {
    * internally with `target.actorHasMethod`. Takes advantage of caching if
    * definition was fetched previously with the corresponding actor information.
    * Actors are lazily loaded, so not only must the tool using a specific actor
    * be in use, the actors are only registered after invoking a method (for
    * performance reasons, added in bug 988237), so to use these actor detection
    * methods, one must already be communicating with a specific actor of that
    * type.
    *
-   * Must be a remote target.
-   *
    * @return {Promise}
    * {
    *   "category": "actor",
    *   "typeName": "longstractor",
    *   "methods": [{
    *     "name": "substring",
    *     "request": {
    *       "type": "substring",
@@ -310,34 +297,34 @@ Target.prototype = {
     }
     const description = await this.client.mainRoot.protocolDescription();
     this._protocolDescription = description;
     return description.types[actorName];
   },
 
   /**
    * Returns a boolean indicating whether or not the specific actor
-   * type exists. Must be a remote target.
+   * type exists.
    *
    * @param {String} actorName
    * @return {Boolean}
    */
   hasActor: function(actorName) {
     if (this.form) {
       return !!this.form[actorName + "Actor"];
     }
     return false;
   },
 
   /**
    * Queries the protocol description to see if an actor has
    * an available method. The actor must already be lazily-loaded (read
    * the restrictions in the `getActorDescription` comments),
    * so this is for use inside of tool. Returns a promise that
-   * resolves to a boolean. Must be a remote target.
+   * resolves to a boolean.
    *
    * @param {String} actorName
    * @param {String} methodName
    * @return {Promise}
    */
   actorHasMethod: function(actorName, methodName) {
     return this.getActorDescription(actorName).then(desc => {
       if (desc && desc.methods) {
@@ -363,17 +350,19 @@ Target.prototype = {
     return this.client.traits[traitName];
   },
 
   get tab() {
     return this._tab;
   },
 
   get form() {
-    return this._form;
+    // Target constructor either receive a form or a Front.
+    // If a front is passed, fetch the form from it.
+    return this._form || this.activeTab.targetForm;
   },
 
   // Get a promise of the RootActor's form
   get root() {
     return this.client.mainRoot.rootForm;
   },
 
   // Temporary fix for bug #1493131 - inspector has a different life cycle
@@ -428,17 +417,17 @@ Target.prototype = {
   // interface and requires to call `attach` request before being used and
   // `detach` during cleanup.
   get isBrowsingContext() {
     return this._isBrowsingContext;
   },
 
   get name() {
     if (this.isAddon) {
-      return this._form.name;
+      return this.form.name;
     }
     return this._title;
   },
 
   get url() {
     return this._url;
   },
 
@@ -446,34 +435,34 @@ Target.prototype = {
     return this.isLegacyAddon || this.isWebExtension;
   },
 
   get isWorkerTarget() {
     return this.activeTab && this.activeTab.typeName === "workerTarget";
   },
 
   get isLegacyAddon() {
-    return !!(this._form && this._form.actor &&
-      this._form.actor.match(/conn\d+\.addon(Target)?\d+/));
+    return !!(this.form && this.form.actor &&
+      this.form.actor.match(/conn\d+\.addon(Target)?\d+/));
   },
 
   get isWebExtension() {
-    return !!(this._form && this._form.actor && (
-      this._form.actor.match(/conn\d+\.webExtension(Target)?\d+/) ||
-      this._form.actor.match(/child\d+\/webExtension(Target)?\d+/)
+    return !!(this.form && this.form.actor && (
+      this.form.actor.match(/conn\d+\.webExtension(Target)?\d+/) ||
+      this.form.actor.match(/child\d+\/webExtension(Target)?\d+/)
     ));
   },
 
   get isContentProcess() {
     // browser content toolbox's form will be of the form:
     //   server0.conn0.content-process0/contentProcessTarget7
     // while xpcshell debugging will be:
     //   server1.conn0.contentProcessTarget7
-    return !!(this._form && this._form.actor &&
-      this._form.actor.match(/conn\d+\.(content-process\d+\/)?contentProcessTarget\d+/));
+    return !!(this.form && this.form.actor &&
+      this.form.actor.match(/conn\d+\.(content-process\d+\/)?contentProcessTarget\d+/));
   },
 
   get isLocalTab() {
     return !!this._tab;
   },
 
   get isMultiProcess() {
     return !this.window;
@@ -529,67 +518,82 @@ Target.prototype = {
    */
   attach() {
     if (this._attach) {
       return this._attach;
     }
 
     // Attach the target actor
     const attachBrowsingContextTarget = async () => {
-      const [, targetFront] = await this._client.attachTarget(this._form.actor);
-      this.activeTab = targetFront;
+      // Some BrowsingContextTargetFront are already instantiated and passed as
+      // contructor's argument, like for ParentProcessTargetActor.
+      // For them, we only need to attach them.
+      // The call to attachTarget is to be removed once all Target are having a front
+      // passed as contructor's argument.
+      if (!this.activeTab) {
+        const [, targetFront] = await this._client.attachTarget(this.form.actor);
+        this.activeTab = targetFront;
+      } else {
+        await this.activeTab.attach();
+      }
 
       this.activeTab.on("tabNavigated", this._onTabNavigated);
       this._onFrameUpdate = packet => {
         this.emit("frame-update", packet);
       };
       this.activeTab.on("frameUpdate", this._onFrameUpdate);
     };
 
     // Attach the console actor
     const attachConsole = async () => {
       const [, consoleClient] = await this._client.attachConsole(
-        this._form.consoleActor, []);
+        this.form.consoleActor, []);
       this.activeConsole = consoleClient;
 
       this._onInspectObject = packet => this.emit("inspect-object", packet);
       this.activeConsole.on("inspectObject", this._onInspectObject);
     };
 
     this._attach = (async () => {
-      if (this._form.isWebExtension &&
+      if (this.form.isWebExtension &&
           this.client.mainRoot.traits.webExtensionAddonConnect) {
         // The addonTargetActor form is related to a WebExtensionActor instance,
         // which isn't a target actor on its own, it is an actor living in the parent
         // process with access to the addon metadata, it can control the addon (e.g.
         // reloading it) and listen to the AddonManager events related to the lifecycle of
         // the addon (e.g. when the addon is disabled or uninstalled).
         // To retrieve the target actor instance, we call its "connect" method, (which
         // fetches the target actor form from a WebExtensionTargetActor instance).
         const {form} = await this._client.request({
-          to: this._form.actor, type: "connect",
+          to: this.form.actor, type: "connect",
         });
 
         this._form = form;
-        this._url = form.url;
-        this._title = form.title;
+        this._url = this.form.url;
+        this._title = this.form.title;
       }
 
       // AddonTargetActor and ContentProcessTargetActor don't inherit from
       // BrowsingContextTargetActor (i.e. this.isBrowsingContext=false) and don't need
       // to be attached via DebuggerClient.attachTarget.
       if (this.isBrowsingContext) {
         await attachBrowsingContextTarget();
       } else if (this.isLegacyAddon) {
-        const [, addonTargetFront] = await this._client.attachAddon(this._form);
+        const [, addonTargetFront] = await this._client.attachAddon(this.form);
         this.activeTab = addonTargetFront;
-      } else if (this.isWorkerTarget || this.isContentProcess) {
-        // Worker and Content process targets are the first target to have their front already
-        // instantiated. The plan is to have all targets to have their front passed as
-        // constructor argument.
+
+      // Worker and Content process targets are the first target to have their front already
+      // instantiated. The plan is to have all targets to have their front passed as
+      // constructor argument.
+      } else if (this.isWorkerTarget) {
+        // Worker is the first front to be completely migrated to have only its attach
+        // method being called from Target.attach. Other fronts should be refactored.
+        await this.activeTab.attach();
+      } else if (this.isContentProcess) {
+        // ContentProcessTarget is the only one target without any attach request.
       } else {
         throw new Error(`Unsupported type of target. Expected target of one of the` +
           ` following types: BrowsingContext, ContentProcess, Worker or ` +
           `Addon (legacy).`);
       }
 
       // _setupRemoteListeners has to be called after the potential call to `attachTarget`
       // as it depends on `activeTab` which is set by this method.
@@ -671,17 +675,17 @@ Target.prototype = {
       // These events should be ultimately listened from the thread client as
       // they are coming from it and no longer go through the Target Actor/Front.
       this._onSourceUpdated = packet => this.emit("source-updated", packet);
       this.activeTab.on("newSource", this._onSourceUpdated);
       this.activeTab.on("updatedSource", this._onSourceUpdated);
     } else {
       this._onTabDetached = (type, packet) => {
         // We have to filter message to ensure that this detach is for this tab
-        if (packet.from == this._form.actor) {
+        if (packet.from == this.form.actor) {
           this.destroy();
         }
       };
       this.client.addListener("tabDetached", this._onTabDetached);
 
       this._onSourceUpdated = (type, packet) => this.emit("source-updated", packet);
       this.client.addListener("newSource", this._onSourceUpdated);
       this.client.addListener("updatedSource", this._onSourceUpdated);
@@ -807,31 +811,31 @@ Target.prototype = {
 
   /**
    * Clean up references to what this target points to.
    */
   _cleanup: function() {
     if (this._tab) {
       targets.delete(this._tab);
     } else {
-      promiseTargets.delete(this._form);
+      promiseTargets.delete(this.form);
     }
 
     this.activeTab = null;
     this.activeConsole = null;
     this._client = null;
     this._tab = null;
     this._form = null;
     this._attach = null;
     this._title = null;
     this._url = null;
   },
 
   toString: function() {
-    const id = this._tab ? this._tab : (this._form && this._form.actor);
+    const id = this._tab ? this._tab : (this.form && this.form.actor);
     return `Target:${id}`;
   },
 
   /**
    * Log an error of some kind to the tab's console.
    *
    * @param {String} text
    *                 The text to log.
--- a/devtools/client/framework/test/browser_target_remote.js
+++ b/devtools/client/framework/test/browser_target_remote.js
@@ -2,20 +2,20 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Ensure target is closed if client is closed directly
 function test() {
   waitForExplicitFinish();
 
-  getParentProcessActors((client, response) => {
+  getParentProcessActors((client, front) => {
     const options = {
-      form: response,
-      client: client,
+      activeTab: front,
+      client,
       chrome: true,
     };
 
     TargetFactory.forRemoteTab(options).then(target => {
       target.on("close", () => {
         ok(true, "Target was closed");
         finish();
       });
--- a/devtools/client/framework/test/browser_target_support.js
+++ b/devtools/client/framework/test/browser_target_support.js
@@ -47,20 +47,20 @@ async function testTarget(client, target
 
   close(target, client);
 }
 
 // Ensure target is closed if client is closed directly
 function test() {
   waitForExplicitFinish();
 
-  getParentProcessActors((client, response) => {
+  getParentProcessActors((client, front) => {
     const options = {
-      form: response,
-      client: client,
+      activeTab: front,
+      client,
       chrome: true,
     };
 
     TargetFactory.forRemoteTab(options).then(testTarget.bind(null, client));
   });
 }
 
 function close(target, client) {
--- a/devtools/client/framework/test/head.js
+++ b/devtools/client/framework/test/head.js
@@ -30,18 +30,18 @@ function getParentProcessActors(callback
 
   DebuggerServer.init();
   DebuggerServer.registerAllActors();
   DebuggerServer.allowChromeProcess = true;
 
   const client = new DebuggerClient(DebuggerServer.connectPipe());
   client.connect()
     .then(() => client.mainRoot.getMainProcess())
-    .then(response => {
-      callback(client, response.form);
+    .then(front => {
+      callback(client, front);
     });
 
   SimpleTest.registerCleanupFunction(() => {
     DebuggerServer.destroy();
   });
 }
 
 function getSourceActor(aSources, aURL) {
--- a/devtools/client/framework/toolbox-process-window.js
+++ b/devtools/client/framework/toolbox-process-window.js
@@ -88,18 +88,18 @@ var connect = async function() {
   await gClient.connect();
 
   appendStatusMessage("Get root form for toolbox");
   if (addonID) {
     const { addons } = await gClient.listAddons();
     const addonTargetActor = addons.filter(addon => addon.id === addonID).pop();
     await openToolbox({form: addonTargetActor, chrome: true});
   } else {
-    const response = await gClient.mainRoot.getMainProcess();
-    await openToolbox({form: response.form, chrome: true});
+    const front = await gClient.mainRoot.getMainProcess();
+    await openToolbox({activeTab: front, chrome: true});
   }
 };
 
 // Certain options should be toggled since we can assume chrome debugging here
 function setPrefDefaults() {
   Services.prefs.setBoolPref("devtools.inspector.showUserAgentStyles", true);
   Services.prefs.setBoolPref("devtools.performance.ui.show-platform-data", true);
   Services.prefs.setBoolPref("devtools.inspector.showAllAnonymousContent", true);
@@ -135,23 +135,24 @@ window.addEventListener("load", async fu
     console.error(e);
   }
 }, { once: true });
 
 function onCloseCommand(event) {
   window.close();
 }
 
-async function openToolbox({ form, chrome }) {
+async function openToolbox({ form, activeTab, chrome }) {
   let options = {
-    form: form,
+    form,
+    activeTab,
     client: gClient,
-    chrome: chrome,
+    chrome,
   };
-  appendStatusMessage(`Create toolbox target: ${JSON.stringify(arguments, null, 2)}`);
+  appendStatusMessage(`Create toolbox target: ${JSON.stringify({form, chrome}, null, 2)}`);
   const target = await TargetFactory.forRemoteTab(options);
   const frame = document.getElementById("toolbox-iframe");
 
   // Remember the last panel that was used inside of this profile.
   // But if we are testing, then it should always open the debugger panel.
   const selectedTool =
     Services.prefs.getCharPref("devtools.browsertoolbox.panel",
       Services.prefs.getCharPref("devtools.toolbox.selectedTool",
--- a/devtools/client/scratchpad/scratchpad.js
+++ b/devtools/client/scratchpad/scratchpad.js
@@ -2066,18 +2066,22 @@ ScratchpadWindow.prototype = extend(Scra
    */
   async _attach() {
     DebuggerServer.init();
     DebuggerServer.registerAllActors();
     DebuggerServer.allowChromeProcess = true;
 
     const client = new DebuggerClient(DebuggerServer.connectPipe());
     await client.connect();
-    const response = await client.mainRoot.getMainProcess();
-    return { form: response.form, client };
+    const front = await client.mainRoot.getMainProcess();
+    const target = await TargetFactory.forRemoteTab({
+      activeTab: front,
+      client,
+    });
+    return target;
   },
 });
 
 function ScratchpadTarget(aTarget) {
   this._target = aTarget;
 }
 
 ScratchpadTarget.consoleFor = ScratchpadTab.consoleFor;
--- a/devtools/client/shared/test/browser_dbg_WorkerTargetActor.attach.js
+++ b/devtools/client/shared/test/browser_dbg_WorkerTargetActor.attach.js
@@ -39,40 +39,40 @@ function test() {
 
     // If a page still has pending network requests, it will not be moved into
     // the bfcache. Consequently, we cannot use waitForWorkerListChanged here,
     // because the worker is not guaranteed to have finished loading when it is
     // registered. Instead, we have to wait for the promise returned by
     // createWorker in the tab to be resolved.
     yield createWorkerInTab(tab, WORKER1_URL);
     let { workers } = yield listWorkers(targetFront);
-    let [, workerTargetFront1] = yield attachWorker(targetFront,
-                                               findWorker(workers, WORKER1_URL));
+    let workerTargetFront1 = findWorker(workers, WORKER1_URL);
+    yield workerTargetFront1.attach();
     is(workerTargetFront1.isClosed, false, "worker in tab 1 should not be closed");
 
     executeSoon(() => {
       BrowserTestUtils.loadURI(tab.linkedBrowser, TAB2_URL);
     });
     yield waitForWorkerClose(workerTargetFront1);
     is(workerTargetFront1.isClosed, true, "worker in tab 1 should be closed");
 
     yield createWorkerInTab(tab, WORKER2_URL);
     ({ workers } = yield listWorkers(targetFront));
-    const [, workerTargetFront2] = yield attachWorker(targetFront,
-                                               findWorker(workers, WORKER2_URL));
+    const workerTargetFront2 = findWorker(workers, WORKER2_URL);
+    yield workerTargetFront2.attach();
     is(workerTargetFront2.isClosed, false, "worker in tab 2 should not be closed");
 
     executeSoon(() => {
       tab.linkedBrowser.goBack();
     });
     yield waitForWorkerClose(workerTargetFront2);
     is(workerTargetFront2.isClosed, true, "worker in tab 2 should be closed");
 
     ({ workers } = yield listWorkers(targetFront));
-    [, workerTargetFront1] = yield attachWorker(targetFront,
-                                           findWorker(workers, WORKER1_URL));
+    workerTargetFront1 = findWorker(workers, WORKER1_URL);
+    yield workerTargetFront1.attach();
     is(workerTargetFront1.isClosed, false, "worker in tab 1 should not be closed");
 
     yield close(client);
     SpecialPowers.setIntPref(MAX_TOTAL_VIEWERS, oldMaxTotalViewers);
     finish();
   });
 }
--- a/devtools/client/shared/test/browser_dbg_worker-window.js
+++ b/devtools/client/shared/test/browser_dbg_worker-window.js
@@ -4,21 +4,16 @@
 "use strict";
 
 // Import helpers for the workers
 /* import-globals-from helper_workers.js */
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/shared/test/helper_workers.js",
   this);
 
-// The following "connectionClosed" rejection should not be left uncaught. This
-// test has been whitelisted until the issue is fixed.
-ChromeUtils.import("resource://testing-common/PromiseTestUtils.jsm", this);
-PromiseTestUtils.expectUncaughtRejection(/[object Object]/);
-
 const TAB_URL = EXAMPLE_URL + "doc_WorkerTargetActor.attachThread-tab.html";
 const WORKER_URL = "code_WorkerTargetActor.attachThread-worker.js";
 
 add_task(async function() {
   await pushPrefs(["devtools.scratchpad.enabled", true]);
 
   DebuggerServer.init();
   DebuggerServer.registerAllActors();
@@ -29,18 +24,17 @@ add_task(async function() {
   const tab = await addTab(TAB_URL);
   const { tabs } = await listTabs(client);
   const [, targetFront] = await attachTarget(client, findTab(tabs, TAB_URL));
 
   await listWorkers(targetFront);
   await createWorkerInTab(tab, WORKER_URL);
 
   const { workers } = await listWorkers(targetFront);
-  const [, workerTargetFront] = await attachWorker(targetFront,
-                                             findWorker(workers, WORKER_URL));
+  const workerTargetFront = findWorker(workers, WORKER_URL);
 
   const toolbox = await gDevTools.showToolbox(TargetFactory.forWorker(workerTargetFront),
                                             "jsdebugger",
                                             Toolbox.HostType.WINDOW);
 
   is(toolbox.hostType, "window", "correct host");
 
   await new Promise(done => {
--- a/devtools/client/shared/test/helper_workers.js
+++ b/devtools/client/shared/test/helper_workers.js
@@ -129,21 +129,16 @@ function findWorker(workers, url) {
   for (const worker of workers) {
     if (worker.url === url) {
       return worker;
     }
   }
   return null;
 }
 
-function attachWorker(targetFront, worker) {
-  info("Attaching to worker with url '" + worker.url + "'.");
-  return targetFront.attachWorker(worker.actor);
-}
-
 function waitForWorkerListChanged(targetFront) {
   info("Waiting for worker list to change.");
   return targetFront.once("workerListChanged");
 }
 
 function attachThread(workerTargetFront, options) {
   info("Attaching to thread.");
   return workerTargetFront.attachThread(options);
@@ -185,18 +180,17 @@ async function initWorkerDebugger(TAB_UR
 
   const tab = await addTab(TAB_URL);
   const { tabs } = await listTabs(client);
   const [, targetFront] = await attachTarget(client, findTab(tabs, TAB_URL));
 
   await createWorkerInTab(tab, WORKER_URL);
 
   const { workers } = await listWorkers(targetFront);
-  const [, workerTargetFront] = await attachWorker(targetFront,
-                                             findWorker(workers, WORKER_URL));
+  const workerTargetFront = findWorker(workers, WORKER_URL);
 
   const toolbox = await gDevTools.showToolbox(TargetFactory.forWorker(workerTargetFront),
                                             "jsdebugger",
                                             Toolbox.HostType.WINDOW);
 
   const debuggerPanel = toolbox.getCurrentPanel();
 
   const gDebugger = debuggerPanel.panelWin;
--- a/devtools/client/webconsole/hudservice.js
+++ b/devtools/client/webconsole/hudservice.js
@@ -131,18 +131,18 @@ HUDService.prototype = {
       // (See Bug 1416105 for rationale).
       DebuggerServer.init();
       DebuggerServer.registerActors({ root: true, target: true });
 
       DebuggerServer.allowChromeProcess = true;
 
       const client = new DebuggerClient(DebuggerServer.connectPipe());
       await client.connect();
-      const response = await client.mainRoot.getMainProcess();
-      return { form: response.form, client, chrome: true };
+      const front = await client.mainRoot.getMainProcess();
+      return { activeTab: front, client, chrome: true };
     }
 
     async function openWindow(t) {
       const win = Services.ww.openWindow(null, Tools.webConsole.url,
                                        "_blank", BC_WINDOW_FEATURES, null);
 
       await new Promise(resolve => {
         win.addEventListener("DOMContentLoaded", resolve, {once: true});
--- a/devtools/client/webide/modules/app-manager.js
+++ b/devtools/client/webide/modules/app-manager.js
@@ -250,19 +250,19 @@ var AppManager = exports.AppManager = {
     }, console.error);
   },
 
   getTarget: function() {
     if (this.selectedProject.type == "mainProcess") {
       // Fx >=39 exposes a ParentProcessTargetActor to debug the main process
       if (this.connection.client.mainRoot.traits.allowChromeProcess) {
         return this.connection.client.mainRoot.getMainProcess()
-                   .then(aResponse => {
+                   .then(front => {
                      return TargetFactory.forRemoteTab({
-                       form: aResponse.form,
+                       activeTab: front,
                        client: this.connection.client,
                        chrome: true,
                      });
                    });
       }
       // Fx <39 exposes chrome target actors on the root actor
       return TargetFactory.forRemoteTab({
           form: this._listTabsResponse,
--- a/devtools/docs/backend/protocol.js.md
+++ b/devtools/docs/backend/protocol.js.md
@@ -612,17 +612,18 @@ For more complex situations, you can def
       }
     }
 
     // implementation:
     getTemporaryChild: function (id) {
       if (!this._temporaryParent) {
         // Create an actor to serve as the parent for all temporary children and explicitly
         // add it as a child of this actor.
-        this._temporaryParent = this.manage(new Actor(this.conn));
+        this._temporaryParent = new Actor(this.conn));
+        this.manage(this._temporaryParent);
       }
       return new ChildActor(this.conn, id);
     }
 
     clearTemporaryChildren: function () {
       if (this._temporaryParent) {
         this._temporaryParent.destroy();
         delete this._temporaryParent;
--- a/devtools/server/actors/targets/browsing-context.js
+++ b/devtools/server/actors/targets/browsing-context.js
@@ -650,18 +650,17 @@ const browsingContextTargetPrototype = {
       if (this._workerTargetActorPool) {
         this._workerTargetActorPool.destroy();
       }
 
       this._workerTargetActorPool = pool;
       this._workerTargetActorList.onListChanged = this._onWorkerTargetActorListChanged;
 
       return {
-        "from": this.actorID,
-        "workers": actors.map((actor) => actor.form()),
+        workers: actors,
       };
     });
   },
 
   logInPage(request) {
     const {text, category, flags} = request;
     const scriptErrorClass = Cc["@mozilla.org/scripterror;1"];
     const scriptError = scriptErrorClass.createInstance(Ci.nsIScriptError);
--- a/devtools/server/actors/targets/webextension.js
+++ b/devtools/server/actors/targets/webextension.js
@@ -71,24 +71,21 @@ const webExtensionTargetPrototype = exte
  *        DebuggerServer.connectToFrame method.
  * @param {string} prefix
  *        the custom RDP prefix to use.
  * @param {string} addonId
  *        the addonId of the target WebExtension.
  */
 webExtensionTargetPrototype.initialize = function(conn, chromeGlobal, prefix, addonId) {
   this.addonId = addonId;
+  this.chromeGlobal = chromeGlobal;
 
   // Try to discovery an existent extension page to attach (which will provide the initial
   // URL shown in the window tittle when the addon debugger is opened).
-  let extensionWindow = this._searchForExtensionWindow();
-  if (!extensionWindow) {
-    this._createFallbackWindow();
-    extensionWindow = this.fallbackWindow;
-  }
+  const extensionWindow = this._searchForExtensionWindow();
 
   parentProcessTargetPrototype.initialize.call(this, conn, extensionWindow);
   this._chromeGlobal = chromeGlobal;
   this._prefix = prefix;
 
   // Redefine the messageManager getter to return the chromeGlobal
   // as the messageManager for this actor (which is the browser XUL
   // element used by the parent actor running in the main process to
@@ -149,59 +146,50 @@ webExtensionTargetPrototype.exit = funct
   this.addon = null;
   this.addonId = null;
 
   return ParentProcessTargetActor.prototype.exit.apply(this);
 };
 
 // Private helpers.
 
-webExtensionTargetPrototype._createFallbackWindow = function() {
+webExtensionTargetPrototype._searchFallbackWindow = function() {
   if (this.fallbackWindow) {
     // Skip if there is already an existent fallback window.
-    return;
+    return this.fallbackWindow;
   }
 
-  // Create an empty hidden window as a fallback (e.g. the background page could be
-  // not defined for the target add-on or not yet when the actor instance has been
-  // created).
-  this.fallbackWebNav = Services.appShell.createWindowlessBrowser(true);
+  // Set and initialized the fallbackWindow (which initially is a empty
+  // about:blank browser), this window is related to a XUL browser element
+  // specifically created for the devtools server and it is never used
+  // or navigated anywhere else.
+  this.fallbackWindow = this.chromeGlobal.content;
+  this.fallbackWindow.location = "data:text/html,<h1>" + FALLBACK_DOC_MESSAGE;
 
-  // Save the reference to the fallback DOMWindow.
-  this.fallbackWindow = this.fallbackWebNav.document.defaultView;
-
-  // Insert the fallback doc message.
-  this.fallbackWindow.document.body.innerText = FALLBACK_DOC_MESSAGE;
+  return this.fallbackWindow;
 };
 
 webExtensionTargetPrototype._destroyFallbackWindow = function() {
-  if (this.fallbackWebNav) {
-    const systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
-    // Explicitly close the fallback windowless browser to prevent it to leak
-    // (and to prevent it to freeze devtools xpcshell tests).
-    this.fallbackWebNav.loadURI("about:blank", 0, null, null, null, systemPrincipal);
-    this.fallbackWebNav.close();
-
-    this.fallbackWebNav = null;
+  if (this.fallbackWindow) {
     this.fallbackWindow = null;
   }
 };
 
 // Discovery an extension page to use as a default target window.
 // NOTE: This currently fail to discovery an extension page running in a
 // windowless browser when running in non-oop mode, and the background page
 // is set later using _onNewExtensionWindow.
 webExtensionTargetPrototype._searchForExtensionWindow = function() {
   for (const window of Services.ww.getWindowEnumerator(null)) {
     if (window.document.nodePrincipal.addonId == this.addonId) {
       return window;
     }
   }
 
-  return undefined;
+  return this._searchFallbackWindow();
 };
 
 // Customized ParentProcessTargetActor/BrowsingContextTargetActor hooks.
 
 webExtensionTargetPrototype._onDocShellDestroy = function(docShell) {
   // Stop watching this docshell (the unwatch() method will check if we
   // started watching it before).
   this._unwatchDocShell(docShell);
@@ -209,43 +197,34 @@ webExtensionTargetPrototype._onDocShellD
   // Let the _onDocShellDestroy notify that the docShell has been destroyed.
   const webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
         .getInterface(Ci.nsIWebProgress);
   this._notifyDocShellDestroy(webProgress);
 
   // If the destroyed docShell was the current docShell and the actor is
   // currently attached, switch to the fallback window
   if (this.attached && docShell == this.docShell) {
-    // Creates a fallback window if it doesn't exist yet.
-    this._createFallbackWindow();
-    this._changeTopLevelDocument(this.fallbackWindow);
+    this._changeTopLevelDocument(this._searchForExtensionWindow());
   }
 };
 
 webExtensionTargetPrototype._onNewExtensionWindow = function(window) {
   if (!this.window || this.window === this.fallbackWindow) {
     this._changeTopLevelDocument(window);
   }
 };
 
 webExtensionTargetPrototype._attach = function() {
   // NOTE: we need to be sure that `this.window` can return a window before calling the
   // ParentProcessTargetActor.onAttach, or the BrowsingContextTargetActor will not be
   // subscribed to the child doc shell updates.
 
   if (!this.window || this.window.document.nodePrincipal.addonId !== this.addonId) {
-    // Discovery an existent extension page to attach.
-    const extensionWindow = this._searchForExtensionWindow();
-
-    if (!extensionWindow) {
-      this._createFallbackWindow();
-      this._setWindow(this.fallbackWindow);
-    } else {
-      this._setWindow(extensionWindow);
-    }
+    // Discovery an existent extension page (or fallback window) to attach.
+    this._setWindow(this._searchForExtensionWindow());
   }
 
   // Call ParentProcessTargetActor's _attach to listen for any new/destroyed chrome
   // docshell.
   ParentProcessTargetActor.prototype._attach.apply(this);
 };
 
 webExtensionTargetPrototype._detach = function() {
--- a/devtools/server/tests/mochitest/test_getProcess.html
+++ b/devtools/server/tests/mochitest/test_getProcess.html
@@ -80,17 +80,18 @@ function runTests() {
     client.mainRoot.listProcesses().then(response => {
       ok(response.processes.length >= 2, "Got at least the parent process and one child");
       is(response.processes.length, processCount + 1,
          "Got one additional process on the second call to listProcesses");
 
       // Connect to the first content processe available
       const content = response.processes.filter(p => (!p.parent))[0];
 
-      client.mainRoot.getProcess(content.id).then(({form: actor}) => {
+      client.mainRoot.getProcess(content.id).then(front => {
+        const actor = front.targetForm;
         ok(actor.consoleActor, "Got the console actor");
         ok(actor.chromeDebugger, "Got the thread actor");
 
         // Ensure sending at least one request to an actor...
         client.request({
           to: actor.consoleActor,
           type: "evaluateJS",
           text: "var a = 42; a",
@@ -101,18 +102,18 @@ function runTests() {
         });
       });
     });
   }
 
   // Assert that calling client.getProcess against the same process id is
   // returning the same actor.
   function getProcessAgain(firstActor, id) {
-    client.mainRoot.getProcess(id).then(response => {
-      const actor = response.form;
+    client.mainRoot.getProcess(id).then(front => {
+      const actor = front.targetForm;
       is(actor, firstActor,
          "Second call to getProcess with the same id returns the same form");
       closeClient();
     });
   }
 
   function processScript() {
     ChromeUtils.import("resource://gre/modules/Services.jsm");
--- a/devtools/server/tests/unit/head_dbg.js
+++ b/devtools/server/tests/unit/head_dbg.js
@@ -96,19 +96,19 @@ async function createTabMemoryFront() {
 async function createFullRuntimeMemoryFront() {
   DebuggerServer.init();
   DebuggerServer.registerAllActors();
   DebuggerServer.allowChromeProcess = true;
 
   const client = new DebuggerClient(DebuggerServer.connectPipe());
   await client.connect();
 
-  const { form } = await client.mainRoot.getMainProcess();
+  const front = await client.mainRoot.getMainProcess();
   const options = {
-    form,
+    activeTab: front,
     client,
     chrome: true,
   };
   const target = await TargetFactory.forRemoteTab(options);
 
   const memoryFront = target.getFront("memory");
   await memoryFront.attach();
 
@@ -400,17 +400,17 @@ async function startTestDebuggerServer(t
 async function finishClient(client) {
   await client.close();
   DebuggerServer.destroy();
   do_test_finished();
 }
 
 function getParentProcessActors(client, server = DebuggerServer) {
   server.allowChromeProcess = true;
-  return client.mainRoot.getMainProcess().then(response => response.form);
+  return client.mainRoot.getMainProcess().then(response => response.targetForm);
 }
 
 /**
  * Takes a relative file path and returns the absolute file url for it.
  */
 function getFileUrl(name, allowMissing = false) {
   const file = do_get_file(name, allowMissing);
   return Services.io.newFileURI(file).spec;
--- a/devtools/server/tests/unit/test_protocol_children.js
+++ b/devtools/server/tests/unit/test_protocol_children.js
@@ -281,17 +281,18 @@ var RootActor = protocol.ActorClassWithS
       child5: this.getChild("child5"),
       more: [ this.getChild("child6"), this.getChild("child7") ],
     };
   },
 
   // This should remind you of a pause actor.
   getTemporaryChild: function(id) {
     if (!this._temporaryHolder) {
-      this._temporaryHolder = this.manage(new protocol.Actor(this.conn));
+      this._temporaryHolder = new protocol.Actor(this.conn);
+      this.manage(this._temporaryHolder);
     }
     return new ChildActor(this.conn, id);
   },
 
   clearTemporaryChildren: function(id) {
     if (!this._temporaryHolder) {
       return;
     }
@@ -310,17 +311,17 @@ var RootFront = protocol.FrontClassWithS
     // Root actor owns itself.
     this.manage(this);
   },
 
   getTemporaryChild: protocol.custom(function(id) {
     if (!this._temporaryHolder) {
       this._temporaryHolder = new protocol.Front(this.conn);
       this._temporaryHolder.actorID = this.actorID + "_temp";
-      this._temporaryHolder = this.manage(this._temporaryHolder);
+      this.manage(this._temporaryHolder);
     }
     return this._getTemporaryChild(id);
   }, {
     impl: "_getTemporaryChild",
   }),
 
   clearTemporaryChildren: protocol.custom(function() {
     if (!this._temporaryHolder) {
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/unit/test_protocol_onFront.js
@@ -0,0 +1,131 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Test Front.onFront method.
+ */
+
+const protocol = require("devtools/shared/protocol");
+const {RetVal} = protocol;
+
+const childSpec = protocol.generateActorSpec({
+  typeName: "childActor",
+});
+
+const ChildActor = protocol.ActorClassWithSpec(childSpec, {
+  initialize(conn, id) {
+    protocol.Actor.prototype.initialize.call(this, conn);
+    this.childID = id;
+  },
+
+  form: function(detail) {
+    if (detail === "actorid") {
+      return this.actorID;
+    }
+    return {
+      actor: this.actorID,
+      childID: this.childID,
+    };
+  },
+});
+
+const rootSpec = protocol.generateActorSpec({
+  typeName: "root",
+
+  methods: {
+    createChild: {
+      request: {},
+      response: { actor: RetVal("childActor") },
+    },
+  },
+});
+
+const RootActor = protocol.ActorClassWithSpec(rootSpec, {
+  typeName: "root",
+
+  initialize: function(conn) {
+    protocol.Actor.prototype.initialize.call(this, conn);
+
+    this.actorID = "root";
+
+    // Root actor owns itself.
+    this.manage(this);
+
+    this.sequence = 0;
+  },
+
+  sayHello() {
+    return {
+      from: "root",
+      applicationType: "xpcshell-tests",
+      traits: [],
+    };
+  },
+
+  createChild() {
+    return new ChildActor(this.conn, this.sequence++);
+  },
+});
+
+const ChildFront = protocol.FrontClassWithSpec(childSpec, {
+  form(form, detail) {
+    if (detail === "actorid") {
+      return;
+    }
+    this.childID = form.childID;
+  },
+});
+
+const RootFront = protocol.FrontClassWithSpec(rootSpec, {
+  initialize(client) {
+    this.actorID = "root";
+    protocol.Front.prototype.initialize.call(this, client);
+    // Root owns itself.
+    this.manage(this);
+  },
+});
+
+add_task(async function run_test() {
+  DebuggerServer.createRootActor = RootActor;
+  DebuggerServer.init();
+
+  const trace = connectPipeTracing();
+  const client = new DebuggerClient(trace);
+  await client.connect();
+
+  const rootFront = new RootFront(client);
+
+  const fronts = [];
+  rootFront.onFront("childActor", front => {
+    fronts.push(front);
+  });
+
+  const firstChild = await rootFront.createChild();
+  ok(firstChild instanceof ChildFront, "createChild returns a ChildFront instance");
+  equal(firstChild.childID, 0, "First child has ID=0");
+
+  equal(fronts.length, 1,
+    "onFront fires the callback, even if the front is created in the future");
+  equal(fronts[0], firstChild,
+    "onFront fires the callback with the right front instance");
+
+  const onFrontAfter = await new Promise(resolve => {
+    rootFront.onFront("childActor", resolve);
+  });
+  equal(onFrontAfter, firstChild,
+    "onFront fires the callback, even if the front is already created, " +
+    " with the same front instance");
+
+  equal(fronts.length, 1,
+    "There is still only one front reported from the first listener");
+
+  const secondChild = await rootFront.createChild();
+
+  equal(fronts.length, 2, "After a second call to createChild, two fronts are reported");
+  equal(fronts[1], secondChild, "And the new front is the right instance");
+
+  trace.close();
+  await client.close();
+});
--- a/devtools/server/tests/unit/test_xpcshell_debugging.js
+++ b/devtools/server/tests/unit/test_xpcshell_debugging.js
@@ -20,21 +20,19 @@ add_task(async function() {
   const client = new DebuggerClient(transport);
   await client.connect();
 
   // Ensure that global actors are available. Just test the device actor.
   const deviceFront = await client.mainRoot.getFront("device");
   const desc = await deviceFront.getDescription();
   equal(desc.geckobuildid, Services.appinfo.platformBuildID, "device actor works");
 
-  // Even though we have no tabs, getMainProcess gives us the chromeDebugger.
-  const response = await client.mainRoot.getMainProcess();
-
-  const { chromeDebugger } = response.form;
-  const [, threadClient] = await client.attachThread(chromeDebugger);
+  // Even though we have no tabs, getMainProcess gives us the chrome debugger.
+  const front = await client.mainRoot.getMainProcess();
+  const [, threadClient] = await front.attachThread();
   const onResumed = new Promise(resolve => {
     threadClient.addOneTimeListener("paused", (event, packet) => {
       equal(packet.why.type, "breakpoint",
           "yay - hit the breakpoint at the first line in our script");
       // Resume again - next stop should be our "debugger" statement.
       threadClient.addOneTimeListener("paused", (event, packet) => {
         equal(packet.why.type, "debuggerStatement",
               "yay - hit the 'debugger' statement in our script");
--- a/devtools/server/tests/unit/xpcshell.ini
+++ b/devtools/server/tests/unit/xpcshell.ini
@@ -101,16 +101,17 @@ skip-if = coverage # bug 1336670
 [test_promises_object_creationtimestamp.js]
 [test_promises_object_timetosettle-01.js]
 [test_promises_object_timetosettle-02.js]
 [test_protocol_abort.js]
 [test_protocol_async.js]
 [test_protocol_children.js]
 [test_protocol_formtype.js]
 [test_protocol_longstring.js]
+[test_protocol_onFront.js]
 [test_protocol_simple.js]
 [test_protocol_stack.js]
 [test_protocol_unregister.js]
 [test_breakpoint-01.js]
 [test_register_actor.js]
 [test_breakpoint-02.js]
 [test_breakpoint-03.js]
 [test_breakpoint-04.js]
--- a/devtools/shared/client/debugger-client.js
+++ b/devtools/shared/client/debugger-client.js
@@ -20,17 +20,16 @@ const {
 loader.lazyRequireGetter(this, "Authentication", "devtools/shared/security/auth");
 loader.lazyRequireGetter(this, "DebuggerSocket", "devtools/shared/security/socket", true);
 loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
 
 loader.lazyRequireGetter(this, "WebConsoleClient", "devtools/shared/webconsole/client", true);
 loader.lazyRequireGetter(this, "AddonTargetFront", "devtools/shared/fronts/targets/addon", true);
 loader.lazyRequireGetter(this, "RootFront", "devtools/shared/fronts/root", true);
 loader.lazyRequireGetter(this, "BrowsingContextTargetFront", "devtools/shared/fronts/targets/browsing-context", true);
-loader.lazyRequireGetter(this, "WorkerTargetFront", "devtools/shared/fronts/targets/worker", 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];
 
@@ -55,17 +54,17 @@ function DebuggerClient(transport) {
   // To be removed once all clients are refactored to protocol.js
   this._clients = new Map();
 
   // Pool of fronts instanciated by this class.
   // This is useful for actors that have already been transitioned to protocol.js
   // Once RootClient becomes a protocol.js actor, these actors can be attached to it
   // instead of this pool.
   // This Pool will automatically be added to this._pools via addActorPool once the first
-  // Front will be added to it (in attachTarget, attachWorker,...).
+  // Front will be added to it (in attachTarget, ...).
   // And it does not need to destroyed explicitly as all Pools are destroyed on client
   // closing.
   this._frontPool = new Pool(this);
 
   this._pendingRequests = new Map();
   this._activeRequests = new Map();
   this._eventsEnabled = true;
 
@@ -376,27 +375,16 @@ DebuggerClient.prototype = {
       front = new BrowsingContextTargetFront(this, { actor: targetActor });
       this._frontPool.manage(front);
     }
 
     const response = await front.attach();
     return [response, front];
   },
 
-  attachWorker: async function(workerTargetActor) {
-    let front = this._frontPool.actor(workerTargetActor);
-    if (!front) {
-      front = new WorkerTargetFront(this, { actor: workerTargetActor });
-      this._frontPool.manage(front);
-    }
-
-    const response = await front.attach();
-    return [response, front];
-  },
-
   /**
    * Attach to an addon target actor.
    *
    * @param string addonTargetActor
    *        The actor ID for the addon to attach.
    */
   attachAddon: async function(form) {
     let front = this._frontPool.actor(form.actor);
--- a/devtools/shared/fronts/root.js
+++ b/devtools/shared/fronts/root.js
@@ -4,16 +4,17 @@
 "use strict";
 
 const {Ci} = require("chrome");
 const {rootSpec} = require("devtools/shared/specs/root");
 const protocol = require("devtools/shared/protocol");
 const {custom} = protocol;
 
 loader.lazyRequireGetter(this, "getFront", "devtools/shared/protocol", true);
+loader.lazyRequireGetter(this, "BrowsingContextTargetFront", "devtools/shared/fronts/targets/browsing-context", true);
 loader.lazyRequireGetter(this, "ContentProcessTargetFront", "devtools/shared/fronts/targets/content-process", true);
 
 const RootFront = protocol.FrontClassWithSpec(rootSpec, {
   initialize: function(client, form) {
     protocol.Front.prototype.initialize.call(this, client, { actor: form.from });
 
     this.applicationType = form.applicationType;
     this.traits = form.traits;
@@ -63,22 +64,18 @@ const RootFront = protocol.FrontClassWit
 
       // And then from the Child processes
       const { processes } = await this.listProcesses();
       for (const process of processes) {
         // Ignore parent process
         if (process.parent) {
           continue;
         }
-        const { form } = await this.getProcess(process.id);
-        const processActor = form.actor;
-        const response = await this._client.request({
-          to: processActor,
-          type: "listWorkers",
-        });
+        const front = await this.getProcess(process.id);
+        const response = await front.listWorkers();
         workers = workers.concat(response.workers);
       }
     } catch (e) {
       // Something went wrong, maybe our client is disconnected?
     }
 
     const result = {
       service: [],
@@ -93,40 +90,40 @@ const RootFront = protocol.FrontClassWit
         scope: form.scope,
         fetch: form.fetch,
         registrationActor: form.actor,
         active: form.active,
         lastUpdateTime: form.lastUpdateTime,
       });
     });
 
-    workers.forEach(form => {
+    workers.forEach(front => {
       const worker = {
-        name: form.url,
-        url: form.url,
-        workerTargetActor: form.actor,
+        name: front.url,
+        url: front.url,
+        workerTargetFront: front,
       };
-      switch (form.type) {
+      switch (front.type) {
         case Ci.nsIWorkerDebugger.TYPE_SERVICE:
-          const registration = result.service.find(r => r.scope === form.scope);
+          const registration = result.service.find(r => r.scope === front.scope);
           if (registration) {
             // XXX: Race, sometimes a ServiceWorkerRegistrationInfo doesn't
             // have a scriptSpec, but its associated WorkerDebugger does.
             if (!registration.url) {
-              registration.name = registration.url = form.url;
+              registration.name = registration.url = front.url;
             }
-            registration.workerTargetActor = form.actor;
+            registration.workerTargetFront = front;
           } else {
-            worker.fetch = form.fetch;
+            worker.fetch = front.fetch;
 
             // If a service worker registration could not be found, this means we are in
             // e10s, and registrations are not forwarded to other processes until they
             // reach the activated state. Augment the worker as a registration worker to
             // display it in aboutdebugging.
-            worker.scope = form.scope;
+            worker.scope = front.scope;
             worker.active = false;
             result.service.push(worker);
           }
           break;
         case Ci.nsIWorkerDebugger.TYPE_SHARED:
           result.shared.push(worker);
           break;
         default:
@@ -142,16 +139,43 @@ const RootFront = protocol.FrontClassWit
    *
    * `getProcess` requests allows to fetch the target actor for any process
    * and the main process is having the process ID zero.
    */
   getMainProcess() {
     return this.getProcess(0);
   },
 
+  getProcess: custom(async function(id) {
+    // Do not use specification automatic marshalling as getProcess may return
+    // two different type: ParentProcessTargetActor or ContentProcessTargetActor.
+    // Also, we do want to memoize the fronts and return already existing ones.
+    const { form } = await this._getProcess(id);
+    let front = this.actor(form.actor);
+    if (front) {
+      return front;
+    }
+    // getProcess may return a ContentProcessTargetActor or a ParentProcessTargetActor
+    // In most cases getProcess(0) will return the main process target actor,
+    // which is a ParentProcessTargetActor, but not in xpcshell, which uses a
+    // ContentProcessTargetActor. So select the right front based on the actor ID.
+    if (form.actor.includes("contentProcessTarget")) {
+      front = new ContentProcessTargetFront(this._client, form);
+    } else {
+      // ParentProcessTargetActor doesn't have a specific front, instead it uses
+      // BrowsingContextTargetFront on the client side.
+      front = new BrowsingContextTargetFront(this._client, form);
+    }
+    this.manage(front);
+
+    return front;
+  }, {
+    impl: "_getProcess",
+  }),
+
   /**
    * Fetch the target actor for the currently selected tab, or for a specific
    * tab given as first parameter.
    *
    * @param [optional] object filter
    *        A dictionary object with following optional attributes:
    *         - outerWindowID: used to match tabs in parent process
    *         - tabId: used to match tabs in child processes
@@ -186,25 +210,16 @@ const RootFront = protocol.FrontClassWit
       }
     }
 
     return this._getTab(packet);
   }, {
     impl: "_getTab",
   }),
 
-  attachContentProcessTarget: async function(form) {
-    let front = this.actor(form.actor);
-    if (!front) {
-      front = new ContentProcessTargetFront(this._client, form);
-      this.manage(front);
-    }
-    return front;
-  },
-
   /**
    * Test request that returns the object passed as first argument.
    *
    * `echo` is special as all the property of the given object have to be passed
    * on the packet object. That's not something that can be achieve by requester helper.
    */
 
   echo(packet) {
--- a/devtools/shared/fronts/targets/browsing-context.js
+++ b/devtools/shared/fronts/targets/browsing-context.js
@@ -19,16 +19,20 @@ protocol.FrontClassWithSpec(browsingCont
     // Cache the value of some target properties that are being returned by `attach`
     // request and then keep them up-to-date in `reconfigure` request.
     this.configureOptions = {
       javascriptEnabled: null,
     };
 
     // TODO: remove once ThreadClient becomes a front
     this.client = client;
+
+    // Save the full form for Target class usage
+    // Do not use `form` name to avoid colliding with protocol.js's `form` method
+    this.targetForm = form;
   },
 
   /**
    * Attach to a thread actor.
    *
    * @param object options
    *        Configuration options.
    *        - useSourceMaps: whether to use source maps or not.
@@ -92,15 +96,11 @@ protocol.FrontClassWithSpec(browsingCont
     }
 
     this.destroy();
 
     return response;
   }, {
     impl: "_detach",
   }),
-
-  attachWorker: function(workerTargetActor) {
-    return this.client.attachWorker(workerTargetActor);
-  },
 });
 
 exports.BrowsingContextTargetFront = BrowsingContextTargetFront;
--- a/devtools/shared/fronts/targets/content-process.js
+++ b/devtools/shared/fronts/targets/content-process.js
@@ -8,16 +8,20 @@ const protocol = require("devtools/share
 
 const ContentProcessTargetFront = protocol.FrontClassWithSpec(contentProcessTargetSpec, {
   initialize: function(client, form) {
     protocol.Front.prototype.initialize.call(this, client, form);
 
     this.client = client;
     this.chromeDebugger = form.chromeDebugger;
 
+    // Save the full form for Target class usage
+    // Do not use `form` name to avoid colliding with protocol.js's `form` method
+    this.targetForm = form;
+
     this.traits = {};
   },
 
   attachThread() {
     return this.client.attachThread(this.chromeDebugger);
   },
 
   reconfigure: function() {
--- a/devtools/shared/fronts/targets/worker.js
+++ b/devtools/shared/fronts/targets/worker.js
@@ -5,31 +5,43 @@
 
 const {workerTargetSpec} = require("devtools/shared/specs/targets/worker");
 const protocol = require("devtools/shared/protocol");
 const {custom} = protocol;
 
 loader.lazyRequireGetter(this, "ThreadClient", "devtools/shared/client/thread-client");
 
 const WorkerTargetFront = protocol.FrontClassWithSpec(workerTargetSpec, {
-  initialize: function(client, form) {
-    protocol.Front.prototype.initialize.call(this, client, form);
+  initialize: function(client) {
+    protocol.Front.prototype.initialize.call(this, client);
 
     this.thread = null;
     this.traits = {};
 
     // TODO: remove once ThreadClient becomes a front
     this.client = client;
 
     this._isClosed = false;
 
     this.destroy = this.destroy.bind(this);
     this.on("close", this.destroy);
   },
 
+  form(json) {
+    this.actorID = json.actor;
+
+    // Save the full form for Target class usage.
+    // Do not use `form` name to avoid colliding with protocol.js's `form` method
+    this.targetForm = json;
+    this.url = json.url;
+    this.type = json.type;
+    this.scope = json.scope;
+    this.fetch = json.fetch;
+  },
+
   get isClosed() {
     return this._isClosed;
   },
 
   destroy: function() {
     this.off("close", this.destroy);
     this._isClosed = true;
 
@@ -45,17 +57,18 @@ const WorkerTargetFront = protocol.Front
   attach: custom(async function() {
     const response = await this._attach();
 
     this.url = response.url;
 
     // Immediately call `connect` in other to fetch console and thread actors
     // that will be later used by Target.
     const connectResponse = await this.connect({});
-    this.consoleActor = connectResponse.consoleActor;
+    // Set the console actor ID on the form to expose it to Target.attach's attachConsole
+    this.targetForm.consoleActor = connectResponse.consoleActor;
     this.threadActor = connectResponse.threadActor;
 
     return response;
   }, {
     impl: "_attach",
   }),
 
   detach: custom(async function() {
@@ -80,17 +93,17 @@ const WorkerTargetFront = protocol.Front
     return Promise.resolve();
   },
 
   attachThread: async function(options = {}) {
     if (this.thread) {
       const response = [{
         type: "connected",
         threadActor: this.thread._actor,
-        consoleActor: this.consoleActor,
+        consoleActor: this.targetForm.consoleActor,
       }, this.thread];
       return response;
     }
 
     const attachResponse = await this.client.request({
       to: this.threadActor,
       type: "attach",
       options,
--- a/devtools/shared/protocol.js
+++ b/devtools/shared/protocol.js
@@ -867,17 +867,16 @@ Pool.prototype = extend(EventEmitter.pro
       // TODO: not all actors have been moved to protocol.js, so they do not all have
       // a parent field. Remove the check for the parent once the conversion is finished
       const parent = this.poolFor(actor.actorID);
       if (parent) {
         parent.unmanage(actor);
       }
     }
     this._poolMap.set(actor.actorID, actor);
-    return actor;
   },
 
   /**
    * Remove an actor as a child of this pool.
    */
   unmanage: function(actor) {
     this.__poolMap && this.__poolMap.delete(actor.actorID);
   },
@@ -1281,16 +1280,20 @@ exports.ActorClassWithSpec = ActorClassW
  * @param optional form
  *   The json form provided by the server.
  * @constructor
  */
 var Front = function(conn = null, form = null, detail = null, context = null) {
   Pool.call(this, conn);
   this._requests = [];
 
+  // Front listener functions registered via `onFront` get notified
+  // of new fronts via this dedicated EventEmitter object.
+  this._frontListeners = new EventEmitter();
+
   // protocol.js no longer uses this data in the constructor, only external
   // uses do.  External usage of manually-constructed fronts will be
   // drastically reduced if we convert the root and target actors to
   // protocol.js, in which case this can probably go away.
   if (form) {
     this.actorID = form.actor;
     form = types.getType(this.typeName).formType(detail).read(form, this, detail);
     this.form(form, detail, context);
@@ -1310,24 +1313,41 @@ Front.prototype = extend(Pool.prototype,
       const { deferred, to, type, stack } = this._requests.shift();
       const msg = "Connection closed, pending request to " + to +
                 ", type " + type + " failed" +
                 "\n\nRequest stack:\n" + stack.formattedStack;
       deferred.reject(new Error(msg));
     }
     Pool.prototype.destroy.call(this);
     this.actorID = null;
+    this._frontListeners = null;
   },
 
   manage: function(front) {
     if (!front.actorID) {
       throw new Error("Can't manage front without an actor ID.\n" +
                       "Ensure server supports " + front.typeName + ".");
     }
-    return Pool.prototype.manage.call(this, front);
+    Pool.prototype.manage.call(this, front);
+
+    // Call listeners registered via `onFront` method
+    this._frontListeners.emit(front.typeName, front);
+  },
+
+  // Run callback on every front of this type that currently exists, and on every
+  // instantiation of front type in the future.
+  onFront(typeName, callback) {
+    // First fire the callback on already instantiated fronts
+    for (const front of this.poolChildren()) {
+      if (front.typeName == typeName) {
+        callback(front);
+      }
+    }
+    // Then register the callback for fronts instantiated in the future
+    this._frontListeners.on(typeName, callback);
   },
 
   toString: function() {
     return "[Front for " + this.typeName + "/" + this.actorID + "]";
   },
 
   /**
    * Update the actor from its representation.
--- a/devtools/shared/specs/index.js
+++ b/devtools/shared/specs/index.js
@@ -267,17 +267,17 @@ const Types = exports.__TypesForTests = 
   {
     types: ["webExtensionTarget"],
     spec: "devtools/shared/specs/targets/webextension",
     front: null,
   },
   {
     types: ["workerTarget"],
     spec: "devtools/shared/specs/targets/worker",
-    front: null,
+    front: "devtools/shared/fronts/targets/worker",
   },
   {
     types: ["audionode", "webaudio"],
     spec: "devtools/shared/specs/webaudio",
     front: "devtools/shared/fronts/webaudio",
   },
   {
     types: ["console"],
--- a/devtools/shared/specs/root.js
+++ b/devtools/shared/specs/root.js
@@ -10,17 +10,17 @@ types.addDictType("root.getTab", {
 });
 types.addDictType("root.getWindow", {
   window: "json",
 });
 types.addDictType("root.listAddons", {
   addons: "array:json",
 });
 types.addDictType("root.listWorkers", {
-  workers: "array:json",
+  workers: "array:workerTarget",
 });
 types.addDictType("root.listServiceWorkerRegistrations", {
   registrations: "array:json",
 });
 types.addDictType("root.listProcesses", {
   processes: "array:json",
 });
 
--- a/devtools/shared/specs/targets/browsing-context.js
+++ b/devtools/shared/specs/targets/browsing-context.js
@@ -32,17 +32,17 @@ types.addDictType("browsingContextTarget
   parentID: "nullable:string",
   url: "nullable:string", // should be present if not destroying
   title: "nullable:string", // should be present if not destroying
   destroy: "nullable:boolean", // not present if not destroying
 });
 
 types.addDictType("browsingContextTarget.workers", {
   error: "nullable:string",
-  workers: "nullable:array:json",
+  workers: "nullable:array:workerTarget",
 });
 
 types.addDictType("browsingContextTarget.reload", {
   force: "boolean",
 });
 
 types.addDictType("browsingContextTarget.reconfigure", {
   javascriptEnabled: "nullable:boolean",
--- a/devtools/shared/specs/targets/content-process.js
+++ b/devtools/shared/specs/targets/content-process.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 {types, Option, RetVal, generateActorSpec} = require("devtools/shared/protocol");
 
 types.addDictType("contentProcessTarget.workers", {
   error: "nullable:string",
-  workers: "nullable:array:json",
+  workers: "nullable:array:workerTarget",
 });
 
 const contentProcessTargetSpec = generateActorSpec({
   typeName: "contentProcessTarget",
 
   methods: {
     listWorkers: {
       request: {},
@@ -22,12 +22,15 @@ const contentProcessTargetSpec = generat
 
   events: {
     // newSource is being sent by ThreadActor in the name of its parent,
     // i.e. ContentProcessTargetActor
     newSource: {
       type: "newSource",
       source: Option(0, "json"),
     },
+    workerListChanged: {
+      type: "workerListChanged",
+    },
   },
 });
 
 exports.contentProcessTargetSpec = contentProcessTargetSpec;
--- a/devtools/shared/webconsole/test/common.js
+++ b/devtools/shared/webconsole/test/common.js
@@ -73,19 +73,19 @@ var _attachConsole = async function(
   if (response.error) {
     console.error("client.connect() failed: " + response.error + " " +
                   response.message);
     callback(state, response);
     return;
   }
 
   if (!attachToTab) {
-    response = await state.dbgClient.mainRoot.getMainProcess();
-    await state.dbgClient.attachTarget(response.form.actor);
-    const consoleActor = response.form.consoleActor;
+    const front = await state.dbgClient.mainRoot.getMainProcess();
+    await front.attach();
+    const consoleActor = front.targetForm.consoleActor;
     state.actor = consoleActor;
     state.dbgClient.attachConsole(consoleActor, listeners)
       .then(_onAttachConsole.bind(null, state), _onAttachError.bind(null, state));
     return;
   }
   response = await state.dbgClient.listTabs();
   if (response.error) {
     console.error("listTabs failed: " + response.error + " " +
@@ -100,32 +100,25 @@ var _attachConsole = async function(
     const worker = new Worker(workerName);
     // Keep a strong reference to the Worker to avoid it being
     // GCd during the test (bug 1237492).
     // eslint-disable-next-line camelcase
     state._worker_ref = worker;
     await waitForMessage(worker);
 
     const { workers } = await targetFront.listWorkers();
-    const workerTargetActor = workers.filter(w => w.url == workerName)[0].actor;
-    if (!workerTargetActor) {
-      console.error("listWorkers failed. Unable to find the " +
-                    "worker actor\n");
+    const workerTargetFront = workers.filter(w => w.url == workerName)[0];
+    if (!workerTargetFront) {
+      console.error("listWorkers failed. Unable to find the worker actor\n");
       return;
     }
-    const [workerResponse, workerTargetFront] =
-      await targetFront.attachWorker(workerTargetActor);
-    if (!workerTargetFront || workerResponse.error) {
-      console.error("attachWorker failed. No worker target front or " +
-                    " error: " + workerResponse.error);
-      return;
-    }
+    await workerTargetFront.attach();
     await workerTargetFront.attachThread({});
-    state.actor = workerTargetFront.consoleActor;
-    state.dbgClient.attachConsole(workerTargetFront.consoleActor, listeners)
+    state.actor = workerTargetFront.targetForm.consoleActor;
+    state.dbgClient.attachConsole(workerTargetFront.targetForm.consoleActor, listeners)
       .then(_onAttachConsole.bind(null, state), _onAttachError.bind(null, state));
   } else {
     state.actor = tab.consoleActor;
     state.dbgClient.attachConsole(tab.consoleActor, listeners)
       .then(_onAttachConsole.bind(null, state), _onAttachError.bind(null, state));
   }
 };
 
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -1507,16 +1507,24 @@ NS_IMETHODIMP
 nsDocShell::GetHasForeignCookiesBeenBlocked(bool* aHasForeignCookiesBeenBlocked)
 {
   nsCOMPtr<nsIDocument> doc(GetDocument());
   *aHasForeignCookiesBeenBlocked = doc && doc->GetHasForeignCookiesBlocked();
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsDocShell::GetHasCookiesLoaded(bool* aHasCookiesLoaded)
+{
+  nsCOMPtr<nsIDocument> doc(GetDocument());
+  *aHasCookiesLoaded = doc && doc->GetHasCookiesLoaded();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsDocShell::GetAllowPlugins(bool* aAllowPlugins)
 {
   NS_ENSURE_ARG_POINTER(aAllowPlugins);
 
   *aAllowPlugins = mAllowPlugins;
   return NS_OK;
 }
 
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -644,16 +644,22 @@ interface nsIDocShell : nsIDocShellTreeI
    [infallible] readonly attribute boolean hasAllCookiesBeenBlocked;
 
    /**
    * This attribute determines whether a document seen cookies or storage
    * blocked due to cookie behavior settings blocking all third-party cookies.
    */
    [infallible] readonly attribute boolean hasForeignCookiesBeenBlocked;
 
+   /**
+   * This attribute determines whether a document seen cookies or storage
+   * attempts ever whether they've been allowed or blocked.
+   */
+   [infallible] readonly attribute boolean hasCookiesLoaded;
+
   /**
    * Disconnects this docshell's editor from its window, and stores the
    * editor data in the open document's session history entry.  This
    * should be called only during page transitions.
    */
   [noscript, notxpcom] void DetachEditorFromWindow();
 
   /**
--- a/dom/base/ContentBlockingLog.h
+++ b/dom/base/ContentBlockingLog.h
@@ -3,17 +3,19 @@
 /* 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 mozilla_dom_ContentBlockingLog_h
 #define mozilla_dom_ContentBlockingLog_h
 
 #include "mozilla/JSONWriter.h"
+#include "mozilla/Maybe.h"
 #include "mozilla/StaticPrefs.h"
+#include "mozilla/Tuple.h"
 #include "mozilla/UniquePtr.h"
 #include "nsClassHashtable.h"
 #include "nsHashKeys.h"
 #include "nsReadableUtils.h"
 #include "nsTArray.h"
 #include "nsWindowSizes.h"
 
 namespace mozilla {
@@ -25,17 +27,17 @@ class ContentBlockingLog final
     uint32_t mType;
     uint32_t mRepeatCount;
     bool mBlocked;
   };
 
   // Each element is a tuple of (type, blocked, repeatCount). The type values
   // come from the blocking types defined in nsIWebProgressListener.
   typedef nsTArray<LogEntry> OriginLog;
-  typedef Pair<bool, OriginLog> OriginData;
+  typedef Tuple<bool, Maybe<bool>, OriginLog> OriginData;
   typedef nsClassHashtable<nsStringHashKey, OriginData> OriginDataHashTable;
 
   struct StringWriteFunc : public JSONWriteFunc
   {
     nsAString& mBuffer; // The lifetime of the struct must be bound to the buffer
     explicit StringWriteFunc(nsAString& aBuffer)
       : mBuffer(aBuffer)
     {}
@@ -54,20 +56,28 @@ public:
   {
     if (aOrigin.IsVoid()) {
       return;
     }
     auto entry = mLog.LookupForAdd(aOrigin);
     if (entry) {
       auto& data = entry.Data();
       if (aType == nsIWebProgressListener::STATE_LOADED_TRACKING_CONTENT) {
-        data->first() = aBlocked;
+        Get<0>(*data) = aBlocked;
         return;
       }
-      auto& log = data->second();
+      if (aType == nsIWebProgressListener::STATE_COOKIES_LOADED) {
+        if (Get<1>(*data).isSome()) {
+          Get<1>(*data).ref() = aBlocked;
+        } else {
+          Get<1>(*data).emplace(aBlocked);
+        }
+        return;
+      }
+      auto& log = Get<2>(*data);
       if (!log.IsEmpty()) {
         auto& last = log.LastElement();
         if (last.mType == aType &&
             last.mBlocked == aBlocked) {
           ++last.mRepeatCount;
           // Don't record recorded events.  This helps compress our log.
           return;
         }
@@ -75,21 +85,27 @@ public:
       if (log.Length() ==
             std::max(1u, StaticPrefs::browser_contentblocking_originlog_length())) {
         // Cap the size at the maximum length adjustable by the pref
         log.RemoveElementAt(0);
       }
       log.AppendElement(LogEntry{aType, 1u, aBlocked});
     } else {
       entry.OrInsert([=] {
-        nsAutoPtr<OriginData> data(new OriginData(false, OriginLog()));
+        nsAutoPtr<OriginData> data(new OriginData(false, Maybe<bool>(), OriginLog()));
         if (aType == nsIWebProgressListener::STATE_LOADED_TRACKING_CONTENT) {
-          data->first() = true;
+          Get<0>(*data) = aBlocked;
+        } else if (aType == nsIWebProgressListener::STATE_COOKIES_LOADED) {
+          if (Get<1>(*data).isSome()) {
+            Get<1>(*data).ref() = aBlocked;
+          } else {
+            Get<1>(*data).emplace(aBlocked);
+          }
         } else {
-          data->second().AppendElement(LogEntry{aType, 1u, aBlocked});
+          Get<2>(*data).AppendElement(LogEntry{aType, 1u, aBlocked});
         }
         return data.forget();
       });
     }
   }
 
   nsAutoString Stringify()
   {
@@ -102,26 +118,35 @@ public:
       if (!iter.UserData()) {
         w.StartArrayProperty(NS_ConvertUTF16toUTF8(iter.Key()).get(), w.SingleLineStyle);
         w.EndArray();
         continue;
       }
 
       w.StartArrayProperty(NS_ConvertUTF16toUTF8(iter.Key()).get(), w.SingleLineStyle);
       auto& data = *iter.UserData();
-      if (data.first()) {
+      if (Get<0>(data)) {
         w.StartArrayElement(w.SingleLineStyle);
         {
           w.IntElement(nsIWebProgressListener::STATE_LOADED_TRACKING_CONTENT);
           w.BoolElement(true); // blocked
           w.IntElement(1);     // repeat count
         }
         w.EndArray();
       }
-      for (auto& item: data.second()) {
+      if (Get<1>(data).isSome()) {
+        w.StartArrayElement(w.SingleLineStyle);
+        {
+          w.IntElement(nsIWebProgressListener::STATE_COOKIES_LOADED);
+          w.BoolElement(Get<1>(data).value()); // blocked
+          w.IntElement(1);                     // repeat count
+        }
+        w.EndArray();
+      }
+      for (auto& item: Get<2>(data)) {
         w.StartArrayElement(w.SingleLineStyle);
         {
           w.IntElement(item.mType);
           w.BoolElement(item.mBlocked);
           w.IntElement(item.mRepeatCount);
         }
         w.EndArray();
       }
@@ -136,21 +161,26 @@ public:
   bool HasBlockedAnyOfType(uint32_t aType)
   {
     for (auto iter = mLog.Iter(); !iter.Done(); iter.Next()) {
       if (!iter.UserData()) {
         continue;
       }
 
       if (aType == nsIWebProgressListener::STATE_LOADED_TRACKING_CONTENT) {
-        if (iter.UserData()->first()) {
+        if (Get<0>(*iter.UserData())) {
           return true;
         }
+      } else if (aType == nsIWebProgressListener::STATE_COOKIES_LOADED) {
+        if (Get<1>(*iter.UserData()).isSome()) {
+          return Get<1>(*iter.UserData()).value();
+        }
+        return true; // true means blocked, aka not loaded any cookies
       } else {
-        for (auto& item: iter.UserData()->second()) {
+        for (auto& item: Get<2>(*iter.UserData())) {
           if ((item.mType & aType) != 0) {
             return true;
           }
         }
       }
     }
     return false;
   }
@@ -161,17 +191,17 @@ public:
 
     // Now add the sizes of each origin log queue.
     // The const_cast is needed because the nsTHashtable::Iterator interface is
     // not const-safe.  :-(
     for (auto iter = const_cast<OriginDataHashTable&>(mLog).Iter();
          !iter.Done(); iter.Next()) {
       if (iter.UserData()) {
         aSizes.mDOMOtherSize +=
-          iter.UserData()->second().ShallowSizeOfIncludingThis(aSizes.mState.mMallocSizeOf);
+          Get<2>(*iter.UserData()).ShallowSizeOfIncludingThis(aSizes.mState.mMallocSizeOf);
       }
     }
   }
 
 private:
   OriginDataHashTable mLog;
 };
 
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -539,26 +539,27 @@ Navigator::CookieEnabled()
 
   if (!codebaseURI) {
     // Not a codebase, so technically can't set cookies, but let's
     // just return the default value.
     return cookieEnabled;
   }
 
   uint32_t rejectedReason = 0;
-  if (AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(mWindow,
-                                                              codebaseURI,
-                                                              &rejectedReason)) {
-    return true;
-  }
+  bool granted =
+    AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(mWindow,
+                                                            codebaseURI,
+                                                            &rejectedReason);
 
-  if (rejectedReason) {
-    AntiTrackingCommon::NotifyRejection(mWindow, rejectedReason);
-  }
-  return false;
+  AntiTrackingCommon::NotifyBlockingDecision(mWindow,
+                                             granted ?
+                                               AntiTrackingCommon::BlockingDecision::eAllow :
+                                               AntiTrackingCommon::BlockingDecision::eBlock,
+                                             rejectedReason);
+  return granted;
 }
 
 bool
 Navigator::OnLine()
 {
   return !NS_IsOffline();
 }
 
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -8951,21 +8951,29 @@ nsContentUtils::StorageDisabledByAntiTra
                                               nsIChannel* aChannel,
                                               nsIPrincipal* aPrincipal,
                                               nsIURI* aURI)
 {
   uint32_t rejectedReason = 0;
   bool disabled =
     StorageDisabledByAntiTrackingInternal(aWindow, aChannel, aPrincipal, aURI,
                                           &rejectedReason);
-  if (disabled && sAntiTrackingControlCenterUIEnabled && rejectedReason) {
+  if (sAntiTrackingControlCenterUIEnabled) {
     if (aWindow) {
-      AntiTrackingCommon::NotifyRejection(aWindow, rejectedReason);
+      AntiTrackingCommon::NotifyBlockingDecision(aWindow,
+                                                 disabled ?
+                                                   AntiTrackingCommon::BlockingDecision::eBlock :
+                                                   AntiTrackingCommon::BlockingDecision::eAllow,
+                                                 rejectedReason);
     } else if (aChannel) {
-      AntiTrackingCommon::NotifyRejection(aChannel, rejectedReason);
+      AntiTrackingCommon::NotifyBlockingDecision(aChannel,
+                                                 disabled ?
+                                                   AntiTrackingCommon::BlockingDecision::eBlock :
+                                                   AntiTrackingCommon::BlockingDecision::eAllow,
+                                                 rejectedReason);
     }
   }
   return disabled;
 }
 
 // static, private
 nsContentUtils::StorageAccess
 nsContentUtils::InternalStorageAllowedForPrincipal(nsIPrincipal* aPrincipal,
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -5370,16 +5370,24 @@ nsGlobalWindowOuter::NotifyContentBlocki
     if (!aBlocked) {
       unblocked = !doc->GetHasAllCookiesBlocked();
     }
   } else if (aState == nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN) {
     doc->SetHasForeignCookiesBlocked(aBlocked, origin);
     if (!aBlocked) {
       unblocked = !doc->GetHasForeignCookiesBlocked();
     }
+  } else if (aState == nsIWebProgressListener::STATE_COOKIES_LOADED) {
+    MOZ_ASSERT(!aBlocked, "We don't expected to see blocked STATE_COOKIES_LOADED");
+    // Note that the logic in this branch is the logical negation of the logic
+    // in other branches, since the nsIDocument API we have is phrased in
+    // "loaded" terms as opposed to "blocked" terms.
+    doc->SetHasCookiesLoaded(!aBlocked, origin);
+    aBlocked = true;
+    unblocked = false;
   } else {
     // Ignore nsIWebProgressListener::STATE_BLOCKED_UNSAFE_CONTENT;
   }
   const uint32_t oldState = state;
   if (aBlocked) {
     state |= aState;
   } else if (unblocked) {
     state &= ~aState;
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -1107,16 +1107,36 @@ public:
                                         const nsAString& aOriginBlocked)
   {
     RecordContentBlockingLog(aOriginBlocked,
                              nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION,
                              aHasCookiesBlockedByPermission);
   }
 
   /**
+   * Set the cookies loaded flag for this document.
+   */
+  void SetHasCookiesLoaded(bool aHasCookiesLoaded,
+                           const nsAString& aOriginLoaded)
+  {
+    RecordContentBlockingLog(aOriginLoaded,
+                             nsIWebProgressListener::STATE_COOKIES_LOADED,
+                             aHasCookiesLoaded);
+  }
+
+  /**
+   * Get cookies loaded flag for this document.
+   */
+  bool GetHasCookiesLoaded()
+  {
+    return mContentBlockingLog.HasBlockedAnyOfType(
+        nsIWebProgressListener::STATE_COOKIES_LOADED);
+  }
+
+  /**
    * Get tracking content loaded flag for this document.
    */
   bool GetHasTrackingContentLoaded()
   {
     return mContentBlockingLog.HasBlockedAnyOfType(
         nsIWebProgressListener::STATE_LOADED_TRACKING_CONTENT);
   }
 
--- a/dom/clients/manager/ClientManagerService.cpp
+++ b/dom/clients/manager/ClientManagerService.cpp
@@ -13,17 +13,19 @@
 #include "ClientPrincipalUtils.h"
 #include "ClientSourceParent.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/ServiceWorkerManager.h"
 #include "mozilla/dom/ServiceWorkerUtils.h"
 #include "mozilla/ipc/BackgroundParent.h"
 #include "mozilla/ipc/PBackgroundSharedTypes.h"
 #include "mozilla/ClearOnShutdown.h"
+#include "mozilla/MozPromise.h"
 #include "mozilla/SystemGroup.h"
+#include "jsfriendapi.h"
 #include "nsIAsyncShutdown.h"
 #include "nsIXULRuntime.h"
 #include "nsProxyRelease.h"
 
 namespace mozilla {
 namespace dom {
 
 using mozilla::ipc::AssertIsOnBackgroundThread;
@@ -538,21 +540,44 @@ ClientManagerService::Claim(const Client
   promiseList->MaybeFinish();
 
   return promiseList->GetResultPromise();
 }
 
 RefPtr<ClientOpPromise>
 ClientManagerService::GetInfoAndState(const ClientGetInfoAndStateArgs& aArgs)
 {
-  RefPtr<ClientOpPromise> ref;
+  ClientSourceParent* source = FindSource(aArgs.id(), aArgs.principalInfo());
+
+  if (!source) {
+    RefPtr<ClientOpPromise> ref =
+      ClientOpPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+    return ref.forget();
+  }
+
+  if (!source->ExecutionReady()) {
+    RefPtr<ClientManagerService> self = this;
 
-  ClientSourceParent* source = FindSource(aArgs.id(), aArgs.principalInfo());
-  if (!source || !source->ExecutionReady()) {
-    ref = ClientOpPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+    // rejection ultimately converted to `undefined` in Clients::Get
+    RefPtr<ClientOpPromise> ref =
+      source->ExecutionReadyPromise()
+            ->Then(GetCurrentThreadSerialEventTarget(), __func__,
+                   [self, aArgs] () -> RefPtr<ClientOpPromise> {
+                      ClientSourceParent* source = self->FindSource(aArgs.id(),
+                                                                    aArgs.principalInfo());
+
+                      if (!source) {
+                        RefPtr<ClientOpPromise> ref =
+                          ClientOpPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+                        return ref.forget();
+                      }
+
+                      return source->StartOp(aArgs);
+                   });
+
     return ref.forget();
   }
 
   return source->StartOp(aArgs);
 }
 
 namespace {
 
--- a/dom/clients/manager/ClientSourceParent.cpp
+++ b/dom/clients/manager/ClientSourceParent.cpp
@@ -113,16 +113,18 @@ ClientSourceParent::RecvExecutionReady(c
   mClientInfo.SetURL(aArgs.url());
   mClientInfo.SetFrameType(aArgs.frameType());
   mExecutionReady = true;
 
   for (ClientHandleParent* handle : mHandleList) {
     Unused << handle->SendExecutionReady(mClientInfo.ToIPC());
   }
 
+  mExecutionReadyPromise.ResolveIfExists(true, __func__);
+
   return IPC_OK();
 };
 
 IPCResult
 ClientSourceParent::RecvFreeze()
 {
   MOZ_DIAGNOSTIC_ASSERT(!mFrozen);
   mFrozen = true;
@@ -223,16 +225,18 @@ ClientSourceParent::ClientSourceParent(c
   , mExecutionReady(false)
   , mFrozen(false)
 {
 }
 
 ClientSourceParent::~ClientSourceParent()
 {
   MOZ_DIAGNOSTIC_ASSERT(mHandleList.IsEmpty());
+
+  mExecutionReadyPromise.RejectIfExists(NS_ERROR_FAILURE, __func__);
 }
 
 void
 ClientSourceParent::Init()
 {
   // Ensure the principal is reasonable before adding ourself to the service.
   // Since we validate the principal on the child side as well, any failure
   // here is treated as fatal.
@@ -263,16 +267,25 @@ ClientSourceParent::IsFrozen() const
 }
 
 bool
 ClientSourceParent::ExecutionReady() const
 {
   return mExecutionReady;
 }
 
+RefPtr<GenericPromise>
+ClientSourceParent::ExecutionReadyPromise()
+{
+  // Only call if ClientSourceParent::ExecutionReady() is false; otherwise,
+  // the promise will never resolve
+  MOZ_ASSERT(!mExecutionReady);
+  return mExecutionReadyPromise.Ensure(__func__);
+}
+
 const Maybe<ServiceWorkerDescriptor>&
 ClientSourceParent::GetController() const
 {
   return mController;
 }
 
 void
 ClientSourceParent::ClearController()
--- a/dom/clients/manager/ClientSourceParent.h
+++ b/dom/clients/manager/ClientSourceParent.h
@@ -5,29 +5,31 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #ifndef _mozilla_dom_ClientSourceParent_h
 #define _mozilla_dom_ClientSourceParent_h
 
 #include "ClientInfo.h"
 #include "ClientOpPromise.h"
 #include "mozilla/dom/PClientSourceParent.h"
 #include "mozilla/dom/ServiceWorkerDescriptor.h"
+#include "mozilla/MozPromise.h"
 
 namespace mozilla {
 namespace dom {
 
 class ClientHandleParent;
 class ClientManagerService;
 
 class ClientSourceParent final : public PClientSourceParent
 {
   ClientInfo mClientInfo;
   Maybe<ServiceWorkerDescriptor> mController;
   RefPtr<ClientManagerService> mService;
   nsTArray<ClientHandleParent*> mHandleList;
+  MozPromiseHolder<GenericPromise> mExecutionReadyPromise;
   bool mExecutionReady;
   bool mFrozen;
 
   void
   KillInvalidChild();
 
   // PClientSourceParent
   mozilla::ipc::IPCResult
@@ -71,16 +73,19 @@ public:
   Info() const;
 
   bool
   IsFrozen() const;
 
   bool
   ExecutionReady() const;
 
+  RefPtr<GenericPromise>
+  ExecutionReadyPromise();
+
   const Maybe<ServiceWorkerDescriptor>&
   GetController() const;
 
   void
   ClearController();
 
   void
   AttachHandle(ClientHandleParent* aClientSource);
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -839,16 +839,19 @@ EventStateManager::PreHandleEvent(nsPres
       WidgetCompositionEvent* compositionEvent = aEvent->AsCompositionEvent();
       WidgetQueryContentEvent selectedText(true, eQuerySelectedText,
                                            compositionEvent->mWidget);
       HandleQueryContentEvent(&selectedText);
       NS_ASSERTION(selectedText.mSucceeded, "Failed to get selected text");
       compositionEvent->mData = selectedText.mReply.mString;
     }
     break;
+  case eTouchStart:
+    SetGestureDownPoint(aEvent->AsTouchEvent());
+    break;
   case eTouchEnd:
     NotifyTargetUserActivation(aEvent, aTargetContent);
     break;
   default:
     break;
   }
   return NS_OK;
 }
@@ -1683,18 +1686,17 @@ EventStateManager::BeginTrackingDragGest
                                             nsIFrame* inDownFrame)
 {
   if (!inDownEvent->mWidget) {
     return;
   }
 
   // Note that |inDownEvent| could be either a mouse down event or a
   // synthesized mouse move event.
-  mGestureDownPoint =
-    inDownEvent->mRefPoint + inDownEvent->mWidget->WidgetToScreenOffset();
+  SetGestureDownPoint(inDownEvent);
 
   if (inDownFrame) {
     inDownFrame->GetContentForEvent(inDownEvent,
                                     getter_AddRefs(mGestureDownContent));
 
     mGestureDownFrameOwner = inDownFrame->GetContent();
     if (!mGestureDownFrameOwner) {
       mGestureDownFrameOwner = mGestureDownContent;
@@ -1705,16 +1707,31 @@ EventStateManager::BeginTrackingDragGest
 
   if (inDownEvent->mMessage != eMouseTouchDrag && Prefs::ClickHoldContextMenu()) {
     // fire off a timer to track click-hold
     CreateClickHoldTimer(aPresContext, inDownFrame, inDownEvent);
   }
 }
 
 void
+EventStateManager::SetGestureDownPoint(WidgetGUIEvent* aEvent)
+{
+  mGestureDownPoint =
+    GetEventRefPoint(aEvent) + aEvent->mWidget->WidgetToScreenOffset();
+}
+
+LayoutDeviceIntPoint
+EventStateManager::GetEventRefPoint(WidgetEvent* aEvent) const
+{
+  auto touchEvent = aEvent->AsTouchEvent();
+  return (touchEvent && !touchEvent->mTouches.IsEmpty()) ?
+    aEvent->AsTouchEvent()->mTouches[0]->mRefPoint : aEvent->mRefPoint;
+}
+
+void
 EventStateManager::BeginTrackingRemoteDragGesture(nsIContent* aContent)
 {
   mGestureDownContent = aContent;
   mGestureDownFrameOwner = aContent;
 }
 
 //
 // StopTrackingDragGesture
@@ -1803,21 +1820,18 @@ EventStateManager::IsEventOutsideDragThr
     sPixelThresholdY =
       LookAndFeel::GetInt(LookAndFeel::eIntID_DragThresholdY, 0);
     if (!sPixelThresholdX)
       sPixelThresholdX = 5;
     if (!sPixelThresholdY)
       sPixelThresholdY = 5;
   }
 
-  auto touchEvent = aEvent->AsTouchEvent();
-  LayoutDeviceIntPoint pt = aEvent->mWidget->WidgetToScreenOffset() +
-    ((touchEvent && !touchEvent->mTouches.IsEmpty())
-      ? aEvent->AsTouchEvent()->mTouches[0]->mRefPoint
-      : aEvent->mRefPoint);
+  LayoutDeviceIntPoint pt =
+    aEvent->mWidget->WidgetToScreenOffset() + GetEventRefPoint(aEvent);
   LayoutDeviceIntPoint distance = pt - mGestureDownPoint;
   return
     Abs(distance.x) > AssertedCast<uint32_t>(sPixelThresholdX) ||
     Abs(distance.y) > AssertedCast<uint32_t>(sPixelThresholdY);
 }
 
 //
 // GenerateDragGesture
--- a/dom/events/EventStateManager.h
+++ b/dom/events/EventStateManager.h
@@ -1091,16 +1091,20 @@ protected:
   void DecideGestureEvent(WidgetGestureNotifyEvent* aEvent,
                           nsIFrame* targetFrame);
 
   // routines for the d&d gesture tracking state machine
   void BeginTrackingDragGesture(nsPresContext* aPresContext,
                                 WidgetMouseEvent* aDownEvent,
                                 nsIFrame* aDownFrame);
 
+  void SetGestureDownPoint(WidgetGUIEvent* aEvent);
+
+  LayoutDeviceIntPoint GetEventRefPoint(WidgetEvent* aEvent) const;
+
   friend class mozilla::dom::TabParent;
   void BeginTrackingRemoteDragGesture(nsIContent* aContent);
   void StopTrackingDragGesture();
   void GenerateDragGesture(nsPresContext* aPresContext,
                            WidgetInputEvent* aEvent);
 
   /**
    * When starting a dnd session, UA must fire a pointercancel event and stop
--- a/dom/indexedDB/IDBFactory.cpp
+++ b/dom/indexedDB/IDBFactory.cpp
@@ -928,22 +928,26 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(IDBFactory)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IDBFactory)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTabChild)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEventTarget)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IDBFactory)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
   tmp->mOwningObject = nullptr;
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mTabChild)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mEventTarget)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(IDBFactory)
   NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mOwningObject)
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
 JSObject*
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -854,17 +854,19 @@ public:
     MOZ_ASSERT(mMaster->mLooping);
   }
 
   void Exit() override
   {
     mAudioDataRequest.DisconnectIfExists();
     mAudioSeekRequest.DisconnectIfExists();
     if (ShouldDiscardLoopedAudioData()) {
+      mMaster->mAudioDataRequest.DisconnectIfExists();
       DiscardLoopedAudioData();
+      AudioQueue().Finish();
     }
     DecodingState::Exit();
   }
 
   State GetState() const override
   {
     return DECODER_STATE_LOOPING_DECODING;
   }
@@ -890,17 +892,18 @@ public:
     // so we need to add the last ending time as the offset to correct the
     // audio data time in the next round when seamless looping is enabled.
     mAudioLoopingOffset = mMaster->mDecodedAudioEndTime;
 
     if (mMaster->mAudioDecodedDuration.isNothing()) {
       mMaster->mAudioDecodedDuration.emplace(mMaster->mDecodedAudioEndTime);
     }
 
-    SLOG("received EOS when seamless looping, starts seeking");
+    SLOG("received EOS when seamless looping, starts seeking, "
+         "AudioLoopingOffset=[%" PRId64 "]", mAudioLoopingOffset.ToMicroseconds());
     Reader()->ResetDecode(TrackInfo::kAudioTrack);
     Reader()->Seek(SeekTarget(media::TimeUnit::Zero(), SeekTarget::Accurate))
       ->Then(OwnerThread(), __func__,
               [this] () -> void {
                 mAudioSeekRequest.Complete();
                 SLOG("seeking completed, start to request first sample, "
                      "queueing audio task - queued=%zu, decoder-queued=%zu",
                      AudioQueue().GetSize(), Reader()->SizeOfAudioQueueInFrames());
@@ -950,30 +953,33 @@ private:
     return aAudio->mTime.IsValid() ?
               MediaResult(NS_OK) :
               MediaResult(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR,
                           "Audio sample overflow during looping time adjustment");
   }
 
   bool ShouldDiscardLoopedAudioData() const
   {
+    if (!mMaster->mMediaSink->IsStarted()) {
+      return false;
+    }
     /**
      * If media cancels looping, we should check whether there are audio data
      * whose time is later than EOS. If so, we should discard them because we
      * won't have a chance to play them.
      *
      *    playback                     last decoded
      *    position          EOS        data time
      *   ----|---------------|------------|---------> (Increasing timeline)
      *    mCurrent        mLooping      mMaster's
-     * PlaybackPosition    Offset      mDecodedAudioEndTime
+     *    ClockTime        Offset      mDecodedAudioEndTime
      *
      */
     return (mAudioLoopingOffset != media::TimeUnit::Zero() &&
-            mMaster->mCurrentPosition.Ref() < mAudioLoopingOffset &&
+            mMaster->GetClock() < mAudioLoopingOffset &&
             mAudioLoopingOffset < mMaster->mDecodedAudioEndTime);
   }
 
   void DiscardLoopedAudioData()
   {
     if (mAudioLoopingOffset == media::TimeUnit::Zero()) {
         return;
     }
--- a/dom/payments/test/mochitest.ini
+++ b/dom/payments/test/mochitest.ini
@@ -21,24 +21,26 @@ support-files =
   RequestShippingChromeScript.js
   RetryPaymentChromeScript.js
   ShippingOptionsChromeScript.js
   ShowPaymentChromeScript.js
   UpdateErrorsChromeScript.js
 
 [test_abortPayment.html]
 run-if = nightly_build # Bug 1390018: Depends on the Nightly-only UI service
+skip-if = debug # Bug 1507251 - Leak
 [test_basiccard.html]
 [test_basiccarderrors.html]
 [test_block_none10s.html]
 skip-if = e10s # Bug 1408250: Don't expose PaymentRequest Constructor in non-e10s
 [test_bug1478740.html]
 [test_bug1490698.html]
 [test_canMakePayment.html]
 run-if = nightly_build # Bug 1390737: Depends on the Nightly-only UI service
+skip-if = debug # Bug 1507251 - Leak
 [test_closePayment.html]
 [test_constructor.html]
 [test_currency_amount_validation.html]
 skip-if = (verify && debug)
 [test_payerDetails.html]
 [test_payment-request-in-iframe.html]
 [test_pmi_validation.html]
 skip-if = (verify && debug)
--- a/dom/serviceworkers/ServiceWorkerEvents.cpp
+++ b/dom/serviceworkers/ServiceWorkerEvents.cpp
@@ -156,16 +156,17 @@ FetchEvent::Constructor(const GlobalObje
   MOZ_ASSERT(owner);
   RefPtr<FetchEvent> e = new FetchEvent(owner);
   bool trusted = e->Init(owner);
   e->InitEvent(aType, aOptions.mBubbles, aOptions.mCancelable);
   e->SetTrusted(trusted);
   e->SetComposed(aOptions.mComposed);
   e->mRequest = aOptions.mRequest;
   e->mClientId = aOptions.mClientId;
+  e->mResultingClientId = aOptions.mResultingClientId;
   e->mIsReload = aOptions.mIsReload;
   return e.forget();
 }
 
 namespace {
 
 struct RespondWithClosure
 {
--- a/dom/serviceworkers/ServiceWorkerEvents.h
+++ b/dom/serviceworkers/ServiceWorkerEvents.h
@@ -118,16 +118,17 @@ public:
 class FetchEvent final : public ExtendableEvent
 {
   nsMainThreadPtrHandle<nsIInterceptedChannel> mChannel;
   nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> mRegistration;
   RefPtr<Request> mRequest;
   nsCString mScriptSpec;
   nsCString mPreventDefaultScriptSpec;
   nsString mClientId;
+  nsString mResultingClientId;
   uint32_t mPreventDefaultLineNumber;
   uint32_t mPreventDefaultColumnNumber;
   bool mIsReload;
   bool mWaitToRespond;
 protected:
   explicit FetchEvent(EventTarget* aOwner);
   ~FetchEvent();
 
@@ -164,16 +165,22 @@ public:
   }
 
   void
   GetClientId(nsAString& aClientId) const
   {
     aClientId = mClientId;
   }
 
+  void
+  GetResultingClientId(nsAString& aResultingClientId) const
+  {
+    aResultingClientId = mResultingClientId;
+  }
+
   bool
   IsReload() const
   {
     return mIsReload;
   }
 
   void
   RespondWith(JSContext* aCx, Promise& aArg, ErrorResult& aRv);
--- a/dom/serviceworkers/ServiceWorkerManager.cpp
+++ b/dom/serviceworkers/ServiceWorkerManager.cpp
@@ -2013,31 +2013,53 @@ public:
     nsresult status;
     rv = channel->GetStatus(&status);
     if (NS_WARN_IF(NS_FAILED(rv) || NS_FAILED(status))) {
       HandleError();
       return NS_OK;
     }
 
     nsString clientId;
+    nsString resultingClientId;
     nsCOMPtr<nsILoadInfo> loadInfo = channel->GetLoadInfo();
     if (loadInfo) {
+      char buf[NSID_LENGTH];
       Maybe<ClientInfo> clientInfo = loadInfo->GetClientInfo();
       if (clientInfo.isSome()) {
-        char buf[NSID_LENGTH];
         clientInfo.ref().Id().ToProvidedString(buf);
         NS_ConvertASCIItoUTF16 uuid(buf);
 
         // Remove {} and the null terminator
         clientId.Assign(Substring(uuid, 1, NSID_LENGTH - 3));
       }
+
+      // Having an initial or reserved client are mutually exclusive events:
+      // either an initial client is used upon navigating an about:blank
+      // iframe, or a new, reserved environment/client is created (e.g.
+      // upon a top-level navigation). See step 4 of
+      // https://html.spec.whatwg.org/#process-a-navigate-fetch as well as
+      // https://github.com/w3c/ServiceWorker/issues/1228#issuecomment-345132444
+      Maybe<ClientInfo> resulting = loadInfo->GetInitialClientInfo();
+
+      if (resulting.isNothing()) {
+        resulting = loadInfo->GetReservedClientInfo();
+      } else {
+        MOZ_ASSERT(loadInfo->GetReservedClientInfo().isNothing());
+      }
+
+      if (resulting.isSome()) {
+        resulting.ref().Id().ToProvidedString(buf);
+        NS_ConvertASCIItoUTF16 uuid(buf);
+
+        resultingClientId.Assign(Substring(uuid, 1, NSID_LENGTH - 3));
+      }
     }
 
     rv = mServiceWorkerPrivate->SendFetchEvent(mChannel, mLoadGroup, clientId,
-                                               mIsReload);
+                                               resultingClientId, mIsReload);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       HandleError();
     }
 
     return NS_OK;
   }
 };
 
--- a/dom/serviceworkers/ServiceWorkerPrivate.cpp
+++ b/dom/serviceworkers/ServiceWorkerPrivate.cpp
@@ -1304,56 +1304,62 @@ class FetchEventRunnable : public Extend
   nsMainThreadPtrHandle<nsIInterceptedChannel> mInterceptedChannel;
   const nsCString mScriptSpec;
   nsTArray<nsCString> mHeaderNames;
   nsTArray<nsCString> mHeaderValues;
   nsCString mSpec;
   nsCString mFragment;
   nsCString mMethod;
   nsString mClientId;
+  nsString mResultingClientId;
   bool mIsReload;
   bool mMarkLaunchServiceWorkerEnd;
   RequestCache mCacheMode;
   RequestMode mRequestMode;
   RequestRedirect mRequestRedirect;
   RequestCredentials mRequestCredentials;
   nsContentPolicyType mContentPolicyType;
   nsCOMPtr<nsIInputStream> mUploadStream;
   int64_t mUploadStreamContentLength;
   nsCString mReferrer;
   ReferrerPolicy mReferrerPolicy;
   nsString mIntegrity;
+  const bool mIsNonSubresourceRequest;
 public:
   FetchEventRunnable(WorkerPrivate* aWorkerPrivate,
                      KeepAliveToken* aKeepAliveToken,
                      nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
                      // CSP checks might require the worker script spec
                      // later on.
                      const nsACString& aScriptSpec,
                      nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration,
                      const nsAString& aClientId,
+                     const nsAString& aResultingClientId,
                      bool aIsReload,
-                     bool aMarkLaunchServiceWorkerEnd)
+                     bool aMarkLaunchServiceWorkerEnd,
+                     bool aIsNonSubresourceRequest)
     : ExtendableFunctionalEventWorkerRunnable(
         aWorkerPrivate, aKeepAliveToken, aRegistration)
     , mInterceptedChannel(aChannel)
     , mScriptSpec(aScriptSpec)
     , mClientId(aClientId)
+    , mResultingClientId(aResultingClientId)
     , mIsReload(aIsReload)
     , mMarkLaunchServiceWorkerEnd(aMarkLaunchServiceWorkerEnd)
     , mCacheMode(RequestCache::Default)
     , mRequestMode(RequestMode::No_cors)
     , mRequestRedirect(RequestRedirect::Follow)
     // By default we set it to same-origin since normal HTTP fetches always
     // send credentials to same-origin websites unless explicitly forbidden.
     , mRequestCredentials(RequestCredentials::Same_origin)
     , mContentPolicyType(nsIContentPolicy::TYPE_INVALID)
     , mUploadStreamContentLength(-1)
     , mReferrer(kFETCH_CLIENT_REFERRER_STR)
     , mReferrerPolicy(ReferrerPolicy::_empty)
+    , mIsNonSubresourceRequest(aIsNonSubresourceRequest)
   {
     MOZ_ASSERT(aWorkerPrivate);
   }
 
   NS_DECL_ISUPPORTS_INHERITED
 
   NS_IMETHOD
   VisitHeader(const nsACString& aHeader, const nsACString& aValue) override
@@ -1619,22 +1625,36 @@ private:
     MOZ_ASSERT_IF(internalReq->IsNavigationRequest(),
                   request->Redirect() == RequestRedirect::Manual);
 
     RootedDictionary<FetchEventInit> init(aCx);
     init.mRequest = request;
     init.mBubbles = false;
     init.mCancelable = true;
     // Only expose the FetchEvent.clientId on subresource requests for now.
-    // Once we implement .resultingClientId and .targetClientId we can then
-    // start exposing .clientId on non-subresource requests as well.  See
-    // bug 1264177.
+    // Once we implement .targetClientId we can then start exposing .clientId
+    // on non-subresource requests as well.  See bug 1487534.
     if (!mClientId.IsEmpty() && !internalReq->IsNavigationRequest()) {
       init.mClientId = mClientId;
     }
+
+    /*
+     * https://w3c.github.io/ServiceWorker/#on-fetch-request-algorithm
+     *
+     * "If request is a non-subresource request and request’s
+     * destination is not "report", initialize e’s resultingClientId attribute
+     * to reservedClient’s [resultingClient's] id, and to the empty string
+     * otherwise." (Step 18.8)
+     */
+    if (!mResultingClientId.IsEmpty() &&
+        mIsNonSubresourceRequest &&
+        internalReq->Destination() != RequestDestination::Report) {
+      init.mResultingClientId = mResultingClientId;
+    }
+
     init.mIsReload = mIsReload;
     RefPtr<FetchEvent> event =
       FetchEvent::Constructor(globalObj, NS_LITERAL_STRING("fetch"), init, result);
     if (NS_WARN_IF(result.Failed())) {
       result.SuppressException();
       return false;
     }
 
@@ -1668,17 +1688,19 @@ private:
 
 NS_IMPL_ISUPPORTS_INHERITED(FetchEventRunnable, WorkerRunnable, nsIHttpHeaderVisitor)
 
 } // anonymous namespace
 
 nsresult
 ServiceWorkerPrivate::SendFetchEvent(nsIInterceptedChannel* aChannel,
                                      nsILoadGroup* aLoadGroup,
-                                     const nsAString& aClientId, bool aIsReload)
+                                     const nsAString& aClientId,
+                                     const nsAString& aResultingClientId,
+                                     bool aIsReload)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
   if (NS_WARN_IF(!mInfo || !swm)) {
     return NS_ERROR_FAILURE;
   }
 
@@ -1733,21 +1755,27 @@ ServiceWorkerPrivate::SendFetchEvent(nsI
       "nsIInterceptedChannel", aChannel, false));
 
   nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> regInfo(
     new nsMainThreadPtrHolder<ServiceWorkerRegistrationInfo>(
       "ServiceWorkerRegistrationInfoProxy", registration, false));
 
   RefPtr<KeepAliveToken> token = CreateEventKeepAliveToken();
 
+  nsCOMPtr<nsIChannel> channel;
+  rv = aChannel->GetChannel(getter_AddRefs(channel));
+  NS_ENSURE_SUCCESS(rv, rv);
+  bool isNonSubresourceRequest = nsContentUtils::IsNonSubresourceRequest(channel);
 
   RefPtr<FetchEventRunnable> r =
     new FetchEventRunnable(mWorkerPrivate, token, handle,
                            mInfo->ScriptSpec(), regInfo,
-                           aClientId, aIsReload, newWorkerCreated);
+                           aClientId, aResultingClientId,
+                           aIsReload, newWorkerCreated,
+                           isNonSubresourceRequest);
   rv = r->Init();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   if (mInfo->State() == ServiceWorkerState::Activating) {
     mPendingFunctionalEvents.AppendElement(r.forget());
     return NS_OK;
--- a/dom/serviceworkers/ServiceWorkerPrivate.h
+++ b/dom/serviceworkers/ServiceWorkerPrivate.h
@@ -120,18 +120,21 @@ public:
                         const nsAString& aBody,
                         const nsAString& aTag,
                         const nsAString& aIcon,
                         const nsAString& aData,
                         const nsAString& aBehavior,
                         const nsAString& aScope);
 
   nsresult
-  SendFetchEvent(nsIInterceptedChannel* aChannel, nsILoadGroup* aLoadGroup,
-                 const nsAString& aClientId, bool aIsReload);
+  SendFetchEvent(nsIInterceptedChannel* aChannel,
+                 nsILoadGroup* aLoadGroup,
+                 const nsAString& aClientId,
+                 const nsAString& aResultingClientId,
+                 bool aIsReload);
 
   bool
   MaybeStoreISupports(nsISupports* aSupports);
 
   void
   RemoveISupports(nsISupports* aSupports);
 
   // This will terminate the current running worker thread and drop the
--- a/dom/webidl/FetchEvent.webidl
+++ b/dom/webidl/FetchEvent.webidl
@@ -8,19 +8,21 @@
  */
 
 [Constructor(DOMString type, FetchEventInit eventInitDict),
  Func="ServiceWorkerVisible",
  Exposed=(ServiceWorker)]
 interface FetchEvent : ExtendableEvent {
   [SameObject] readonly attribute Request request;
   readonly attribute DOMString clientId;
+  readonly attribute DOMString resultingClientId;
   readonly attribute boolean isReload;
 
   [Throws]
   void respondWith(Promise<Response> r);
 };
 
 dictionary FetchEventInit : EventInit {
   required Request request;
   DOMString clientId = "";
+  DOMString resultingClientId = "";
   boolean isReload = false;
 };
--- a/js/src/builtin/ReflectParse.cpp
+++ b/js/src/builtin/ReflectParse.cpp
@@ -16,16 +16,19 @@
 
 #include "builtin/Array.h"
 #include "builtin/Reflect.h"
 #include "frontend/ModuleSharedContext.h"
 #include "frontend/ParseNode.h"
 #include "frontend/Parser.h"
 #include "js/CharacterEncoding.h"
 #include "js/StableStringChars.h"
+#ifdef ENABLE_BIGINT
+#include "vm/BigIntType.h"
+#endif
 #include "vm/JSAtom.h"
 #include "vm/JSObject.h"
 #include "vm/RegExpObject.h"
 
 #include "vm/JSObject-inl.h"
 
 using namespace js;
 using namespace js::frontend;
@@ -3142,16 +3145,19 @@ ASTSerializer::expression(ParseNode* pn,
 
         return builder.templateLiteral(elts, &list->pn_pos, dst);
       }
 
       case ParseNodeKind::TemplateString:
       case ParseNodeKind::String:
       case ParseNodeKind::RegExp:
       case ParseNodeKind::Number:
+#ifdef ENABLE_BIGINT
+      case ParseNodeKind::BigInt:
+#endif
       case ParseNodeKind::True:
       case ParseNodeKind::False:
       case ParseNodeKind::Null:
       case ParseNodeKind::RawUndefined:
         return literal(pn, dst);
 
       case ParseNodeKind::YieldStar:
       {
@@ -3307,32 +3313,42 @@ ASTSerializer::literal(ParseNode* pn, Mu
     switch (pn->getKind()) {
       case ParseNodeKind::TemplateString:
       case ParseNodeKind::String:
         val.setString(pn->as<NameNode>().atom());
         break;
 
       case ParseNodeKind::RegExp:
       {
-        RootedObject re1(cx, pn->as<RegExpLiteral>().objbox()->object);
+        RootedObject re1(cx, pn->as<RegExpLiteral>().objbox()->object());
         LOCAL_ASSERT(re1 && re1->is<RegExpObject>());
 
         RootedObject re2(cx, CloneRegExpObject(cx, re1.as<RegExpObject>()));
         if (!re2) {
             return false;
         }
 
         val.setObject(*re2);
         break;
       }
 
       case ParseNodeKind::Number:
         val.setNumber(pn->as<NumericLiteral>().value());
         break;
 
+#ifdef ENABLE_BIGINT
+      case ParseNodeKind::BigInt:
+      {
+        BigInt* x = pn->as<BigIntLiteral>().box()->value();
+        cx->check(x);
+        val.setBigInt(x);
+        break;
+      }
+#endif
+
       case ParseNodeKind::Null:
         val.setNull();
         break;
 
       case ParseNodeKind::RawUndefined:
         val.setUndefined();
         break;
 
--- a/js/src/frontend/BinSource.h
+++ b/js/src/frontend/BinSource.h
@@ -46,17 +46,17 @@ class BinASTParserBase: private JS::Auto
 
     bool hasUsedName(HandlePropertyName name);
 
     // --- GC.
 
     virtual void doTrace(JSTracer* trc) {}
 
     void trace(JSTracer* trc) {
-        ObjectBox::TraceList(trc, traceListHead_);
+        TraceListNode::TraceList(trc, traceListHead_);
         doTrace(trc);
     }
 
 
   public:
     ParseNode* allocParseNode(size_t size) {
         MOZ_ASSERT(size == sizeof(ParseNode));
         return static_cast<ParseNode*>(nodeAlloc_.allocNode());
@@ -68,17 +68,17 @@ class BinASTParserBase: private JS::Auto
     friend void TraceBinParser(JSTracer* trc, JS::AutoGCRooter* parser);
 
   protected:
     JSContext* cx_;
 
     // ---- Memory-related stuff
   protected:
     LifoAlloc& alloc_;
-    ObjectBox* traceListHead_;
+    TraceListNode* traceListHead_;
     UsedNameTracker& usedNames_;
   private:
     LifoAlloc::Mark tempPoolMark_;
     ParseNodeAllocator nodeAlloc_;
 
     // ---- Parsing-related stuff
   protected:
     // Root atoms and objects allocated for the parse tree.
@@ -251,20 +251,20 @@ class BinASTParser : public BinASTParser
         /*
          * We use JSContext.tempLifoAlloc to allocate parsed objects and place them
          * on a list in this Parser to ensure GC safety. Thus the tempLifoAlloc
          * arenas containing the entries must be alive until we are done with
          * scanning, parsing and code generation for the whole script or top-level
          * function.
          */
 
-         ObjectBox* objbox = alloc_.new_<ObjectBox>(obj, traceListHead_);
-         if (!objbox) {
-             ReportOutOfMemory(cx_);
-             return nullptr;
+        ObjectBox* objbox = alloc_.new_<ObjectBox>(obj, traceListHead_);
+        if (!objbox) {
+            ReportOutOfMemory(cx_);
+            return nullptr;
         }
 
         traceListHead_ = objbox;
 
         return objbox;
     }
 
 
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -1057,16 +1057,23 @@ BytecodeEmitter::checkSideEffects(ParseN
         *answer = false;
         return true;
 
       case ParseNodeKind::Number:
         MOZ_ASSERT(pn->is<NumericLiteral>());
         *answer = false;
         return true;
 
+#ifdef ENABLE_BIGINT
+      case ParseNodeKind::BigInt:
+        MOZ_ASSERT(pn->is<BigIntLiteral>());
+        *answer = false;
+        return true;
+#endif
+
       // |this| can throw in derived class constructors, including nested arrow
       // functions or eval.
       case ParseNodeKind::This:
         MOZ_ASSERT(pn->is<UnaryNode>());
         *answer = sc->needsThisTDZChecks();
         return true;
 
       // Trivial binary nodes with more token pos holders.
@@ -1995,17 +2002,17 @@ BytecodeEmitter::emitNumberOp(double dva
             if (!emitN(JSOP_INT32, 4, &off)) {
                 return false;
             }
             SET_INT32(code(off), ival);
         }
         return true;
     }
 
-    if (!numberList.append(dval)) {
+    if (!numberList.append(DoubleValue(dval))) {
         return false;
     }
 
     return emitIndex32(JSOP_DOUBLE, numberList.length() - 1);
 }
 
 /*
  * Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047.
@@ -4148,16 +4155,21 @@ ParseNode::getConstantValue(JSContext* c
                             NewObjectKind newKind)
 {
     MOZ_ASSERT(newKind == TenuredObject || newKind == SingletonObject);
 
     switch (getKind()) {
       case ParseNodeKind::Number:
         vp.setNumber(as<NumericLiteral>().value());
         return true;
+#ifdef ENABLE_BIGINT
+      case ParseNodeKind::BigInt:
+        vp.setBigInt(as<BigIntLiteral>().box()->value());
+        return true;
+#endif
       case ParseNodeKind::TemplateString:
       case ParseNodeKind::String:
         vp.setString(as<NameNode>().atom());
         return true;
       case ParseNodeKind::True:
         vp.setBoolean(true);
         return true;
       case ParseNodeKind::False:
@@ -4717,16 +4729,27 @@ BytecodeEmitter::emitCopyDataProperties(
     if (!emit1(JSOP_POP)) {                    // -
         return false;
     }
 
     MOZ_ASSERT(depth - int(argc) == this->stackDepth);
     return true;
 }
 
+#ifdef ENABLE_BIGINT
+bool
+BytecodeEmitter::emitBigIntOp(BigInt* bigint)
+{
+    if (!numberList.append(BigIntValue(bigint))) {
+        return false;
+    }
+    return emitIndex32(JSOP_BIGINT, numberList.length() - 1);
+}
+#endif
+
 bool
 BytecodeEmitter::emitIterator()
 {
     // Convert iterable to iterator.
     if (!emit1(JSOP_DUP)) {                                       // OBJ OBJ
         return false;
     }
     if (!emit2(JSOP_SYMBOL, uint8_t(JS::SymbolCode::iterator))) { // OBJ OBJ @@ITERATOR
@@ -8910,16 +8933,24 @@ BytecodeEmitter::emitTree(ParseNode* pn,
         break;
 
       case ParseNodeKind::Number:
         if (!emitNumberOp(pn->as<NumericLiteral>().value())) {
             return false;
         }
         break;
 
+#ifdef ENABLE_BIGINT
+      case ParseNodeKind::BigInt:
+        if (!emitBigIntOp(pn->as<BigIntLiteral>().box()->value())) {
+            return false;
+        }
+        break;
+#endif
+
       case ParseNodeKind::RegExp:
         if (!emitRegExp(objectList.add(pn->as<RegExpLiteral>().objbox()))) {
             return false;
         }
         break;
 
       case ParseNodeKind::True:
       case ParseNodeKind::False:
@@ -9219,51 +9250,52 @@ BytecodeEmitter::copySrcNotes(jssrcnote*
 }
 
 void
 CGNumberList::finish(mozilla::Span<GCPtrValue> array)
 {
     MOZ_ASSERT(length() == array.size());
 
     for (unsigned i = 0; i < length(); i++) {
-        array[i].init(DoubleValue(list[i]));
+        array[i].init(vector[i]);
     }
 }
 
 /*
  * Find the index of the given object for code generator.
  *
  * Since the emitter refers to each parsed object only once, for the index we
  * use the number of already indexed objects. We also add the object to a list
  * to convert the list to a fixed-size array when we complete code generation,
  * see js::CGObjectList::finish below.
  */
 unsigned
 CGObjectList::add(ObjectBox* objbox)
 {
+    MOZ_ASSERT(objbox->isObjectBox());
     MOZ_ASSERT(!objbox->emitLink);
     objbox->emitLink = lastbox;
     lastbox = objbox;
     return length++;
 }
 
 void
 CGObjectList::finish(mozilla::Span<GCPtrObject> array)
 {
     MOZ_ASSERT(length <= INDEX_LIMIT);
     MOZ_ASSERT(length == array.size());
 
     ObjectBox* objbox = lastbox;
     for (GCPtrObject& obj : mozilla::Reversed(array)) {
         MOZ_ASSERT(obj == nullptr);
-        MOZ_ASSERT(objbox->object->isTenured());
+        MOZ_ASSERT(objbox->object()->isTenured());
         if (objbox->isFunctionBox()) {
             objbox->asFunctionBox()->finish();
         }
-        obj.init(objbox->object);
+        obj.init(objbox->object());
         objbox = objbox->emitLink;
     }
 }
 
 void
 CGScopeList::finish(mozilla::Span<GCPtrScope> array)
 {
     MOZ_ASSERT(length() <= INDEX_LIMIT);
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -24,30 +24,32 @@
 #include "vm/BytecodeUtil.h"
 #include "vm/Interpreter.h"
 #include "vm/Iteration.h"
 
 namespace js {
 namespace frontend {
 
 class CGNumberList {
-    Vector<double> list;
+    Rooted<ValueVector> vector;
 
   public:
-    explicit CGNumberList(JSContext* cx) : list(cx) {}
-    MOZ_MUST_USE bool append(double v) {
-        return list.append(v);
+    explicit CGNumberList(JSContext* cx)
+      : vector(cx, ValueVector(cx))
+    { }
+    MOZ_MUST_USE bool append(const Value& v) {
+        return vector.append(v);
     }
-    size_t length() const { return list.length(); }
+    size_t length() const { return vector.length(); }
     void finish(mozilla::Span<GCPtrValue> array);
 };
 
 struct CGObjectList {
     uint32_t            length;     /* number of emitted so far objects */
-    ObjectBox*          lastbox;   /* last emitted object */
+    ObjectBox*          lastbox;    /* last emitted object */
 
     CGObjectList() : length(0), lastbox(nullptr) {}
 
     unsigned add(ObjectBox* objbox);
     void finish(mozilla::Span<GCPtrObject> array);
 };
 
 struct MOZ_STACK_CLASS CGScopeList {
@@ -189,17 +191,17 @@ struct MOZ_STACK_CLASS BytecodeEmitter
     EmitterScope* innermostEmitterScope() const {
         MOZ_ASSERT(!unstableEmitterScope);
         return innermostEmitterScopeNoCheck();
     }
     EmitterScope* innermostEmitterScopeNoCheck() const {
         return innermostEmitterScope_;
     }
 
-    CGNumberList     numberList;     /* number constants to be included with the script */
+    CGNumberList     numberList;     /* double and bigint values used by script */
     CGObjectList     objectList;     /* list of emitted objects */
     CGScopeList      scopeList;      /* list of emitted scopes */
     CGTryNoteList    tryNoteList;    /* list of emitted try notes */
     CGScopeNoteList  scopeNoteList;  /* list of emitted block scope notes */
 
     // Certain ops (yield, await, gosub) have an entry in the script's
     // resumeOffsets list. This can be used to map from the op's resumeIndex to
     // the bytecode offset of the next pc. This indirection makes it easy to
@@ -525,16 +527,20 @@ struct MOZ_STACK_CLASS BytecodeEmitter
     // Emit a bytecode followed by an uint32 immediate operand.
     MOZ_MUST_USE bool emitUint32Operand(JSOp op, uint32_t operand);
 
     // Emit (1 + extra) bytecodes, for N bytes of op and its immediate operand.
     MOZ_MUST_USE bool emitN(JSOp op, size_t extra, ptrdiff_t* offset = nullptr);
 
     MOZ_MUST_USE bool emitNumberOp(double dval);
 
+#ifdef ENABLE_BIGINT
+    MOZ_MUST_USE bool emitBigIntOp(BigInt* bigint);
+#endif
+
     MOZ_MUST_USE bool emitThisLiteral(ThisLiteral* pn);
     MOZ_MUST_USE bool emitGetFunctionThis(NameNode* thisName);
     MOZ_MUST_USE bool emitGetFunctionThis(const mozilla::Maybe<uint32_t>& offset);
     MOZ_MUST_USE bool emitGetThisForSuperBase(UnaryNode* superBase);
     MOZ_MUST_USE bool emitSetThis(BinaryNode* setThisNode);
     MOZ_MUST_USE bool emitCheckDerivedClassConstructorReturn();
 
     // Handle jump opcodes and jump targets.
--- a/js/src/frontend/FoldConstants.cpp
+++ b/js/src/frontend/FoldConstants.cpp
@@ -374,16 +374,19 @@ ContainsHoistedDeclaration(JSContext* cx
       case ParseNodeKind::RegExp:
       case ParseNodeKind::True:
       case ParseNodeKind::False:
       case ParseNodeKind::Null:
       case ParseNodeKind::RawUndefined:
       case ParseNodeKind::This:
       case ParseNodeKind::Elision:
       case ParseNodeKind::Number:
+#ifdef ENABLE_BIGINT
+      case ParseNodeKind::BigInt:
+#endif
       case ParseNodeKind::New:
       case ParseNodeKind::Generator:
       case ParseNodeKind::ParamsBody:
       case ParseNodeKind::Catch:
       case ParseNodeKind::ForIn:
       case ParseNodeKind::ForOf:
       case ParseNodeKind::ForHead:
       case ParseNodeKind::ClassMethod:
@@ -467,30 +470,38 @@ ReplaceNode(ParseNode** pnp, ParseNode* 
 static bool
 IsEffectless(ParseNode* node)
 {
     return node->isKind(ParseNodeKind::True) ||
            node->isKind(ParseNodeKind::False) ||
            node->isKind(ParseNodeKind::String) ||
            node->isKind(ParseNodeKind::TemplateString) ||
            node->isKind(ParseNodeKind::Number) ||
+#ifdef ENABLE_BIGINT
+           node->isKind(ParseNodeKind::BigInt) ||
+#endif
            node->isKind(ParseNodeKind::Null) ||
            node->isKind(ParseNodeKind::RawUndefined) ||
            node->isKind(ParseNodeKind::Function);
 }
 
 enum Truthiness { Truthy, Falsy, Unknown };
 
 static Truthiness
 Boolish(ParseNode* pn)
 {
     switch (pn->getKind()) {
       case ParseNodeKind::Number:
         return (pn->as<NumericLiteral>().value() != 0 && !IsNaN(pn->as<NumericLiteral>().value())) ? Truthy : Falsy;
 
+#ifdef ENABLE_BIGINT
+      case ParseNodeKind::BigInt:
+        return (pn->as<BigIntLiteral>().box()->value()->toBoolean()) ? Truthy : Falsy;
+#endif
+
       case ParseNodeKind::String:
       case ParseNodeKind::TemplateString:
         return (pn->as<NameNode>().atom()->length() > 0) ? Truthy : Falsy;
 
       case ParseNodeKind::True:
       case ParseNodeKind::Function:
         return Truthy;
 
@@ -562,17 +573,23 @@ FoldTypeOfExpr(JSContext* cx, UnaryNode*
     ParseNode* expr = node->kid();
 
     // Constant-fold the entire |typeof| if given a constant with known type.
     RootedPropertyName result(cx);
     if (expr->isKind(ParseNodeKind::String) || expr->isKind(ParseNodeKind::TemplateString)) {
         result = cx->names().string;
     } else if (expr->isKind(ParseNodeKind::Number)) {
         result = cx->names().number;
-    } else if (expr->isKind(ParseNodeKind::Null)) {
+    }
+#ifdef ENABLE_BIGINT
+    else if (expr->isKind(ParseNodeKind::BigInt)) {
+        result = cx->names().bigint;
+    }
+#endif
+    else if (expr->isKind(ParseNodeKind::Null)) {
         result = cx->names().object;
     } else if (expr->isKind(ParseNodeKind::True) || expr->isKind(ParseNodeKind::False)) {
         result = cx->names().boolean;
     } else if (expr->isKind(ParseNodeKind::Function)) {
         result = cx->names().function;
     }
 
     if (result) {
@@ -1629,16 +1646,22 @@ Fold(JSContext* cx, ParseNode** pnp, Per
       case ParseNodeKind::RegExp:
         MOZ_ASSERT(pn->is<RegExpLiteral>());
         return true;
 
       case ParseNodeKind::Number:
         MOZ_ASSERT(pn->is<NumericLiteral>());
         return true;
 
+#ifdef ENABLE_BIGINT
+      case ParseNodeKind::BigInt:
+        MOZ_ASSERT(pn->is<BigIntLiteral>());
+        return true;
+#endif
+
       case ParseNodeKind::SuperBase:
       case ParseNodeKind::TypeOfName: {
 #ifdef DEBUG
         UnaryNode* node = &pn->as<UnaryNode>();
         NameNode* nameNode = &node->kid()->as<NameNode>();
         MOZ_ASSERT(nameNode->isKind(ParseNodeKind::Name));
         MOZ_ASSERT(!nameNode->initializer());
 #endif
--- a/js/src/frontend/FullParseHandler.h
+++ b/js/src/frontend/FullParseHandler.h
@@ -126,16 +126,30 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS)
     NameNodeType newObjectLiteralPropertyName(JSAtom* atom, const TokenPos& pos) {
         return new_<NameNode>(ParseNodeKind::ObjectPropertyName, JSOP_NOP, atom, pos);
     }
 
     NumericLiteralType newNumber(double value, DecimalPoint decimalPoint, const TokenPos& pos) {
         return new_<NumericLiteral>(value, decimalPoint, pos);
     }
 
+#ifdef ENABLE_BIGINT
+    // The Boxer object here is any object that can allocate BigIntBoxes.
+    // Specifically, a Boxer has a .newBigIntBox(T) method that accepts a
+    // BigInt* argument and returns a BigIntBox*.
+    template <class Boxer>
+    BigIntLiteralType newBigInt(BigInt* bi, const TokenPos& pos, Boxer& boxer) {
+        BigIntBox* box = boxer.newBigIntBox(bi);
+        if (!box) {
+            return null();
+        }
+        return new_<BigIntLiteral>(box, pos);
+    }
+#endif
+
     BooleanLiteralType newBooleanLiteral(bool cond, const TokenPos& pos) {
         return new_<BooleanLiteral>(cond, pos);
     }
 
     NameNodeType newStringLiteral(JSAtom* atom, const TokenPos& pos) {
         return new_<NameNode>(ParseNodeKind::String, JSOP_NOP, atom, pos);
     }
 
--- a/js/src/frontend/NameFunctions.cpp
+++ b/js/src/frontend/NameFunctions.cpp
@@ -458,16 +458,22 @@ class NameResolver
           case ParseNodeKind::RegExp:
             MOZ_ASSERT(cur->is<RegExpLiteral>());
             break;
 
           case ParseNodeKind::Number:
             MOZ_ASSERT(cur->is<NumericLiteral>());
             break;
 
+#ifdef ENABLE_BIGINT
+          case ParseNodeKind::BigInt:
+            MOZ_ASSERT(cur->is<BigIntLiteral>());
+            break;
+#endif
+
           case ParseNodeKind::TypeOfName:
           case ParseNodeKind::SuperBase:
             MOZ_ASSERT(cur->as<UnaryNode>().kid()->isKind(ParseNodeKind::Name));
             MOZ_ASSERT(!cur->as<UnaryNode>().kid()->as<NameNode>().initializer());
             break;
 
           case ParseNodeKind::NewTarget:
           case ParseNodeKind::ImportMeta: {
--- a/js/src/frontend/ParseNode.cpp
+++ b/js/src/frontend/ParseNode.cpp
@@ -175,16 +175,21 @@ ParseNode::dump(GenericPrinter& out, int
         as<NameNode>().dump(out, indent);
         return;
       case PN_FIELD:
         as<ClassField>().dump(out, indent);
         return;
       case PN_NUMBER:
         as<NumericLiteral>().dump(out, indent);
         return;
+#ifdef ENABLE_BIGINT
+      case PN_BIGINT:
+        as<BigIntLiteral>().dump(out, indent);
+        return;
+#endif
       case PN_REGEXP:
         as<RegExpLiteral>().dump(out, indent);
         return;
       case PN_LOOP:
         as<LoopControlStatement>().dump(out, indent);
         return;
       case PN_SCOPE:
         as<LexicalScopeNode>().dump(out, indent);
@@ -218,16 +223,24 @@ NumericLiteral::dump(GenericPrinter& out
     }
     if (cstr) {
         out.printf("%s", cstr);
     } else {
         out.printf("%g", value());
     }
 }
 
+#ifdef ENABLE_BIGINT
+void
+BigIntLiteral::dump(GenericPrinter& out, int indent)
+{
+    out.printf("(%s)", parseNodeNames[size_t(getKind())]);
+}
+#endif
+
 void
 RegExpLiteral::dump(GenericPrinter& out, int indent)
 {
     out.printf("(%s)", parseNodeNames[size_t(getKind())]);
 }
 
 void
 LoopControlStatement::dump(GenericPrinter& out, int indent)
@@ -431,54 +444,80 @@ LexicalScopeNode::dump(GenericPrinter& o
     out.putChar(']');
     indent += 2;
     IndentNewLine(out, indent);
     DumpParseTree(scopeBody(), out, indent);
     out.printf(")");
 }
 #endif
 
-ObjectBox::ObjectBox(JSObject* object, ObjectBox* traceLink)
-  : object(object),
-    traceLink(traceLink),
-    emitLink(nullptr)
+TraceListNode::TraceListNode(js::gc::Cell* gcThing, TraceListNode* traceLink)
+  : gcThing(gcThing),
+    traceLink(traceLink)
 {
-    MOZ_ASSERT(!object->is<JSFunction>());
-    MOZ_ASSERT(object->isTenured());
+    MOZ_ASSERT(gcThing->isTenured());
 }
 
-ObjectBox::ObjectBox(JSFunction* function, ObjectBox* traceLink)
-  : object(function),
-    traceLink(traceLink),
+#ifdef ENABLE_BIGINT
+BigIntBox*
+TraceListNode::asBigIntBox()
+{
+    MOZ_ASSERT(isBigIntBox());
+    return static_cast<BigIntBox*>(this);
+}
+#endif
+
+ObjectBox*
+TraceListNode::asObjectBox()
+{
+    MOZ_ASSERT(isObjectBox());
+    return static_cast<ObjectBox*>(this);
+}
+
+#ifdef ENABLE_BIGINT
+BigIntBox::BigIntBox(BigInt* bi, TraceListNode* traceLink)
+  : TraceListNode(bi, traceLink)
+{
+}
+#endif
+
+ObjectBox::ObjectBox(JSObject* obj, TraceListNode* traceLink)
+  : TraceListNode(obj, traceLink),
     emitLink(nullptr)
 {
-    MOZ_ASSERT(object->is<JSFunction>());
+    MOZ_ASSERT(!object()->is<JSFunction>());
+}
+
+ObjectBox::ObjectBox(JSFunction* function, TraceListNode* traceLink)
+  : TraceListNode(function, traceLink),
+    emitLink(nullptr)
+{
+    MOZ_ASSERT(object()->is<JSFunction>());
     MOZ_ASSERT(asFunctionBox()->function() == function);
-    MOZ_ASSERT(object->isTenured());
 }
 
 FunctionBox*
 ObjectBox::asFunctionBox()
 {
     MOZ_ASSERT(isFunctionBox());
     return static_cast<FunctionBox*>(this);
 }
 
 /* static */ void
-ObjectBox::TraceList(JSTracer* trc, ObjectBox* listHead)
+TraceListNode::TraceList(JSTracer* trc, TraceListNode* listHead)
 {
-    for (ObjectBox* box = listHead; box; box = box->traceLink) {
-        box->trace(trc);
+    for (TraceListNode* node = listHead; node; node = node->traceLink) {
+        node->trace(trc);
     }
 }
 
 void
-ObjectBox::trace(JSTracer* trc)
+TraceListNode::trace(JSTracer* trc)
 {
-    TraceRoot(trc, &object, "parser.object");
+    TraceGenericPointerRoot(trc, &gcThing, "parser.traceListNode");
 }
 
 void
 FunctionBox::trace(JSTracer* trc)
 {
     ObjectBox::trace(trc);
     if (enclosingScope_) {
         TraceRoot(trc, &enclosingScope_, "funbox-enclosingScope");
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -5,16 +5,19 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef frontend_ParseNode_h
 #define frontend_ParseNode_h
 
 #include "mozilla/Attributes.h"
 
 #include "frontend/TokenStream.h"
+#ifdef ENABLE_BIGINT
+#include "vm/BigIntType.h"
+#endif
 #include "vm/BytecodeUtil.h"
 #include "vm/Printer.h"
 #include "vm/Scope.h"
 
 // A few notes on lifetime of ParseNode trees:
 //
 // - All the `ParseNode` instances MUST BE explicitly allocated in the context's `LifoAlloc`.
 //   This is typically implemented by the `FullParseHandler` or it can be reimplemented with
@@ -37,16 +40,19 @@
 
 namespace js {
 namespace frontend {
 
 class ParseContext;
 class FullParseHandler;
 class FunctionBox;
 class ObjectBox;
+#ifdef ENABLE_BIGINT
+class BigIntBox;
+#endif
 
 #define FOR_EACH_PARSE_NODE_KIND(F) \
     F(EmptyStatement, PN_NULLARY) \
     F(ExpressionStatement, PN_UNARY) \
     F(Comma, PN_LIST) \
     F(Conditional, PN_TERNARY) \
     F(Colon, PN_BINARY) \
     F(Shorthand, PN_BINARY) \
@@ -66,16 +72,17 @@ class ObjectBox;
     F(Object, PN_LIST) \
     F(Call, PN_BINARY) \
     F(Arguments, PN_LIST) \
     F(Name, PN_NAME) \
     F(ObjectPropertyName, PN_NAME) \
     F(PrivateName, PN_NAME) \
     F(ComputedName, PN_UNARY) \
     F(Number, PN_NUMBER) \
+    IF_BIGINT(F(BigInt, PN_BIGINT),/**/) \
     F(String, PN_NAME) \
     F(TemplateStringList, PN_LIST) \
     F(TemplateString, PN_NAME) \
     F(TaggedTemplate, PN_BINARY) \
     F(CallSiteObj, PN_LIST) \
     F(RegExp, PN_REGEXP) \
     F(True, PN_NULLARY) \
     F(False, PN_NULLARY) \
@@ -480,16 +487,18 @@ IsTypeofKind(ParseNodeKind kind)
  *          TemplateString nodes
  *            Array [, cooked TemplateString]+
  *          where the Array is
  *            [raw TemplateString]+
  * RegExp (RegExpLiteral)
  *   regexp: RegExp model object
  * Number (NumericLiteral)
  *   value: double value of numeric literal
+ * BigInt (BigIntLiteral)
+ *   box: BigIntBox holding BigInt* value
  * True, False (BooleanLiteral)
  *   pn_op: JSOp bytecode
  * Null (NullLiteral)
  *   pn_op: JSOp bytecode
  * RawUndefined (RawUndefinedLiteral)
  *   pn_op: JSOp bytecode
  *
  * This (UnaryNode)
@@ -519,16 +528,19 @@ enum ParseNodeArity
     PN_UNARY,                           /* one kid, plus a couple of scalars */
     PN_BINARY,                          /* two kids, plus a couple of scalars */
     PN_TERNARY,                         /* three kids */
     PN_CODE,                            /* module or function definition node */
     PN_LIST,                            /* generic singly linked list */
     PN_NAME,                            /* name, label, string */
     PN_FIELD,                           /* field name, optional initializer */
     PN_NUMBER,                          /* numeric literal */
+#ifdef ENABLE_BIGINT
+    PN_BIGINT,                          /* BigInt literal */
+#endif
     PN_REGEXP,                          /* regexp literal */
     PN_LOOP,                            /* loop control (break/continue) */
     PN_SCOPE                            /* lexical scope */
 };
 
 // FIXME: Remove `*Type` (bug 1489008)
 #define FOR_EACH_PARSENODE_SUBCLASS(macro) \
     macro(BinaryNode, BinaryNodeType, asBinary) \
@@ -558,16 +570,17 @@ enum ParseNodeArity
     \
     macro(NullaryNode, NullaryNodeType, asNullary) \
     macro(BooleanLiteral, BooleanLiteralType, asBooleanLiteral) \
     macro(DebuggerStatement, DebuggerStatementType, asDebuggerStatement) \
     macro(NullLiteral, NullLiteralType, asNullLiteral) \
     macro(RawUndefinedLiteral, RawUndefinedLiteralType, asRawUndefinedLiteral) \
     \
     macro(NumericLiteral, NumericLiteralType, asNumericLiteral) \
+    IF_BIGINT(macro(BigIntLiteral, BigIntLiteralType, asBigIntLiteral),) \
     \
     macro(RegExpLiteral, RegExpLiteralType, asRegExpLiteral) \
     \
     macro(TernaryNode, TernaryNodeType, asTernary) \
     macro(ClassNode, ClassNodeType, asClass) \
     macro(ConditionalExpression, ConditionalExpressionType, asConditionalExpression) \
     macro(TryNode, TryNodeType, asTry) \
     \
@@ -729,16 +742,23 @@ class ParseNode
             ParseNode*          body;
         } scope;
         struct {
           private:
             friend class NumericLiteral;
             double       value;         /* aligned numeric literal value */
             DecimalPoint decimalPoint;  /* Whether the number has a decimal point */
         } number;
+#ifdef ENABLE_BIGINT
+        struct {
+          private:
+            friend class BigIntLiteral;
+            BigIntBox* box;
+        } bigint;
+#endif
         class {
           private:
             friend class LoopControlStatement;
             PropertyName*    label;    /* target of break/continue statement */
         } loopControl;
     } pn_u;
 
   public:
@@ -748,16 +768,19 @@ class ParseNode
      */
     static ParseNode*
     appendOrCreateList(ParseNodeKind kind, ParseNode* left, ParseNode* right,
                        FullParseHandler* handler, ParseContext* pc);
 
     /* True if pn is a parsenode representing a literal constant. */
     bool isLiteral() const {
         return isKind(ParseNodeKind::Number) ||
+#ifdef ENABLE_BIGINT
+               isKind(ParseNodeKind::BigInt) ||
+#endif
                isKind(ParseNodeKind::String) ||
                isKind(ParseNodeKind::True) ||
                isKind(ParseNodeKind::False) ||
                isKind(ParseNodeKind::Null) ||
                isKind(ParseNodeKind::RawUndefined);
     }
 
     // True iff this is a for-in/of loop variable declaration (var/let/const).
@@ -1521,16 +1544,42 @@ class NumericLiteral : public ParseNode
         return pn_u.number.decimalPoint;
     }
 
     void setValue(double v) {
         pn_u.number.value = v;
     }
 };
 
+#ifdef ENABLE_BIGINT
+class BigIntLiteral : public ParseNode
+{
+  public:
+    BigIntLiteral(BigIntBox* bibox, const TokenPos& pos)
+      : ParseNode(ParseNodeKind::BigInt, JSOP_NOP, pos)
+    {
+        pn_u.bigint.box = bibox;
+    }
+
+    static bool test(const ParseNode& node) {
+        bool match = node.isKind(ParseNodeKind::BigInt);
+        MOZ_ASSERT_IF(match, node.isArity(PN_BIGINT));
+        return match;
+    }
+
+#ifdef DEBUG
+    void dump(GenericPrinter& out, int indent);
+#endif
+
+    BigIntBox* box() const {
+        return pn_u.bigint.box;
+    }
+};
+#endif
+
 class LexicalScopeNode : public ParseNode
 {
   public:
     LexicalScopeNode(LexicalScope::Data* bindings, ParseNode* body)
       : ParseNode(ParseNodeKind::LexicalScope, JSOP_NOP, body->pn_pos)
     {
         pn_u.scope.bindings = bindings;
         pn_u.scope.body = body;
@@ -2178,35 +2227,64 @@ ParseNode::isConstant()
       case ParseNodeKind::Array:
       case ParseNodeKind::Object:
         return !as<ListNode>().hasNonConstInitializer();
       default:
         return false;
     }
 }
 
-class ObjectBox
+class TraceListNode
 {
-  public:
-    JSObject* object;
+  protected:
+    js::gc::Cell* gcThing;
+    TraceListNode* traceLink;
+
+    TraceListNode(js::gc::Cell* gcThing, TraceListNode* traceLink);
 
-    ObjectBox(JSObject* object, ObjectBox* traceLink);
-    bool isFunctionBox() { return object->is<JSFunction>(); }
-    FunctionBox* asFunctionBox();
+#ifdef ENABLE_BIGINT
+    bool isBigIntBox() const { return gcThing->is<BigInt>(); }
+#endif
+    bool isObjectBox() const { return gcThing->is<JSObject>(); }
+
+#ifdef ENABLE_BIGINT
+    BigIntBox* asBigIntBox();
+#endif
+    ObjectBox* asObjectBox();
+
     virtual void trace(JSTracer* trc);
 
-    static void TraceList(JSTracer* trc, ObjectBox* listHead);
+  public:
+    static void TraceList(JSTracer* trc, TraceListNode* listHead);
+};
 
+#ifdef ENABLE_BIGINT
+class BigIntBox : public TraceListNode
+{
+  public:
+    BigIntBox(BigInt* bi, TraceListNode* link);
+    BigInt* value() const { return gcThing->as<BigInt>(); }
+};
+#endif
+
+class ObjectBox : public TraceListNode
+{
   protected:
     friend struct CGObjectList;
-
-    ObjectBox* traceLink;
     ObjectBox* emitLink;
 
-    ObjectBox(JSFunction* function, ObjectBox* traceLink);
+    ObjectBox(JSFunction* function, TraceListNode* link);
+
+  public:
+    ObjectBox(JSObject* obj, TraceListNode* link);
+
+    JSObject* object() const { return gcThing->as<JSObject>(); }
+
+    bool isFunctionBox() const { return object()->is<JSFunction>(); }
+    FunctionBox* asFunctionBox();
 };
 
 enum ParseReportKind
 {
     ParseError,
     ParseWarning,
     ParseExtraWarning,
     ParseStrictError
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -455,39 +455,54 @@ Parser<FullParseHandler, Unit>::setInPar
 
 template <class ParseHandler, typename Unit>
 inline void
 GeneralParser<ParseHandler, Unit>::setInParametersOfAsyncFunction(bool inParameters)
 {
     asFinalParser()->setInParametersOfAsyncFunction(inParameters);
 }
 
-ObjectBox*
-ParserBase::newObjectBox(JSObject* obj)
-{
-    MOZ_ASSERT(obj);
+template <typename BoxT, typename ArgT>
+BoxT*
+ParserBase::newTraceListNode(ArgT* arg)
+{
+    MOZ_ASSERT(arg);
 
     /*
      * We use JSContext.tempLifoAlloc to allocate parsed objects and place them
      * on a list in this Parser to ensure GC safety. Thus the tempLifoAlloc
      * arenas containing the entries must be alive until we are done with
      * scanning, parsing and code generation for the whole script or top-level
      * function.
      */
 
-    ObjectBox* objbox = alloc.template new_<ObjectBox>(obj, traceListHead);
-    if (!objbox) {
+    BoxT* box = alloc.template new_<BoxT>(arg, traceListHead);
+    if (!box) {
         ReportOutOfMemory(context);
         return nullptr;
     }
 
-    traceListHead = objbox;
-
-    return objbox;
-}
+    traceListHead = box;
+
+    return box;
+}
+
+ObjectBox*
+ParserBase::newObjectBox(JSObject* obj)
+{
+    return newTraceListNode<ObjectBox, JSObject>(obj);
+}
+
+#ifdef ENABLE_BIGINT
+BigIntBox*
+ParserBase::newBigIntBox(BigInt* val)
+{
+    return newTraceListNode<BigIntBox, BigInt>(val);
+}
+#endif
 
 template <class ParseHandler>
 FunctionBox*
 PerHandlerParser<ParseHandler>::newFunctionBox(CodeNodeType funNode, JSFunction* fun,
                                                uint32_t toStringStart,
                                                Directives inheritedDirectives,
                                                GeneratorKind generatorKind,
                                                FunctionAsyncKind asyncKind)
@@ -515,17 +530,17 @@ PerHandlerParser<ParseHandler>::newFunct
     }
 
     return funbox;
 }
 
 void
 ParserBase::trace(JSTracer* trc)
 {
-    ObjectBox::TraceList(trc, traceListHead);
+    TraceListNode::TraceList(trc, traceListHead);
 }
 
 void
 TraceParser(JSTracer* trc, AutoGCRooter* parser)
 {
     static_cast<ParserBase*>(parser)->trace(trc);
 }
 
@@ -9340,16 +9355,56 @@ Parser<SyntaxParseHandler, Unit>::newReg
 
 template <class ParseHandler, typename Unit>
 typename ParseHandler::RegExpLiteralType
 GeneralParser<ParseHandler, Unit>::newRegExp()
 {
     return asFinalParser()->newRegExp();
 }
 
+#ifdef ENABLE_BIGINT
+template <typename Unit>
+BigIntLiteral*
+Parser<FullParseHandler, Unit>::newBigInt()
+{
+    // The token's charBuffer contains the DecimalIntegerLiteral or
+    // NumericLiteralBase production, and as such does not include the
+    // BigIntLiteralSuffix (the trailing "n").  Note that NumericLiteralBase
+    // productions may start with 0[bBoOxX], indicating binary/octal/hex.
+    const auto& chars = tokenStream.getCharBuffer();
+    mozilla::Range<const char16_t> source(chars.begin(), chars.length());
+
+    BigInt* b = js::StringToBigInt(context, source);
+    if (!b) {
+        return null();
+    }
+
+    // newBigInt immediately puts "b" in a BigIntBox, which is allocated using
+    // tempLifoAlloc, avoiding any potential GC.  Therefore it's OK to pass a
+    // raw pointer.
+    return handler.newBigInt(b, pos(), *this);
+}
+
+template <typename Unit>
+SyntaxParseHandler::BigIntLiteralType
+Parser<SyntaxParseHandler, Unit>::newBigInt()
+{
+    // The tokenizer has already checked the syntax of the bigint.
+
+    return handler.newBigInt();
+}
+
+template <class ParseHandler, typename Unit>
+typename ParseHandler::BigIntLiteralType
+GeneralParser<ParseHandler, Unit>::newBigInt()
+{
+    return asFinalParser()->newBigInt();
+}
+#endif /* ENABLE_BIGINT */
+
 // |exprPossibleError| is the PossibleError state within |expr|,
 // |possibleError| is the surrounding PossibleError state.
 template <class ParseHandler, typename Unit>
 bool
 GeneralParser<ParseHandler, Unit>::checkDestructuringAssignmentTarget(Node expr, TokenPos exprPos,
                                                                       PossibleError* exprPossibleError,
                                                                       PossibleError* possibleError,
                                                                       TargetBehavior behavior)
@@ -10364,16 +10419,21 @@ GeneralParser<ParseHandler, Unit>::prima
       }
 
       case TokenKind::RegExp:
         return newRegExp();
 
       case TokenKind::Number:
         return newNumber(anyChars.currentToken());
 
+#ifdef ENABLE_BIGINT
+      case TokenKind::BigInt:
+        return newBigInt();
+#endif
+
       case TokenKind::True:
         return handler.newBooleanLiteral(true, pos());
       case TokenKind::False:
         return handler.newBooleanLiteral(false, pos());
       case TokenKind::This: {
         if (pc->isFunctionBox()) {
             pc->functionBox()->usesThis = true;
         }
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -242,18 +242,18 @@ class MOZ_STACK_CLASS ParserBase
   public:
     JSContext* const context;
 
     LifoAlloc& alloc;
 
     TokenStreamAnyChars anyChars;
     LifoAlloc::Mark tempPoolMark;
 
-    /* list of parsed objects for GC tracing */
-    ObjectBox* traceListHead;
+    /* list of parsed objects and BigInts for GC tracing */
+    TraceListNode* traceListHead;
 
     /* innermost parse context (stack-allocated) */
     ParseContext* pc;
 
     // For tracking used names in this parsing session.
     UsedNameTracker& usedNames;
 
     ScriptSource*       ss;
@@ -343,30 +343,38 @@ class MOZ_STACK_CLASS ParserBase
 
     // A Parser::Mark is the extension of the LifoAlloc::Mark to the entire
     // Parser's state. Note: clients must still take care that any ParseContext
     // that points into released ParseNodes is destroyed.
     class Mark
     {
         friend class ParserBase;
         LifoAlloc::Mark mark;
-        ObjectBox* traceListHead;
+        TraceListNode* traceListHead;
     };
     Mark mark() const {
         Mark m;
         m.mark = alloc.mark();
         m.traceListHead = traceListHead;
         return m;
     }
     void release(Mark m) {
         alloc.release(m.mark);
         traceListHead = m.traceListHead;
     }
 
+  private:
+    template <typename BoxT, typename ArgT>
+    BoxT* newTraceListNode(ArgT* arg);
+
+  public:
     ObjectBox* newObjectBox(JSObject* obj);
+#ifdef ENABLE_BIGINT
+    BigIntBox* newBigIntBox(BigInt* val);
+#endif
 
     mozilla::Maybe<GlobalScope::Data*> newGlobalScopeData(ParseContext::Scope& scope);
     mozilla::Maybe<ModuleScope::Data*> newModuleScopeData(ParseContext::Scope& scope);
     mozilla::Maybe<EvalScope::Data*> newEvalScopeData(ParseContext::Scope& scope);
     mozilla::Maybe<FunctionScope::Data*> newFunctionScopeData(ParseContext::Scope& scope,
                                                               bool hasParameterExprs);
     mozilla::Maybe<VarScope::Data*> newVarScopeData(ParseContext::Scope& scope);
     mozilla::Maybe<LexicalScope::Data*> newLexicalScopeData(ParseContext::Scope& scope);
@@ -1290,16 +1298,20 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE
     bool checkDestructuringAssignmentElement(Node expr, TokenPos exprPos,
                                              PossibleError* exprPossibleError,
                                              PossibleError* possibleError);
 
     NumericLiteralType newNumber(const Token& tok) {
         return handler.newNumber(tok.number(), tok.decimalPoint(), tok.pos);
     }
 
+#ifdef ENABLE_BIGINT
+    inline BigIntLiteralType newBigInt();
+#endif
+
   protected:
     // Match the current token against the BindingIdentifier production with
     // the given Yield parameter.  If there is no match, report a syntax
     // error.
     PropertyName* bindingIdentifier(YieldHandling yieldHandling);
 
     bool checkLabelOrIdentifierReference(PropertyName* ident, uint32_t offset,
                                          YieldHandling yieldHandling,
@@ -1405,16 +1417,19 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE
     }
 
     // Functions present in both Parser<ParseHandler, Unit> specializations.
 
     inline void setAwaitHandling(AwaitHandling awaitHandling);
     inline void setInParametersOfAsyncFunction(bool inParameters);
 
     RegExpLiteralType newRegExp();
+#ifdef ENABLE_BIGINT
+    BigIntLiteralType newBigInt();
+#endif
 
     // Parse a module.
     CodeNodeType moduleBody(ModuleSharedContext* modulesc);
 
     inline BinaryNodeType importDeclaration();
     inline bool checkLocalExportNames(ListNodeType node);
     inline bool checkExportedName(JSAtom* exportName);
     inline bool checkExportedNamesForArrayBinding(ListNodeType array);
@@ -1532,16 +1547,19 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE
 
     friend class AutoAwaitIsKeyword<SyntaxParseHandler, Unit>;
     inline void setAwaitHandling(AwaitHandling awaitHandling);
 
     friend class AutoInParametersOfAsyncFunction<SyntaxParseHandler, Unit>;
     inline void setInParametersOfAsyncFunction(bool inParameters);
 
     RegExpLiteralType newRegExp();
+#ifdef ENABLE_BIGINT
+    BigIntLiteralType newBigInt();
+#endif
 
     // Parse a module.
     CodeNodeType moduleBody(ModuleSharedContext* modulesc);
 
     BinaryNodeType importDeclaration();
     bool checkLocalExportNames(ListNodeType node);
     bool checkExportedName(JSAtom* exportName);
     bool checkExportedNamesForArrayBinding(ListNodeType array);
--- a/js/src/frontend/SharedContext.cpp
+++ b/js/src/frontend/SharedContext.cpp
@@ -116,17 +116,17 @@ EvalSharedContext::EvalSharedContext(JSC
 #ifdef DEBUG
 bool
 FunctionBox::atomsAreKept()
 {
     return context->zone()->hasKeptAtoms();
 }
 #endif
 
-FunctionBox::FunctionBox(JSContext* cx, ObjectBox* traceListHead,
+FunctionBox::FunctionBox(JSContext* cx, TraceListNode* traceListHead,
                          JSFunction* fun, uint32_t toStringStart,
                          Directives directives, bool extraWarnings,
                          GeneratorKind generatorKind, FunctionAsyncKind asyncKind)
   : ObjectBox(fun, traceListHead),
     SharedContext(cx, Kind::FunctionBox, directives, extraWarnings),
     enclosingScope_(nullptr),
     namedLambdaBindings_(nullptr),
     functionScopeBindings_(nullptr),
--- a/js/src/frontend/SharedContext.h
+++ b/js/src/frontend/SharedContext.h
@@ -287,17 +287,17 @@ inline EvalSharedContext*
 SharedContext::asEvalContext()
 {
     MOZ_ASSERT(isEvalContext());
     return static_cast<EvalSharedContext*>(this);
 }
 
 class FunctionBox : public ObjectBox, public SharedContext
 {
-    // The parser handles tracing the fields below via the ObjectBox linked
+    // The parser handles tracing the fields below via the TraceListNode linked
     // list.
 
     // This field is used for two purposes:
     //   * If this FunctionBox refers to the function being compiled, this field
     //     holds its enclosing scope, used for compilation.
     //   * If this FunctionBox refers to a lazy child of the function being
     //     compiled, this field holds the child's immediately enclosing scope.
     //     Once compilation succeeds, we will store it in the child's
@@ -398,17 +398,17 @@ class FunctionBox : public ObjectBox, pu
 
     // Whether this function has a .this binding. If true, we need to emit
     // JSOP_FUNCTIONTHIS in the prologue to initialize it.
     bool hasThisBinding_:1;
 
     // Whether this function has nested functions.
     bool hasInnerFunctions_:1;
 
-    FunctionBox(JSContext* cx, ObjectBox* traceListHead, JSFunction* fun,
+    FunctionBox(JSContext* cx, TraceListNode* traceListHead, JSFunction* fun,
                 uint32_t toStringStart, Directives directives, bool extraWarnings,
                 GeneratorKind generatorKind, FunctionAsyncKind asyncKind);
 
 #ifdef DEBUG
     bool atomsAreKept();
 #endif
 
     MutableHandle<LexicalScope::Data*> namedLambdaBindings() {
@@ -432,17 +432,18 @@ class FunctionBox : public ObjectBox, pu
 
     inline bool isLazyFunctionWithoutEnclosingScope() const {
         return function()->isInterpretedLazy() &&
                !function()->lazyScript()->hasEnclosingScope();
     }
     void setEnclosingScopeForInnerLazyFunction(Scope* enclosingScope);
     void finish();
 
-    JSFunction* function() const { return &object->as<JSFunction>(); }
+    JSFunction* function() const { return &object()->as<JSFunction>(); }
+    void clobberFunction(JSFunction* function) { gcThing = function; }
 
     Scope* compilationEnclosingScope() const override {
         // This method is used to distinguish the outermost SharedContext. If
         // a FunctionBox is the outermost SharedContext, it must be a lazy
         // function.
 
         // If the function is lazy and it has enclosing scope, the function is
         // being delazified.  In that case the enclosingScope_ field is copied
--- a/js/src/frontend/SyntaxParseHandler.h
+++ b/js/src/frontend/SyntaxParseHandler.h
@@ -203,16 +203,22 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS)
     NameNodeType newObjectLiteralPropertyName(JSAtom* atom, const TokenPos& pos) {
         return NodeName;
     }
 
     NumericLiteralType newNumber(double value, DecimalPoint decimalPoint, const TokenPos& pos) {
         return NodeGeneric;
     }
 
+#ifdef ENABLE_BIGINT
+    BigIntLiteralType newBigInt() {
+        return NodeGeneric;
+    }
+#endif
+
     BooleanLiteralType newBooleanLiteral(bool cond, const TokenPos& pos) { return NodeGeneric; }
 
     NameNodeType newStringLiteral(JSAtom* atom, const TokenPos& pos) {
         lastAtom = atom;
         lastStringPos = pos;
         return NodeUnparenthesizedString;
     }
 
--- a/js/src/frontend/TokenKind.h
+++ b/js/src/frontend/TokenKind.h
@@ -71,16 +71,17 @@
     macro(LeftCurly,    "'{'") \
     macro(RightCurly,   "'}'") \
     macro(LeftParen,    "'('") \
     macro(RightParen,   "')'") \
     macro(Name,         "identifier") \
     macro(PrivateName,  "private identifier") \
     macro(Number,       "numeric literal") \
     macro(String,       "string literal") \
+    IF_BIGINT(macro(BigInt, "bigint literal"),) \
     \
     /* start of template literal with substitutions */ \
     macro(TemplateHead,    "'${'") \
     /* template literal without substitutions */ \
     macro(NoSubsTemplate, "template literal") \
     \
     macro(RegExp,       "regular expression literal") \
     macro(True,         "boolean literal 'true'") \
--- a/js/src/frontend/TokenStream.cpp
+++ b/js/src/frontend/TokenStream.cpp
@@ -2267,29 +2267,39 @@ TokenStreamSpecific<Unit, AnyCharsAccess
 
     // Consume integral component digits.
     while (IsAsciiDigit(unit)) {
         unit = getCodeUnit();
     }
 
     // Numbers contain no escapes, so we can read directly from |sourceUnits|.
     double dval;
+#ifdef ENABLE_BIGINT
+    bool isBigInt = false;
+#endif
     DecimalPoint decimalPoint = NoDecimal;
-    if (unit != '.' && unit != 'e' && unit != 'E') {
+    if (unit != '.' && unit != 'e' && unit != 'E' && IF_BIGINT(unit != 'n', true)) {
         // NOTE: |unit| may be EOF here.
         ungetCodeUnit(unit);
 
         // Most numbers are pure decimal integers without fractional component
         // or exponential notation.  Handle that with optimized code.
         if (!GetDecimalInteger(anyCharsAccess().cx, numStart,
                                this->sourceUnits.addressOfNextCodeUnit(), &dval))
         {
             return false;
         }
-    } else {
+    }
+#ifdef ENABLE_BIGINT
+    else if (unit == 'n') {
+        isBigInt = true;
+        unit = peekCodeUnit();
+    }
+#endif
+    else {
         // Consume any decimal dot and fractional component.
         if (unit == '.') {
             decimalPoint = HasDecimal;
             do {
                 unit = getCodeUnit();
             } while (IsAsciiDigit(unit));
         }
 
@@ -2341,16 +2351,23 @@ TokenStreamSpecific<Unit, AnyCharsAccess
             if (!peeked.isNone() && unicode::IsIdentifierStart(peeked.codePoint())) {
                 error(JSMSG_IDSTART_AFTER_NUMBER);
                 return false;
             }
         }
     }
 
     noteBadToken.release();
+
+#ifdef ENABLE_BIGINT
+    if (isBigInt) {
+        return bigIntLiteral(start, modifier, out);
+    }
+#endif
+
     newNumberToken(dval, decimalPoint, start, modifier, out);
     return true;
 }
 
 template<typename Unit, class AnyCharsAccess>
 MOZ_MUST_USE bool
 TokenStreamSpecific<Unit, AnyCharsAccess>::regexpLiteral(TokenStart start, TokenKind* out)
 {
@@ -2471,16 +2488,45 @@ TokenStreamSpecific<Unit, AnyCharsAccess
         reflags = RegExpFlag(reflags | flag);
     }
     ungetCodeUnit(unit);
 
     newRegExpToken(reflags, start, out);
     return true;
 }
 
+#ifdef ENABLE_BIGINT
+template<typename Unit, class AnyCharsAccess>
+MOZ_MUST_USE bool
+TokenStreamSpecific<Unit, AnyCharsAccess>::bigIntLiteral(TokenStart start,
+                                                         Modifier modifier,
+                                                         TokenKind* out)
+{
+    MOZ_ASSERT(this->sourceUnits.previousCodeUnit() == toUnit('n'));
+    MOZ_ASSERT(this->sourceUnits.offset() > start.offset());
+    uint32_t length = this->sourceUnits.offset() - start.offset();
+    MOZ_ASSERT(length >= 2);
+    this->charBuffer.clear();
+    mozilla::Range<const Unit> chars(this->sourceUnits.codeUnitPtrAt(start.offset()),
+                                     length);
+    for (uint32_t idx = 0; idx < length - 1; idx++) {
+        int32_t unit = CodeUnitValue(chars[idx]);
+        // Char buffer may start with a 0[bBoOxX] prefix, then follows with
+        // binary, octal, decimal, or hex digits.  Already checked by caller, as
+        // the "n" indicating bigint comes at the end.
+        MOZ_ASSERT(isAsciiCodePoint(unit));
+        if (!this->appendCodePointToCharBuffer(unit)) {
+            return false;
+        }
+    }
+    newBigIntToken(start, modifier, out);
+    return true;
+}
+#endif
+
 template<typename Unit, class AnyCharsAccess>
 MOZ_MUST_USE bool
 TokenStreamSpecific<Unit, AnyCharsAccess>::getTokenInternal(TokenKind* const ttp,
                                                             const Modifier modifier)
 {
     // Assume we'll fail: success cases will overwrite this.
 #ifdef DEBUG
     *ttp = TokenKind::Limit;
@@ -2635,18 +2681,21 @@ TokenStreamSpecific<Unit, AnyCharsAccess
         }
 
         // From a '0', look for a hexadecimal, binary, octal, or "noctal" (a
         // number starting with '0' that contains '8' or '9' and is treated as
         // decimal) number.
         //
         if (c1kind == ZeroDigit) {
             TokenStart start(this->sourceUnits, -1);
-
             int radix;
+#ifdef ENABLE_BIGINT
+            bool isLegacyOctalOrNoctal = false;
+            bool isBigInt = false;
+#endif
             const Unit* numStart;
             unit = getCodeUnit();
             if (unit == 'x' || unit == 'X') {
                 radix = 16;
                 unit = getCodeUnit();
                 if (!JS7_ISHEX(unit)) {
                     // NOTE: |unit| may be EOF here.
                     ungetCodeUnit(unit);
@@ -2689,16 +2738,19 @@ TokenStreamSpecific<Unit, AnyCharsAccess
                 // one past the '0o'
                 numStart = this->sourceUnits.addressOfNextCodeUnit() - 1;
 
                 while (JS7_ISOCT(unit)) {
                     unit = getCodeUnit();
                 }
             } else if (IsAsciiDigit(unit)) {
                 radix = 8;
+#ifdef ENABLE_BIGINT
+                isLegacyOctalOrNoctal = true;
+#endif
                 // one past the '0'
                 numStart = this->sourceUnits.addressOfNextCodeUnit() - 1;
 
                 do {
                     // Octal integer literals are not permitted in strict mode
                     // code.
                     if (!reportStrictModeError(JSMSG_DEPRECATED_OCTAL)) {
                         return badToken();
@@ -2723,17 +2775,30 @@ TokenStreamSpecific<Unit, AnyCharsAccess
                 // '0' not followed by [XxBbOo0-9];  scan as a decimal number.
                 numStart = this->sourceUnits.addressOfNextCodeUnit() - 1;
 
                 // NOTE: |unit| may be EOF here.  (This is permitted by case #3
                 //       in TokenStream.h docs for this function.)
                 return decimalNumber(unit, start, numStart, modifier, ttp);
             }
 
+#ifdef ENABLE_BIGINT
+            if (unit == 'n') {
+                if (isLegacyOctalOrNoctal) {
+                    error(JSMSG_BIGINT_INVALID_SYNTAX);
+                    return badToken();
+                }
+                isBigInt = true;
+                unit = peekCodeUnit();
+            } else {
+                ungetCodeUnit(unit);
+            }
+#else
             ungetCodeUnit(unit);
+#endif
 
             // Error if an identifier-start code point appears immediately
             // after the number.  Somewhat surprisingly, if we don't check
             // here, we'll never check at all.
             if (MOZ_LIKELY(isAsciiCodePoint(unit))) {
                 if (unicode::IsIdentifierStart(char16_t(unit))) {
                     error(JSMSG_IDSTART_AFTER_NUMBER);
                     return badToken();
@@ -2743,16 +2808,22 @@ TokenStreamSpecific<Unit, AnyCharsAccess
                 // handle source text after the number will do so.
                 PeekedCodePoint<Unit> peeked = this->sourceUnits.peekCodePoint();
                 if (!peeked.isNone() && unicode::IsIdentifierStart(peeked.codePoint())) {
                     error(JSMSG_IDSTART_AFTER_NUMBER);
                     return badToken();
                 }
             }
 
+#ifdef ENABLE_BIGINT
+            if (isBigInt) {
+                return bigIntLiteral(start, modifier, ttp);
+            }
+#endif
+
             double dval;
             if (!GetFullInteger(anyCharsAccess().cx, numStart,
                                 this->sourceUnits.addressOfNextCodeUnit(), radix, &dval))
             {
                 return badToken();
             }
             newNumberToken(dval, NoDecimal, start, modifier, ttp);
             return true;
--- a/js/src/frontend/TokenStream.h
+++ b/js/src/frontend/TokenStream.h
@@ -1906,16 +1906,23 @@ class GeneralTokenStreamChars
 
     void newNumberToken(double dval, DecimalPoint decimalPoint, TokenStart start,
                         TokenStreamShared::Modifier modifier, TokenKind* out)
     {
         Token* token = newToken(TokenKind::Number, start, modifier, out);
         token->setNumber(dval, decimalPoint);
     }
 
+#ifdef ENABLE_BIGINT
+    void newBigIntToken(TokenStart start, TokenStreamShared::Modifier modifier, TokenKind* out)
+    {
+        newToken(TokenKind::BigInt, start, modifier, out);
+    }
+#endif
+
     void newAtomToken(TokenKind kind, JSAtom* atom, TokenStart start,
                       TokenStreamShared::Modifier modifier, TokenKind* out)
     {
         MOZ_ASSERT(kind == TokenKind::String ||
                    kind == TokenKind::TemplateHead ||
                    kind == TokenKind::NoSubsTemplate);
 
         Token* token = newToken(kind, start, modifier, out);
@@ -2344,16 +2351,19 @@ class MOZ_STACK_CLASS TokenStreamSpecifi
     using CharsBase::matchCodeUnit;
     using CharsBase::matchLineTerminator;
     using GeneralCharsBase::matchUnicodeEscapeIdent;
     using GeneralCharsBase::matchUnicodeEscapeIdStart;
     using GeneralCharsBase::newAtomToken;
     using GeneralCharsBase::newNameToken;
     using GeneralCharsBase::newPrivateNameToken;
     using GeneralCharsBase::newNumberToken;
+#ifdef ENABLE_BIGINT
+    using GeneralCharsBase::newBigIntToken;
+#endif
     using GeneralCharsBase::newRegExpToken;
     using GeneralCharsBase::newSimpleToken;
     using CharsBase::peekCodeUnit;
     // Deliberately don't |using| |sourceUnits| because of bug 1472569.  :-(
     using CharsBase::toUnit;
     using GeneralCharsBase::ungetCodeUnit;
     using GeneralCharsBase::updateLineInfoForEOL;
 
@@ -2511,16 +2521,22 @@ class MOZ_STACK_CLASS TokenStreamSpecifi
      * And incredibly, it *improves* on the goto-based horror that predated it.
      */
     MOZ_MUST_USE bool decimalNumber(int32_t unit, TokenStart start, const Unit* numStart,
                                     Modifier modifier, TokenKind* out);
 
     /** Tokenize a regular expression literal beginning at |start|. */
     MOZ_MUST_USE bool regexpLiteral(TokenStart start, TokenKind* out);
 
+    /**
+     * Slurp characters between |start| and sourceUnits.current() into
+     * charBuffer, to later parse into a bigint.
+     */
+    MOZ_MUST_USE bool bigIntLiteral(TokenStart start, Modifier modifier, TokenKind* out);
+
   public:
     // Advance to the next token.  If the token stream encountered an error,
     // return false.  Otherwise return true and store the token kind in |*ttp|.
     MOZ_MUST_USE bool getToken(TokenKind* ttp, Modifier modifier = None) {
         // Check for a pushed-back token resulting from mismatching lookahead.
         TokenStreamAnyChars& anyChars = anyCharsAccess();
         if (anyChars.lookahead != 0) {
             MOZ_ASSERT(!anyChars.flags.hadError);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/xdr/bigint.js
@@ -0,0 +1,10 @@
+// |jit-test| skip-if: !('BigInt' in this)
+load(libdir + 'bytecode-cache.js');
+
+let test = `
+  assertEq(2n**64n - 1n, BigInt("0xffffFFFFffffFFFF"));
+`;
+evalWithCache(test, {
+  assertEqBytecode: true,
+  assertEqResult : true
+});
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -1866,16 +1866,25 @@ BaselineCompiler::emit_JSOP_RESUMEINDEX(
 
 bool
 BaselineCompiler::emit_JSOP_DOUBLE()
 {
     frame.push(script->getConst(GET_UINT32_INDEX(pc)));
     return true;
 }
 
+#ifdef ENABLE_BIGINT
+bool
+BaselineCompiler::emit_JSOP_BIGINT()
+{
+    frame.push(script->getConst(GET_UINT32_INDEX(pc)));
+    return true;
+}
+#endif
+
 bool
 BaselineCompiler::emit_JSOP_STRING()
 {
     frame.push(StringValue(script->getAtom(pc)));
     return true;
 }
 
 bool
--- a/js/src/jit/BaselineCompiler.h
+++ b/js/src/jit/BaselineCompiler.h
@@ -49,16 +49,17 @@ namespace jit {
     _(JSOP_ZERO)               \
     _(JSOP_ONE)                \
     _(JSOP_INT8)               \
     _(JSOP_INT32)              \
     _(JSOP_UINT16)             \
     _(JSOP_UINT24)             \
     _(JSOP_RESUMEINDEX)        \
     _(JSOP_DOUBLE)             \
+    IF_BIGINT(_(JSOP_BIGINT),) \
     _(JSOP_STRING)             \
     _(JSOP_SYMBOL)             \
     _(JSOP_OBJECT)             \
     _(JSOP_CALLSITEOBJ)        \
     _(JSOP_REGEXP)             \
     _(JSOP_LAMBDA)             \
     _(JSOP_LAMBDA_ARROW)       \
     _(JSOP_SETFUNNAME)         \
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -1985,16 +1985,19 @@ IonBuilder::inspectOpcode(JSOp op)
       case JSOP_STRICTNE:
       case JSOP_LT:
       case JSOP_LE:
       case JSOP_GT:
       case JSOP_GE:
         return jsop_compare(op);
 
       case JSOP_DOUBLE:
+#ifdef ENABLE_BIGINT
+      case JSOP_BIGINT:
+#endif
         pushConstant(info().getConst(pc));
         return Ok();
 
       case JSOP_STRING:
         pushConstant(StringValue(info().getAtom(pc)));
         return Ok();
 
       case JSOP_SYMBOL: {
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4403,16 +4403,23 @@ JS::GetPromiseID(JS::HandleObject promis
 JS_PUBLIC_API(JS::Value)
 JS::GetPromiseResult(JS::HandleObject promiseObj)
 {
     PromiseObject* promise = &promiseObj->as<PromiseObject>();
     MOZ_ASSERT(promise->state() != JS::PromiseState::Pending);
     return promise->state() == JS::PromiseState::Fulfilled ? promise->value() : promise->reason();
 }
 
+JS_PUBLIC_API(bool)
+JS::GetPromiseIsHandled(JS::HandleObject promiseObj)
+{
+    PromiseObject* promise = &promiseObj->as<PromiseObject>();
+    return !promise->isUnhandled();
+}
+
 JS_PUBLIC_API(JSObject*)
 JS::GetPromiseAllocationSite(JS::HandleObject promise)
 {
     return promise->as<PromiseObject>().allocationSite();
 }
 
 JS_PUBLIC_API(JSObject*)
 JS::GetPromiseResolutionSite(JS::HandleObject promise)
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -3390,16 +3390,24 @@ GetPromiseID(JS::HandleObject promise);
 /**
  * Returns the given Promise's result: either the resolution value for
  * fulfilled promises, or the rejection reason for rejected ones.
  */
 extern JS_PUBLIC_API(JS::Value)
 GetPromiseResult(JS::HandleObject promise);
 
 /**
+ * Returns whether the given promise's rejection is already handled or not.
+ *
+ * The caller must check the given promise is rejected before checking it's handled or not.
+ */
+extern JS_PUBLIC_API(bool)
+GetPromiseIsHandled(JS::HandleObject promise);
+
+/**
  * Returns a js::SavedFrame linked list of the stack that lead to the given
  * Promise's allocation.
  */
 extern JS_PUBLIC_API(JSObject*)
 GetPromiseAllocationSite(JS::HandleObject promise);
 
 extern JS_PUBLIC_API(JSObject*)
 GetPromiseResolutionSite(JS::HandleObject promise);
--- a/js/src/tests/jstests.list
+++ b/js/src/tests/jstests.list
@@ -103,49 +103,83 @@ skip script test262/language/expressions
 skip script test262/language/expressions/function/name.js
 skip script test262/language/expressions/generators/name.js
 
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1317422
 skip script test262/built-ins/global/property-descriptor.js
 skip script test262/built-ins/global/global-object.js
 
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1079853
+skip script test262/built-ins/TypedArray/prototype/every/BigInt/callbackfn-detachbuffer.js
 skip script test262/built-ins/TypedArray/prototype/every/callbackfn-detachbuffer.js
+skip script test262/built-ins/TypedArray/prototype/filter/BigInt/callbackfn-detachbuffer.js
 skip script test262/built-ins/TypedArray/prototype/filter/callbackfn-detachbuffer.js
+skip script test262/built-ins/TypedArray/prototype/find/BigInt/predicate-may-detach-buffer.js
 skip script test262/built-ins/TypedArray/prototype/find/predicate-may-detach-buffer.js
+skip script test262/built-ins/TypedArray/prototype/findIndex/BigInt/predicate-may-detach-buffer.js
 skip script test262/built-ins/TypedArray/prototype/findIndex/predicate-may-detach-buffer.js
+skip script test262/built-ins/TypedArray/prototype/forEach/BigInt/callbackfn-detachbuffer.js
 skip script test262/built-ins/TypedArray/prototype/forEach/callbackfn-detachbuffer.js
+skip script test262/built-ins/TypedArray/prototype/map/BigInt/callbackfn-detachbuffer.js
 skip script test262/built-ins/TypedArray/prototype/map/callbackfn-detachbuffer.js
+skip script test262/built-ins/TypedArray/prototype/reduce/BigInt/callbackfn-detachbuffer.js
+skip script test262/built-ins/TypedArray/prototype/reduce/callbackfn-detachbuffer.js
+skip script test262/built-ins/TypedArray/prototype/reduceRight/BigInt/callbackfn-detachbuffer.js
 skip script test262/built-ins/TypedArray/prototype/reduceRight/callbackfn-detachbuffer.js
-skip script test262/built-ins/TypedArray/prototype/reduce/callbackfn-detachbuffer.js
+skip script test262/built-ins/TypedArray/prototype/some/BigInt/callbackfn-detachbuffer.js
 skip script test262/built-ins/TypedArray/prototype/some/callbackfn-detachbuffer.js
+skip script test262/built-ins/TypedArrayConstructors/internals/DefineOwnProperty/BigInt/detached-buffer-realm.js
+skip script test262/built-ins/TypedArrayConstructors/internals/DefineOwnProperty/BigInt/detached-buffer.js
+skip script test262/built-ins/TypedArrayConstructors/internals/DefineOwnProperty/BigInt/tonumber-value-detached-buffer.js
 skip script test262/built-ins/TypedArrayConstructors/internals/DefineOwnProperty/detached-buffer-realm.js
 skip script test262/built-ins/TypedArrayConstructors/internals/DefineOwnProperty/detached-buffer.js
 skip script test262/built-ins/TypedArrayConstructors/internals/DefineOwnProperty/tonumber-value-detached-buffer.js
+skip script test262/built-ins/TypedArrayConstructors/internals/Get/BigInt/detached-buffer-realm.js
+skip script test262/built-ins/TypedArrayConstructors/internals/Get/BigInt/detached-buffer.js
+skip script test262/built-ins/TypedArrayConstructors/internals/Get/BigInt/infinity-detached-buffer.js
 skip script test262/built-ins/TypedArrayConstructors/internals/Get/detached-buffer-realm.js
 skip script test262/built-ins/TypedArrayConstructors/internals/Get/detached-buffer.js
 skip script test262/built-ins/TypedArrayConstructors/internals/Get/infinity-detached-buffer.js
+skip script test262/built-ins/TypedArrayConstructors/internals/GetOwnProperty/BigInt/detached-buffer-realm.js
+skip script test262/built-ins/TypedArrayConstructors/internals/GetOwnProperty/BigInt/detached-buffer.js
+skip script test262/built-ins/TypedArrayConstructors/internals/GetOwnProperty/BigInt/enumerate-detached-buffer.js
 skip script test262/built-ins/TypedArrayConstructors/internals/GetOwnProperty/detached-buffer-realm.js
 skip script test262/built-ins/TypedArrayConstructors/internals/GetOwnProperty/detached-buffer.js
 skip script test262/built-ins/TypedArrayConstructors/internals/GetOwnProperty/enumerate-detached-buffer.js
+skip script test262/built-ins/TypedArrayConstructors/internals/HasProperty/BigInt/detached-buffer-realm.js
+skip script test262/built-ins/TypedArrayConstructors/internals/HasProperty/BigInt/detached-buffer.js
+skip script test262/built-ins/TypedArrayConstructors/internals/HasProperty/BigInt/infinity-with-detached-buffer.js
 skip script test262/built-ins/TypedArrayConstructors/internals/HasProperty/detached-buffer-realm.js
 skip script test262/built-ins/TypedArrayConstructors/internals/HasProperty/detached-buffer.js
 skip script test262/built-ins/TypedArrayConstructors/internals/HasProperty/infinity-with-detached-buffer.js
+skip script test262/built-ins/TypedArrayConstructors/internals/Set/BigInt/detached-buffer-realm.js
+skip script test262/built-ins/TypedArrayConstructors/internals/Set/BigInt/detached-buffer.js
+skip script test262/built-ins/TypedArrayConstructors/internals/Set/BigInt/tonumber-value-detached-buffer.js
 skip script test262/built-ins/TypedArrayConstructors/internals/Set/detached-buffer-realm.js
 skip script test262/built-ins/TypedArrayConstructors/internals/Set/detached-buffer.js
 skip script test262/built-ins/TypedArrayConstructors/internals/Set/tonumber-value-detached-buffer.js
 
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1129202
+skip script test262/built-ins/TypedArrayConstructors/internals/DefineOwnProperty/BigInt/key-is-not-canonical-index.js
+skip script test262/built-ins/TypedArrayConstructors/internals/DefineOwnProperty/BigInt/key-is-not-integer.js
 skip script test262/built-ins/TypedArrayConstructors/internals/DefineOwnProperty/key-is-not-canonical-index.js
 skip script test262/built-ins/TypedArrayConstructors/internals/DefineOwnProperty/key-is-not-integer.js
+skip script test262/built-ins/TypedArrayConstructors/internals/Get/BigInt/key-is-not-canonical-index.js
+skip script test262/built-ins/TypedArrayConstructors/internals/Get/BigInt/key-is-not-integer.js
 skip script test262/built-ins/TypedArrayConstructors/internals/Get/key-is-not-canonical-index.js
 skip script test262/built-ins/TypedArrayConstructors/internals/Get/key-is-not-integer.js
+skip script test262/built-ins/TypedArrayConstructors/internals/GetOwnProperty/BigInt/key-is-not-canonical-index.js
 skip script test262/built-ins/TypedArrayConstructors/internals/GetOwnProperty/key-is-not-canonical-index.js
+skip script test262/built-ins/TypedArrayConstructors/internals/HasProperty/BigInt/key-is-not-canonical-index.js
+skip script test262/built-ins/TypedArrayConstructors/internals/HasProperty/BigInt/key-is-not-integer.js
 skip script test262/built-ins/TypedArrayConstructors/internals/HasProperty/key-is-not-canonical-index.js
 skip script test262/built-ins/TypedArrayConstructors/internals/HasProperty/key-is-not-integer.js
+skip script test262/built-ins/TypedArrayConstructors/internals/Set/BigInt/key-is-not-canonical-index.js
+skip script test262/built-ins/TypedArrayConstructors/internals/Set/BigInt/key-is-not-integer.js
+skip script test262/built-ins/TypedArrayConstructors/internals/Set/BigInt/tonumber-value-throws.js
 skip script test262/built-ins/TypedArrayConstructors/internals/Set/key-is-not-canonical-index.js
 skip script test262/built-ins/TypedArrayConstructors/internals/Set/key-is-not-integer.js
 skip script test262/built-ins/TypedArrayConstructors/internals/Set/tonumber-value-throws.js
 
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1317405
 skip script test262/language/computed-property-names/class/static/method-number.js
 skip script test262/language/computed-property-names/class/static/method-string.js
 skip script test262/language/computed-property-names/class/static/method-symbol.js
@@ -271,16 +305,24 @@ skip script test262/built-ins/GeneratorF
 skip script test262/built-ins/Map/proto-from-ctor-realm.js
 skip script test262/built-ins/Number/proto-from-ctor-realm.js
 skip script test262/built-ins/Object/proto-from-ctor.js
 skip script test262/built-ins/Promise/proto-from-ctor-realm.js
 skip script test262/built-ins/RegExp/proto-from-ctor-realm.js
 skip script test262/built-ins/Set/proto-from-ctor-realm.js
 skip script test262/built-ins/SharedArrayBuffer/proto-from-ctor-realm.js
 skip script test262/built-ins/String/proto-from-ctor-realm.js
+skip script test262/built-ins/TypedArrayConstructors/ctors-bigint/buffer-arg/proto-from-ctor-realm.js
+skip script test262/built-ins/TypedArrayConstructors/ctors-bigint/buffer-arg/proto-from-ctor-realm-sab.js
+skip script test262/built-ins/TypedArrayConstructors/ctors-bigint/length-arg/proto-from-ctor-realm.js
+skip script test262/built-ins/TypedArrayConstructors/ctors-bigint/no-args/proto-from-ctor-realm.js
+skip script test262/built-ins/TypedArrayConstructors/ctors-bigint/object-arg/proto-from-ctor-realm.js
+skip script test262/built-ins/TypedArrayConstructors/ctors-bigint/typedarray-arg/other-ctor-buffer-ctor-custom-species-proto-from-ctor-realm.js
+skip script test262/built-ins/TypedArrayConstructors/ctors-bigint/typedarray-arg/proto-from-ctor-realm.js
+skip script test262/built-ins/TypedArrayConstructors/ctors-bigint/typedarray-arg/same-ctor-buffer-ctor-species-custom-proto-from-ctor-realm.js
 skip script test262/built-ins/TypedArrayConstructors/ctors/buffer-arg/proto-from-ctor-realm.js
 skip script test262/built-ins/TypedArrayConstructors/ctors/buffer-arg/proto-from-ctor-realm-sab.js
 skip script test262/built-ins/TypedArrayConstructors/ctors/length-arg/proto-from-ctor-realm.js
 skip script test262/built-ins/TypedArrayConstructors/ctors/no-args/proto-from-ctor-realm.js
 skip script test262/built-ins/TypedArrayConstructors/ctors/object-arg/proto-from-ctor-realm.js
 skip script test262/built-ins/TypedArrayConstructors/ctors/typedarray-arg/other-ctor-buffer-ctor-custom-species-proto-from-ctor-realm.js
 skip script test262/built-ins/TypedArrayConstructors/ctors/typedarray-arg/proto-from-ctor-realm.js
 skip script test262/built-ins/TypedArrayConstructors/ctors/typedarray-arg/same-ctor-buffer-ctor-species-custom-proto-from-ctor-realm.js
--- a/js/src/tests/non262/reflect-parse/expression.js
+++ b/js/src/tests/non262/reflect-parse/expression.js
@@ -3,16 +3,19 @@ function test() {
 
 // primary expressions
 assertExpr("true", lit(true));
 assertExpr("false", lit(false));
 assertExpr("42", lit(42));
 assertExpr("(/asdf/)", lit(/asdf/));
 assertExpr("this", thisExpr);
 assertExpr("foo", ident("foo"));
+if ("BigInt" in this) {
+    assertExpr("1234n", lit(BigInt(1234)));
+}
 
 // member expressions
 assertExpr("foo.bar", dotExpr(ident("foo"), ident("bar")));
 assertExpr("foo[bar]", memExpr(ident("foo"), ident("bar")));
 assertExpr("foo['bar']", memExpr(ident("foo"), lit("bar")));
 assertExpr("foo[42]", memExpr(ident("foo"), lit(42)));
 
 // function expressions
--- a/js/src/vm/BigIntType.cpp
+++ b/js/src/vm/BigIntType.cpp
@@ -23,16 +23,18 @@
 #include "builtin/BigInt.h"
 #include "gc/Allocator.h"
 #include "gc/Tracer.h"
 #include "js/Initialization.h"
 #include "js/Utility.h"
 #include "vm/JSContext.h"
 #include "vm/SelfHosting.h"
 
+#include "vm/JSContext-inl.h"
+
 using namespace js;
 
 using mozilla::Abs;
 using mozilla::BitwiseCast;
 using mozilla::CheckedInt;
 using mozilla::Maybe;
 using mozilla::Some;
 using mozilla::Nothing;
@@ -1059,16 +1061,32 @@ js::StringToBigInt(JSContext* cx, Handle
                 return res.get();
             }
         }
     }
 
     return nullptr;
 }
 
+BigInt*
+js::StringToBigInt(JSContext* cx, const Range<const char16_t>& chars)
+{
+    RootedBigInt res(cx, BigInt::create(cx));
+    if (!res) {
+        return nullptr;
+    }
+
+    uint8_t radix = 0;
+    if (StringToBigIntImpl(chars, radix, res)) {
+        return res.get();
+    }
+
+    return nullptr;
+}
+
 size_t
 BigInt::byteLength(BigInt* x)
 {
     if (mpz_sgn(x->num_) == 0) {
         return 0;
     }
     return JS_HOWMANY(mpz_sizeinbase(x->num_, 2), 8);
 }
@@ -1142,8 +1160,63 @@ JS::ubi::Node::Size
 JS::ubi::Concrete<BigInt>::size(mozilla::MallocSizeOf mallocSizeOf) const
 {
     BigInt& bi = get();
     MOZ_ASSERT(bi.isTenured());
     size_t size = js::gc::Arena::thingSize(bi.asTenured().getAllocKind());
     size += bi.sizeOfExcludingThis(mallocSizeOf);
     return size;
 }
+
+template<XDRMode mode>
+XDRResult
+js::XDRBigInt(XDRState<mode>* xdr, MutableHandleBigInt bi)
+{
+    JSContext* cx = xdr->cx();
+
+    uint8_t sign;
+    uint32_t length;
+
+    if (mode == XDR_ENCODE) {
+        cx->check(bi);
+        sign = static_cast<uint8_t>(bi->sign());
+        uint64_t sz = BigInt::byteLength(bi);
+        // As the maximum source code size is currently UINT32_MAX code units
+        // (see BytecodeCompiler::checkLength), any bigint literal's length in
+        // word-sized digits will be less than UINT32_MAX as well.  That could
+        // change or FoldConstants could start creating these though, so leave
+        // this as a release-enabled assert.
+        MOZ_RELEASE_ASSERT(sz <= UINT32_MAX);
+        length = static_cast<uint32_t>(sz);
+    }
+
+    MOZ_TRY(xdr->codeUint8(&sign));
+    MOZ_TRY(xdr->codeUint32(&length));
+    
+    UniquePtr<uint8_t> buf(cx->pod_malloc<uint8_t>(length));
+    if (!buf) {
+        ReportOutOfMemory(cx);
+        return xdr->fail(JS::TranscodeResult_Throw);
+    }
+
+    if (mode == XDR_ENCODE) {
+        BigInt::writeBytes(bi, RangedPtr<uint8_t>(buf.get(), length));
+    }
+
+    MOZ_TRY(xdr->codeBytes(buf.get(), length));
+
+    if (mode == XDR_DECODE) {
+        BigInt* res = BigInt::createFromBytes(cx, static_cast<int8_t>(sign),
+                                              buf.get(), length);
+        if (!res) {
+            return xdr->fail(JS::TranscodeResult_Throw);
+        }
+        bi.set(res);
+    }
+
+    return Ok();
+}
+
+template XDRResult
+js::XDRBigInt(XDRState<XDR_ENCODE>* xdr, MutableHandleBigInt bi);
+
+template XDRResult
+js::XDRBigInt(XDRState<XDR_DECODE>* xdr, MutableHandleBigInt bi);
--- a/js/src/vm/BigIntType.h
+++ b/js/src/vm/BigIntType.h
@@ -16,33 +16,39 @@
 #include "gc/GC.h"
 #include "gc/Heap.h"
 #include "js/AllocPolicy.h"
 #include "js/GCHashTable.h"
 #include "js/Result.h"
 #include "js/RootingAPI.h"
 #include "js/TypeDecls.h"
 #include "vm/StringType.h"
+#include "vm/Xdr.h"
 
 namespace js {
 
 template <typename CharT>
 static bool StringToBigIntImpl(const mozilla::Range<const CharT>& chars,
                                uint8_t radix, Handle<JS::BigInt*> res);
 
+template<XDRMode mode>
+XDRResult XDRBigInt(XDRState<mode>* xdr, MutableHandleBigInt bi);
+
 } // namespace js
 
 namespace JS {
 
 class BigInt final : public js::gc::TenuredCell
 {
     // StringToBigIntImpl modifies the num_ field of the res argument.
     template <typename CharT>
     friend bool js::StringToBigIntImpl(const mozilla::Range<const CharT>& chars,
                                        uint8_t radix, Handle<BigInt*> res);
+    template <js::XDRMode mode>
+    friend js::XDRResult js::XDRBigInt(js::XDRState<mode>* xdr, MutableHandleBigInt bi);
 
   protected:
     // Reserved word for Cell GC invariants. This also ensures minimum
     // structure size.
     uintptr_t reserved_;
 
   private:
     mpz_t num_;
@@ -156,14 +162,18 @@ BigIntToAtom(JSContext* cx, JS::BigInt* 
 
 extern JS::BigInt*
 NumberToBigInt(JSContext* cx, double d);
 
 // Convert a string to a BigInt, returning nullptr if parsing fails.
 extern JS::Result<JS::BigInt*, JS::OOM&>
 StringToBigInt(JSContext* cx, JS::Handle<JSString*> str, uint8_t radix);
 
+// Same.
+extern JS::BigInt*
+StringToBigInt(JSContext* cx, const mozilla::Range<const char16_t>& chars);
+
 extern JS::BigInt*
 ToBigInt(JSContext* cx, JS::Handle<JS::Value> v);
 
 } // namespace js
 
 #endif
--- a/js/src/vm/BytecodeUtil.cpp
+++ b/js/src/vm/BytecodeUtil.cpp
@@ -1490,16 +1490,20 @@ Disassemble1(JSContext* cx, HandleScript
             return 0;
         }
         if (!sp->jsprintf(" %s", bytes.get())) {
             return 0;
         }
         break;
       }
 
+#ifdef ENABLE_BIGINT
+      case JOF_BIGINT:
+        // Fallthrough.
+#endif
       case JOF_DOUBLE: {
         RootedValue v(cx, script->getConst(GET_UINT32_INDEX(pc)));
         UniqueChars bytes = ToDisassemblySource(cx, v);
         if (!bytes) {
             return 0;
         }
         if (!sp->jsprintf(" %s", bytes.get())) {
             return 0;
--- a/js/src/vm/BytecodeUtil.h
+++ b/js/src/vm/BytecodeUtil.h
@@ -52,16 +52,19 @@ enum {
     JOF_QARG            = 11,       /* function argument index */
     JOF_LOCAL           = 12,       /* var or block-local variable */
     JOF_RESUMEINDEX     = 13,       /* yield, await, or gosub resume index */
     JOF_ATOM            = 14,       /* uint32_t constant index */
     JOF_OBJECT          = 15,       /* uint32_t object index */
     JOF_REGEXP          = 16,       /* uint32_t regexp index */
     JOF_DOUBLE          = 17,       /* uint32_t index for double value */
     JOF_SCOPE           = 18,       /* uint32_t scope index */
+#ifdef ENABLE_BIGINT
+    JOF_BIGINT          = 19,       /* uint32_t index for BigInt value */
+#endif
     JOF_TYPEMASK        = 0x001f,   /* mask for above immediate types */
 
     JOF_NAME            = 1 << 5,   /* name operation */
     JOF_PROP            = 2 << 5,   /* obj.prop operation */
     JOF_ELEM            = 3 << 5,   /* obj[index] operation */
     JOF_MODEMASK        = 3 << 5,   /* mask for above addressing modes */
 
     JOF_PROPSET         = 1 << 7,   /* property/element/name set operation */
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -4785,16 +4785,25 @@ CASE(JSOP_DEBUGCHECKSELFHOSTED)
 #endif
 }
 END_CASE(JSOP_DEBUGCHECKSELFHOSTED)
 
 CASE(JSOP_IS_CONSTRUCTING)
     PUSH_MAGIC(JS_IS_CONSTRUCTING);
 END_CASE(JSOP_IS_CONSTRUCTING)
 
+#ifdef ENABLE_BIGINT
+CASE(JSOP_BIGINT)
+{
+    PUSH_COPY(script->getConst(GET_UINT32_INDEX(REGS.pc)));
+    MOZ_ASSERT(REGS.sp[-1].isBigInt());
+}
+END_CASE(JSOP_BIGINT)
+#endif
+
 DEFAULT()
 {
     char numBuf[12];
     SprintfLiteral(numBuf, "%d", *REGS.pc);
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_BYTECODE, numBuf);
     goto error;
 }
 
--- a/js/src/vm/JSContext-inl.h
+++ b/js/src/vm/JSContext-inl.h
@@ -7,16 +7,19 @@
 #ifndef vm_JSContext_inl_h
 #define vm_JSContext_inl_h
 
 #include "vm/JSContext.h"
 
 #include "builtin/Object.h"
 #include "jit/JitFrames.h"
 #include "proxy/Proxy.h"
+#ifdef ENABLE_BIGINT
+#include "vm/BigIntType.h"
+#endif
 #include "vm/HelperThreads.h"
 #include "vm/Interpreter.h"
 #include "vm/Iteration.h"
 #include "vm/Realm.h"
 #include "vm/SymbolType.h"
 
 namespace js {
 
@@ -109,24 +112,35 @@ class ContextChecks
             check(str->zone(), argIndex);
         }
     }
 
     void check(JS::Symbol* symbol, int argIndex) {
         checkAtom(symbol, argIndex);
     }
 
+#ifdef ENABLE_BIGINT
+    void check(JS::BigInt* bi, int argIndex) {
+        check(bi->zone(), argIndex);
+    }
+#endif
+
     void check(const js::Value& v, int argIndex) {
         if (v.isObject()) {
             check(&v.toObject(), argIndex);
         } else if (v.isString()) {
             check(v.toString(), argIndex);
         } else if (v.isSymbol()) {
             check(v.toSymbol(), argIndex);
         }
+#ifdef ENABLE_BIGINT
+        else if (v.isBigInt()) {
+            check(v.toBigInt(), argIndex);
+        }
+#endif
     }
 
     // Check the contents of any container class that supports the C++
     // iteration protocol, eg GCVector<jsid>.
     template <typename Container>
     typename mozilla::EnableIf<
         mozilla::IsSame<
             decltype(((Container*)nullptr)->begin()),
--- a/js/src/vm/JSScript.cpp
+++ b/js/src/vm/JSScript.cpp
@@ -96,17 +96,20 @@ js::XDRScriptConst(XDRState<mode>* xdr, 
         SCRIPT_INT,
         SCRIPT_DOUBLE,
         SCRIPT_ATOM,
         SCRIPT_TRUE,
         SCRIPT_FALSE,
         SCRIPT_NULL,
         SCRIPT_OBJECT,
         SCRIPT_VOID,
-        SCRIPT_HOLE
+        SCRIPT_HOLE,
+#ifdef ENABLE_BIGINT
+        SCRIPT_BIGINT
+#endif
     };
 
     ConstTag tag;
     if (mode == XDR_ENCODE) {
         if (vp.isInt32()) {
             tag = SCRIPT_INT;
         } else if (vp.isDouble()) {
             tag = SCRIPT_DOUBLE;
@@ -117,17 +120,23 @@ js::XDRScriptConst(XDRState<mode>* xdr, 
         } else if (vp.isFalse()) {
             tag = SCRIPT_FALSE;
         } else if (vp.isNull()) {
             tag = SCRIPT_NULL;
         } else if (vp.isObject()) {
             tag = SCRIPT_OBJECT;
         } else if (vp.isMagic(JS_ELEMENTS_HOLE)) {
             tag = SCRIPT_HOLE;
-        } else {
+        }
+#ifdef ENABLE_BIGINT
+        else if (vp.isBigInt()) {
+            tag = SCRIPT_BIGINT;
+        }
+#endif
+        else {
             MOZ_ASSERT(vp.isUndefined());
             tag = SCRIPT_VOID;
         }
     }
 
     MOZ_TRY(xdr->codeEnum32(&tag));
 
     switch (tag) {
@@ -197,16 +206,31 @@ js::XDRScriptConst(XDRState<mode>* xdr, 
             vp.set(UndefinedValue());
         }
         break;
       case SCRIPT_HOLE:
         if (mode == XDR_DECODE) {
             vp.setMagic(JS_ELEMENTS_HOLE);
         }
         break;
+#ifdef ENABLE_BIGINT
+      case SCRIPT_BIGINT: {
+        RootedBigInt bi(cx);
+        if (mode == XDR_ENCODE) {
+            bi = vp.toBigInt();
+        }
+
+        MOZ_TRY(XDRBigInt(xdr, &bi));
+
+        if (mode == XDR_DECODE) {
+            vp.setBigInt(bi);
+        }
+        break;
+      }
+#endif
       default:
         // Fail in debug, but only soft-fail in release
         MOZ_ASSERT(false, "Bad XDR value kind");
         return xdr->fail(JS::TranscodeResult_Failure_BadDecode);
     }
     return Ok();
 }
 
@@ -3906,19 +3930,17 @@ js::detail::CopyScript(JSContext* cx, Ha
     }
 
     /* NB: Keep this in sync with XDRScript. */
 
     /* Some embeddings are not careful to use ExposeObjectToActiveJS as needed. */
     MOZ_ASSERT(!src->sourceObject()->isMarkedGray());
 
     uint32_t nscopes = src->scopes().size();
-#ifdef DEBUG
     uint32_t nconsts = src->hasConsts() ? src->consts().size() : 0;
-#endif
     uint32_t nobjects = src->hasObjects() ? src->objects().size() : 0;
 
     /* Script data */
 
     size_t size = src->dataSize();
     UniquePtr<uint8_t, JS::FreePolicy> data(AllocScriptData(cx, size));
     if (!data) {
         return false;
@@ -3939,16 +3961,51 @@ js::detail::CopyScript(JSContext* cx, Ha
             original = elem.get();
             clone = Scope::clone(cx, original, scopes[FindScopeIndex(src, *original->enclosing())]);
             if (!clone || !scopes.append(clone)) {
                 return false;
             }
         }
     }
 
+    /* Constants */
+
+    AutoValueVector consts(cx);
+    if (nconsts != 0) {
+        RootedValue val(cx);
+        RootedValue clone(cx);
+        for (const GCPtrValue& elem : src->consts()) {
+            val = elem.get();
+            if (val.isDouble()) {
+                clone = val;
+            }
+#ifdef ENABLE_BIGINT
+            else if (val.isBigInt()) {
+                if (cx->zone() == val.toBigInt()->zone()) {
+                    clone.setBigInt(val.toBigInt());
+                } else {
+                    RootedBigInt b(cx, val.toBigInt());
+                    BigInt* copy = BigInt::copy(cx, b);
+                    if (!copy) {
+                        return false;
+                    }
+                    clone.setBigInt(copy);
+                }
+            }
+#endif
+            else {
+                MOZ_ASSERT_UNREACHABLE("bad script consts() element");
+            }
+
+            if (!consts.append(clone)) {
+                return false;
+            }
+        }
+    }
+
     /* Objects */
 
     AutoObjectVector objects(cx);
     if (nobjects != 0) {
         RootedObject obj(cx);
         RootedObject clone(cx);
         for (const GCPtrObject& elem : src->objects()) {
             obj = elem.get();
@@ -4019,25 +4076,22 @@ js::detail::CopyScript(JSContext* cx, Ha
     }
 
     {
         auto array = dst->data_->scopes();
         for (uint32_t i = 0; i < nscopes; ++i) {
             array[i].init(scopes[i]);
         }
     }
-#ifdef DEBUG
     if (nconsts) {
         auto array = dst->data_->consts();
         for (unsigned i = 0; i < nconsts; ++i) {
-            // We don't support GCThings here and thus don't need to call |init|.
-            MOZ_ASSERT(!array[i].isGCThing());
+            array[i].init(consts[i]);
         }
     }
-#endif
     if (nobjects) {
         auto array = dst->data_->objects();
         for (unsigned i = 0; i < nobjects; ++i) {
             array[i].init(objects[i]);
         }
     }
 
     return true;
--- a/js/src/vm/Opcodes.h
+++ b/js/src/vm/Opcodes.h
@@ -2493,24 +2493,32 @@
      * Dynamic import of the module specified by the string value on the top of
      * the stack.
      *
      *   Category: Variables and Scopes
      *   Type: Modules
      *   Operands:
      *   Stack: arg => rval
      */ \
-    macro(JSOP_DYNAMIC_IMPORT, 233, "call-import", NULL, 1, 1, 1, JOF_BYTE)
+    macro(JSOP_DYNAMIC_IMPORT, 233, "call-import", NULL, 1, 1, 1, JOF_BYTE) \
+    /*
+     * Pushes a BigInt constant onto the stack.
+     *   Category: Literals
+     *   Type: Constants
+     *   Operands: uint32_t constIndex
+     *   Stack: => val
+     */ \
+    IF_BIGINT(macro(JSOP_BIGINT, 234, "bigint", NULL, 5, 0, 1, JOF_BIGINT),)
 
 /*
  * In certain circumstances it may be useful to "pad out" the opcode space to
  * a power of two.  Use this macro to do so.
  */
 #define FOR_EACH_TRAILING_UNUSED_OPCODE(macro) \
-    macro(234) \
+    IF_BIGINT(,macro(234)) \
     macro(235) \
     macro(236) \
     macro(237) \
     macro(238) \
     macro(239) \
     macro(240) \
     macro(241) \
     macro(242) \
--- a/js/src/wasm/AsmJS.cpp
+++ b/js/src/wasm/AsmJS.cpp
@@ -7469,17 +7469,17 @@ js::CompileAsmJS(JSContext* cx, AsmJSPar
         return false;
     }
 
     // Finished! Clobber the default function created by the parser with the new
     // asm.js module function. Special cases in the bytecode emitter avoid
     // generating bytecode for asm.js functions, allowing this asm.js module
     // function to be the finished result.
     MOZ_ASSERT(funbox->function()->isInterpreted());
-    funbox->object = moduleFun;
+    funbox->clobberFunction(moduleFun);
 
     // Success! Write to the console with a "warning" message.
     *validated = true;
     SuccessfulValidation(parser, std::move(message));
     return NoExceptionPending(cx);
 }
 
 /*****************************************************************************/
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -742,18 +742,17 @@ PresShell::AccessibleCaretEnabled(nsIDoc
       dom::TouchEvent::PrefEnabled(aDocShell)) {
     return true;
   }
   // Otherwise, disabled.
   return false;
 }
 
 nsIPresShell::nsIPresShell()
-    : mFrameConstructor(nullptr)
-    , mViewManager(nullptr)
+    : mViewManager(nullptr)
     , mFrameManager(nullptr)
 #ifdef ACCESSIBILITY
     , mDocAccessible(nullptr)
 #endif
 #ifdef DEBUG
     , mDrawEventTargetFrame(nullptr)
 #endif
     , mPaintCount(0)
@@ -828,17 +827,17 @@ PresShell::PresShell()
   , mForceDispatchKeyPressEventsForNonPrintableKeys(false)
   , mForceUseLegacyKeyCodeAndCharCodeValues(false)
   , mInitializedWithKeyPressEventDispatchingBlacklist(false)
 #endif // #ifdef NIGHTLY_BUILD
 {
   MOZ_LOG(gLog, LogLevel::Debug, ("PresShell::PresShell this=%p", this));
 
 #ifdef MOZ_REFLOW_PERF
-  mReflowCountMgr = new ReflowCountMgr();
+  mReflowCountMgr = MakeUnique<ReflowCountMgr>();
   mReflowCountMgr->SetPresContext(mPresContext);
   mReflowCountMgr->SetPresShell(this);
 #endif
   mLastOSWake = mLoadBegin = TimeStamp::Now();
 
   mSelectionFlags = nsISelectionDisplay::DISPLAY_TEXT | nsISelectionDisplay::DISPLAY_IMAGES;
   mIsActive = true;
   // FIXME/bug 735029: find a better solution to this problem
@@ -887,17 +886,18 @@ PresShell::~PresShell()
   // be re-used by another presentation.
   if (mPaintingIsFrozen) {
     mPresContext->RefreshDriver()->Thaw();
   }
 
   MOZ_ASSERT(mAllocatedPointers.IsEmpty(), "Some pres arena objects were not freed");
 
   mStyleSet = nullptr;
-  delete mFrameConstructor;
+  mFrameManager = nullptr;
+  mFrameConstructor = nullptr;
 
   mCurrentEventContent = nullptr;
 }
 
 /**
  * Initialize the presentation shell. Create view manager and style
  * manager.
  * Note this can't be merged into our constructor because caret initialization
@@ -924,19 +924,19 @@ PresShell::Init(nsIDocument* aDocument,
   // mDocument is now set.  It might have a display document whose "need layout/
   // style" flush flags are not set, but ours will be set.  To keep these
   // consistent, call the flag setting functions to propagate those flags up
   // to the display document.
   SetNeedLayoutFlush();
   SetNeedStyleFlush();
 
   // Create our frame constructor.
-  mFrameConstructor = new nsCSSFrameConstructor(mDocument, this);
-
-  mFrameManager = mFrameConstructor;
+  mFrameConstructor = MakeUnique<nsCSSFrameConstructor>(mDocument, this);
+
+  mFrameManager = mFrameConstructor.get();
 
   // The document viewer owns both view manager and pres shell.
   mViewManager->SetPresShell(this);
 
   // Bind the context to the presentation shell.
   mPresContext = aPresContext;
   mPresContext->AttachShell(this);
 
@@ -1177,20 +1177,17 @@ PresShell::Destroy()
     } else {
       Telemetry::Accumulate(Telemetry::WEBFONT_PER_PAGE, 0);
       Telemetry::Accumulate(Telemetry::WEBFONT_SIZE_PER_PAGE, 0);
     }
   }
 
 #ifdef MOZ_REFLOW_PERF
   DumpReflows();
-  if (mReflowCountMgr) {
-    delete mReflowCountMgr;
-    mReflowCountMgr = nullptr;
-  }
+  mReflowCountMgr = nullptr;
 #endif
 
   if (mZoomConstraintsClient) {
     mZoomConstraintsClient->Destroy();
     mZoomConstraintsClient = nullptr;
   }
   if (mMobileViewportManager) {
     mMobileViewportManager->Destroy();
@@ -6960,20 +6957,18 @@ PresShell::HandleEvent(nsIFrame* aFrame,
     }
   }
 
   if (aEvent->mClass == eKeyboardEventClass &&
       mDocument && mDocument->EventHandlingSuppressed()) {
     if (aEvent->mMessage == eKeyDown) {
       mNoDelayedKeyEvents = true;
     } else if (!mNoDelayedKeyEvents) {
-      DelayedEvent* event = new DelayedKeyEvent(aEvent->AsKeyboardEvent());
-      if (!mDelayedEvents.AppendElement(event)) {
-        delete event;
-      }
+      auto event = MakeUnique<DelayedKeyEvent>(aEvent->AsKeyboardEvent());
+      mDelayedEvents.AppendElement(std::move(event));
     }
     aEvent->mFlags.mIsSuppressedOrDelayed = true;
     return NS_OK;
   }
 
   nsIFrame* frame = aFrame;
 
   if (aEvent->IsUsingCoordinates()) {
@@ -7182,20 +7177,18 @@ PresShell::HandleEvent(nsIFrame* aFrame,
     if (aEvent->mClass == eMouseEventClass &&
         frame->PresContext()->Document()->EventHandlingSuppressed()) {
       if (aEvent->mMessage == eMouseDown) {
         mNoDelayedMouseEvents = true;
       } else if (!mNoDelayedMouseEvents && (aEvent->mMessage == eMouseUp ||
         // contextmenu is triggered after right mouseup on Windows and right
         // mousedown on other platforms.
         aEvent->mMessage == eContextMenu)) {
-        DelayedEvent* event = new DelayedMouseEvent(aEvent->AsMouseEvent());
-        if (!mDelayedEvents.AppendElement(event)) {
-          delete event;
-        }
+        auto event = MakeUnique<DelayedMouseEvent>(aEvent->AsMouseEvent());
+        mDelayedEvents.AppendElement(std::move(event));
       }
       return NS_OK;
     }
 
     if (!frame) {
       NS_WARNING("Nothing to handle this event!");
       return NS_OK;
     }
@@ -8722,17 +8715,17 @@ PresShell::FireOrClearDelayedEvents(bool
     mDelayedEvents.Clear();
     return;
   }
 
   if (mDocument) {
     nsCOMPtr<nsIDocument> doc = mDocument;
     while (!mIsDestroying && mDelayedEvents.Length() &&
            !doc->EventHandlingSuppressed()) {
-      nsAutoPtr<DelayedEvent> ev(mDelayedEvents[0].forget());
+      UniquePtr<DelayedEvent> ev = std::move(mDelayedEvents[0]);
       mDelayedEvents.RemoveElementAt(0);
       if (ev->IsKeyPressEvent() && mIsLastKeyDownCanceled) {
         continue;
       }
       ev->Dispatch();
     }
     if (!doc->EventHandlingSuppressed()) {
       mDelayedEvents.Clear();
--- a/layout/base/PresShell.h
+++ b/layout/base/PresShell.h
@@ -11,17 +11,16 @@
 
 #include "MobileViewportManager.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/layers/FocusTarget.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/ServoStyleSet.h"
 #include "mozilla/UniquePtr.h"
-#include "nsAutoPtr.h"
 #include "nsContentUtils.h" // For AddScriptBlocker().
 #include "nsCRT.h"
 #include "nsIObserver.h"
 #include "nsIPresShell.h"
 #include "nsISelectionController.h"
 #include "nsIWidget.h"
 #include "nsPresContext.h"
 #include "nsRefreshDriver.h"
@@ -756,17 +755,17 @@ private:
                                      nsIFrame* aFrame);
 #ifdef DEBUG
   // The reflow root under which we're currently reflowing.  Null when
   // not in reflow.
   nsIFrame* mCurrentReflowRoot;
 #endif
 
 #ifdef MOZ_REFLOW_PERF
-  ReflowCountMgr* mReflowCountMgr;
+  UniquePtr<ReflowCountMgr> mReflowCountMgr;
 #endif
 
   // This is used for synthetic mouse events that are sent when what is under
   // the mouse pointer may have changed without the mouse moving (eg scrolling,
   // change to the document contents).
   // It is set only on a presshell for a root document, this value represents
   // the last observed location of the mouse relative to that root document. It
   // is set to (NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE) if the mouse isn't
@@ -780,17 +779,17 @@ private:
 
   // mStyleSet owns it but we maintain a ref, may be null
   RefPtr<StyleSheet> mPrefStyleSheet;
 
   // Set of frames that we should mark with NS_FRAME_HAS_DIRTY_CHILDREN after
   // we finish reflowing mCurrentReflowRoot.
   nsTHashtable<nsPtrHashKey<nsIFrame> > mFramesToDirty;
 
-  nsTArray<nsAutoPtr<DelayedEvent> > mDelayedEvents;
+  nsTArray<UniquePtr<DelayedEvent>> mDelayedEvents;
 private:
   nsRevocableEventPtr<nsSynthMouseMoveEvent> mSynthMouseMoveEvent;
   nsCOMPtr<nsIContent> mLastAnchorScrolledTo;
   RefPtr<nsCaret> mCaret;
   RefPtr<nsCaret> mOriginalCaret;
   nsCallbackEventRequest* mFirstCallbackEventRequest;
   nsCallbackEventRequest* mLastCallbackEventRequest;
 
--- a/layout/base/nsIPresShell.h
+++ b/layout/base/nsIPresShell.h
@@ -276,17 +276,17 @@ public:
   void SetDocAccessible(mozilla::a11y::DocAccessible* aDocAccessible)
   {
     mDocAccessible = aDocAccessible;
   }
 #endif
 
   mozilla::ServoStyleSet* StyleSet() const { return mStyleSet.get(); }
 
-  nsCSSFrameConstructor* FrameConstructor() const { return mFrameConstructor; }
+  nsCSSFrameConstructor* FrameConstructor() const { return mFrameConstructor.get(); }
 
   /* Enable/disable author style level. Disabling author style disables the entire
    * author level of the cascade, including the HTML preshint level.
    */
   // XXX these could easily be inlined, but there is a circular #include
   // problem with nsStyleSet.
   void SetAuthorStyleDisabled(bool aDisabled);
   bool GetAuthorStyleDisabled() const;
@@ -1738,17 +1738,17 @@ protected:
   // has been explicitly checked.  If you add any members to this class,
   // please make the ownership explicit (pinkerton, scc).
 
   // These are the same Document and PresContext owned by the DocViewer.
   // we must share ownership.
   nsCOMPtr<nsIDocument>     mDocument;
   RefPtr<nsPresContext>   mPresContext;
   mozilla::UniquePtr<mozilla::ServoStyleSet> mStyleSet;
-  nsCSSFrameConstructor*    mFrameConstructor; // [OWNS]
+  mozilla::UniquePtr<nsCSSFrameConstructor> mFrameConstructor;
   nsViewManager*           mViewManager;   // [WEAK] docViewer owns it so I don't have to
   nsPresArena               mFrameArena;
   RefPtr<nsFrameSelection> mSelection;
   // Pointer into mFrameConstructor - this is purely so that GetRootFrame() can
   // be inlined:
   nsFrameManager*       mFrameManager;
   mozilla::WeakPtr<nsDocShell>                 mForwardingContainer;
 #ifdef ACCESSIBILITY
--- a/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
+++ b/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
@@ -1606,16 +1606,17 @@ JsepSessionImpl::UpdateTransceiversFromR
     if (!mSdpHelper.MsectionIsDisabled(msection)) {
       transceiver->Associate(msection.GetAttributeList().GetMid());
       if (!transceiver->IsAssociated()) {
         transceiver->Associate(GetNewMid());
       } else {
         mUsedMids.insert(transceiver->GetMid());
       }
     } else {
+      transceiver->mTransport.Close();
       transceiver->Disassociate();
       // This cannot be rolled back.
       transceiver->Stop();
       continue;
     }
 
     if (msection.GetMediaType() == SdpMediaSection::MediaType::kApplication) {
       continue;
--- a/mobile/android/app/geckoview-prefs.js
+++ b/mobile/android/app/geckoview-prefs.js
@@ -27,8 +27,11 @@ pref("geckoview.logging", "Debug");
 // Disable Web Push until we get it working
 pref("dom.push.enabled", false);
 
 // Unlike Fennec, GeckoView may have WebRender enabled, and with WebRender we're
 // going with containerless scrolling (because there are no layers at all with
 // WR, so why not go containerless). So we set this pref to pick up the value
 // in gfxPrefs.h from whether or not WR is enabled.
 pref("layout.scroll.root-frame-containers", 2);
+
+// Inherit locale from the OS, used for multi-locale builds
+pref("intl.locale.requested", "");
--- a/mobile/android/components/geckoview/GeckoViewStartup.js
+++ b/mobile/android/components/geckoview/GeckoViewStartup.js
@@ -151,15 +151,15 @@ GeckoViewStartup.prototype = {
             prefs.set(name, aData[name]);
           } catch (e) {
             warn `Failed to set preference ${name}: ${e}`;
           }
         }
         break;
       }
       case "GeckoView:SetLocale":
-        Services.locale.requestedLocales = [aData.languageTag];
+        Services.locale.requestedLocales = aData.requestedLocales;
         break;
     }
   },
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([GeckoViewStartup]);
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/LocaleTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/LocaleTest.kt
@@ -15,17 +15,17 @@ import org.junit.Test
 import org.junit.runner.RunWith
 
 @MediumTest
 @RunWith(AndroidJUnit4::class)
 @WithDevToolsAPI
 class LocaleTest : BaseSessionTest() {
 
     @Test fun setLocale() {
-        sessionRule.runtime.getSettings().setLocale("en-GB");
+        sessionRule.runtime.getSettings().setLocales(arrayOf("en-GB"));
 
         val index = sessionRule.waitForChromeJS(String.format(
                 "(function() {" +
                 "  return ChromeUtils.import('resource://gre/modules/Services.jsm', {})" +
                 "    .Services.locale.requestedLocales.indexOf('en-GB');" +
                 "})()")) as Double;
 
         assertThat("Requested locale is found", index, greaterThanOrEqualTo(0.0));
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java
@@ -279,16 +279,18 @@ public class GeckoThread extends Thread 
             Locale.setDefault(mappedLocale);
             Configuration config = res.getConfiguration();
             config.locale = mappedLocale;
             res.updateConfiguration(config, null);
         }
 
         final String resourcePath = context.getPackageResourcePath();
 
+        GeckoSystemStateListener.getInstance().initialize(context);
+
         try {
             loadGeckoLibs(context, resourcePath);
             return;
         } catch (final Exception e) {
             // Cannot load libs; try clearing the cached files.
             Log.w(LOGTAG, "Clearing cache after load libs exception", e);
         }
 
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java
@@ -227,18 +227,16 @@ public final class GeckoRuntime implemen
         // Bug 1453062 -- the EventDispatcher should really live here (or in GeckoThread)
         EventDispatcher.getInstance().registerUiThreadListener(mEventListener, "Gecko:Exited");
 
         mSettings.runtime = this;
         mSettings.flush();
 
         // Initialize the system ClipboardManager by accessing it on the main thread.
         GeckoAppShell.getApplicationContext().getSystemService(Context.CLIPBOARD_SERVICE);
-
-        GeckoSystemStateListener.getInstance().initialize(context);
         return true;
     }
 
     /* package */ void setDefaultPrefs(GeckoBundle prefs) {
         EventDispatcher.getInstance().dispatch("GeckoView:SetDefaultPrefs", prefs);
     }
 
     /**
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java
@@ -309,21 +309,21 @@ public final class GeckoRuntimeSettings 
         public @NonNull Builder crashHandler(final Class<? extends Service> handler) {
             mSettings.mCrashHandler = handler;
             return this;
         }
 
         /**
          * Set the locale.
          *
-         * @param languageTag The locale code in Gecko format ("en" or "en-US").
+         * @param requestedLocales List of locale codes in Gecko format ("en" or "en-US").
          * @return The builder instance.
          */
-        public @NonNull Builder locale(String languageTag) {
-            mSettings.mLocale = languageTag;
+        public @NonNull Builder locales(String[] requestedLocales) {
+            mSettings.mRequestedLocales = requestedLocales;
             return this;
         }
     }
 
     /* package */ GeckoRuntime runtime;
     /* package */ boolean mUseContentProcess;
     /* package */ String[] mArgs;
     /* package */ Bundle mExtras;
@@ -404,17 +404,17 @@ public final class GeckoRuntimeSettings 
 
     /* package */ boolean mDebugPause;
     /* package */ boolean mUseMaxScreenDepth;
     /* package */ float mDisplayDensityOverride = -1.0f;
     /* package */ int mDisplayDpiOverride;
     /* package */ int mScreenWidthOverride;
     /* package */ int mScreenHeightOverride;
     /* package */ Class<? extends Service> mCrashHandler;
-    /* package */ String mLocale;
+    /* package */ String[] mRequestedLocales;
 
     private final Pref<?>[] mPrefs = new Pref<?>[] {
         mCookieBehavior, mCookieLifetime, mConsoleOutput,
         mJavaScript, mRemoteDebugging, mSafebrowsingMalware,
         mSafebrowsingPhishing, mTrackingProtection, mWebFonts,
     };
 
     /* package */ GeckoRuntimeSettings() {
@@ -448,30 +448,30 @@ public final class GeckoRuntimeSettings 
 
         mDebugPause = settings.mDebugPause;
         mUseMaxScreenDepth = settings.mUseMaxScreenDepth;
         mDisplayDensityOverride = settings.mDisplayDensityOverride;
         mDisplayDpiOverride = settings.mDisplayDpiOverride;
         mScreenWidthOverride = settings.mScreenWidthOverride;
         mScreenHeightOverride = settings.mScreenHeightOverride;
         mCrashHandler = settings.mCrashHandler;
-        mLocale = settings.mLocale;
+        mRequestedLocales = settings.mRequestedLocales;
     }
 
     /* package */ Map<String, Object> getPrefsMap() {
         final ArrayMap<String, Object> prefs = new ArrayMap<>(mPrefs.length);
         for (final Pref<?> pref : mPrefs) {
             prefs.put(pref.name, pref.get());
         }
 
         return Collections.unmodifiableMap(prefs);
     }
 
     /* package */ void flush() {
-        flushLocale();
+        flushLocales();
 
         // Prefs are flushed individually when they are set, and
         // initial values are handled by GeckoRuntime itself.
         // We may have user prefs due to previous versions of
         // this class operating differently, though, so we'll
         // send a message to clear any user prefs that may have
         // been set on the prefs we manage.
         final String[] names = new String[mPrefs.length];
@@ -622,35 +622,37 @@ public final class GeckoRuntimeSettings 
     public Rect getScreenSizeOverride() {
         if ((mScreenWidthOverride > 0) && (mScreenHeightOverride > 0)) {
             return new Rect(0, 0, mScreenWidthOverride, mScreenHeightOverride);
         }
         return null;
     }
 
     /**
-     * @return The locale code in Gecko format ("en" or "en-US").
+     * Gets the list of requested locales.
+     *
+     * @return A list of locale codes in Gecko format ("en" or "en-US").
      */
-    public String getLocale() {
-        return mLocale;
+    public String[] getLocales() {
+        return mRequestedLocales;
     }
 
     /**
      * Set the locale.
      *
-     * @param languageTag The locale code in Gecko format ("en-US").
+     * @param requestedLocales An ordered list of locales in Gecko format ("en-US").
      */
-    public void setLocale(String languageTag) {
-        mLocale = languageTag;
-        flushLocale();
+    public void setLocales(String[] requestedLocales) {
+        mRequestedLocales = requestedLocales;
+        flushLocales();
     }
 
-    private void flushLocale() {
+    private void flushLocales() {
         final GeckoBundle data = new GeckoBundle(1);
-        data.putString("languageTag", mLocale);
+        data.putStringArray("requestedLocales", mRequestedLocales);
         EventDispatcher.getInstance().dispatch("GeckoView:SetLocale", data);
     }
 
     // Sync values with nsICookieService.idl.
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({ COOKIE_ACCEPT_ALL, COOKIE_ACCEPT_FIRST_PARTY,
               COOKIE_ACCEPT_NONE, COOKIE_ACCEPT_VISITED,
               COOKIE_ACCEPT_NON_TRACKERS })
@@ -860,17 +862,17 @@ public final class GeckoRuntimeSettings 
 
         ParcelableUtils.writeBoolean(out, mDebugPause);
         ParcelableUtils.writeBoolean(out, mUseMaxScreenDepth);
         out.writeFloat(mDisplayDensityOverride);
         out.writeInt(mDisplayDpiOverride);
         out.writeInt(mScreenWidthOverride);
         out.writeInt(mScreenHeightOverride);
         out.writeString(mCrashHandler != null ? mCrashHandler.getName() : null);
-        out.writeString(mLocale);
+        out.writeStringArray(mRequestedLocales);
     }
 
     // AIDL code may call readFromParcel even though it's not part of Parcelable.
     public void readFromParcel(final Parcel source) {
         mUseContentProcess = ParcelableUtils.readBoolean(source);
         mArgs = source.createStringArray();
         mExtras.readFromParcel(source);
 
@@ -895,17 +897,17 @@ public final class GeckoRuntimeSettings 
                 final Class<? extends Service> handler =
                         (Class<? extends Service>) Class.forName(crashHandlerName);
 
                 mCrashHandler = handler;
             } catch (ClassNotFoundException e) {
             }
         }
 
-        mLocale = source.readString();
+        mRequestedLocales = source.createStringArray();
     }
 
     public static final Parcelable.Creator<GeckoRuntimeSettings> CREATOR
         = new Parcelable.Creator<GeckoRuntimeSettings>() {
         @Override
         public GeckoRuntimeSettings createFromParcel(final Parcel in) {
             final GeckoRuntimeSettings settings = new GeckoRuntimeSettings();
             settings.readFromParcel(in);
--- a/modules/brotli/README.mozilla
+++ b/modules/brotli/README.mozilla
@@ -6,9 +6,9 @@ Upstream code can be viewed at
 
 and cloned by
   git clone https://github.com/google/brotli
 
 The in-tree copy is updated by running
   sh update.sh
 from within the modules/brotli directory.
 
-Current version: [commit 6eba239a5bb553fd557b7d78f7da8f0059618b9e].
+Current version: [commit d6d98957ca8ccb1ef45922e978bb10efca0ea541].
--- a/modules/brotli/common/platform.h
+++ b/modules/brotli/common/platform.h
@@ -66,17 +66,17 @@ OR:
 
   if (BROTLI_PREDICT_FALSE(something_rare_or_unexpected_happens)) {
     // compiler should place this code outside of main execution path
   }
 
 */
 #if BROTLI_GNUC_HAS_BUILTIN(__builtin_expect, 3, 0, 0) || \
     BROTLI_INTEL_VERSION_CHECK(16, 0, 0) ||               \
-    BROTLI_SUNPRO_VERSION_CHECK(5, 12, 0) ||              \
+    BROTLI_SUNPRO_VERSION_CHECK(5, 15, 0) ||              \
     BROTLI_ARM_VERSION_CHECK(4, 1, 0) ||                  \
     BROTLI_IBM_VERSION_CHECK(10, 1, 0) ||                 \
     BROTLI_TI_VERSION_CHECK(7, 3, 0) ||                   \
     BROTLI_TINYC_VERSION_CHECK(0, 9, 27)
 #define BROTLI_PREDICT_TRUE(x) (__builtin_expect(!!(x), 1))
 #define BROTLI_PREDICT_FALSE(x) (__builtin_expect(x, 0))
 #else
 #define BROTLI_PREDICT_FALSE(x) (x)
@@ -175,16 +175,22 @@ OR:
 
 #if BROTLI_GNUC_HAS_ATTRIBUTE(unused, 2, 7, 0) || \
     BROTLI_INTEL_VERSION_CHECK(16, 0, 0)
 #define BROTLI_UNUSED_FUNCTION static BROTLI_INLINE __attribute__ ((unused))
 #else
 #define BROTLI_UNUSED_FUNCTION static BROTLI_INLINE
 #endif
 
+#if BROTLI_GNUC_HAS_ATTRIBUTE(aligned, 2, 7, 0)
+#define BROTLI_ALIGNED(N) __attribute__((aligned(N)))
+#else
+#define BROTLI_ALIGNED(N)
+#endif
+
 #if (defined(__ARM_ARCH) && (__ARM_ARCH == 7)) || \
     (defined(M_ARM) && (M_ARM == 7))
 #define BROTLI_TARGET_ARMV7
 #endif  /* ARMv7 */
 
 #if (defined(__ARM_ARCH) && (__ARM_ARCH == 8)) || \
     defined(__aarch64__) || defined(__ARM64_ARCH_8__)
 #define BROTLI_TARGET_ARMV8_ANY
@@ -192,16 +198,20 @@ OR:
 #if defined(__ARM_32BIT_STATE)
 #define BROTLI_TARGET_ARMV8_32
 #elif defined(__ARM_64BIT_STATE)
 #define BROTLI_TARGET_ARMV8_64
 #endif
 
 #endif  /* ARMv8 */
 
+#if defined(__ARM_NEON__) || defined(__ARM_NEON)
+#define BROTLI_TARGET_NEON
+#endif
+
 #if defined(__i386) || defined(_M_IX86)
 #define BROTLI_TARGET_X86
 #endif
 
 #if defined(__x86_64__) || defined(_M_X64)
 #define BROTLI_TARGET_X64
 #endif
 
@@ -338,17 +348,17 @@ static BROTLI_INLINE uint64_t BrotliUnal
 static BROTLI_INLINE void BrotliUnalignedWrite64(void* p, uint64_t v) {
   *(uint64_t*)p = v;
 }
 #else  /* BROTLI_64_BITS */
 /* Avoid emitting LDRD / STRD, which require properly aligned address. */
 /* If __attribute__(aligned) is available, use that. Otherwise, memcpy. */
 
 #if BROTLI_GNUC_HAS_ATTRIBUTE(aligned, 2, 7, 0)
-typedef  __attribute__((aligned(1))) uint64_t brotli_unaligned_uint64_t;
+typedef BROTLI_ALIGNED(1) uint64_t brotli_unaligned_uint64_t;
 
 static BROTLI_INLINE uint64_t BrotliUnalignedRead64(const void* p) {
   return (uint64_t) ((brotli_unaligned_uint64_t*) p)[0];
 }
 static BROTLI_INLINE void BrotliUnalignedWrite64(void* p, uint64_t v) {
   brotli_unaligned_uint64_t* dwords = (brotli_unaligned_uint64_t*) p;
   dwords[0] = (brotli_unaligned_uint64_t) v;
 }
--- a/modules/brotli/common/transform.c
+++ b/modules/brotli/common/transform.c
@@ -186,21 +186,21 @@ static int ToUpperCase(uint8_t* p) {
     return 2;
   }
   /* An arbitrary transform for three byte characters. */
   p[2] ^= 5;
   return 3;
 }
 
 int BrotliTransformDictionaryWord(uint8_t* dst, const uint8_t* word, int len,
-    const BrotliTransforms* transforms, int transfom_idx) {
+    const BrotliTransforms* transforms, int transform_idx) {
   int idx = 0;
-  const uint8_t* prefix = BROTLI_TRANSFORM_PREFIX(transforms, transfom_idx);
-  uint8_t type = BROTLI_TRANSFORM_TYPE(transforms, transfom_idx);
-  const uint8_t* suffix = BROTLI_TRANSFORM_SUFFIX(transforms, transfom_idx);
+  const uint8_t* prefix = BROTLI_TRANSFORM_PREFIX(transforms, transform_idx);
+  uint8_t type = BROTLI_TRANSFORM_TYPE(transforms, transform_idx);
+  const uint8_t* suffix = BROTLI_TRANSFORM_SUFFIX(transforms, transform_idx);
   {
     int prefix_len = *prefix++;
     while (prefix_len--) { dst[idx++] = *prefix++; }
   }
   {
     const int t = type;
     int i = 0;
     if (t <= BROTLI_TRANSFORM_OMIT_LAST_9) {
--- a/modules/brotli/common/version.h
+++ b/modules/brotli/common/version.h
@@ -9,18 +9,18 @@
 #ifndef BROTLI_COMMON_VERSION_H_
 #define BROTLI_COMMON_VERSION_H_
 
 /* This macro should only be used when library is compiled together with client.
    If library is dynamically linked, use BrotliDecoderVersion and
    BrotliEncoderVersion methods. */
 
 /* Semantic version, calculated as (MAJOR << 24) | (MINOR << 12) | PATCH */
-#define BROTLI_VERSION 0x1000006
+#define BROTLI_VERSION 0x1000007
 
 /* This macro is used by build system to produce Libtool-friendly soname. See
    https://www.gnu.org/software/libtool/manual/html_node/Libtool-versioning.html
  */
 
 /* ABI version, calculated as (CURRENT << 24) | (REVISION << 12) | AGE */
-#define BROTLI_ABI_VERSION 0x1006000
+#define BROTLI_ABI_VERSION 0x1007000
 
 #endif  /* BROTLI_COMMON_VERSION_H_ */
--- a/modules/brotli/dec/decode.c
+++ b/modules/brotli/dec/decode.c
@@ -1,34 +1,34 @@
 /* Copyright 2013 Google Inc. All Rights Reserved.
 
    Distributed under MIT license.
    See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
 */
 
 #include <brotli/decode.h>
 
-#if defined(__ARM_NEON__)
-#include <arm_neon.h>
-#endif
-
 #include <stdlib.h>  /* free, malloc */
 #include <string.h>  /* memcpy, memset */
 
 #include "../common/constants.h"
 #include "../common/context.h"
 #include "../common/dictionary.h"
 #include "../common/platform.h"
 #include "../common/transform.h"
 #include "../common/version.h"
 #include "./bit_reader.h"
 #include "./huffman.h"
 #include "./prefix.h"
 #include "./state.h"
 
+#if defined(BROTLI_TARGET_NEON)
+#include <arm_neon.h>
+#endif
+
 #if defined(__cplusplus) || defined(c_plusplus)
 extern "C" {
 #endif
 
 #define BROTLI_FAILURE(CODE) (BROTLI_DUMP(), CODE)
 
 #define BROTLI_LOG_UINT(name)                                       \
   BROTLI_LOG(("[%s] %s = %lu\n", __func__, #name, (unsigned long)(name)))
@@ -162,17 +162,17 @@ static BrotliDecoderErrorCode DecodeWind
     s->window_bits = 8 + n;
     return BROTLI_DECODER_SUCCESS;
   }
   s->window_bits = 17;
   return BROTLI_DECODER_SUCCESS;
 }
 
 static BROTLI_INLINE void memmove16(uint8_t* dst, uint8_t* src) {
-#if defined(__ARM_NEON__)
+#if defined(BROTLI_TARGET_NEON)
   vst1q_u8(dst, vld1q_u8(src));
 #else
   uint32_t buffer[4];
   memcpy(buffer, src, 16);
   memcpy(dst, buffer, 16);
 #endif
 }
 
@@ -342,72 +342,75 @@ static BrotliDecoderErrorCode BROTLI_NOI
 
 /* Decodes the Huffman code.
    This method doesn't read data from the bit reader, BUT drops the amount of
    bits that correspond to the decoded symbol.
    bits MUST contain at least 15 (BROTLI_HUFFMAN_MAX_CODE_LENGTH) valid bits. */
 static BROTLI_INLINE uint32_t DecodeSymbol(uint32_t bits,
                                            const HuffmanCode* table,
                                            BrotliBitReader* br) {
-  table += bits & HUFFMAN_TABLE_MASK;
-  if (table->bits > HUFFMAN_TABLE_BITS) {
-    uint32_t nbits = table->bits - HUFFMAN_TABLE_BITS;
+  BROTLI_HC_MARK_TABLE_FOR_FAST_LOAD(table);
+  BROTLI_HC_ADJUST_TABLE_INDEX(table, bits & HUFFMAN_TABLE_MASK);
+  if (BROTLI_HC_FAST_LOAD_BITS(table) > HUFFMAN_TABLE_BITS) {
+    uint32_t nbits = BROTLI_HC_FAST_LOAD_BITS(table) - HUFFMAN_TABLE_BITS;
     BrotliDropBits(br, HUFFMAN_TABLE_BITS);
-    table += table->value;
-    table += (bits >> HUFFMAN_TABLE_BITS) & BitMask(nbits);
+    BROTLI_HC_ADJUST_TABLE_INDEX(table,
+        BROTLI_HC_FAST_LOAD_VALUE(table) +
+        ((bits >> HUFFMAN_TABLE_BITS) & BitMask(nbits)));
   }
-  BrotliDropBits(br, table->bits);
-  return table->value;
+  BrotliDropBits(br, BROTLI_HC_FAST_LOAD_BITS(table));
+  return BROTLI_HC_FAST_LOAD_VALUE(table);
 }
 
 /* Reads and decodes the next Huffman code from bit-stream.
    This method peeks 16 bits of input and drops 0 - 15 of them. */
 static BROTLI_INLINE uint32_t ReadSymbol(const HuffmanCode* table,
                                          BrotliBitReader* br) {
   return DecodeSymbol(BrotliGet16BitsUnmasked(br), table, br);
 }
 
 /* Same as DecodeSymbol, but it is known that there is less than 15 bits of
    input are currently available. */
 static BROTLI_NOINLINE BROTLI_BOOL SafeDecodeSymbol(
     const HuffmanCode* table, BrotliBitReader* br, uint32_t* result) {
   uint32_t val;
   uint32_t available_bits = BrotliGetAvailableBits(br);
+  BROTLI_HC_MARK_TABLE_FOR_FAST_LOAD(table);
   if (available_bits == 0) {
-    if (table->bits == 0) {
-      *result = table->value;
+    if (BROTLI_HC_FAST_LOAD_BITS(table) == 0) {
+      *result = BROTLI_HC_FAST_LOAD_VALUE(table);
       return BROTLI_TRUE;
     }
     return BROTLI_FALSE;  /* No valid bits at all. */
   }
   val = (uint32_t)BrotliGetBitsUnmasked(br);
-  table += val & HUFFMAN_TABLE_MASK;
-  if (table->bits <= HUFFMAN_TABLE_BITS) {
-    if (table->bits <= available_bits) {
-      BrotliDropBits(br, table->bits);
-      *result = table->value;
+  BROTLI_HC_ADJUST_TABLE_INDEX(table, val & HUFFMAN_TABLE_MASK);
+  if (BROTLI_HC_FAST_LOAD_BITS(table) <= HUFFMAN_TABLE_BITS) {
+    if (BROTLI_HC_FAST_LOAD_BITS(table) <= available_bits) {
+      BrotliDropBits(br, BROTLI_HC_FAST_LOAD_BITS(table));
+      *result = BROTLI_HC_FAST_LOAD_VALUE(table);
       return BROTLI_TRUE;
     } else {
       return BROTLI_FALSE;  /* Not enough bits for the first level. */
     }
   }
   if (available_bits <= HUFFMAN_TABLE_BITS) {
     return BROTLI_FALSE;  /* Not enough bits to move to the second level. */
   }
 
   /* Speculatively drop HUFFMAN_TABLE_BITS. */