merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 09 Nov 2016 16:38:04 +0100
changeset 321813 336759fad4621dfcd0a3293840edbed67018accd
parent 321692 6b7f1acfbb4b3fab041817a7dfbe39743416181f (current diff)
parent 321812 eac5fd08280a9cfa83925050cd70facc8252eac9 (diff)
child 321814 3e3b7cff19a51048d6c633630187f2233c882705
child 321826 c06f9e99eeb80b4695f0b44b6e2e6675b272bd00
child 321906 310ae43d23b7392aad985af26c9907a598360b36
push id30934
push usercbook@mozilla.com
push dateWed, 09 Nov 2016 15:38:21 +0000
treeherdermozilla-central@336759fad462 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone52.0a1
first release with
nightly linux32
336759fad462 / 52.0a1 / 20161110030211 / files
nightly linux64
336759fad462 / 52.0a1 / 20161110030211 / files
nightly mac
336759fad462 / 52.0a1 / 20161110030211 / files
nightly win32
336759fad462 / 52.0a1 / 20161110030211 / files
nightly win64
336759fad462 / 52.0a1 / 20161110030211 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-inbound to mozilla-central a=merge
dom/media/compiledtest/TestAudioBuffers.cpp
dom/media/compiledtest/TestAudioMixer.cpp
dom/media/compiledtest/TestAudioPacketizer.cpp
dom/media/compiledtest/TestAudioSegment.cpp
dom/media/compiledtest/moz.build
editor/libeditor/tests/mochitest.ini
gfx/ipc/GPUProcessManager.cpp
gfx/ipc/GPUProcessManager.h
gfx/ipc/InProcessCompositorSession.cpp
gfx/ipc/InProcessCompositorSession.h
gfx/layers/ipc/CompositorBridgeChild.cpp
gfx/layers/ipc/CompositorBridgeChild.h
gfx/vr/ipc/VRManagerChild.cpp
js/src/builtin/AsyncFunctions.js
js/src/jit-test/tests/self-test/assertRecoveredOnBailout.js
testing/web-platform/meta/cookies/secure/set-from-dom.sub.html.ini
testing/web-platform/meta/cookies/secure/set-from-http.sub.html.ini
testing/web-platform/tests/cookies/secure/set-from-dom.sub.html
testing/web-platform/tests/cookies/secure/set-from-http.sub.html
toolkit/components/telemetry/Histograms.json
widget/nsBaseWidget.cpp
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-bug 1313485 - OS X bustage requires clobber to fix
+Bug 1277704 - jemalloc may need a clobber
--- a/accessible/atk/AccessibleWrap.cpp
+++ b/accessible/atk/AccessibleWrap.cpp
@@ -368,16 +368,19 @@ AccessibleWrap::CreateMaiInterfaces(void
   if (IsLink())
     interfacesBits |= 1 << MAI_INTERFACE_HYPERLINK_IMPL;
 
   if (!nsAccUtils::MustPrune(this)) {  // These interfaces require children
     // Table interface.
     if (AsTable())
       interfacesBits |= 1 << MAI_INTERFACE_TABLE;
  
+    if (AsTableCell())
+      interfacesBits |= 1 << MAI_INTERFACE_TABLE_CELL;
+
     // Selection interface.
     if (IsSelect()) {
       interfacesBits |= 1 << MAI_INTERFACE_SELECTION;
     }
   }
 
   return interfacesBits;
 }
@@ -1125,16 +1128,19 @@ GetInterfacesForProxy(ProxyAccessible* a
     interfaces |= 1 << MAI_INTERFACE_HYPERLINK_IMPL;
 
   if (aInterfaces & Interfaces::VALUE)
     interfaces |= 1 << MAI_INTERFACE_VALUE;
 
   if (aInterfaces & Interfaces::TABLE)
     interfaces |= 1 << MAI_INTERFACE_TABLE;
 
+  if (aInterfaces & Interfaces::TABLECELL)
+    interfaces |= 1 << MAI_INTERFACE_TABLE_CELL;
+
   if (aInterfaces & Interfaces::IMAGE)
     interfaces |= 1 << MAI_INTERFACE_IMAGE;
 
   if (aInterfaces & Interfaces::DOCUMENT)
     interfaces |= 1 << MAI_INTERFACE_DOCUMENT;
 
   if (aInterfaces & Interfaces::SELECTION) {
     interfaces |= 1 << MAI_INTERFACE_SELECTION;
--- a/browser/base/content/test/general/head.js
+++ b/browser/base/content/test/general/head.js
@@ -623,18 +623,16 @@ function promiseTabLoadEvent(tab, url)
       info(`Skipping spurious load event for ${loadedUrl}`);
       return false;
     }
 
     info("Tab event received: load");
     return true;
   }
 
-  // Create two promises: one resolved from the content process when the page
-  // loads and one that is rejected if we take too long to load the url.
   let loaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, handle);
 
   if (url)
     BrowserTestUtils.loadURI(tab.linkedBrowser, url);
 
   return loaded;
 }
 
--- a/browser/base/content/test/plugins/head.js
+++ b/browser/base/content/test/plugins/head.js
@@ -85,18 +85,16 @@ function promiseTabLoadEvent(tab, url) {
       info(`Skipping spurious load event for ${loadedUrl}`);
       return false;
     }
 
     info("Tab event received: load");
     return true;
   }
 
-  // Create two promises: one resolved from the content process when the page
-  // loads and one that is rejected if we take too long to load the url.
   let loaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, handle);
 
   if (url)
     BrowserTestUtils.loadURI(tab.linkedBrowser, url);
 
   return loaded;
 }
 
--- a/browser/components/extensions/ext-contextMenus.js
+++ b/browser/components/extensions/ext-contextMenus.js
@@ -4,16 +4,17 @@
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 Cu.import("resource://gre/modules/MatchPattern.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 var {
   EventManager,
+  ExtensionError,
   IconDetails,
 } = ExtensionUtils;
 
 // Map[Extension -> Map[ID -> MenuItem]]
 // Note: we want to enumerate all the menu items so
 // this cannot be a weak map.
 var gContextMenuMap = new Map();
 
@@ -321,17 +322,17 @@ MenuItem.prototype = {
       return;
     }
     let menuMap = gContextMenuMap.get(this.extension);
     if (!menuMap.has(parentId)) {
       throw new Error("Could not find any MenuItem with id: " + parentId);
     }
     for (let item = menuMap.get(parentId); item; item = item.parent) {
       if (item === this) {
-        throw new Error("MenuItem cannot be an ancestor (or self) of its new parent.");
+        throw new ExtensionError("MenuItem cannot be an ancestor (or self) of its new parent.");
       }
     }
   },
 
   set parentId(parentId) {
     this.ensureValidParentId(parentId);
 
     if (this.parent) {
--- a/browser/components/extensions/test/browser/.eslintrc.js
+++ b/browser/components/extensions/test/browser/.eslintrc.js
@@ -24,9 +24,13 @@ module.exports = {  // eslint-disable-li
     "closeContextMenu": true,
     "closeExtensionContextMenu": true,
     "focusWindow": true,
     "makeWidgetId": true,
     "openContextMenu": true,
     "openExtensionContextMenu": true,
     "CustomizableUI": true,
   },
+
+  "rules": {
+    "no-shadow": 0,
+  },
 };
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_context.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_context.js
@@ -1,45 +1,31 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 function* runTests(options) {
-  function background(getTests) {
-    // Gets the current details of the browser action, and returns a
-    // promise that resolves to an object containing them.
-    function getDetails(tabId) {
-      return Promise.all([
-        browser.browserAction.getTitle({tabId}),
-        browser.browserAction.getPopup({tabId}),
-        browser.browserAction.getBadgeText({tabId}),
-        browser.browserAction.getBadgeBackgroundColor({tabId})]
-      ).then(details => {
-        return Promise.resolve({title: details[0],
-                                popup: details[1],
-                                badge: details[2],
-                                badgeBackgroundColor: details[3]});
-      });
-    }
+  async function background(getTests) {
+    async function checkDetails(expecting, tabId) {
+      let title = await browser.browserAction.getTitle({tabId});
+      browser.test.assertEq(expecting.title, title,
+                            "expected value from getTitle");
 
-    function checkDetails(expecting, tabId) {
-      return getDetails(tabId).then(details => {
-        browser.test.assertEq(expecting.title, details.title,
-                              "expected value from getTitle");
-
-        browser.test.assertEq(expecting.popup, details.popup,
-                              "expected value from getPopup");
+      let popup = await browser.browserAction.getPopup({tabId});
+      browser.test.assertEq(expecting.popup, popup,
+                            "expected value from getPopup");
 
-        browser.test.assertEq(expecting.badge, details.badge,
-                              "expected value from getBadge");
+      let badge = await browser.browserAction.getBadgeText({tabId});
+      browser.test.assertEq(expecting.badge, badge,
+                            "expected value from getBadge");
 
-        browser.test.assertEq(String(expecting.badgeBackgroundColor),
-                              String(details.badgeBackgroundColor),
-                              "expected value from getBadgeBackgroundColor");
-      });
+      let badgeBackgroundColor = await browser.browserAction.getBadgeBackgroundColor({tabId});
+      browser.test.assertEq(String(expecting.badgeBackgroundColor),
+                            String(badgeBackgroundColor),
+                            "expected value from getBadgeBackgroundColor");
     }
 
     let expectDefaults = expecting => {
       return checkDetails(expecting);
     };
 
     let tabs = [];
     let tests = getTests(tabs, expectDefaults);
@@ -52,49 +38,37 @@ function* runTests(options) {
         () => browser.browserAction.setTitle({tabId, title: "foo"}),
         () => browser.browserAction.setIcon({tabId, path: "foo.png"}),
         () => browser.browserAction.setPopup({tabId, popup: "foo.html"}),
         () => browser.browserAction.setBadgeText({tabId, text: "foo"}),
         () => browser.browserAction.setBadgeBackgroundColor({tabId, color: [0xff, 0, 0, 0xff]}),
       ];
 
       for (let call of calls) {
-        let checkError = e => {
-          browser.test.assertTrue(e.message.includes(`Invalid tab ID: ${tabId}`),
-                                  `Expected invalid tab ID error, got ${e}`);
-        };
-        try {
-          call().then(() => {
-            browser.test.fail(`Expected call to fail: ${call}`);
-          }, e => {
-            checkError(e);
-          });
-        } catch (e) {
-          checkError(e);
-        }
+        await browser.test.assertRejects(
+          new Promise(resolve => resolve(call())),
+          RegExp(`Invalid tab ID: ${tabId}`),
+          "Expected invalid tab ID error");
       }
     }
 
     // Runs the next test in the `tests` array, checks the results,
     // and passes control back to the outer test scope.
     function nextTest() {
       let test = tests.shift();
 
-      test(expecting => {
+      test(async expecting => {
         // Check that the API returns the expected values, and then
         // run the next test.
-        new Promise(resolve => {
-          return browser.tabs.query({active: true, currentWindow: true}, resolve);
-        }).then(tabs => {
-          return checkDetails(expecting, tabs[0].id);
-        }).then(() => {
-          // Check that the actual icon has the expected values, then
-          // run the next test.
-          browser.test.sendMessage("nextTest", expecting, tests.length);
-        });
+        let tabs = await browser.tabs.query({active: true, currentWindow: true});
+        await checkDetails(expecting, tabs[0].id);
+
+        // Check that the actual icon has the expected values, then
+        // run the next test.
+        browser.test.sendMessage("nextTest", expecting, tests.length);
       });
     }
 
     browser.test.onMessage.addListener((msg) => {
       if (msg != "runNextTest") {
         browser.test.fail("Expecting 'runNextTest' message");
       }
 
@@ -236,117 +210,110 @@ add_task(function* testTabSwitchContext(
         {"icon": browser.runtime.getURL("default-2.png"),
          "popup": browser.runtime.getURL("default-2.html"),
          "title": "Default Title 2",
          "badge": "d2",
          "badgeBackgroundColor": [0, 0xff, 0, 0xff]},
       ];
 
       return [
-        expect => {
+        async expect => {
           browser.test.log("Initial state, expect default properties.");
-          expectDefaults(details[0]).then(() => {
-            expect(details[0]);
-          });
+
+          await expectDefaults(details[0]);
+          expect(details[0]);
         },
-        expect => {
+        async expect => {
           browser.test.log("Change the icon in the current tab. Expect default properties excluding the icon.");
           browser.browserAction.setIcon({tabId: tabs[0], path: "1.png"});
-          expectDefaults(details[0]).then(() => {
-            expect(details[1]);
-          });
+
+          await expectDefaults(details[0]);
+          expect(details[1]);
         },
-        expect => {
+        async expect => {
           browser.test.log("Create a new tab. Expect default properties.");
-          browser.tabs.create({active: true, url: "about:blank?0"}, tab => {
-            tabs.push(tab.id);
-            expectDefaults(details[0]).then(() => {
-              expect(details[0]);
-            });
-          });
+          let tab = await browser.tabs.create({active: true, url: "about:blank?0"});
+          tabs.push(tab.id);
+
+          await expectDefaults(details[0]);
+          expect(details[0]);
         },
-        expect => {
+        async expect => {
           browser.test.log("Change properties. Expect new properties.");
           let tabId = tabs[1];
           browser.browserAction.setIcon({tabId, path: "2.png"});
           browser.browserAction.setPopup({tabId, popup: "2.html"});
           browser.browserAction.setTitle({tabId, title: "Title 2"});
           browser.browserAction.setBadgeText({tabId, text: "2"});
           browser.browserAction.setBadgeBackgroundColor({tabId, color: "#ff0000"});
           browser.browserAction.disable(tabId);
 
-          expectDefaults(details[0]).then(() => {
-            expect(details[2]);
-          });
+          await expectDefaults(details[0]);
+          expect(details[2]);
         },
         expect => {
           browser.test.log("Navigate to a new page. Expect no changes.");
 
           // TODO: This listener should not be necessary, but the |tabs.update|
           // callback currently fires too early in e10s windows.
           browser.tabs.onUpdated.addListener(function listener(tabId, changed) {
             if (tabId == tabs[1] && changed.url) {
               browser.tabs.onUpdated.removeListener(listener);
               expect(details[2]);
             }
           });
 
           browser.tabs.update(tabs[1], {url: "about:blank?1"});
         },
-        expect => {
+        async expect => {
           browser.test.log("Switch back to the first tab. Expect previously set properties.");
-          browser.tabs.update(tabs[0], {active: true}, () => {
-            expect(details[1]);
-          });
+          await browser.tabs.update(tabs[0], {active: true});
+          expect(details[1]);
         },
-        expect => {
+        async expect => {
           browser.test.log("Change default values, expect those changes reflected.");
           browser.browserAction.setIcon({path: "default-2.png"});
           browser.browserAction.setPopup({popup: "default-2.html"});
           browser.browserAction.setTitle({title: "Default Title 2"});
           browser.browserAction.setBadgeText({text: "d2"});
           browser.browserAction.setBadgeBackgroundColor({color: [0, 0xff, 0, 0xff]});
           browser.browserAction.disable();
-          expectDefaults(details[3]).then(() => {
-            expect(details[3]);
-          });
+
+          await expectDefaults(details[3]);
+          expect(details[3]);
         },
-        expect => {
+        async expect => {
           browser.test.log("Re-enable by default. Expect enabled.");
           browser.browserAction.enable();
-          expectDefaults(details[4]).then(() => {
-            expect(details[4]);
-          });
+
+          await expectDefaults(details[4]);
+          expect(details[4]);
         },
-        expect => {
+        async expect => {
           browser.test.log("Switch back to tab 2. Expect former value, unaffected by changes to defaults in previous step.");
-          browser.tabs.update(tabs[1], {active: true}, () => {
-            expectDefaults(details[3]).then(() => {
-              expect(details[2]);
-            });
-          });
+          await browser.tabs.update(tabs[1], {active: true});
+
+          await expectDefaults(details[3]);
+          expect(details[2]);
         },
-        expect => {
+        async expect => {
           browser.test.log("Delete tab, switch back to tab 1. Expect previous results again.");
-          browser.tabs.remove(tabs[1], () => {
-            expect(details[4]);
-          });
+          await browser.tabs.remove(tabs[1]);
+          expect(details[4]);
         },
-        expect => {
+        async expect => {
           browser.test.log("Create a new tab. Expect new default properties.");
-          browser.tabs.create({active: true, url: "about:blank?2"}, tab => {
-            tabs.push(tab.id);
-            expect(details[5]);
-          });
+          let tab = await browser.tabs.create({active: true, url: "about:blank?2"});
+          tabs.push(tab.id);
+          expect(details[5]);
         },
-        expect => {
+        async expect => {
           browser.test.log("Delete tab.");
-          browser.tabs.remove(tabs[2], () => {
-            expect(details[4]);
-          });
+          await browser.tabs.remove(tabs[2]);
+          expect(details[4]);
         },
       ];
     },
   });
 });
 
 add_task(function* testDefaultTitle() {
   yield runTests({
@@ -386,46 +353,46 @@ add_task(function* testDefaultTitle() {
         {"title": "",
          "popup": "",
          "badge": "",
          "badgeBackgroundColor": DEFAULT_BADGE_COLOR,
          "icon": browser.runtime.getURL("icon.png")},
       ];
 
       return [
-        expect => {
+        async expect => {
           browser.test.log("Initial state. Expect extension title as default title.");
-          expectDefaults(details[0]).then(() => {
-            expect(details[0]);
-          });
+
+          await expectDefaults(details[0]);
+          expect(details[0]);
         },
-        expect => {
+        async expect => {
           browser.test.log("Change the title. Expect new title.");
           browser.browserAction.setTitle({tabId: tabs[0], title: "Foo Title"});
-          expectDefaults(details[0]).then(() => {
-            expect(details[1]);
-          });
+
+          await expectDefaults(details[0]);
+          expect(details[1]);
         },
-        expect => {
+        async expect => {
           browser.test.log("Change the default. Expect same properties.");
           browser.browserAction.setTitle({title: "Bar Title"});
-          expectDefaults(details[2]).then(() => {
-            expect(details[1]);
-          });
+
+          await expectDefaults(details[2]);
+          expect(details[1]);
         },
-        expect => {
+        async expect => {
           browser.test.log("Clear the title. Expect new default title.");
           browser.browserAction.setTitle({tabId: tabs[0], title: ""});
-          expectDefaults(details[2]).then(() => {
-            expect(details[2]);
-          });
+
+          await expectDefaults(details[2]);
+          expect(details[2]);
         },
-        expect => {
+        async expect => {
           browser.test.log("Set default title to null string. Expect null string from API, extension title in UI.");
           browser.browserAction.setTitle({title: ""});
-          expectDefaults(details[3]).then(() => {
-            expect(details[3]);
-          });
+
+          await expectDefaults(details[3]);
+          expect(details[3]);
         },
       ];
     },
   });
 });
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon_permissions.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon_permissions.js
@@ -15,24 +15,20 @@ add_task(function* testInvalidIconSizes(
         let tabId = tabs[0].id;
 
         let promises = [];
         for (let api of ["pageAction", "browserAction"]) {
           // helper function to run setIcon and check if it fails
           let assertSetIconThrows = function(detail, error, message) {
             detail.tabId = tabId;
             promises.push(
-              browser[api].setIcon(detail).then(
-                () => {
-                  browser.test.fail("Expected an error on invalid icon size.");
-                  browser.test.notifyFail("setIcon with invalid icon size");
-                },
-                error => {
-                  browser.test.succeed("setIcon with invalid icon size");
-                }));
+              browser.test.assertRejects(
+                browser[api].setIcon(detail),
+                /must be an integer/,
+                "setIcon with invalid icon size"));
           };
 
           let imageData = new ImageData(1, 1);
 
           // test invalid icon size inputs
           for (let type of ["path", "imageData"]) {
             let img = type == "imageData" ? imageData : "test.png";
 
@@ -143,24 +139,20 @@ add_task(function* testSecureURLsDenied(
 
         let urls = ["chrome://browser/content/browser.xul",
                     "javascript:true"];
 
         let promises = [];
         for (let url of urls) {
           for (let api of ["pageAction", "browserAction"]) {
             promises.push(
-              browser[api].setIcon({tabId, path: url}).then(
-                () => {
-                  browser.test.fail(`Load of '${url}' succeeded. Expected failure.`);
-                  browser.test.notifyFail("setIcon security tests");
-                },
-                error => {
-                  browser.test.succeed(`Load of '${url}' failed. Expected failure. ${error}`);
-                }));
+              browser.test.assertRejects(
+                browser[api].setIcon({tabId, path: url}),
+                /Illegal URL/,
+                `Load of '${url}' should fail.`));
           }
         }
 
         Promise.all(promises).then(() => {
           browser.test.notifyPass("setIcon security tests");
         });
       });
     },
--- a/browser/components/extensions/test/browser/browser_ext_contextMenus.js
+++ b/browser/components/extensions/test/browser/browser_ext_contextMenus.js
@@ -47,17 +47,17 @@ add_task(function* () {
 
   gBrowser.selectedTab = tab1;
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["contextMenus"],
     },
 
-    background: function() {
+    background: async function() {
       // A generic onclick callback function.
       function genericOnClick(info, tab) {
         browser.test.sendMessage("onclick", {info, tab});
       }
 
       browser.contextMenus.onClicked.addListener((info, tab) => {
         browser.test.sendMessage("browser.contextMenus.onClicked", {info, tab});
       });
@@ -117,24 +117,22 @@ add_task(function* () {
       });
       browser.contextMenus.remove(parentToDel);
 
       browser.contextMenus.create({
         title: "Without onclick property",
         id: "ext-without-onclick",
       });
 
-      browser.contextMenus.update(parent, {parentId: child2}).then(
-        () => {
-          browser.test.notifyFail("contextmenus");
-        },
-        () => {
-          browser.test.notifyPass("contextmenus");
-        }
-      );
+      await browser.test.assertRejects(
+        browser.contextMenus.update(parent, {parentId: child2}),
+        /cannot be an ancestor/,
+        "Should not be able to reparent an item as descendent of itself");
+
+      browser.test.notifyPass("contextmenus");
     },
   });
 
   yield extension.startup();
   yield extension.awaitFinish("contextmenus");
 
   let expectedClickInfo = {
     menuItemId: "ext-image",
--- a/browser/components/extensions/test/browser/browser_ext_incognito_popup.js
+++ b/browser/components/extensions/test/browser/browser_ext_incognito_popup.js
@@ -9,91 +9,92 @@ add_task(function* testIncognitoPopup() 
       "browser_action": {
         "default_popup": "popup.html",
       },
       "page_action": {
         "default_popup": "popup.html",
       },
     },
 
-    background() {
+    background: async function() {
       let resolveMessage;
       browser.runtime.onMessage.addListener(msg => {
         if (resolveMessage && msg.message == "popup-details") {
           resolveMessage(msg);
         }
       });
 
       let awaitPopup = windowId => {
         return new Promise(resolve => {
           resolveMessage = resolve;
         }).then(msg => {
           browser.test.assertEq(windowId, msg.windowId, "Got popup message from correct window");
           return msg;
         });
       };
 
-      let testWindow = window => {
-        return browser.tabs.query({active: true, windowId: window.id}).then(([tab]) => {
-          return browser.pageAction.show(tab.id);
-        }).then(() => {
-          browser.test.sendMessage("click-pageAction");
+      let testWindow = async window => {
+        let [tab] = await browser.tabs.query({active: true, windowId: window.id});
+
+        await browser.pageAction.show(tab.id);
+        browser.test.sendMessage("click-pageAction");
 
-          return awaitPopup(window.id);
-        }).then(msg => {
-          browser.test.assertEq(window.incognito, msg.incognito, "Correct incognito status in pageAction popup");
+        let msg = await awaitPopup(window.id);
+        browser.test.assertEq(window.incognito, msg.incognito, "Correct incognito status in pageAction popup");
 
-          browser.test.sendMessage("click-browserAction");
+        browser.test.sendMessage("click-browserAction");
 
-          return awaitPopup(window.id);
-        }).then(msg => {
-          browser.test.assertEq(window.incognito, msg.incognito, "Correct incognito status in browserAction popup");
-        });
+        msg = await awaitPopup(window.id);
+        browser.test.assertEq(window.incognito, msg.incognito, "Correct incognito status in browserAction popup");
       };
 
       const URL = "http://example.com/incognito";
       let windowReady = new Promise(resolve => {
         browser.tabs.onUpdated.addListener(function listener(tabId, changed, tab) {
           if (changed.status == "complete" && tab.url == URL) {
             browser.tabs.onUpdated.removeListener(listener);
             resolve();
           }
         });
       });
 
-      browser.windows.getCurrent().then(window => {
-        return testWindow(window);
-      }).then(() => {
-        return browser.windows.create({incognito: true, url: URL});
-      }).then(window => {
-        return windowReady.then(() => {
-          return testWindow(window);
-        }).then(() => {
-          return browser.windows.remove(window.id);
-        });
-      }).then(() => {
+      try {
+        {
+          let window = await browser.windows.getCurrent();
+
+          await testWindow(window);
+        }
+
+        {
+          let window = await browser.windows.create({incognito: true, url: URL});
+          await windowReady;
+
+          await testWindow(window);
+
+          await browser.windows.remove(window.id);
+        }
+
         browser.test.notifyPass("incognito");
-      }).catch(error => {
+      } catch (error) {
         browser.test.fail(`Error: ${error} :: ${error.stack}`);
         browser.test.notifyFail("incognito");
-      });
+      }
     },
 
     files: {
       "popup.html": '<html><head><meta charset="utf-8"><script src="popup.js"></script></head></html>',
 
-      "popup.js": function() {
-        browser.windows.getCurrent().then(win => {
-          browser.runtime.sendMessage({
-            message: "popup-details",
-            windowId: win.id,
-            incognito: browser.extension.inIncognitoContext,
-          });
-          window.close();
+      "popup.js": async function() {
+        let win = await browser.windows.getCurrent();
+        browser.runtime.sendMessage({
+          message: "popup-details",
+          windowId: win.id,
+          incognito: browser.extension.inIncognitoContext,
         });
+        window.close();
       },
     },
   });
 
   extension.onMessage("click-browserAction", () => {
     clickBrowserAction(extension, Services.wm.getMostRecentWindow("navigator:browser"));
   });
 
--- a/browser/components/extensions/test/browser/browser_ext_legacy_extension_context_contentscript.js
+++ b/browser/components/extensions/test/browser/browser_ext_legacy_extension_context_contentscript.js
@@ -22,27 +22,29 @@ function promiseAddonStartup(extension) 
  * tab info.
  */
 add_task(function* test_legacy_extension_context_contentscript_connection() {
   function backgroundScript() {
     // Extract the assigned uuid from the background page url and send it
     // in a test message.
     let uuid = window.location.hostname;
 
-    browser.test.onMessage.addListener(msg => {
+    browser.test.onMessage.addListener(async msg => {
       if (msg == "open-test-tab") {
-        browser.tabs.create({url: "http://example.com/"})
-          .then(tab => browser.test.sendMessage("get-expected-sender-info", {
-            uuid, tab,
-          }));
+        let tab = await browser.tabs.create({url: "http://example.com/"});
+        browser.test.sendMessage("get-expected-sender-info",
+                                 {uuid, tab});
       } else if (msg == "close-current-tab") {
-        browser.tabs.query({active: true})
-          .then(tabs => browser.tabs.remove(tabs[0].id))
-          .then(() => browser.test.sendMessage("current-tab-closed", true))
-          .catch(() => browser.test.sendMessage("current-tab-closed", false));
+        try {
+          let [tab] = await browser.tabs.query({active: true});
+          await browser.tabs.remove(tab.id);
+          browser.test.sendMessage("current-tab-closed", true);
+        } catch (e) {
+          browser.test.sendMessage("current-tab-closed", false);
+        }
       }
     });
 
     browser.test.sendMessage("ready");
   }
 
   function contentScript() {
     browser.runtime.sendMessage("webextension -> legacy_extension message", (reply) => {
--- a/browser/components/extensions/test/browser/browser_ext_optionsPage_privileges.js
+++ b/browser/components/extensions/test/browser/browser_ext_optionsPage_privileges.js
@@ -12,26 +12,27 @@ add_task(function* test_tab_options_priv
           browser.test.log(`Error: ${error} :: ${error.stack}`);
           browser.test.notifyFail("options-ui-privileges");
         });
       }
     });
     browser.runtime.openOptionsPage();
   }
 
-  function optionsScript() {
-    browser.tabs.query({url: "http://example.com/"}).then(tabs => {
-      browser.test.assertEq("http://example.com/", tabs[0].url, "Got the expect tab");
-      return browser.tabs.getCurrent();
-    }).then(tab => {
+  async function optionsScript() {
+    try {
+      let [tab] = await browser.tabs.query({url: "http://example.com/"});
+      browser.test.assertEq("http://example.com/", tab.url, "Got the expect tab");
+
+      tab = await browser.tabs.getCurrent();
       browser.runtime.sendMessage({msgName: "removeTabId", tabId: tab.id});
-    }).catch(error => {
+    } catch (error) {
       browser.test.log(`Error: ${error} :: ${error.stack}`);
       browser.test.notifyFail("options-ui-privileges");
-    });
+    }
   }
 
   const ID = "options_privileges@tests.mozilla.org";
   let extension = ExtensionTestUtils.loadExtension({
     useAddonManager: "temporary",
 
     manifest: {
       applications: {gecko: {id: ID}},
--- a/browser/components/extensions/test/browser/browser_ext_pageAction_context.js
+++ b/browser/components/extensions/test/browser/browser_ext_pageAction_context.js
@@ -74,108 +74,105 @@ add_task(function* testTabSwitchContext(
           browser.tabs.onUpdated.addListener(function listener(tabId, changed) {
             if (tabId == details.id && changed.url == details.url) {
               browser.tabs.onUpdated.removeListener(listener);
               resolve();
             }
           });
         });
       };
+
       return [
         expect => {
           browser.test.log("Initial state. No icon visible.");
           expect(null);
         },
-        expect => {
+        async expect => {
           browser.test.log("Show the icon on the first tab, expect default properties.");
-          browser.pageAction.show(tabs[0]).then(() => {
-            expect(details[0]);
-          });
+          await browser.pageAction.show(tabs[0]);
+          expect(details[0]);
         },
         expect => {
           browser.test.log("Change the icon. Expect default properties excluding the icon.");
           browser.pageAction.setIcon({tabId: tabs[0], path: "1.png"});
           expect(details[1]);
         },
-        expect => {
+        async expect => {
           browser.test.log("Create a new tab. No icon visible.");
-          browser.tabs.create({active: true, url: "about:blank?0"}, tab => {
-            tabs.push(tab.id);
-            expect(null);
-          });
+          let tab = await browser.tabs.create({active: true, url: "about:blank?0"});
+          tabs.push(tab.id);
+          expect(null);
         },
         expect => {
           browser.test.log("Await tab load. No icon visible.");
           expect(null);
         },
-        expect => {
+        async expect => {
           browser.test.log("Change properties. Expect new properties.");
           let tabId = tabs[1];
-          browser.pageAction.show(tabId).then(() => {
-            browser.pageAction.setIcon({tabId, path: "2.png"});
-            browser.pageAction.setPopup({tabId, popup: "2.html"});
-            browser.pageAction.setTitle({tabId, title: "Title 2"});
+          await browser.pageAction.show(tabId);
 
-            expect(details[2]);
-          });
+          browser.pageAction.setIcon({tabId, path: "2.png"});
+          browser.pageAction.setPopup({tabId, popup: "2.html"});
+          browser.pageAction.setTitle({tabId, title: "Title 2"});
+
+          expect(details[2]);
         },
-        expect => {
+        async expect => {
           browser.test.log("Change the hash. Expect same properties.");
 
-          promiseTabLoad({id: tabs[1], url: "about:blank?0#ref"}).then(() => {
-            expect(details[2]);
-          });
+          let promise = promiseTabLoad({id: tabs[1], url: "about:blank?0#ref"});
+          browser.tabs.update(tabs[1], {url: "about:blank?0#ref"});
+          await promise;
 
-          browser.tabs.update(tabs[1], {url: "about:blank?0#ref"});
+          expect(details[2]);
         },
         expect => {
           browser.test.log("Clear the title. Expect default title.");
           browser.pageAction.setTitle({tabId: tabs[1], title: ""});
 
           expect(details[3]);
         },
-        expect => {
+        async expect => {
           browser.test.log("Navigate to a new page. Expect icon hidden.");
 
           // TODO: This listener should not be necessary, but the |tabs.update|
           // callback currently fires too early in e10s windows.
-          promiseTabLoad({id: tabs[1], url: "about:blank?1"}).then(() => {
-            expect(null);
-          });
+          let promise = promiseTabLoad({id: tabs[1], url: "about:blank?1"});
 
           browser.tabs.update(tabs[1], {url: "about:blank?1"});
-        },
-        expect => {
-          browser.test.log("Show the icon. Expect default properties again.");
-          browser.pageAction.show(tabs[1]).then(() => {
-            expect(details[0]);
-          });
+
+          await promise;
+          expect(null);
         },
-        expect => {
+        async expect => {
+          browser.test.log("Show the icon. Expect default properties again.");
+
+          await browser.pageAction.show(tabs[1]);
+          expect(details[0]);
+        },
+        async expect => {
           browser.test.log("Switch back to the first tab. Expect previously set properties.");
-          browser.tabs.update(tabs[0], {active: true}, () => {
-            expect(details[1]);
-          });
+          await browser.tabs.update(tabs[0], {active: true});
+          expect(details[1]);
         },
-        expect => {
+        async expect => {
           browser.test.log("Hide the icon on tab 2. Switch back, expect hidden.");
-          browser.pageAction.hide(tabs[1]).then(() => {
-            browser.tabs.update(tabs[1], {active: true}, () => {
-              expect(null);
-            });
-          });
+          await browser.pageAction.hide(tabs[1]);
+
+          await browser.tabs.update(tabs[1], {active: true});
+          expect(null);
         },
-        expect => {
+        async expect => {
           browser.test.log("Switch back to tab 1. Expect previous results again.");
-          browser.tabs.remove(tabs[1], () => {
-            expect(details[1]);
-          });
+          await browser.tabs.remove(tabs[1]);
+          expect(details[1]);
         },
-        expect => {
+        async expect => {
           browser.test.log("Hide the icon. Expect hidden.");
-          browser.pageAction.hide(tabs[0]).then(() => {
-            expect(null);
-          });
+
+          await browser.pageAction.hide(tabs[0]);
+          expect(null);
         },
       ];
     },
   });
 });
--- a/browser/components/extensions/test/browser/browser_ext_pageAction_popup.js
+++ b/browser/components/extensions/test/browser/browser_ext_pageAction_popup.js
@@ -34,17 +34,17 @@ add_task(function* testPageActionPopup()
 
       "data/popup-b.html": scriptPage("popup-b.js"),
       "data/popup-b.js": function() {
         browser.runtime.sendMessage("from-popup-b");
       },
 
       "data/background.html": scriptPage("background.js"),
 
-      "data/background.js": function() {
+      "data/background.js": async function() {
         let tabId;
 
         let sendClick;
         let tests = [
           () => {
             sendClick({expectEvent: false, expectPopup: "a"});
           },
           () => {
@@ -110,17 +110,17 @@ add_task(function* testPageActionPopup()
           } else {
             browser.test.fail("unexpected click event");
           }
 
           expect.event = false;
           browser.test.sendMessage("next-test");
         });
 
-        browser.test.onMessage.addListener((msg) => {
+        browser.test.onMessage.addListener(msg => {
           if (msg == "close-popup") {
             browser.runtime.sendMessage("close-popup");
             return;
           }
 
           if (msg != "next-test") {
             browser.test.fail("Expecting 'next-test' message");
           }
@@ -128,23 +128,21 @@ add_task(function* testPageActionPopup()
           if (tests.length) {
             let test = tests.shift();
             test();
           } else {
             browser.test.notifyPass("pageaction-tests-done");
           }
         });
 
-        browser.tabs.query({active: true, currentWindow: true}, tabs => {
-          tabId = tabs[0].id;
+        let [tab] = await browser.tabs.query({active: true, currentWindow: true});
+        tabId = tab.id;
 
-          browser.pageAction.show(tabId).then(() => {
-            browser.test.sendMessage("next-test");
-          });
-        });
+        await browser.pageAction.show(tabId);
+        browser.test.sendMessage("next-test");
       },
     },
   });
 
   extension.onMessage("send-click", () => {
     clickPageAction(extension);
   });
 
--- a/browser/components/extensions/test/browser/browser_ext_pageAction_title.js
+++ b/browser/components/extensions/test/browser/browser_ext_pageAction_title.js
@@ -79,106 +79,100 @@ add_task(function* testTabSwitchContext(
           });
         });
       };
       return [
         expect => {
           browser.test.log("Initial state. No icon visible.");
           expect(null);
         },
-        expect => {
+        async expect => {
           browser.test.log("Show the icon on the first tab, expect default properties.");
-          browser.pageAction.show(tabs[0]).then(() => {
-            expect(details[0]);
-          });
+          await browser.pageAction.show(tabs[0]);
+          expect(details[0]);
         },
         expect => {
           browser.test.log("Change the icon. Expect default properties excluding the icon.");
           browser.pageAction.setIcon({tabId: tabs[0], path: "1.png"});
           expect(details[1]);
         },
-        expect => {
+        async expect => {
           browser.test.log("Create a new tab. No icon visible.");
-          browser.tabs.create({active: true, url: "about:blank?0"}, tab => {
-            tabs.push(tab.id);
-            expect(null);
-          });
+          let tab = await browser.tabs.create({active: true, url: "about:blank?0"});
+          tabs.push(tab.id);
+          expect(null);
         },
         expect => {
           browser.test.log("Await tab load. No icon visible.");
           expect(null);
         },
-        expect => {
+        async expect => {
           browser.test.log("Change properties. Expect new properties.");
           let tabId = tabs[1];
-          browser.pageAction.show(tabId).then(() => {
-            browser.pageAction.setIcon({tabId, path: "2.png"});
-            browser.pageAction.setPopup({tabId, popup: "2.html"});
-            browser.pageAction.setTitle({tabId, title: "Title 2"});
 
-            expect(details[2]);
-          });
+          await browser.pageAction.show(tabId);
+          browser.pageAction.setIcon({tabId, path: "2.png"});
+          browser.pageAction.setPopup({tabId, popup: "2.html"});
+          browser.pageAction.setTitle({tabId, title: "Title 2"});
+
+          expect(details[2]);
         },
-        expect => {
+        async expect => {
           browser.test.log("Change the hash. Expect same properties.");
 
-          promiseTabLoad({id: tabs[1], url: "about:blank?0#ref"}).then(() => {
-            expect(details[2]);
-          });
+          let promise = promiseTabLoad({id: tabs[1], url: "about:blank?0#ref"});
 
           browser.tabs.update(tabs[1], {url: "about:blank?0#ref"});
+
+          await promise;
+          expect(details[2]);
         },
         expect => {
           browser.test.log("Clear the title. Expect default title.");
           browser.pageAction.setTitle({tabId: tabs[1], title: ""});
 
           expect(details[3]);
         },
-        expect => {
+        async expect => {
           browser.test.log("Navigate to a new page. Expect icon hidden.");
 
           // TODO: This listener should not be necessary, but the |tabs.update|
           // callback currently fires too early in e10s windows.
-          promiseTabLoad({id: tabs[1], url: "about:blank?1"}).then(() => {
-            expect(null);
-          });
+          let promise = promiseTabLoad({id: tabs[1], url: "about:blank?1"});
 
           browser.tabs.update(tabs[1], {url: "about:blank?1"});
-        },
-        expect => {
-          browser.test.log("Show the icon. Expect default properties again.");
-          browser.pageAction.show(tabs[1]).then(() => {
-            expect(details[0]);
-          });
+
+          await promise;
+          expect(null);
         },
-        expect => {
+        async expect => {
+          browser.test.log("Show the icon. Expect default properties again.");
+          await browser.pageAction.show(tabs[1]);
+          expect(details[0]);
+        },
+        async expect => {
           browser.test.log("Switch back to the first tab. Expect previously set properties.");
-          browser.tabs.update(tabs[0], {active: true}, () => {
-            expect(details[1]);
-          });
+          await browser.tabs.update(tabs[0], {active: true});
+          expect(details[1]);
         },
-        expect => {
+        async expect => {
           browser.test.log("Hide the icon on tab 2. Switch back, expect hidden.");
-          browser.pageAction.hide(tabs[1]).then(() => {
-            browser.tabs.update(tabs[1], {active: true}, () => {
-              expect(null);
-            });
-          });
+          await browser.pageAction.hide(tabs[1]);
+          await browser.tabs.update(tabs[1], {active: true});
+          expect(null);
         },
-        expect => {
+        async expect => {
           browser.test.log("Switch back to tab 1. Expect previous results again.");
-          browser.tabs.remove(tabs[1], () => {
-            expect(details[1]);
-          });
+          await browser.tabs.remove(tabs[1]);
+          expect(details[1]);
         },
-        expect => {
+        async expect => {
           browser.test.log("Hide the icon. Expect hidden.");
-          browser.pageAction.hide(tabs[0]).then(() => {
-            expect(null);
-          });
+          await browser.pageAction.hide(tabs[0]);
+          expect(null);
         },
       ];
     },
   });
 });
 
 add_task(function* testDefaultTitle() {
   yield runTests({
@@ -206,21 +200,20 @@ add_task(function* testDefaultTitle() {
          "icon": browser.runtime.getURL("icon.png")},
       ];
 
       return [
         expect => {
           browser.test.log("Initial state. No icon visible.");
           expect(null);
         },
-        expect => {
+        async expect => {
           browser.test.log("Show the icon on the first tab, expect extension title as default title.");
-          browser.pageAction.show(tabs[0]).then(() => {
-            expect(details[0]);
-          });
+          await browser.pageAction.show(tabs[0]);
+          expect(details[0]);
         },
         expect => {
           browser.test.log("Change the title. Expect new title.");
           browser.pageAction.setTitle({tabId: tabs[0], title: "Foo Title"});
           expect(details[1]);
         },
         expect => {
           browser.test.log("Clear the title. Expect extension title.");
--- a/browser/components/extensions/test/browser/browser_ext_popup_sendMessage.js
+++ b/browser/components/extensions/test/browser/browser_ext_popup_sendMessage.js
@@ -15,55 +15,54 @@ add_task(function* test_popup_sendMessag
       "page_action": {
         "default_popup": "popup.html",
         "browser_style": true,
       },
     },
 
     files: {
       "popup.html": scriptPage("popup.js"),
-      "popup.js": function() {
-        browser.runtime.onMessage.addListener(msg => {
+      "popup.js": async function() {
+        browser.runtime.onMessage.addListener(async msg => {
           if (msg == "popup-ping") {
             return Promise.resolve("popup-pong");
           }
         });
 
-        browser.runtime.sendMessage("background-ping").then(response => {
-          browser.test.sendMessage("background-ping-response", response);
-        });
+        let response = await browser.runtime.sendMessage("background-ping");
+        browser.test.sendMessage("background-ping-response", response);
       },
     },
 
-    background() {
-      browser.tabs.query({active: true, currentWindow: true}).then(([tab]) => {
-        return browser.pageAction.show(tab.id);
-      }).then(() => {
-        browser.test.sendMessage("page-action-ready");
-      });
+    async background() {
+      browser.runtime.onMessage.addListener(async msg => {
+        if (msg == "background-ping") {
+          let response = await browser.runtime.sendMessage("popup-ping");
 
-      browser.runtime.onMessage.addListener(msg => {
-        if (msg == "background-ping") {
-          browser.runtime.sendMessage("popup-ping").then(response => {
-            browser.test.sendMessage("popup-ping-response", response);
-          });
+          browser.test.sendMessage("popup-ping-response", response);
 
-          return new Promise(resolve => {
+          await new Promise(resolve => {
             // Wait long enough that we're relatively sure the docShells have
             // been swapped. Note that this value is fairly arbitrary. The load
             // event that triggers the swap should happen almost immediately
             // after the message is sent. The extra quarter of a second gives us
             // enough leeway that we can expect to respond after the swap in the
             // vast majority of cases.
             setTimeout(resolve, 250);
-          }).then(() => {
-            return "background-pong";
           });
+
+          return "background-pong";
         }
       });
+
+      let [tab] = await browser.tabs.query({active: true, currentWindow: true});
+
+      await browser.pageAction.show(tab.id);
+
+      browser.test.sendMessage("page-action-ready");
     },
   });
 
   yield extension.startup();
 
   {
     clickBrowserAction(extension);
 
--- a/browser/components/extensions/test/browser/browser_ext_popup_shutdown.js
+++ b/browser/components/extensions/test/browser/browser_ext_popup_shutdown.js
@@ -1,19 +1,18 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 let getExtension = () => {
   return ExtensionTestUtils.loadExtension({
-    background() {
-      browser.tabs.query({active: true, currentWindow: true}, tabs => {
-        browser.pageAction.show(tabs[0].id)
-          .then(() => { browser.test.sendMessage("pageAction ready"); });
-      });
+    background: async function() {
+      let [tab] = await browser.tabs.query({active: true, currentWindow: true});
+      await browser.pageAction.show(tab.id);
+      browser.test.sendMessage("pageAction ready");
     },
 
     manifest: {
       "browser_action": {
         "default_popup": "popup.html",
         "browser_style": false,
       },
 
--- a/browser/components/extensions/test/browser/browser_ext_runtime_openOptionsPage.js
+++ b/browser/components/extensions/test/browser/browser_ext_runtime_openOptionsPage.js
@@ -56,17 +56,17 @@ add_tasks(function* test_inline_options(
   let extension = yield loadExtension(Object.assign({}, extraOptions, {
     manifest: {
       applications: {gecko: {id: "inline_options@tests.mozilla.org"}},
       "options_ui": {
         "page": "options.html",
       },
     },
 
-    background: function() {
+    background: async function() {
       let _optionsPromise;
       let awaitOptions = () => {
         browser.test.assertFalse(_optionsPromise, "Should not be awaiting options already");
 
         return new Promise(resolve => {
           _optionsPromise = {resolve};
         });
       };
@@ -77,76 +77,73 @@ add_tasks(function* test_inline_options(
             _optionsPromise.resolve(sender.tab);
             _optionsPromise = null;
           } else {
             browser.test.fail("Saw unexpected options page load");
           }
         }
       });
 
-      let firstTab, optionsTab;
-      browser.tabs.query({currentWindow: true, active: true}).then(tabs => {
-        firstTab = tabs[0].id;
+      try {
+        let [firstTab] = await browser.tabs.query({currentWindow: true, active: true});
 
         browser.test.log("Open options page. Expect fresh load.");
-        return Promise.all([
+
+        let [, optionsTab] = await Promise.all([
           browser.runtime.openOptionsPage(),
           awaitOptions(),
         ]);
-      }).then(([, tab]) => {
-        browser.test.assertEq("about:addons", tab.url, "Tab contains AddonManager");
-        browser.test.assertTrue(tab.active, "Tab is active");
-        browser.test.assertTrue(tab.id != firstTab, "Tab is a new tab");
 
-        optionsTab = tab.id;
+        browser.test.assertEq("about:addons", optionsTab.url, "Tab contains AddonManager");
+        browser.test.assertTrue(optionsTab.active, "Tab is active");
+        browser.test.assertTrue(optionsTab.id != firstTab.id, "Tab is a new tab");
+
         browser.test.assertEq(0, browser.extension.getViews({type: "popup"}).length, "viewType is not popup");
         browser.test.assertEq(1, browser.extension.getViews({type: "tab"}).length, "viewType is tab");
-        browser.test.assertEq(1, browser.extension.getViews({windowId: tab.windowId}).length, "windowId matches");
+        browser.test.assertEq(1, browser.extension.getViews({windowId: optionsTab.windowId}).length, "windowId matches");
+
         let views = browser.extension.getViews();
         browser.test.assertEq(2, views.length, "Expected the options page and the background page");
         browser.test.assertTrue(views.includes(window), "One of the views is the background page");
         browser.test.assertTrue(views.some(w => w.iAmOption), "One of the views is the options page");
 
         browser.test.log("Switch tabs.");
-        return browser.tabs.update(firstTab, {active: true});
-      }).then(() => {
+        await browser.tabs.update(firstTab.id, {active: true});
+
         browser.test.log("Open options page again. Expect tab re-selected, no new load.");
 
-        return browser.runtime.openOptionsPage();
-      }).then(() => {
-        return browser.tabs.query({currentWindow: true, active: true});
-      }).then(([tab]) => {
-        browser.test.assertEq(optionsTab, tab.id, "Tab is the same as the previous options tab");
+        await browser.runtime.openOptionsPage();
+        let [tab] = await browser.tabs.query({currentWindow: true, active: true});
+
+        browser.test.assertEq(optionsTab.id, tab.id, "Tab is the same as the previous options tab");
         browser.test.assertEq("about:addons", tab.url, "Tab contains AddonManager");
 
         browser.test.log("Ping options page.");
-        return browser.runtime.sendMessage("ping");
-      }).then((pong) => {
+        let pong = await browser.runtime.sendMessage("ping");
         browser.test.assertEq("pong", pong, "Got pong.");
 
         browser.test.log("Remove options tab.");
-        return browser.tabs.remove(optionsTab);
-      }).then(() => {
+        await browser.tabs.remove(optionsTab.id);
+
         browser.test.log("Open options page again. Expect fresh load.");
-        return Promise.all([
+        [, tab] = await Promise.all([
           browser.runtime.openOptionsPage(),
           awaitOptions(),
         ]);
-      }).then(([, tab]) => {
         browser.test.assertEq("about:addons", tab.url, "Tab contains AddonManager");
         browser.test.assertTrue(tab.active, "Tab is active");
-        browser.test.assertTrue(tab.id != optionsTab, "Tab is a new tab");
+        browser.test.assertTrue(tab.id != optionsTab.id, "Tab is a new tab");
 
-        return browser.tabs.remove(tab.id);
-      }).then(() => {
+        await browser.tabs.remove(tab.id);
+
         browser.test.notifyPass("options-ui");
-      }).catch(error => {
-        browser.test.log(`Error: ${error} :: ${error.stack}`);
+      } catch (error) {
+        browser.test.fail(`Error: ${error} :: ${error.stack}`);
         browser.test.notifyFail("options-ui");
-      });
+      }
     },
   }));
 
   yield extension.awaitFinish("options-ui");
   yield extension.unload();
 
   yield BrowserTestUtils.removeTab(tab);
 });
@@ -160,17 +157,17 @@ add_tasks(function* test_tab_options(ext
     manifest: {
       applications: {gecko: {id: "tab_options@tests.mozilla.org"}},
       "options_ui": {
         "page": "options.html",
         "open_in_tab": true,
       },
     },
 
-    background: function() {
+    background: async function() {
       let _optionsPromise;
       let awaitOptions = () => {
         browser.test.assertFalse(_optionsPromise, "Should not be awaiting options already");
 
         return new Promise(resolve => {
           _optionsPromise = {resolve};
         });
       };
@@ -183,77 +180,73 @@ add_tasks(function* test_tab_options(ext
           } else {
             browser.test.fail("Saw unexpected options page load");
           }
         }
       });
 
       let optionsURL = browser.extension.getURL("options.html");
 
-      let firstTab, optionsTab;
-      browser.tabs.query({currentWindow: true, active: true}).then(tabs => {
-        firstTab = tabs[0].id;
+      try {
+        let [firstTab] = await browser.tabs.query({currentWindow: true, active: true});
 
         browser.test.log("Open options page. Expect fresh load.");
-        return Promise.all([
+        let [, optionsTab] = await Promise.all([
           browser.runtime.openOptionsPage(),
           awaitOptions(),
         ]);
-      }).then(([, tab]) => {
-        browser.test.assertEq(optionsURL, tab.url, "Tab contains options.html");
-        browser.test.assertTrue(tab.active, "Tab is active");
-        browser.test.assertTrue(tab.id != firstTab, "Tab is a new tab");
+        browser.test.assertEq(optionsURL, optionsTab.url, "Tab contains options.html");
+        browser.test.assertTrue(optionsTab.active, "Tab is active");
+        browser.test.assertTrue(optionsTab.id != firstTab.id, "Tab is a new tab");
 
-        optionsTab = tab.id;
         browser.test.assertEq(0, browser.extension.getViews({type: "popup"}).length, "viewType is not popup");
         browser.test.assertEq(1, browser.extension.getViews({type: "tab"}).length, "viewType is tab");
-        browser.test.assertEq(1, browser.extension.getViews({windowId: tab.windowId}).length, "windowId matches");
+        browser.test.assertEq(1, browser.extension.getViews({windowId: optionsTab.windowId}).length, "windowId matches");
+
         let views = browser.extension.getViews();
         browser.test.assertEq(2, views.length, "Expected the options page and the background page");
         browser.test.assertTrue(views.includes(window), "One of the views is the background page");
         browser.test.assertTrue(views.some(w => w.iAmOption), "One of the views is the options page");
 
         browser.test.log("Switch tabs.");
-        return browser.tabs.update(firstTab, {active: true});
-      }).then(() => {
+        await browser.tabs.update(firstTab.id, {active: true});
+
         browser.test.log("Open options page again. Expect tab re-selected, no new load.");
 
-        return browser.runtime.openOptionsPage();
-      }).then(() => {
-        return browser.tabs.query({currentWindow: true, active: true});
-      }).then(([tab]) => {
-        browser.test.assertEq(optionsTab, tab.id, "Tab is the same as the previous options tab");
+        await browser.runtime.openOptionsPage();
+        let [tab] = await browser.tabs.query({currentWindow: true, active: true});
+
+        browser.test.assertEq(optionsTab.id, tab.id, "Tab is the same as the previous options tab");
         browser.test.assertEq(optionsURL, tab.url, "Tab contains options.html");
 
         // Unfortunately, we can't currently do this, since onMessage doesn't
         // currently support responses when there are multiple listeners.
         //
         // browser.test.log("Ping options page.");
         // return new Promise(resolve => browser.runtime.sendMessage("ping", resolve));
 
         browser.test.log("Remove options tab.");
-        return browser.tabs.remove(optionsTab);
-      }).then(() => {
+        await browser.tabs.remove(optionsTab.id);
+
         browser.test.log("Open options page again. Expect fresh load.");
-        return Promise.all([
+        [, tab] = await Promise.all([
           browser.runtime.openOptionsPage(),
           awaitOptions(),
         ]);
-      }).then(([, tab]) => {
         browser.test.assertEq(optionsURL, tab.url, "Tab contains options.html");
         browser.test.assertTrue(tab.active, "Tab is active");
-        browser.test.assertTrue(tab.id != optionsTab, "Tab is a new tab");
+        browser.test.assertTrue(tab.id != optionsTab.id, "Tab is a new tab");
 
-        return browser.tabs.remove(tab.id);
-      }).then(() => {
+        await browser.tabs.remove(tab.id);
+
         browser.test.notifyPass("options-ui-tab");
-      }).catch(error => {
-        browser.test.log(`Error: ${error} :: ${error.stack}`);
+      } catch (error) {
+        browser.test.fail(`Error: ${error} :: ${error.stack}`);
         browser.test.notifyFail("options-ui-tab");
-      });
+      }
     },
   }));
 
   yield extension.awaitFinish("options-ui-tab");
   yield extension.unload();
 
   yield BrowserTestUtils.removeTab(tab);
 });
@@ -261,34 +254,23 @@ add_tasks(function* test_tab_options(ext
 add_tasks(function* test_options_no_manifest(extraOptions) {
   info(`Test with no manifest key (${JSON.stringify(extraOptions)})`);
 
   let extension = yield loadExtension(Object.assign({}, extraOptions, {
     manifest: {
       applications: {gecko: {id: "no_options@tests.mozilla.org"}},
     },
 
-    background: function() {
+    async background() {
       browser.test.log("Try to open options page when not specified in the manifest.");
 
-      browser.runtime.openOptionsPage().then(
-        () => {
-          browser.test.fail("Opening options page without one specified in the manifest generated an error");
-          browser.test.notifyFail("options-no-manifest");
-        },
-        error => {
-          let expected = "No `options_ui` declared";
-          browser.test.assertTrue(
-            error.message.includes(expected),
-            `Got expected error (got: '${error.message}', expected: '${expected}'`);
-        }
-      ).then(() => {
-        browser.test.notifyPass("options-no-manifest");
-      }).catch(error => {
-        browser.test.log(`Error: ${error} :: ${error.stack}`);
-        browser.test.notifyFail("options-no-manifest");
-      });
+      await browser.test.assertRejects(
+        browser.runtime.openOptionsPage(),
+        /No `options_ui` declared/,
+        "Expected error from openOptionsPage()");
+
+      browser.test.notifyPass("options-no-manifest");
     },
   }));
 
   yield extension.awaitFinish("options-no-manifest");
   yield extension.unload();
 });
--- a/browser/components/extensions/test/browser/browser_ext_runtime_openOptionsPage_uninstall.js
+++ b/browser/components/extensions/test/browser/browser_ext_runtime_openOptionsPage_uninstall.js
@@ -43,17 +43,17 @@ add_task(function* test_inline_options_u
   let extension = yield loadExtension({
     manifest: {
       applications: {gecko: {id: "inline_options_uninstall@tests.mozilla.org"}},
       "options_ui": {
         "page": "options.html",
       },
     },
 
-    background: function() {
+    background: async function() {
       let _optionsPromise;
       let awaitOptions = () => {
         browser.test.assertFalse(_optionsPromise, "Should not be awaiting options already");
 
         return new Promise(resolve => {
           _optionsPromise = {resolve};
         });
       };
@@ -64,34 +64,33 @@ add_task(function* test_inline_options_u
             _optionsPromise.resolve(sender.tab);
             _optionsPromise = null;
           } else {
             browser.test.fail("Saw unexpected options page load");
           }
         }
       });
 
-      let firstTab;
-      browser.tabs.query({currentWindow: true, active: true}).then(tabs => {
-        firstTab = tabs[0].id;
+      try {
+        let [firstTab] = await browser.tabs.query({currentWindow: true, active: true});
 
         browser.test.log("Open options page. Expect fresh load.");
-        return Promise.all([
+        let [, tab] = await Promise.all([
           browser.runtime.openOptionsPage(),
           awaitOptions(),
         ]);
-      }).then(([, tab]) => {
+
         browser.test.assertEq("about:addons", tab.url, "Tab contains AddonManager");
         browser.test.assertTrue(tab.active, "Tab is active");
-        browser.test.assertTrue(tab.id != firstTab, "Tab is a new tab");
+        browser.test.assertTrue(tab.id != firstTab.id, "Tab is a new tab");
 
         browser.test.sendMessage("options-ui-open");
-      }).catch(error => {
+      } catch (error) {
         browser.test.fail(`Error: ${error} :: ${error.stack}`);
-      });
+      }
     },
   });
 
   yield extension.awaitMessage("options-ui-open");
   yield extension.unload();
 
   is(gBrowser.selectedBrowser.currentURI.spec, "about:addons",
      "Add-on manager tab should still be open");
--- a/browser/components/extensions/test/browser/browser_ext_runtime_setUninstallURL.js
+++ b/browser/components/extensions/test/browser/browser_ext_runtime_setUninstallURL.js
@@ -25,71 +25,62 @@ function* makeAndInstallXPI(id, backgrou
   let loadTab = yield loadPromise;
   yield BrowserTestUtils.removeTab(loadTab);
 
   return addon;
 }
 
 
 add_task(function* test_setuninstallurl_badargs() {
-  function backgroundScript() {
-    let promises = [
-      browser.runtime.setUninstallURL("this is not a url")
-        .then(() => {
-          browser.test.notifyFail("setUninstallURL should have failed with bad url");
-        })
-        .catch(error => {
-          browser.test.assertTrue(/Invalid URL/.test(error.message), "error message indicates malformed url");
-        }),
+  async function background() {
+    await browser.test.assertRejects(
+      browser.runtime.setUninstallURL("this is not a url"),
+      /Invalid URL/,
+      "setUninstallURL with an invalid URL should fail");
 
-      browser.runtime.setUninstallURL("file:///etc/passwd")
-        .then(() => {
-          browser.test.notifyFail("setUninstallURL should have failed with non-http[s] url");
-        })
-        .catch(error => {
-          browser.test.assertTrue(/must have the scheme http or https/.test(error.message), "error message indicates bad scheme");
-        }),
-    ];
+    await browser.test.assertRejects(
+      browser.runtime.setUninstallURL("file:///etc/passwd"),
+      /must have the scheme http or https/,
+      "setUninstallURL with an illegal URL should fail");
 
-    Promise.all(promises)
-      .then(() => browser.test.notifyPass("setUninstallURL bad params"));
+    browser.test.notifyPass("setUninstallURL bad params");
   }
 
   let extension = ExtensionTestUtils.loadExtension({
-    background: "(" + backgroundScript.toString() + ")()",
+    background,
   });
   yield extension.startup();
   yield extension.awaitFinish();
   yield extension.unload();
 });
 
 // Test the documented behavior of setUninstallURL() that passing an
 // empty string is equivalent to not setting an uninstall URL
 // (i.e., no new tab is opened upon uninstall)
 add_task(function* test_setuninstall_empty_url() {
-  function backgroundScript() {
-    browser.runtime.setUninstallURL("")
-      .then(() => browser.tabs.create({url: "http://example.com/addon_loaded"}));
+  async function backgroundScript() {
+    await browser.runtime.setUninstallURL("");
+    browser.tabs.create({url: "http://example.com/addon_loaded"});
   }
 
   let addon = yield makeAndInstallXPI("test_uinstallurl2@tests.mozilla.org",
                                       backgroundScript,
                                       "http://example.com/addon_loaded");
 
   addon.uninstall(true);
   info("uninstalled");
 
   // no need to explicitly check for the absence of a new tab,
   // BrowserTestUtils will eventually complain if one is opened.
 });
 
 add_task(function* test_setuninstallurl() {
-  function backgroundScript() {
-    browser.runtime.setUninstallURL("http://example.com/addon_uninstalled")
-      .then(() => browser.tabs.create({url: "http://example.com/addon_loaded"}));
+  async function backgroundScript() {
+    await browser.runtime.setUninstallURL("http://example.com/addon_uninstalled");
+    browser.tabs.create({url: "http://example.com/addon_loaded"});
   }
 
   let addon = yield makeAndInstallXPI("test_uinstallurl@tests.mozilla.org",
                                       backgroundScript,
                                       "http://example.com/addon_loaded");
 
   // look for a new tab with the uninstall url.
   let uninstallPromise = BrowserTestUtils.waitForNewTab(gBrowser, "http://example.com/addon_uninstalled");
--- a/browser/components/extensions/test/browser/browser_ext_tabs_audio.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_audio.js
@@ -3,27 +3,17 @@
 "use strict";
 
 add_task(function* () {
   let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank?1");
   let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank?2");
 
   gBrowser.selectedTab = tab1;
 
-  function background() {
-    // Wrap API methods in promise-based variants.
-    let promiseTabs = {};
-    Object.keys(browser.tabs).forEach(method => {
-      promiseTabs[method] = (...args) => {
-        return new Promise(resolve => {
-          browser.tabs[method](...args, resolve);
-        });
-      };
-    });
-
+  async function background() {
     function promiseUpdated(tabId, attr) {
       return new Promise(resolve => {
         let onUpdated = (tabId_, changeInfo, tab) => {
           if (tabId == tabId_ && attr in changeInfo) {
             browser.tabs.onUpdated.removeListener(onUpdated);
 
             resolve({changeInfo, tab});
           }
@@ -42,134 +32,129 @@ add_task(function* () {
     function changeTab(tabId, attr, on) {
       return new Promise((resolve, reject) => {
         deferred[tabId] = {resolve, reject};
         browser.test.sendMessage("change-tab", tabId, attr, on);
       });
     }
 
 
-    let windowId;
-    let tabIds;
-    promiseTabs.query({lastFocusedWindow: true}).then(tabs => {
+    try {
+      let tabs = await browser.tabs.query({lastFocusedWindow: true});
       browser.test.assertEq(tabs.length, 3, "We have three tabs");
 
       for (let tab of tabs) {
         // Note: We want to check that these are actual boolean values, not
         // just that they evaluate as false.
         browser.test.assertEq(false, tab.mutedInfo.muted, "Tab is not muted");
         browser.test.assertEq(undefined, tab.mutedInfo.reason, "Tab has no muted info reason");
         browser.test.assertEq(false, tab.audible, "Tab is not audible");
       }
 
-      windowId = tabs[0].windowId;
-      tabIds = [tabs[1].id, tabs[2].id];
+      let windowId = tabs[0].windowId;
+      let tabIds = [tabs[1].id, tabs[2].id];
 
       browser.test.log("Test initial queries for muted and audible return no tabs");
-      return Promise.all([
-        promiseTabs.query({windowId, audible: false}),
-        promiseTabs.query({windowId, audible: true}),
-        promiseTabs.query({windowId, muted: true}),
-        promiseTabs.query({windowId, muted: false}),
-      ]);
-    }).then(([silent, audible, muted, nonMuted]) => {
+      let silent = await browser.tabs.query({windowId, audible: false});
+      let audible = await browser.tabs.query({windowId, audible: true});
+      let muted = await browser.tabs.query({windowId, muted: true});
+      let nonMuted = await browser.tabs.query({windowId, muted: false});
+
       browser.test.assertEq(3, silent.length, "Three silent tabs");
       browser.test.assertEq(0, audible.length, "No audible tabs");
 
       browser.test.assertEq(0, muted.length, "No muted tabs");
       browser.test.assertEq(3, nonMuted.length, "Three non-muted tabs");
 
       browser.test.log("Toggle muted and audible externally on one tab each, and check results");
-      return Promise.all([
+      [muted, audible] = await Promise.all([
         promiseUpdated(tabIds[0], "mutedInfo"),
         promiseUpdated(tabIds[1], "audible"),
         changeTab(tabIds[0], "muted", true),
         changeTab(tabIds[1], "audible", true),
       ]);
-    }).then(([muted, audible]) => {
+
       for (let obj of [muted.changeInfo, muted.tab]) {
         browser.test.assertEq(true, obj.mutedInfo.muted, "Tab is muted");
         browser.test.assertEq("user", obj.mutedInfo.reason, "Tab was muted by the user");
       }
 
       browser.test.assertEq(true, audible.changeInfo.audible, "Tab audible state changed");
       browser.test.assertEq(true, audible.tab.audible, "Tab is audible");
 
       browser.test.log("Re-check queries. Expect one audible and one muted tab");
-      return Promise.all([
-        promiseTabs.query({windowId, audible: false}),
-        promiseTabs.query({windowId, audible: true}),
-        promiseTabs.query({windowId, muted: true}),
-        promiseTabs.query({windowId, muted: false}),
-      ]);
-    }).then(([silent, audible, muted, nonMuted]) => {
+      silent = await browser.tabs.query({windowId, audible: false});
+      audible = await browser.tabs.query({windowId, audible: true});
+      muted = await browser.tabs.query({windowId, muted: true});
+      nonMuted = await browser.tabs.query({windowId, muted: false});
+
       browser.test.assertEq(2, silent.length, "Two silent tabs");
       browser.test.assertEq(1, audible.length, "One audible tab");
 
       browser.test.assertEq(1, muted.length, "One muted tab");
       browser.test.assertEq(2, nonMuted.length, "Two non-muted tabs");
 
       browser.test.assertEq(true, muted[0].mutedInfo.muted, "Tab is muted");
       browser.test.assertEq("user", muted[0].mutedInfo.reason, "Tab was muted by the user");
 
       browser.test.assertEq(true, audible[0].audible, "Tab is audible");
 
       browser.test.log("Toggle muted internally on two tabs, and check results");
-      return Promise.all([
+      [nonMuted, muted] = await Promise.all([
         promiseUpdated(tabIds[0], "mutedInfo"),
         promiseUpdated(tabIds[1], "mutedInfo"),
-        promiseTabs.update(tabIds[0], {muted: false}),
-        promiseTabs.update(tabIds[1], {muted: true}),
+        browser.tabs.update(tabIds[0], {muted: false}),
+        browser.tabs.update(tabIds[1], {muted: true}),
       ]);
-    }).then(([unmuted, muted]) => {
-      for (let obj of [unmuted.changeInfo, unmuted.tab]) {
+
+      for (let obj of [nonMuted.changeInfo, nonMuted.tab]) {
         browser.test.assertEq(false, obj.mutedInfo.muted, "Tab is not muted");
       }
       for (let obj of [muted.changeInfo, muted.tab]) {
         browser.test.assertEq(true, obj.mutedInfo.muted, "Tab is muted");
       }
 
-      for (let obj of [unmuted.changeInfo, unmuted.tab, muted.changeInfo, muted.tab]) {
+      for (let obj of [nonMuted.changeInfo, nonMuted.tab, muted.changeInfo, muted.tab]) {
         browser.test.assertEq("extension", obj.mutedInfo.reason, "Mute state changed by extension");
 
         // FIXME: browser.runtime.id is currently broken.
         browser.test.assertEq(browser.i18n.getMessage("@@extension_id"),
                               obj.mutedInfo.extensionId,
                               "Mute state changed by extension");
       }
 
       browser.test.log("Test that mutedInfo is preserved by sessionstore");
-      return changeTab(tabIds[1], "duplicate").then(promiseTabs.get);
-    }).then(tab => {
+      let tab = await changeTab(tabIds[1], "duplicate").then(browser.tabs.get);
+
       browser.test.assertEq(true, tab.mutedInfo.muted, "Tab is muted");
 
       browser.test.assertEq("extension", tab.mutedInfo.reason, "Mute state changed by extension");
 
       // FIXME: browser.runtime.id is currently broken.
       browser.test.assertEq(browser.i18n.getMessage("@@extension_id"),
                             tab.mutedInfo.extensionId,
                             "Mute state changed by extension");
 
       browser.test.log("Unmute externally, and check results");
-      return Promise.all([
+      [nonMuted] = await Promise.all([
         promiseUpdated(tabIds[1], "mutedInfo"),
         changeTab(tabIds[1], "muted", false),
-        promiseTabs.remove(tab.id),
+        browser.tabs.remove(tab.id),
       ]);
-    }).then(([unmuted]) => {
-      for (let obj of [unmuted.changeInfo, unmuted.tab]) {
+
+      for (let obj of [nonMuted.changeInfo, nonMuted.tab]) {
         browser.test.assertEq(false, obj.mutedInfo.muted, "Tab is not muted");
         browser.test.assertEq("user", obj.mutedInfo.reason, "Mute state changed by user");
       }
 
       browser.test.notifyPass("tab-audio");
-    }).catch(e => {
+    } catch (e) {
       browser.test.fail(`${e} :: ${e.stack}`);
       browser.test.notifyFail("tab-audio");
-    });
+    }
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["tabs"],
     },
 
     background,
--- a/browser/components/extensions/test/browser/browser_ext_tabs_captureVisibleTab.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_captureVisibleTab.js
@@ -21,103 +21,95 @@ function* runTest(options) {
     </html>
   `;
 
   let url = `data:text/html,${encodeURIComponent(html)}`;
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url, true);
 
   tab.linkedBrowser.fullZoom = options.fullZoom;
 
-  function background(options) {
-    // Wrap API methods in promise-based variants.
-    let promiseTabs = {};
-    Object.keys(browser.tabs).forEach(method => {
-      promiseTabs[method] = (...args) => {
-        return new Promise(resolve => {
-          browser.tabs[method](...args, resolve);
-        });
-      };
-    });
-
+  async function background(options) {
     browser.test.log(`Test color ${options.color} at fullZoom=${options.fullZoom}`);
 
-    promiseTabs.query({currentWindow: true, active: true}).then(([tab]) => {
-      return Promise.all([
-        promiseTabs.captureVisibleTab(tab.windowId, {format: "jpeg", quality: 95}),
-        promiseTabs.captureVisibleTab(tab.windowId, {format: "png", quality: 95}),
-        promiseTabs.captureVisibleTab(tab.windowId, {quality: 95}),
-        promiseTabs.captureVisibleTab(tab.windowId),
-      ]).then(([jpeg, png, ...pngs]) => {
-        browser.test.assertTrue(pngs.every(url => url == png), "All PNGs are identical");
+    try {
+      let [tab] = await browser.tabs.query({currentWindow: true, active: true});
 
-        browser.test.assertTrue(jpeg.startsWith("data:image/jpeg;base64,"), "jpeg is JPEG");
-        browser.test.assertTrue(png.startsWith("data:image/png;base64,"), "png is PNG");
+      let [jpeg, png, ...pngs] = await Promise.all([
+        browser.tabs.captureVisibleTab(tab.windowId, {format: "jpeg", quality: 95}),
+        browser.tabs.captureVisibleTab(tab.windowId, {format: "png", quality: 95}),
+        browser.tabs.captureVisibleTab(tab.windowId, {quality: 95}),
+        browser.tabs.captureVisibleTab(tab.windowId),
+      ]);
+
+      browser.test.assertTrue(pngs.every(url => url == png), "All PNGs are identical");
+
+      browser.test.assertTrue(jpeg.startsWith("data:image/jpeg;base64,"), "jpeg is JPEG");
+      browser.test.assertTrue(png.startsWith("data:image/png;base64,"), "png is PNG");
 
-        let promises = [jpeg, png].map(url => new Promise(resolve => {
-          let img = new Image();
-          img.src = url;
-          img.onload = () => resolve(img);
-        }));
-        return Promise.all(promises);
-      }).then(([jpeg, png]) => {
-        let tabDims = `${tab.width}\u00d7${tab.height}`;
+      let promises = [jpeg, png].map(url => new Promise(resolve => {
+        let img = new Image();
+        img.src = url;
+        img.onload = () => resolve(img);
+      }));
 
-        let images = {jpeg, png};
-        for (let format of Object.keys(images)) {
-          let img = images[format];
+      [jpeg, png] = await Promise.all(promises);
+      let tabDims = `${tab.width}\u00d7${tab.height}`;
 
-          let dims = `${img.width}\u00d7${img.height}`;
-          browser.test.assertEq(tabDims, dims, `${format} dimensions are correct`);
+      let images = {jpeg, png};
+      for (let format of Object.keys(images)) {
+        let img = images[format];
 
-          let canvas = document.createElement("canvas");
-          canvas.width = img.width;
-          canvas.height = img.height;
-          canvas.mozOpaque = true;
+        let dims = `${img.width}\u00d7${img.height}`;
+        browser.test.assertEq(tabDims, dims, `${format} dimensions are correct`);
 
-          let ctx = canvas.getContext("2d");
-          ctx.drawImage(img, 0, 0);
+        let canvas = document.createElement("canvas");
+        canvas.width = img.width;
+        canvas.height = img.height;
+        canvas.mozOpaque = true;
 
-          // Check the colors of the first and last pixels of the image, to make
-          // sure we capture the entire frame, and scale it correctly.
-          let coords = [
-            {x: 0, y: 0,
-             color: options.color},
-            {x: img.width - 1,
-             y: img.height - 1,
-             color: options.color},
-            {x: img.width / 2 | 0,
-             y: img.height / 2 | 0,
-             color: options.neutral},
-          ];
+        let ctx = canvas.getContext("2d");
+        ctx.drawImage(img, 0, 0);
 
-          for (let {x, y, color} of coords) {
-            let imageData = ctx.getImageData(x, y, 1, 1).data;
+        // Check the colors of the first and last pixels of the image, to make
+        // sure we capture the entire frame, and scale it correctly.
+        let coords = [
+          {x: 0, y: 0,
+           color: options.color},
+          {x: img.width - 1,
+           y: img.height - 1,
+           color: options.color},
+          {x: img.width / 2 | 0,
+           y: img.height / 2 | 0,
+           color: options.neutral},
+        ];
 
-            if (format == "png") {
-              browser.test.assertEq(`rgba(${color},255)`, `rgba(${[...imageData]})`, `${format} image color is correct at (${x}, ${y})`);
-            } else {
-              // Allow for some deviation in JPEG version due to lossy compression.
-              const SLOP = 3;
+        for (let {x, y, color} of coords) {
+          let imageData = ctx.getImageData(x, y, 1, 1).data;
 
-              browser.test.log(`Testing ${format} image color at (${x}, ${y}), have rgba(${[...imageData]}), expecting approx. rgba(${color},255)`);
+          if (format == "png") {
+            browser.test.assertEq(`rgba(${color},255)`, `rgba(${[...imageData]})`, `${format} image color is correct at (${x}, ${y})`);
+          } else {
+            // Allow for some deviation in JPEG version due to lossy compression.
+            const SLOP = 3;
 
-              browser.test.assertTrue(Math.abs(color[0] - imageData[0]) <= SLOP, `${format} image color.red is correct at (${x}, ${y})`);
-              browser.test.assertTrue(Math.abs(color[1] - imageData[1]) <= SLOP, `${format} image color.green is correct at (${x}, ${y})`);
-              browser.test.assertTrue(Math.abs(color[2] - imageData[2]) <= SLOP, `${format} image color.blue is correct at (${x}, ${y})`);
-              browser.test.assertEq(255, imageData[3], `${format} image color.alpha is correct at (${x}, ${y})`);
-            }
+            browser.test.log(`Testing ${format} image color at (${x}, ${y}), have rgba(${[...imageData]}), expecting approx. rgba(${color},255)`);
+
+            browser.test.assertTrue(Math.abs(color[0] - imageData[0]) <= SLOP, `${format} image color.red is correct at (${x}, ${y})`);
+            browser.test.assertTrue(Math.abs(color[1] - imageData[1]) <= SLOP, `${format} image color.green is correct at (${x}, ${y})`);
+            browser.test.assertTrue(Math.abs(color[2] - imageData[2]) <= SLOP, `${format} image color.blue is correct at (${x}, ${y})`);
+            browser.test.assertEq(255, imageData[3], `${format} image color.alpha is correct at (${x}, ${y})`);
           }
         }
+      }
 
-        browser.test.notifyPass("captureVisibleTab");
-      });
-    }).catch(e => {
+      browser.test.notifyPass("captureVisibleTab");
+    } catch (e) {
       browser.test.fail(`Error: ${e} :: ${e.stack}`);
       browser.test.notifyFail("captureVisibleTab");
-    });
+    }
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["<all_urls>"],
     },
 
     background: `(${background})(${JSON.stringify(options)})`,
--- a/browser/components/extensions/test/browser/browser_ext_tabs_cookieStoreId.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_cookieStoreId.js
@@ -37,107 +37,93 @@ add_task(function* () {
 
     background: function() {
       function testTab(data, tab) {
         browser.test.assertTrue(data.success, "we want a success");
         browser.test.assertTrue(!!tab, "we have a tab");
         browser.test.assertEq(data.expectedCookieStoreId, tab.cookieStoreId, "tab should have the correct cookieStoreId");
       }
 
-      function runTest(data) {
-        // Tab Creation
-        browser.tabs.create({windowId: data.privateTab ? this.privateWindowId : this.defaultWindowId,
-                             cookieStoreId: data.cookieStoreId})
+      async function runTest(data) {
+        try {
+          // Tab Creation
+          let tab;
+          try {
+            tab = await browser.tabs.create({
+              windowId: data.privateTab ? this.privateWindowId : this.defaultWindowId,
+              cookieStoreId: data.cookieStoreId,
+            });
+
+            browser.test.assertTrue(!data.failure, "we want a success");
+          } catch (error) {
+            browser.test.assertTrue(!!data.failure, "we want a failure");
 
-        // Tests for tab creation
-        .then((tab) => {
-          testTab(data, tab);
-          return tab;
-        }, (error) => {
-          browser.test.assertTrue(!!data.failure, "we want a failure");
-          if (data.failure == "illegal") {
-            browser.test.assertTrue(/Illegal cookieStoreId/.test(error.message),
-                                    "runtime.lastError should report the expected error message");
-          } else if (data.failure == "defaultToPrivate") {
-            browser.test.assertTrue("Illegal to set private cookieStorageId in a non private window",
-                                    error.message,
-                                    "runtime.lastError should report the expected error message");
-          } else if (data.failure == "privateToDefault") {
-            browser.test.assertTrue("Illegal to set non private cookieStorageId in a private window",
-                                    error.message,
-                                    "runtime.lastError should report the expected error message");
-          } else if (data.failure == "exist") {
-            browser.test.assertTrue(/No cookie store exists/.test(error.message),
-                                    "runtime.lastError should report the expected error message");
-          } else {
-            browser.test.fail("The test is broken");
+            if (data.failure == "illegal") {
+              browser.test.assertTrue(/Illegal cookieStoreId/.test(error.message),
+                                      "runtime.lastError should report the expected error message");
+            } else if (data.failure == "defaultToPrivate") {
+              browser.test.assertTrue("Illegal to set private cookieStorageId in a non private window",
+                                      error.message,
+                                      "runtime.lastError should report the expected error message");
+            } else if (data.failure == "privateToDefault") {
+              browser.test.assertTrue("Illegal to set non private cookieStorageId in a private window",
+                                      error.message,
+                                      "runtime.lastError should report the expected error message");
+            } else if (data.failure == "exist") {
+              browser.test.assertTrue(/No cookie store exists/.test(error.message),
+                                      "runtime.lastError should report the expected error message");
+            } else {
+              browser.test.fail("The test is broken");
+            }
+
+            browser.test.sendMessage("test-done");
+            return;
           }
 
-          return null;
-        })
+          // Tests for tab creation
+          testTab(data, tab);
 
-        // Tests for tab querying
-        .then((tab) => {
-          if (tab) {
-            return browser.tabs.query({windowId: data.privateTab ? this.privateWindowId : this.defaultWindowId,
-                                       cookieStoreId: data.cookieStoreId})
-                   .then((tabs) => {
-                     browser.test.assertTrue(tabs.length >= 1, "Tab found!");
-                     testTab(data, tabs[0]);
-                     return tab;
-                   });
-          }
-        })
+          {
+            // Tests for tab querying
+            let [tab] = await browser.tabs.query({
+              windowId: data.privateTab ? this.privateWindowId : this.defaultWindowId,
+              cookieStoreId: data.cookieStoreId,
+            });
 
-        .then((tab) => {
-          if (tab) {
-            return browser.cookies.getAllCookieStores()
-                   .then(stores => {
-                     let store = stores.find(store => store.id === tab.cookieStoreId);
-                     browser.test.assertTrue(!!store, "We have a store for this tab.");
-                     return tab;
-                   });
+            browser.test.assertTrue(tab != undefined, "Tab found!");
+            testTab(data, tab);
           }
-        })
+
+          let stores = await browser.cookies.getAllCookieStores();
 
-        .then((tab) => {
-          if (tab) {
-            return browser.tabs.remove(tab.id);
-          }
-        })
+          let store = stores.find(store => store.id === tab.cookieStoreId);
+          browser.test.assertTrue(!!store, "We have a store for this tab.");
+
+          await browser.tabs.remove(tab.id);
 
-        .then(() => {
           browser.test.sendMessage("test-done");
-        }, () => {
-          browser.test.fail("An exception has ben thrown");
-        });
+        } catch (e) {
+          browser.test.fail("An exception has been thrown");
+        }
       }
 
-      function initialize() {
-        browser.windows.create({incognito: true})
-        .then((win) => {
-          this.privateWindowId = win.id;
-          return browser.windows.create({incognito: false});
-        })
-        .then((win) => {
-          this.defaultWindowId = win.id;
-        })
-        .then(() => {
-          browser.test.sendMessage("ready");
-        });
+      async function initialize() {
+        let win = await browser.windows.create({incognito: true});
+        this.privateWindowId = win.id;
+
+        win = await browser.windows.create({incognito: false});
+        this.defaultWindowId = win.id;
+
+        browser.test.sendMessage("ready");
       }
 
-      function shutdown() {
-        browser.windows.remove(this.privateWindowId)
-        .then(() => {
-          browser.windows.remove(this.defaultWindowId);
-        })
-        .then(() => {
-          browser.test.sendMessage("gone");
-        });
+      async function shutdown() {
+        await browser.windows.remove(this.privateWindowId);
+        await browser.windows.remove(this.defaultWindowId);
+        browser.test.sendMessage("gone");
       }
 
       // Waiting for messages
       browser.test.onMessage.addListener((msg, data) => {
         if (msg == "be-ready") {
           initialize();
         } else if (msg == "test") {
           runTest(data);
--- a/browser/components/extensions/test/browser/browser_ext_tabs_create.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_create.js
@@ -85,17 +85,17 @@ add_task(function* () {
               result: {index: 1, active: false},
             },
             {
               create: {windowId: activeWindow},
               result: {windowId: activeWindow},
             },
           ];
 
-          function nextTest() {
+          async function nextTest() {
             if (!tests.length) {
               browser.test.notifyPass("tabs.create");
               return;
             }
 
             let test = tests.shift();
             let expected = Object.assign({}, DEFAULTS, test.result);
 
@@ -114,43 +114,39 @@ add_task(function* () {
             let createdPromise = new Promise(resolve => {
               let onCreated = tab => {
                 browser.test.assertTrue("id" in tab, `Expected tabs.onCreated callback to receive tab object`);
                 resolve();
               };
               browser.tabs.onCreated.addListener(onCreated);
             });
 
-            let tabId;
-            Promise.all([
+            let [tab] = await Promise.all([
               browser.tabs.create(test.create),
               createdPromise,
-            ]).then(([tab]) => {
-              tabId = tab.id;
+            ]);
+            let tabId = tab.id;
 
-              for (let key of Object.keys(expected)) {
-                if (key === "url") {
-                  // FIXME: This doesn't get updated until later in the load cycle.
-                  continue;
-                }
-
-                browser.test.assertEq(expected[key], tab[key], `Expected value for tab.${key}`);
+            for (let key of Object.keys(expected)) {
+              if (key === "url") {
+                // FIXME: This doesn't get updated until later in the load cycle.
+                continue;
               }
 
-              return updatedPromise;
-            }).then(updated => {
-              browser.test.assertEq(tabId, updated.tabId, `Expected value for tab.id`);
-              browser.test.assertEq(expected.url, updated.url, `Expected value for tab.url`);
+              browser.test.assertEq(expected[key], tab[key], `Expected value for tab.${key}`);
+            }
 
-              return browser.tabs.remove(tabId);
-            }).then(() => {
-              return browser.tabs.update(activeTab, {active: true});
-            }).then(() => {
-              nextTest();
-            });
+            let updated = await updatedPromise;
+            browser.test.assertEq(tabId, updated.tabId, `Expected value for tab.id`);
+            browser.test.assertEq(expected.url, updated.url, `Expected value for tab.url`);
+
+            await browser.tabs.remove(tabId);
+            await browser.tabs.update(activeTab, {active: true});
+
+            nextTest();
           }
 
           nextTest();
         }
 
         browser.tabs.query({active: true, currentWindow: true}, tabs => {
           activeTab = tabs[0].id;
           activeWindow = tabs[0].windowId;
--- a/browser/components/extensions/test/browser/browser_ext_tabs_detectLanguage.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_detectLanguage.js
@@ -3,48 +3,44 @@
 "use strict";
 
 add_task(function* testDetectLanguage() {
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["tabs"],
     },
 
-    background() {
+    background: async function() {
       const BASE_PATH = "browser/browser/components/extensions/test/browser";
 
       function loadTab(url) {
         return browser.tabs.create({url});
       }
 
-      loadTab(`http://example.co.jp/${BASE_PATH}/file_language_ja.html`).then(tab => {
-        return browser.tabs.detectLanguage(tab.id).then(lang => {
-          browser.test.assertEq("ja", lang, "Japanese document should be detected as Japanese");
-          return browser.tabs.remove(tab.id);
-        });
-      }).then(() => {
-        return loadTab(`http://example.co.jp/${BASE_PATH}/file_language_fr_en.html`);
-      }).then(tab => {
-        return browser.tabs.detectLanguage(tab.id).then(lang => {
-          browser.test.assertEq("fr", lang, "French/English document should be detected as primarily French");
-          return browser.tabs.remove(tab.id);
-        });
-      }).then(() => {
-        return loadTab(`http://example.co.jp/${BASE_PATH}/file_language_tlh.html`);
-      }).then(tab => {
-        return browser.tabs.detectLanguage(tab.id).then(lang => {
-          browser.test.assertEq("und", lang, "Klingon document should not be detected, should return 'und'");
-          return browser.tabs.remove(tab.id);
-        });
-      }).then(() => {
+      try {
+        let tab = await loadTab(`http://example.co.jp/${BASE_PATH}/file_language_ja.html`);
+        let lang = await browser.tabs.detectLanguage(tab.id);
+        browser.test.assertEq("ja", lang, "Japanese document should be detected as Japanese");
+        await browser.tabs.remove(tab.id);
+
+        tab = await loadTab(`http://example.co.jp/${BASE_PATH}/file_language_fr_en.html`);
+        lang = await browser.tabs.detectLanguage(tab.id);
+        browser.test.assertEq("fr", lang, "French/English document should be detected as primarily French");
+        await browser.tabs.remove(tab.id);
+
+        tab = await loadTab(`http://example.co.jp/${BASE_PATH}/file_language_tlh.html`);
+        lang = await browser.tabs.detectLanguage(tab.id);
+        browser.test.assertEq("und", lang, "Klingon document should not be detected, should return 'und'");
+        await browser.tabs.remove(tab.id);
+
         browser.test.notifyPass("detectLanguage");
-      }).catch(e => {
+      } catch (e) {
         browser.test.fail(`Error: ${e} :: ${e.stack}`);
         browser.test.notifyFail("detectLanguage");
-      });
+      }
     },
   });
 
   yield extension.startup();
 
   yield extension.awaitFinish("detectLanguage");
 
   yield extension.unload();
--- a/browser/components/extensions/test/browser/browser_ext_tabs_duplicate.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_duplicate.js
@@ -36,17 +36,17 @@ add_task(function* testDuplicateTab() {
   yield extension.unload();
 
   while (gBrowser.tabs[0].linkedBrowser.currentURI.spec === "http://example.net/") {
     yield BrowserTestUtils.removeTab(gBrowser.tabs[0]);
   }
 });
 
 add_task(function* testDuplicateTabLazily() {
-  function background() {
+  async function background() {
     let tabLoadComplete = new Promise(resolve => {
       browser.test.onMessage.addListener((message, tabId, result) => {
         if (message == "duplicate-tab-done") {
           resolve(tabId);
         }
       });
     });
 
@@ -56,42 +56,38 @@ add_task(function* testDuplicateTabLazil
           if (tabId == tabId_ && changed.status == "complete") {
             browser.tabs.onUpdated.removeListener(listener);
             resolve();
           }
         });
       });
     }
 
-    let startTabId;
-    let url = "http://example.com/browser/browser/components/extensions/test/browser/file_dummy.html";
-    browser.tabs.create({url}, tab => {
-      startTabId = tab.id;
+    try {
+      let url = "http://example.com/browser/browser/components/extensions/test/browser/file_dummy.html";
+      let tab = await browser.tabs.create({url});
+      let startTabId = tab.id;
 
-      awaitLoad(startTabId).then(() => {
-        browser.test.sendMessage("duplicate-tab", startTabId);
+      await awaitLoad(startTabId);
+      browser.test.sendMessage("duplicate-tab", startTabId);
 
-        tabLoadComplete.then(unloadedTabId => {
-          browser.tabs.get(startTabId, loadedtab => {
-            browser.test.assertEq("Dummy test page", loadedtab.title, "Title should be returned for loaded pages");
-            browser.test.assertEq("complete", loadedtab.status, "Tab status should be complete for loaded pages");
-          });
+      let unloadedTabId = await tabLoadComplete;
+      let loadedtab = await browser.tabs.get(startTabId);
+      browser.test.assertEq("Dummy test page", loadedtab.title, "Title should be returned for loaded pages");
+      browser.test.assertEq("complete", loadedtab.status, "Tab status should be complete for loaded pages");
 
-          browser.tabs.get(unloadedTabId, unloadedtab => {
-            browser.test.assertEq("Dummy test page", unloadedtab.title, "Title should be returned after page has been unloaded");
-          });
+      let unloadedtab = await browser.tabs.get(unloadedTabId);
+      browser.test.assertEq("Dummy test page", unloadedtab.title, "Title should be returned after page has been unloaded");
 
-          browser.tabs.remove([tab.id, unloadedTabId]);
-          browser.test.notifyPass("tabs.hasCorrectTabTitle");
-        });
-      }).catch(e => {
-        browser.test.fail(`${e} :: ${e.stack}`);
-        browser.test.notifyFail("tabs.hasCorrectTabTitle");
-      });
-    });
+      await browser.tabs.remove([tab.id, unloadedTabId]);
+      browser.test.notifyPass("tabs.hasCorrectTabTitle");
+    } catch (e) {
+      browser.test.fail(`${e} :: ${e.stack}`);
+      browser.test.notifyFail("tabs.hasCorrectTabTitle");
+    }
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["tabs"],
     },
 
     background,
--- a/browser/components/extensions/test/browser/browser_ext_tabs_events.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_events.js
@@ -1,14 +1,14 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 add_task(function* testTabEvents() {
-  function background() {
+  async function background() {
     let events = [];
     browser.tabs.onCreated.addListener(tab => {
       events.push({type: "onCreated", tab});
     });
 
     browser.tabs.onAttached.addListener((tabId, info) => {
       events.push(Object.assign({type: "onAttached", tabId}, info));
     });
@@ -20,136 +20,131 @@ add_task(function* testTabEvents() {
     browser.tabs.onRemoved.addListener((tabId, info) => {
       events.push(Object.assign({type: "onRemoved", tabId}, info));
     });
 
     browser.tabs.onMoved.addListener((tabId, info) => {
       events.push(Object.assign({type: "onMoved", tabId}, info));
     });
 
-    function expectEvents(names) {
+    async function expectEvents(names) {
       browser.test.log(`Expecting events: ${names.join(", ")}`);
 
-      return new Promise(resolve => {
-        setTimeout(resolve, 0);
-      }).then(() => {
-        browser.test.assertEq(names.length, events.length, "Got expected number of events");
-        for (let [i, name] of names.entries()) {
-          browser.test.assertEq(name, i in events && events[i].type,
-                                `Got expected ${name} event`);
-        }
-        return events.splice(0);
-      });
+      await new Promise(resolve => setTimeout(resolve, 0));
+
+      browser.test.assertEq(names.length, events.length, "Got expected number of events");
+      for (let [i, name] of names.entries()) {
+        browser.test.assertEq(name, i in events && events[i].type,
+                              `Got expected ${name} event`);
+      }
+      return events.splice(0);
     }
 
-    browser.test.log("Create second browser window");
-    let windowId;
-    Promise.all([
-      browser.windows.getCurrent(),
-      browser.windows.create({url: "about:blank"}),
-    ]).then(windows => {
-      windowId = windows[0].id;
+    try {
+      browser.test.log("Create second browser window");
+
+      let windows = await Promise.all([
+        browser.windows.getCurrent(),
+        browser.windows.create({url: "about:blank"}),
+      ]);
+
+      let windowId = windows[0].id;
       let otherWindowId = windows[1].id;
-      let initialTab;
 
-      return expectEvents(["onCreated"]).then(([created]) => {
-        initialTab = created.tab;
+      let [created] = await expectEvents(["onCreated"]);
+      let initialTab = created.tab;
+
 
-        browser.test.log("Create tab in window 1");
-        return browser.tabs.create({windowId, index: 0, url: "about:blank"});
-      }).then(tab => {
-        let oldIndex = tab.index;
-        browser.test.assertEq(0, oldIndex, "Tab has the expected index");
+      browser.test.log("Create tab in window 1");
+      let tab = await browser.tabs.create({windowId, index: 0, url: "about:blank"});
+      let oldIndex = tab.index;
+      browser.test.assertEq(0, oldIndex, "Tab has the expected index");
+
+      [created] = await expectEvents(["onCreated"]);
+      browser.test.assertEq(tab.id, created.tab.id, "Got expected tab ID");
+      browser.test.assertEq(oldIndex, created.tab.index, "Got expected tab index");
+
 
-        return expectEvents(["onCreated"]).then(([created]) => {
-          browser.test.assertEq(tab.id, created.tab.id, "Got expected tab ID");
-          browser.test.assertEq(oldIndex, created.tab.index, "Got expected tab index");
+      browser.test.log("Move tab to window 2");
+      await browser.tabs.move([tab.id], {windowId: otherWindowId, index: 0});
 
-          browser.test.log("Move tab to window 2");
-          return browser.tabs.move([tab.id], {windowId: otherWindowId, index: 0});
-        }).then(() => {
-          return expectEvents(["onDetached", "onAttached"]);
-        }).then(([detached, attached]) => {
-          browser.test.assertEq(oldIndex, detached.oldPosition, "Expected old index");
-          browser.test.assertEq(windowId, detached.oldWindowId, "Expected old window ID");
+      let [detached, attached] = await expectEvents(["onDetached", "onAttached"]);
+      browser.test.assertEq(oldIndex, detached.oldPosition, "Expected old index");
+      browser.test.assertEq(windowId, detached.oldWindowId, "Expected old window ID");
+
+      browser.test.assertEq(0, attached.newPosition, "Expected new index");
+      browser.test.assertEq(otherWindowId, attached.newWindowId, "Expected new window ID");
+
 
-          browser.test.assertEq(0, attached.newPosition, "Expected new index");
-          browser.test.assertEq(otherWindowId, attached.newWindowId, "Expected new window ID");
+      browser.test.log("Move tab within the same window");
+      let [moved] = await browser.tabs.move([tab.id], {index: 1});
+      browser.test.assertEq(1, moved.index, "Expected new index");
+
+      [moved] = await expectEvents(["onMoved"]);
+      browser.test.assertEq(tab.id, moved.tabId, "Expected tab ID");
+      browser.test.assertEq(0, moved.fromIndex, "Expected old index");
+      browser.test.assertEq(1, moved.toIndex, "Expected new index");
+      browser.test.assertEq(otherWindowId, moved.windowId, "Expected window ID");
 
-          browser.test.log("Move tab within the same window");
-          return browser.tabs.move([tab.id], {index: 1});
-        }).then(([moved]) => {
-          browser.test.assertEq(1, moved.index, "Expected new index");
+
+      browser.test.log("Remove tab");
+      await browser.tabs.remove(tab.id);
+      let [removed] = await expectEvents(["onRemoved"]);
 
-          return expectEvents(["onMoved"]);
-        }).then(([moved]) => {
-          browser.test.assertEq(tab.id, moved.tabId, "Expected tab ID");
-          browser.test.assertEq(0, moved.fromIndex, "Expected old index");
-          browser.test.assertEq(1, moved.toIndex, "Expected new index");
-          browser.test.assertEq(otherWindowId, moved.windowId, "Expected window ID");
+      browser.test.assertEq(tab.id, removed.tabId, "Expected removed tab ID");
+      browser.test.assertEq(otherWindowId, removed.windowId, "Expected removed tab window ID");
+      // Note: We want to test for the actual boolean value false here.
+      browser.test.assertEq(false, removed.isWindowClosing, "Expected isWindowClosing value");
+
 
-          browser.test.log("Remove tab");
-          return browser.tabs.remove(tab.id);
-        }).then(() => {
-          return expectEvents(["onRemoved"]);
-        }).then(([removed]) => {
-          browser.test.assertEq(tab.id, removed.tabId, "Expected removed tab ID");
-          browser.test.assertEq(otherWindowId, removed.windowId, "Expected removed tab window ID");
-          // Note: We want to test for the actual boolean value false here.
-          browser.test.assertEq(false, removed.isWindowClosing, "Expected isWindowClosing value");
+      browser.test.log("Close second window");
+      await browser.windows.remove(otherWindowId);
+      [removed] = await expectEvents(["onRemoved"]);
+      browser.test.assertEq(initialTab.id, removed.tabId, "Expected removed tab ID");
+      browser.test.assertEq(otherWindowId, removed.windowId, "Expected removed tab window ID");
+      browser.test.assertEq(true, removed.isWindowClosing, "Expected isWindowClosing value");
+
 
-          browser.test.log("Close second window");
-          return browser.windows.remove(otherWindowId);
-        }).then(() => {
-          return expectEvents(["onRemoved"]);
-        }).then(([removed]) => {
-          browser.test.assertEq(initialTab.id, removed.tabId, "Expected removed tab ID");
-          browser.test.assertEq(otherWindowId, removed.windowId, "Expected removed tab window ID");
-          browser.test.assertEq(true, removed.isWindowClosing, "Expected isWindowClosing value");
+      browser.test.log("Create additional tab in window 1");
+      tab = await browser.tabs.create({windowId, url: "about:blank"});
+      await expectEvents(["onCreated"]);
+
+
+      browser.test.log("Create a new window, adopting the new tab");
+      // We have to explicitly wait for the event here, since its timing is
+      // not predictable.
+      let promiseAttached = new Promise(resolve => {
+        browser.tabs.onAttached.addListener(function listener(tabId) {
+          browser.tabs.onAttached.removeListener(listener);
+          resolve();
         });
       });
-    }).then(() => {
-      browser.test.log("Create additional tab in window 1");
-      return browser.tabs.create({windowId, url: "about:blank"});
-    }).then(tab => {
-      return expectEvents(["onCreated"]).then(() => {
-        browser.test.log("Create a new window, adopting the new tab");
 
-        // We have to explicitly wait for the event here, since its timing is
-        // not predictable.
-        let promiseAttached = new Promise(resolve => {
-          browser.tabs.onAttached.addListener(function listener(tabId) {
-            browser.tabs.onAttached.removeListener(listener);
-            resolve();
-          });
-        });
+      let [window] = await Promise.all([
+        browser.windows.create({tabId: tab.id}),
+        promiseAttached,
+      ]);
+
+      [detached, attached] = await expectEvents(["onDetached", "onAttached"]);
+
+      browser.test.assertEq(tab.id, detached.tabId, "Expected onDetached tab ID");
 
-        return Promise.all([
-          browser.windows.create({tabId: tab.id}),
-          promiseAttached,
-        ]);
-      }).then(([window]) => {
-        return expectEvents(["onDetached", "onAttached"]).then(([detached, attached]) => {
-          browser.test.assertEq(tab.id, detached.tabId, "Expected onDetached tab ID");
+      browser.test.assertEq(tab.id, attached.tabId, "Expected onAttached tab ID");
+      browser.test.assertEq(0, attached.newPosition, "Expected onAttached new index");
+      browser.test.assertEq(window.id, attached.newWindowId,
+                            "Expected onAttached new window id");
 
-          browser.test.assertEq(tab.id, attached.tabId, "Expected onAttached tab ID");
-          browser.test.assertEq(0, attached.newPosition, "Expected onAttached new index");
-          browser.test.assertEq(window.id, attached.newWindowId,
-                                "Expected onAttached new window id");
+      browser.test.log("Close the new window");
+      await browser.windows.remove(window.id);
 
-          browser.test.log("Close the new window");
-          return browser.windows.remove(window.id);
-        });
-      });
-    }).then(() => {
       browser.test.notifyPass("tabs-events");
-    }).catch(e => {
+    } catch (e) {
       browser.test.fail(`${e} :: ${e.stack}`);
       browser.test.notifyFail("tabs-events");
-    });
+    }
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["tabs"],
     },
 
     background,
@@ -171,26 +166,24 @@ add_task(function* testTabEventsSize() {
     });
 
     browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
       if (changeInfo.status == "complete") {
         sendSizeMessages(tab, "on-updated");
       }
     });
 
-    browser.test.onMessage.addListener((msg, arg) => {
+    browser.test.onMessage.addListener(async (msg, arg) => {
       if (msg === "create-tab") {
-        browser.tabs.create({url: "http://example.com/"}).then(tab => {
-          sendSizeMessages(tab, "create");
-          browser.test.sendMessage("created-tab-id", tab.id);
-        });
+        let tab = await browser.tabs.create({url: "http://example.com/"});
+        sendSizeMessages(tab, "create");
+        browser.test.sendMessage("created-tab-id", tab.id);
       } else if (msg === "update-tab") {
-        browser.tabs.update(arg, {url: "http://example.org/"}).then(tab => {
-          sendSizeMessages(tab, "update");
-        });
+        let tab = await browser.tabs.update(arg, {url: "http://example.org/"});
+        sendSizeMessages(tab, "update");
       } else if (msg === "remove-tab") {
         browser.tabs.remove(arg);
         browser.test.sendMessage("tab-removed");
       }
     });
 
     browser.test.sendMessage("ready");
   }
@@ -233,19 +226,17 @@ add_task(function* testTabEventsSize() {
     yield extension.awaitMessage("tab-removed");
   }
 
   yield extension.unload();
   SpecialPowers.clearUserPref(RESOLUTION_PREF);
 });
 
 add_task(function* testTabRemovalEvent() {
-  function background() {
-    let removalTabId;
-
+  async function background() {
     function awaitLoad(tabId) {
       return new Promise(resolve => {
         browser.tabs.onUpdated.addListener(function listener(tabId_, changed, tab) {
           if (tabId == tabId_ && changed.status == "complete") {
             browser.tabs.onUpdated.removeListener(listener);
             resolve();
           }
         });
@@ -257,27 +248,26 @@ add_task(function* testTabRemovalEvent()
       chrome.tabs.query({}, tabs => {
         for (let tab of tabs) {
           browser.test.assertTrue(tab.id != tabId, "Tab query should not include removed tabId");
         }
         browser.test.notifyPass("tabs-events");
       });
     });
 
-    let url = "http://example.com/browser/browser/components/extensions/test/browser/context.html";
-    browser.tabs.create({url: url})
-    .then(tab => {
-      removalTabId = tab.id;
-      return awaitLoad(tab.id);
-    }).then(() => {
-      return browser.tabs.remove(removalTabId);
-    }).catch(e => {
+    try {
+      let url = "http://example.com/browser/browser/components/extensions/test/browser/context.html";
+      let tab = await browser.tabs.create({url: url});
+      await awaitLoad(tab.id);
+
+      await browser.tabs.remove(tab.id);
+    } catch (e) {
       browser.test.fail(`${e} :: ${e.stack}`);
       browser.test.notifyFail("tabs-events");
-    });
+    }
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["tabs"],
     },
 
     background,
--- a/browser/components/extensions/test/browser/browser_ext_tabs_executeScript.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript.js
@@ -31,22 +31,23 @@ add_task(function* testExecuteScript() {
 
   let messageManagersSize = countMM(MessageChannel.messageManagers);
   let responseManagersSize = countMM(MessageChannel.responseManagers);
 
   const BASE = "http://mochi.test:8888/browser/browser/components/extensions/test/browser/";
   const URL = BASE + "file_iframe_document.html";
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, URL, true);
 
-  function background() {
-    browser.tabs.query({active: true, currentWindow: true}).then(tabs => {
-      return browser.webNavigation.getAllFrames({tabId: tabs[0].id});
-    }).then(frames => {
+  async function background() {
+    try {
+      let [tab] = await browser.tabs.query({active: true, currentWindow: true});
+      let frames = await browser.webNavigation.getAllFrames({tabId: tab.id});
+
       browser.test.log(`FRAMES: ${frames[1].frameId} ${JSON.stringify(frames)}\n`);
-      return Promise.all([
+      await Promise.all([
         browser.tabs.executeScript({
           code: "42",
         }).then(result => {
           browser.test.assertEq(1, result.length, "Expected one callback result");
           browser.test.assertEq(42, result[0], "Expected callback result");
         }),
 
         browser.tabs.executeScript({
@@ -122,30 +123,30 @@ add_task(function* testExecuteScript() {
           let details = {
             frame_id: Number.MAX_SAFE_INTEGER,
             matchesHost: ["http://mochi.test/", "http://example.com/"],
           };
           browser.test.assertEq(`No window matching ${JSON.stringify(details)}`,
                                 error.message, "Got expected error");
         }),
 
-        browser.tabs.create({url: "http://example.net/", active: false}).then(tab => {
-          return browser.tabs.executeScript(tab.id, {
+        browser.tabs.create({url: "http://example.net/", active: false}).then(async tab => {
+          await browser.tabs.executeScript(tab.id, {
             code: "42",
           }).then(result => {
             browser.test.fail("Expected error when trying to execute on invalid domain");
           }, error => {
             let details = {
               matchesHost: ["http://mochi.test/", "http://example.com/"],
             };
             browser.test.assertEq(`No window matching ${JSON.stringify(details)}`,
                                   error.message, "Got expected error");
-          }).then(() => {
-            return browser.tabs.remove(tab.id);
           });
+
+          await browser.tabs.remove(tab.id);
         }),
 
         browser.tabs.executeScript({
           code: "Promise.resolve(42)",
         }).then(result => {
           browser.test.assertEq(42, result[0], "Got expected promise resolution value as result");
         }),
 
@@ -173,37 +174,37 @@ add_task(function* testExecuteScript() {
         browser.tabs.executeScript({
           code: "location.href;",
           frameId: frames[1].frameId,
         }).then(result => {
           browser.test.assertEq(1, result.length, "Expected one result");
           browser.test.assertEq("http://mochi.test:8888/", result[0], "Result for frameId[1] is correct");
         }),
 
-        browser.tabs.create({url: "http://example.com/"}).then(tab => {
-          return browser.tabs.executeScript(tab.id, {code: "location.href"}).then(result => {
-            browser.test.assertEq("http://example.com/", result[0], "Script executed correctly in new tab");
+        browser.tabs.create({url: "http://example.com/"}).then(async tab => {
+          let result = await browser.tabs.executeScript(tab.id, {code: "location.href"});
 
-            return browser.tabs.remove(tab.id);
-          });
+          browser.test.assertEq("http://example.com/", result[0], "Script executed correctly in new tab");
+
+          await browser.tabs.remove(tab.id);
         }),
 
         new Promise(resolve => {
           browser.runtime.onMessage.addListener(message => {
             browser.test.assertEq("script ran", message, "Expected runtime message");
             resolve();
           });
         }),
       ]);
-    }).then(() => {
+
       browser.test.notifyPass("executeScript");
-    }).catch(e => {
+    } catch (e) {
       browser.test.fail(`Error: ${e} :: ${e.stack}`);
       browser.test.notifyFail("executeScript");
-    });
+    }
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["http://mochi.test/", "http://example.com/", "webNavigation"],
     },
 
     background,
--- a/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_bad.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_bad.js
@@ -1,16 +1,16 @@
 "use strict";
 
 // This is a pretty terrible hack, but it's the best we can do until we
 // support |executeScript| callbacks and |lastError|.
 function* testHasNoPermission(params) {
   let contentSetup = params.contentSetup || (() => Promise.resolve());
 
-  function background(contentSetup) {
+  async function background(contentSetup) {
     browser.runtime.onMessage.addListener((msg, sender) => {
       browser.test.assertEq(msg, "second script ran", "second script ran");
       browser.test.notifyPass("executeScript");
     });
 
     browser.test.onMessage.addListener(msg => {
       browser.test.assertEq(msg, "execute-script");
 
@@ -25,19 +25,19 @@ function* testHasNoPermission(params) {
         // it, but it's just about the best we can do until we
         // support callbacks for executeScript.
         browser.tabs.executeScript(tabs[1].id, {
           file: "second-script.js",
         });
       });
     });
 
-    contentSetup().then(() => {
-      browser.test.sendMessage("ready");
-    });
+    await contentSetup();
+
+    browser.test.sendMessage("ready");
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: params.manifest,
 
     background: `(${background})(${contentSetup})`,
 
     files: {
@@ -127,83 +127,76 @@ add_task(function* testBadPermissions() 
   });
 
   info("Test active tab, page action, no click");
   yield testHasNoPermission({
     manifest: {
       "permissions": ["http://example.com/", "activeTab"],
       "page_action": {},
     },
-    contentSetup() {
-      return new Promise(resolve => {
-        browser.tabs.query({active: true, currentWindow: true}, tabs => {
-          browser.pageAction.show(tabs[0].id).then(() => {
-            resolve();
-          });
-        });
-      });
+    async contentSetup() {
+      let [tab] = await browser.tabs.query({active: true, currentWindow: true});
+      await browser.pageAction.show(tab.id);
     },
   });
 
   yield BrowserTestUtils.removeTab(tab2);
   yield BrowserTestUtils.removeTab(tab1);
 });
 
 add_task(function* testBadURL() {
-  function background() {
-    browser.tabs.query({currentWindow: true}, tabs => {
-      let promises = [
-        new Promise(resolve => {
-          browser.tabs.executeScript({
-            file: "http://example.com/script.js",
-          }, result => {
-            browser.test.assertEq(undefined, result, "Result value");
-
-            browser.test.assertTrue(browser.extension.lastError instanceof Error,
-                                    "runtime.lastError is Error");
-
-            browser.test.assertTrue(browser.runtime.lastError instanceof Error,
-                                    "runtime.lastError is Error");
-
-            browser.test.assertEq(
-              "Files to be injected must be within the extension",
-              browser.extension.lastError && browser.extension.lastError.message,
-              "extension.lastError value");
-
-            browser.test.assertEq(
-              "Files to be injected must be within the extension",
-              browser.runtime.lastError && browser.runtime.lastError.message,
-              "runtime.lastError value");
-
-            resolve();
-          });
-        }),
-
+  async function background() {
+    let promises = [
+      new Promise(resolve => {
         browser.tabs.executeScript({
           file: "http://example.com/script.js",
-        }).catch(error => {
-          browser.test.assertTrue(error instanceof Error, "Error is Error");
+        }, result => {
+          browser.test.assertEq(undefined, result, "Result value");
+
+          browser.test.assertTrue(browser.extension.lastError instanceof Error,
+                                  "runtime.lastError is Error");
 
-          browser.test.assertEq(null, browser.extension.lastError,
-                                "extension.lastError value");
+          browser.test.assertTrue(browser.runtime.lastError instanceof Error,
+                                  "runtime.lastError is Error");
 
-          browser.test.assertEq(null, browser.runtime.lastError,
-                                "runtime.lastError value");
+          browser.test.assertEq(
+            "Files to be injected must be within the extension",
+            browser.extension.lastError && browser.extension.lastError.message,
+            "extension.lastError value");
 
           browser.test.assertEq(
             "Files to be injected must be within the extension",
-            error && error.message,
-            "error value");
-        }),
-      ];
+            browser.runtime.lastError && browser.runtime.lastError.message,
+            "runtime.lastError value");
+
+          resolve();
+        });
+      }),
+
+      browser.tabs.executeScript({
+        file: "http://example.com/script.js",
+      }).catch(error => {
+        browser.test.assertTrue(error instanceof Error, "Error is Error");
 
-      Promise.all(promises).then(() => {
-        browser.test.notifyPass("executeScript-lastError");
-      });
-    });
+        browser.test.assertEq(null, browser.extension.lastError,
+                              "extension.lastError value");
+
+        browser.test.assertEq(null, browser.runtime.lastError,
+                              "runtime.lastError value");
+
+        browser.test.assertEq(
+          "Files to be injected must be within the extension",
+          error && error.message,
+          "error value");
+      }),
+    ];
+
+    await Promise.all(promises);
+
+    browser.test.notifyPass("executeScript-lastError");
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["<all_urls>"],
     },
 
     background,
--- a/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_good.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_good.js
@@ -2,33 +2,33 @@
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 requestLongerTimeout(2);
 
 function* testHasPermission(params) {
   let contentSetup = params.contentSetup || (() => Promise.resolve());
 
-  function background(contentSetup) {
+  async function background(contentSetup) {
     browser.runtime.onMessage.addListener((msg, sender) => {
       browser.test.assertEq(msg, "script ran", "script ran");
       browser.test.notifyPass("executeScript");
     });
 
     browser.test.onMessage.addListener(msg => {
       browser.test.assertEq(msg, "execute-script");
 
       browser.tabs.executeScript({
         file: "script.js",
       });
     });
 
-    contentSetup().then(() => {
-      browser.test.sendMessage("ready");
-    });
+    await contentSetup();
+
+    browser.test.sendMessage("ready");
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: params.manifest,
 
     background: `(${background})(${contentSetup})`,
 
     files: {
@@ -117,57 +117,46 @@ add_task(function* testGoodPermissions()
   });
 
   info("Test activeTab permission with a page action click");
   yield testHasPermission({
     manifest: {
       "permissions": ["activeTab"],
       "page_action": {},
     },
-    contentSetup() {
-      return new Promise(resolve => {
-        browser.tabs.query({active: true, currentWindow: true}, tabs => {
-          browser.pageAction.show(tabs[0].id).then(() => {
-            resolve();
-          });
-        });
-      });
+    contentSetup: async () => {
+      let [tab] = await browser.tabs.query({active: true, currentWindow: true});
+      await browser.pageAction.show(tab.id);
     },
     setup: clickPageAction,
     tearDown: closePageAction,
   });
 
   info("Test activeTab permission with a browser action w/popup click");
   yield testHasPermission({
     manifest: {
       "permissions": ["activeTab"],
       "browser_action": {"default_popup": "_blank.html"},
     },
-    setup: extension => {
-      return clickBrowserAction(extension).then(() => {
-        return awaitExtensionPanel(extension, window, "_blank.html");
-      });
+    setup: async extension => {
+      await clickBrowserAction(extension);
+      return awaitExtensionPanel(extension, window, "_blank.html");
     },
     tearDown: closeBrowserAction,
   });
 
   info("Test activeTab permission with a page action w/popup click");
   yield testHasPermission({
     manifest: {
       "permissions": ["activeTab"],
       "page_action": {"default_popup": "_blank.html"},
     },
-    contentSetup() {
-      return new Promise(resolve => {
-        browser.tabs.query({active: true, currentWindow: true}, tabs => {
-          browser.pageAction.show(tabs[0].id).then(() => {
-            resolve();
-          });
-        });
-      });
+    contentSetup: async () => {
+      let [tab] = await browser.tabs.query({active: true, currentWindow: true});
+      await browser.pageAction.show(tab.id);
     },
     setup: clickPageAction,
     tearDown: closePageAction,
   });
 
   info("Test activeTab permission with a context menu click");
   yield testHasPermission({
     manifest: {
--- a/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_runAt.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_runAt.js
@@ -12,91 +12,86 @@
  *
  * And since we can't actually rely on that timing, it retries any attempts that
  * fail to load as early as expected, but don't load at any illegal time.
  */
 
 add_task(function* testExecuteScript() {
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank", true);
 
-  function background() {
+  async function background() {
     let tab;
 
     const BASE = "http://mochi.test:8888/browser/browser/components/extensions/test/browser/";
     const URL = BASE + "file_iframe_document.sjs";
 
     const MAX_TRIES = 10;
-    let tries = 0;
+
+    try {
+      [tab] = await browser.tabs.query({active: true, currentWindow: true});
 
-    function again() {
-      if (tries++ == MAX_TRIES) {
-        return Promise.reject(new Error("Max tries exceeded"));
-      }
-
-      let url = `${URL}?r=${Math.random()}`;
+      let success = false;
+      for (let tries = 0; !success && tries < MAX_TRIES; tries++) {
+        let url = `${URL}?r=${Math.random()}`;
 
-      let loadingPromise = new Promise(resolve => {
-        browser.tabs.onUpdated.addListener(function listener(tabId, changed, tab_) {
-          if (tabId == tab.id && changed.status == "loading" && tab_.url == url) {
-            browser.tabs.onUpdated.removeListener(listener);
-            resolve();
-          }
+        let loadingPromise = new Promise(resolve => {
+          browser.tabs.onUpdated.addListener(function listener(tabId, changed, tab_) {
+            if (tabId == tab.id && changed.status == "loading" && tab_.url == url) {
+              browser.tabs.onUpdated.removeListener(listener);
+              resolve();
+            }
+          });
         });
-      });
 
-      // TODO: Test allFrames and frameId.
+        // TODO: Test allFrames and frameId.
 
-      return browser.tabs.update({url}).then(() => {
-        return loadingPromise;
-      }).then(() => {
-        return Promise.all([
+        await browser.tabs.update({url});
+        await loadingPromise;
+
+        let states = await Promise.all([
           // Send the executeScript requests in the reverse order that we expect
           // them to execute in, to avoid them passing only because of timing
           // races.
           browser.tabs.executeScript({
             code: "document.readyState",
             runAt: "document_idle",
           }),
           browser.tabs.executeScript({
             code: "document.readyState",
             runAt: "document_end",
           }),
           browser.tabs.executeScript({
             code: "document.readyState",
             runAt: "document_start",
           }),
         ].reverse());
-      }).then(states => {
+
         browser.test.log(`Got states: ${states}`);
 
         // Make sure that none of our scripts executed earlier than expected,
         // regardless of retries.
         browser.test.assertTrue(states[1] == "interactive" || states[1] == "complete",
                                 `document_end state is valid: ${states[1]}`);
         browser.test.assertTrue(states[2] == "complete",
                                 `document_idle state is valid: ${states[2]}`);
 
         // If we have the earliest valid states for each script, we're done.
         // Otherwise, try again.
-        if (states[0] != "loading" || states[1] != "interactive" || states[2] != "complete") {
-          return again();
-        }
-      });
-    }
+        success = (states[0] == "loading" &&
+                   states[1] == "interactive" &&
+                   states[2] == "complete");
+      }
 
-    browser.tabs.query({active: true, currentWindow: true}).then(tabs => {
-      tab = tabs[0];
+      browser.test.assertTrue(success, "Got the earliest expected states at least once");
 
-      return again();
-    }).then(() => {
       browser.test.notifyPass("executeScript-runAt");
-    }).catch(e => {
+    } catch (e) {
       browser.test.fail(`Error: ${e} :: ${e.stack}`);
       browser.test.notifyFail("executeScript-runAt");
-    });
+    }
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["http://mochi.test/", "tabs"],
     },
 
     background,
--- a/browser/components/extensions/test/browser/browser_ext_tabs_insertCSS.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_insertCSS.js
@@ -5,18 +5,18 @@
 add_task(function* testExecuteScript() {
   let {MessageChannel} = Cu.import("resource://gre/modules/MessageChannel.jsm", {});
 
   let messageManagersSize = MessageChannel.messageManagers.size;
   let responseManagersSize = MessageChannel.responseManagers.size;
 
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/", true);
 
-  function background() {
-    let promises = [
+  async function background() {
+    let tasks = [
       {
         background: "transparent",
         foreground: "rgb(0, 113, 4)",
         promise: () => {
           return browser.tabs.insertCSS({
             file: "file2.css",
           });
         },
@@ -32,41 +32,35 @@ add_task(function* testExecuteScript() {
       },
     ];
 
     function checkCSS() {
       let computedStyle = window.getComputedStyle(document.body);
       return [computedStyle.backgroundColor, computedStyle.color];
     }
 
-    function next() {
-      if (!promises.length) {
-        return;
-      }
+    try {
+      for (let {promise, background, foreground} of tasks) {
+        let result = await promise();
 
-      let {promise, background, foreground} = promises.shift();
-      return promise().then(result => {
         browser.test.assertEq(undefined, result, "Expected callback result");
 
-        return browser.tabs.executeScript({
+        [result] = await browser.tabs.executeScript({
           code: `(${checkCSS})()`,
         });
-      }).then(([result]) => {
+
         browser.test.assertEq(background, result[0], "Expected background color");
         browser.test.assertEq(foreground, result[1], "Expected foreground color");
-        return next();
-      });
-    }
+      }
 
-    next().then(() => {
       browser.test.notifyPass("insertCSS");
-    }).catch(e => {
+    } catch (e) {
       browser.test.fail(`Error: ${e} :: ${e.stack}`);
       browser.test.notifyFailure("insertCSS");
-    });
+    }
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["http://mochi.test/"],
     },
 
     background,
--- a/browser/components/extensions/test/browser/browser_ext_tabs_move.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_move.js
@@ -8,116 +8,94 @@ add_task(function* () {
 
   gBrowser.selectedTab = tab1;
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["tabs"],
     },
 
-    background: function() {
-      browser.tabs.query({
-        lastFocusedWindow: true,
-      }, function(tabs) {
-        let tab = tabs[0];
-        browser.tabs.move(tab.id, {index: 0});
-        browser.tabs.query(
-          {lastFocusedWindow: true},
-          tabs => {
-            browser.test.assertEq(tabs[0].url, tab.url, "should be first tab");
-            browser.test.notifyPass("tabs.move.single");
-          });
-      });
+    background: async function() {
+      let [tab] = await browser.tabs.query({lastFocusedWindow: true});
+
+      browser.tabs.move(tab.id, {index: 0});
+      let tabs = await browser.tabs.query({lastFocusedWindow: true});
+
+      browser.test.assertEq(tabs[0].url, tab.url, "should be first tab");
+      browser.test.notifyPass("tabs.move.single");
     },
   });
 
   yield extension.startup();
   yield extension.awaitFinish("tabs.move.single");
   yield extension.unload();
 
   extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["tabs"],
     },
 
-    background: function() {
-      browser.tabs.query(
-        {lastFocusedWindow: true},
-        tabs => {
-          tabs.sort(function(a, b) { return a.url > b.url; });
-          browser.tabs.move(tabs.map(tab => tab.id), {index: 0});
-          browser.tabs.query(
-            {lastFocusedWindow: true},
-            tabs => {
-              browser.test.assertEq(tabs[0].url, "about:blank", "should be first tab");
-              browser.test.assertEq(tabs[1].url, "about:config", "should be second tab");
-              browser.test.assertEq(tabs[2].url, "about:robots", "should be third tab");
-              browser.test.notifyPass("tabs.move.multiple");
-            });
-        });
+    background: async function() {
+      let tabs = await browser.tabs.query({lastFocusedWindow: true});
+
+      tabs.sort(function(a, b) { return a.url > b.url; });
+
+      browser.tabs.move(tabs.map(tab => tab.id), {index: 0});
+
+      tabs = await browser.tabs.query({lastFocusedWindow: true});
+
+      browser.test.assertEq(tabs[0].url, "about:blank", "should be first tab");
+      browser.test.assertEq(tabs[1].url, "about:config", "should be second tab");
+      browser.test.assertEq(tabs[2].url, "about:robots", "should be third tab");
+
+      browser.test.notifyPass("tabs.move.multiple");
     },
   });
 
   yield extension.startup();
   yield extension.awaitFinish("tabs.move.multiple");
   yield extension.unload();
 
   extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["tabs"],
     },
 
-    background: function() {
-      browser.tabs.query(
-        {lastFocusedWindow: true},
-        tabs => {
-          let tab = tabs[1];
-          // Assuming that tab.id of 12345 does not exist.
-          browser.tabs.move([tab.id, 12345], {index: 0})
-          .then(
-            tabs => { browser.test.fail("Promise should not resolve"); },
-            e => {
-              browser.test.assertTrue(/Invalid tab/.test(e),
-                                      "Invalid tab should be in error");
-            })
-          .then(
-            browser.tabs.query({lastFocusedWindow: true})
-            .then(
-              (tabs) => {
-                browser.test.assertEq(tabs[1].url, tab.url, "should be second tab");
-                browser.test.notifyPass("tabs.move.invalid");
-              }
-            )
-          );
-        });
+    async background() {
+      let [, tab] = await browser.tabs.query({lastFocusedWindow: true});
+
+      // Assuming that tab.id of 12345 does not exist.
+      await browser.test.assertRejects(
+        browser.tabs.move([tab.id, 12345], {index: 0}),
+        /Invalid tab/,
+        "Should receive invalid tab error");
+
+      let tabs = await browser.tabs.query({lastFocusedWindow: true});
+      browser.test.assertEq(tabs[1].url, tab.url, "should be second tab");
+      browser.test.notifyPass("tabs.move.invalid");
     },
   });
 
   yield extension.startup();
   yield extension.awaitFinish("tabs.move.invalid");
   yield extension.unload();
 
   extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["tabs"],
     },
 
-    background: function() {
-      browser.tabs.query(
-        {lastFocusedWindow: true},
-        tabs => {
-          let tab = tabs[0];
-          browser.tabs.move(tab.id, {index: -1});
-          browser.tabs.query(
-            {lastFocusedWindow: true},
-            tabs => {
-              browser.test.assertEq(tabs[2].url, tab.url, "should be last tab");
-              browser.test.notifyPass("tabs.move.last");
-            });
-        });
+    background: async function() {
+      let [tab] = await browser.tabs.query({lastFocusedWindow: true});
+      browser.tabs.move(tab.id, {index: -1});
+
+      let tabs = await browser.tabs.query({lastFocusedWindow: true});
+
+      browser.test.assertEq(tabs[2].url, tab.url, "should be last tab");
+      browser.test.notifyPass("tabs.move.last");
     },
   });
 
   yield extension.startup();
   yield extension.awaitFinish("tabs.move.last");
   yield extension.unload();
 
   yield BrowserTestUtils.removeTab(tab1);
--- a/browser/components/extensions/test/browser/browser_ext_tabs_move_window.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_move_window.js
@@ -7,42 +7,33 @@ add_task(function* () {
   let window1 = yield BrowserTestUtils.openNewBrowserWindow();
   yield BrowserTestUtils.openNewForegroundTab(window1.gBrowser, "http://example.com/");
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["tabs"],
     },
 
-    background: function() {
-      browser.tabs.query({
-        url: "<all_urls>",
-      }, function(tabs) {
-        let destination = tabs[0];
-        let source = tabs[1]; // skip over about:blank in window1
-        browser.tabs.move(source.id, {windowId: destination.windowId, index: 0});
+    async background() {
+      let tabs = await browser.tabs.query({url: "<all_urls>"});
+      let destination = tabs[0];
+      let source = tabs[1]; // skip over about:blank in window1
 
-        browser.tabs.query(
-          {url: "<all_urls>"},
-          tabs => {
-            browser.test.assertEq(tabs[0].url, "http://example.com/");
-            browser.test.assertEq(tabs[0].windowId, destination.windowId);
-            browser.test.notifyPass("tabs.move.window");
-          });
+      // Assuming that this windowId does not exist.
+      await browser.test.assertRejects(
+        browser.tabs.move(source.id, {windowId: 123144576, index: 0}),
+        /Invalid window/,
+        "Should receive invalid window error");
 
-        // Assuming that this windowId does not exist.
-        browser.tabs.move(source.id, {windowId: 123144576, index: 0})
-        .then(
-          tabs => { browser.test.fail("Promise should not resolve"); },
-          e => {
-            browser.test.assertTrue(/Invalid window/.test(e),
-                                    "Invalid window should be in error");
-          }
-        );
-      });
+      browser.tabs.move(source.id, {windowId: destination.windowId, index: 0});
+
+      tabs = await browser.tabs.query({url: "<all_urls>"});
+      browser.test.assertEq(tabs[0].url, "http://example.com/");
+      browser.test.assertEq(tabs[0].windowId, destination.windowId);
+      browser.test.notifyPass("tabs.move.window");
     },
   });
 
   yield extension.startup();
   yield extension.awaitFinish("tabs.move.window");
   yield extension.unload();
 
   for (let tab of window.gBrowser.tabs) {
@@ -61,33 +52,33 @@ add_task(function* test_currentWindowAft
             browser.test.sendMessage("id", win.id);
           });
         }
       });
       browser.test.sendMessage("ready");
     },
   };
 
-  function background() {
+  async function background() {
     let tabId;
+
     const url = browser.extension.getURL("current.html");
-    browser.tabs.create({url}).then(tab => {
-      tabId = tab.id;
-    });
-    browser.test.onMessage.addListener(msg => {
+
+    browser.test.onMessage.addListener(async msg => {
       if (msg === "move") {
-        browser.windows.create({tabId}).then(() => {
-          browser.test.sendMessage("moved");
-        });
+        await browser.windows.create({tabId});
+        browser.test.sendMessage("moved");
       } else if (msg === "close") {
-        browser.tabs.remove(tabId).then(() => {
-          browser.test.sendMessage("done");
-        });
+        await browser.tabs.remove(tabId);
+        browser.test.sendMessage("done");
       }
     });
+
+    let tab = await browser.tabs.create({url});
+    tabId = tab.id;
   }
 
   const extension = ExtensionTestUtils.loadExtension({files, background});
 
   yield extension.startup();
   yield extension.awaitMessage("ready");
 
   extension.sendMessage("current");
--- a/browser/components/extensions/test/browser/browser_ext_tabs_onHighlighted.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_onHighlighted.js
@@ -1,14 +1,14 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 add_task(function* testTabEvents() {
-  function background() {
+  async function background() {
     /** The list of active tab ID's */
     let tabIds = [];
 
     /**
      * Stores the events that fire for each tab.
      *
      * events {
      *   tabId1: [event1, event2, ...],
@@ -34,89 +34,87 @@ add_task(function* testTabEvents() {
     });
 
     /**
      * Asserts that the expected events are fired for the tab with id = tabId.
      * The events associated to the specified tab are removed after this check is made.
      *
      * @param {number} tabId
      * @param {Array<string>} expectedEvents
-     * @returns {Promise}
      */
-    function expectEvents(tabId, expectedEvents) {
+    async function expectEvents(tabId, expectedEvents) {
       browser.test.log(`Expecting events: ${expectedEvents.join(", ")}`);
 
-      return new Promise(resolve => {
-        setTimeout(resolve, 0);
-      }).then(() => {
-        browser.test.assertEq(expectedEvents.length, events[tabId].length,
-         `Got expected number of events for ${tabId}`);
-        for (let [i, name] of expectedEvents.entries()) {
-          browser.test.assertEq(name, i in events[tabId] && events[tabId][i],
-                                `Got expected ${name} event`);
-        }
-        delete events[tabId];
-      });
+      await new Promise(resolve => setTimeout(resolve, 0));
+
+      browser.test.assertEq(expectedEvents.length, events[tabId].length,
+                            `Got expected number of events for ${tabId}`);
+
+      for (let [i, name] of expectedEvents.entries()) {
+        browser.test.assertEq(name, i in events[tabId] && events[tabId][i],
+                              `Got expected ${name} event`);
+      }
+      delete events[tabId];
     }
 
     /**
      * Opens a new tab and asserts that the correct events are fired.
      *
      * @param {number} windowId
-     * @returns {Promise}
      */
-    function openTab(windowId) {
-      return browser.tabs.create({windowId}).then(tab => {
-        tabIds.push(tab.id);
-        browser.test.log(`Opened tab ${tab.id}`);
-        return expectEvents(tab.id, [
-          "onActivated",
-          "onHighlighted",
-        ]);
-      });
+    async function openTab(windowId) {
+      let tab = await browser.tabs.create({windowId});
+
+      tabIds.push(tab.id);
+      browser.test.log(`Opened tab ${tab.id}`);
+
+      await expectEvents(tab.id, [
+        "onActivated",
+        "onHighlighted",
+      ]);
     }
 
     /**
      * Highlights an existing tab and asserts that the correct events are fired.
      *
      * @param {number} tabId
-     * @returns {Promise}
      */
-    function highlightTab(tabId) {
+    async function highlightTab(tabId) {
       browser.test.log(`Highlighting tab ${tabId}`);
-      return browser.tabs.update(tabId, {active: true}).then(tab => {
-        browser.test.assertEq(tab.id, tabId, `Tab ${tab.id} highlighted`);
-        return expectEvents(tab.id, [
-          "onActivated",
-          "onHighlighted",
-        ]);
-      });
+      let tab = await browser.tabs.update(tabId, {active: true});
+
+      browser.test.assertEq(tab.id, tabId, `Tab ${tab.id} highlighted`);
+
+      await expectEvents(tab.id, [
+        "onActivated",
+        "onHighlighted",
+      ]);
     }
 
     /**
      * The main entry point to the tests.
      */
-    browser.tabs.query({active: true, currentWindow: true}, tabs => {
-      let activeWindow = tabs[0].windowId;
-      Promise.all([
-        openTab(activeWindow),
-        openTab(activeWindow),
-        openTab(activeWindow),
-      ]).then(() => {
-        return Promise.all([
-          highlightTab(tabIds[0]),
-          highlightTab(tabIds[1]),
-          highlightTab(tabIds[2]),
-        ]);
-      }).then(() => {
-        return Promise.all(tabIds.map(id => browser.tabs.remove(id)));
-      }).then(() => {
-        browser.test.notifyPass("tabs.highlight");
-      });
-    });
+    let tabs = await browser.tabs.query({active: true, currentWindow: true});
+
+    let activeWindow = tabs[0].windowId;
+    await Promise.all([
+      openTab(activeWindow),
+      openTab(activeWindow),
+      openTab(activeWindow),
+    ]);
+
+    await Promise.all([
+      highlightTab(tabIds[0]),
+      highlightTab(tabIds[1]),
+      highlightTab(tabIds[2]),
+    ]);
+
+    await Promise.all(tabIds.map(id => browser.tabs.remove(id)));
+
+    browser.test.notifyPass("tabs.highlight");
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["tabs"],
     },
 
     background,
--- a/browser/components/extensions/test/browser/browser_ext_tabs_query.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_query.js
@@ -134,21 +134,21 @@ add_task(function* () {
 
   // test width and height
   extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["tabs"],
     },
 
     background: function() {
-      browser.test.onMessage.addListener((msg) => {
-        browser.tabs.query({active: true}).then(tabs => {
-          browser.test.assertEq(tabs.length, 1, "should have one tab");
-          browser.test.sendMessage("dims", {width: tabs[0].width, height: tabs[0].height});
-        });
+      browser.test.onMessage.addListener(async msg => {
+        let tabs = await browser.tabs.query({active: true});
+
+        browser.test.assertEq(tabs.length, 1, "should have one tab");
+        browser.test.sendMessage("dims", {width: tabs[0].width, height: tabs[0].height});
       });
       browser.test.sendMessage("ready");
     },
   });
 
   const RESOLUTION_PREF = "layout.css.devPixelsPerPx";
   registerCleanupFunction(() => {
     SpecialPowers.clearUserPref(RESOLUTION_PREF);
@@ -177,47 +177,47 @@ add_task(function* () {
 });
 
 add_task(function* testQueryPermissions() {
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": [],
     },
 
-    background: function(x) {
-      browser.tabs.query({currentWindow: true, active: true}).then((tabs) => {
+    async background() {
+      try {
+        let tabs = await browser.tabs.query({currentWindow: true, active: true});
         browser.test.assertEq(tabs.length, 1, "Expect query to return tabs");
         browser.test.notifyPass("queryPermissions");
-      }).catch((e) => {
+      } catch (e) {
         browser.test.notifyFail("queryPermissions");
-      });
+      }
     },
   });
 
   yield extension.startup();
 
   yield extension.awaitFinish("queryPermissions");
 
   yield extension.unload();
 });
 
 add_task(function* testQueryWithURLPermissions() {
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": [],
     },
 
-    background: function(x) {
-      browser.tabs.query({"url": "http://www.bbc.com/"}).then(() => {
-        browser.test.notifyFail("queryWithURLPermissions");
-      }).catch((e) => {
-        browser.test.assertEq('The "tabs" permission is required to use the query API with the "url" parameter',
-                              e.message, "Expected permissions error message");
-        browser.test.notifyPass("queryWithURLPermissions");
-      });
+    async background() {
+      await browser.test.assertRejects(
+        browser.tabs.query({"url": "http://www.bbc.com/"}),
+        'The "tabs" permission is required to use the query API with the "url" parameter',
+        "Expected tabs.query with 'url' to fail with permissions error message");
+
+      browser.test.notifyPass("queryWithURLPermissions");
     },
   });
 
   yield extension.startup();
 
   yield extension.awaitFinish("queryWithURLPermissions");
 
   yield extension.unload();
--- a/browser/components/extensions/test/browser/browser_ext_tabs_reload.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_reload.js
@@ -14,41 +14,41 @@ add_task(function* () {
       },
       "tab.html":
         `<head>
           <meta charset="utf-8">
           <script src="tab.js"></script>
         </head>`,
     },
 
-    background: function() {
+    async background() {
       let tabLoadedCount = 0;
 
-      browser.tabs.create({url: "tab.html", active: true}).then(tab => {
-        browser.runtime.onMessage.addListener(msg => {
-          if (msg == "tab-loaded") {
-            tabLoadedCount++;
+      let tab = await browser.tabs.create({url: "tab.html", active: true});
 
-            if (tabLoadedCount == 1) {
-              // Reload the tab once passing no arguments.
-              return browser.tabs.reload();
-            }
+      browser.runtime.onMessage.addListener(msg => {
+        if (msg == "tab-loaded") {
+          tabLoadedCount++;
+
+          if (tabLoadedCount == 1) {
+            // Reload the tab once passing no arguments.
+            return browser.tabs.reload();
+          }
 
-            if (tabLoadedCount == 2) {
-              // Reload the tab again with explicit arguments.
-              return browser.tabs.reload(tab.id, {
-                bypassCache: false,
-              });
-            }
+          if (tabLoadedCount == 2) {
+            // Reload the tab again with explicit arguments.
+            return browser.tabs.reload(tab.id, {
+              bypassCache: false,
+            });
+          }
 
-            if (tabLoadedCount == 3) {
-              browser.test.notifyPass("tabs.reload");
-            }
+          if (tabLoadedCount == 3) {
+            browser.test.notifyPass("tabs.reload");
           }
-        });
+        }
       });
     },
   });
 
   yield extension.startup();
   yield extension.awaitFinish("tabs.reload");
   yield extension.unload();
 });
--- a/browser/components/extensions/test/browser/browser_ext_tabs_reload_bypass_cache.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_reload_bypass_cache.js
@@ -3,57 +3,56 @@
 "use strict";
 
 add_task(function* () {
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["tabs", "<all_urls>"],
     },
 
-    background: function() {
+    async background() {
       const BASE = "http://mochi.test:8888/browser/browser/components/extensions/test/browser/";
       const URL = BASE + "file_bypass_cache.sjs";
 
       function awaitLoad(tabId) {
         return new Promise(resolve => {
           browser.tabs.onUpdated.addListener(function listener(tabId_, changed, tab) {
             if (tabId == tabId_ && changed.status == "complete" && tab.url == URL) {
               browser.tabs.onUpdated.removeListener(listener);
               resolve();
             }
           });
         });
       }
 
-      let tabId;
-      browser.tabs.create({url: URL}).then((tab) => {
-        tabId = tab.id;
-        return awaitLoad(tabId);
-      }).then(() => {
-        return browser.tabs.reload(tabId, {bypassCache: false});
-      }).then(() => {
-        return awaitLoad(tabId);
-      }).then(() => {
-        return browser.tabs.executeScript(tabId, {code: "document.body.textContent"});
-      }).then(([textContent]) => {
+      try {
+        let tab = await browser.tabs.create({url: URL});
+        await awaitLoad(tab.id);
+
+        await browser.tabs.reload(tab.id, {bypassCache: false});
+        await awaitLoad(tab.id);
+
+        let [textContent] = await browser.tabs.executeScript(tab.id, {code: "document.body.textContent"});
         browser.test.assertEq("", textContent, "`textContent` should be empty when bypassCache=false");
-        return browser.tabs.reload(tabId, {bypassCache: true});
-      }).then(() => {
-        return awaitLoad(tabId);
-      }).then(() => {
-        return browser.tabs.executeScript(tabId, {code: "document.body.textContent"});
-      }).then(([textContent]) => {
+
+        await browser.tabs.reload(tab.id, {bypassCache: true});
+        await awaitLoad(tab.id);
+
+        [textContent] = await browser.tabs.executeScript(tab.id, {code: "document.body.textContent"});
+
         let [pragma, cacheControl] = textContent.split(":");
         browser.test.assertEq("no-cache", pragma, "`pragma` should be set to `no-cache` when bypassCache is true");
         browser.test.assertEq("no-cache", cacheControl, "`cacheControl` should be set to `no-cache` when bypassCache is true");
-        browser.tabs.remove(tabId);
+
+        await browser.tabs.remove(tab.id);
+
         browser.test.notifyPass("tabs.reload_bypass_cache");
-      }).catch(error => {
+      } catch (error) {
         browser.test.fail(`${error} :: ${error.stack}`);
         browser.test.notifyFail("tabs.reload_bypass_cache");
-      });
+      }
     },
   });
 
   yield extension.startup();
   yield extension.awaitFinish("tabs.reload_bypass_cache");
   yield extension.unload();
 });
--- a/browser/components/extensions/test/browser/browser_ext_tabs_removeCSS.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_removeCSS.js
@@ -1,17 +1,17 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 add_task(function* testExecuteScript() {
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/", true);
 
-  function background() {
-    let promises = [
+  async function background() {
+    let tasks = [
       // Insert CSS file.
       {
         background: "transparent",
         foreground: "rgb(0, 113, 4)",
         promise: () => {
           return browser.tabs.insertCSS({
             file: "file2.css",
           });
@@ -49,41 +49,33 @@ add_task(function* testExecuteScript() {
       },
     ];
 
     function checkCSS() {
       let computedStyle = window.getComputedStyle(document.body);
       return [computedStyle.backgroundColor, computedStyle.color];
     }
 
-    function next() {
-      if (!promises.length) {
-        return;
-      }
-
-      let {promise, background, foreground} = promises.shift();
-      return promise().then(result => {
+    try {
+      for (let {promise, background, foreground} of tasks) {
+        let result = await promise();
         browser.test.assertEq(undefined, result, "Expected callback result");
 
-        return browser.tabs.executeScript({
+        [result] = await browser.tabs.executeScript({
           code: `(${checkCSS})()`,
         });
-      }).then(([result]) => {
         browser.test.assertEq(background, result[0], "Expected background color");
         browser.test.assertEq(foreground, result[1], "Expected foreground color");
-        return next();
-      });
-    }
+      }
 
-    next().then(() => {
       browser.test.notifyPass("removeCSS");
-    }).catch(e => {
+    } catch (e) {
       browser.test.fail(`Error: ${e} :: ${e.stack}`);
       browser.test.notifyFailure("removeCSS");
-    });
+    }
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["http://mochi.test/"],
     },
 
     background,
--- a/browser/components/extensions/test/browser/browser_ext_tabs_sendMessage.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_sendMessage.js
@@ -9,17 +9,17 @@ add_task(function* tabsSendMessageReply(
 
       "content_scripts": [{
         "matches": ["http://example.com/"],
         "js": ["content-script.js"],
         "run_at": "document_start",
       }],
     },
 
-    background: function() {
+    background: async function() {
       let firstTab;
       let promiseResponse = new Promise(resolve => {
         browser.runtime.onMessage.addListener((msg, sender, respond) => {
           if (msg == "content-script-ready") {
             let tabId = sender.tab.id;
 
             Promise.all([
               promiseResponse,
@@ -61,50 +61,50 @@ add_task(function* tabsSendMessageReply(
 
             return Promise.resolve("expected-response");
           } else if (msg[0] == "got-response") {
             resolve(msg[1]);
           }
         });
       });
 
-      browser.tabs.query({currentWindow: true, active: true}).then(tabs => {
-        firstTab = tabs[0].id;
-        browser.tabs.create({url: "http://example.com/"});
-      });
+      let tabs = await browser.tabs.query({currentWindow: true, active: true});
+      firstTab = tabs[0].id;
+      browser.tabs.create({url: "http://example.com/"});
     },
 
     files: {
-      "content-script.js": function() {
+      "content-script.js": async function() {
         browser.runtime.onMessage.addListener((msg, sender, respond) => {
           if (msg == "respond-now") {
             respond(msg);
           } else if (msg == "respond-soon") {
             setTimeout(() => { respond(msg); }, 0);
             return true;
           } else if (msg == "respond-promise") {
             return Promise.resolve(msg);
           } else if (msg == "respond-never") {
             return;
           } else if (msg == "respond-error") {
             return Promise.reject(new Error(msg));
           } else if (msg == "throw-error") {
             throw new Error(msg);
           }
         });
+
         browser.runtime.onMessage.addListener((msg, sender, respond) => {
           if (msg == "respond-now") {
             respond("hello");
           } else if (msg == "respond-now-2") {
             respond(msg);
           }
         });
-        browser.runtime.sendMessage("content-script-ready").then(response => {
-          browser.runtime.sendMessage(["got-response", response]);
-        });
+
+        let response = await browser.runtime.sendMessage("content-script-ready");
+        browser.runtime.sendMessage(["got-response", response]);
       },
     },
   });
 
   yield extension.startup();
 
   yield extension.awaitFinish("sendMessage");
 
@@ -119,62 +119,60 @@ add_task(function* tabsSendHidden() {
 
       "content_scripts": [{
         "matches": ["http://example.com/content*"],
         "js": ["content-script.js"],
         "run_at": "document_start",
       }],
     },
 
-    background: function() {
+    background: async function() {
       let resolveContent;
       browser.runtime.onMessage.addListener((msg, sender) => {
         if (msg[0] == "content-ready") {
           resolveContent(msg[1]);
         }
       });
 
       let awaitContent = url => {
         return new Promise(resolve => {
           resolveContent = resolve;
         }).then(result => {
           browser.test.assertEq(url, result, "Expected content script URL");
         });
       };
 
-      const URL1 = "http://example.com/content1.html";
-      const URL2 = "http://example.com/content2.html";
-      browser.tabs.create({url: URL1}).then(tab => {
-        return awaitContent(URL1).then(() => {
-          return browser.tabs.sendMessage(tab.id, URL1);
-        }).then(url => {
-          browser.test.assertEq(URL1, url, "Should get response from expected content window");
+      try {
+        const URL1 = "http://example.com/content1.html";
+        const URL2 = "http://example.com/content2.html";
+
+        let tab = await browser.tabs.create({url: URL1});
+        await awaitContent(URL1);
+
+        let url = await browser.tabs.sendMessage(tab.id, URL1);
+        browser.test.assertEq(URL1, url, "Should get response from expected content window");
+
+        await browser.tabs.update(tab.id, {url: URL2});
+        await awaitContent(URL2);
 
-          return browser.tabs.update(tab.id, {url: URL2});
-        }).then(() => {
-          return awaitContent(URL2);
-        }).then(() => {
-          return browser.tabs.sendMessage(tab.id, URL2);
-        }).then(url => {
-          browser.test.assertEq(URL2, url, "Should get response from expected content window");
+        url = await browser.tabs.sendMessage(tab.id, URL2);
+        browser.test.assertEq(URL2, url, "Should get response from expected content window");
 
-          // Repeat once just to be sure the first message was processed by all
-          // listeners before we exit the test.
-          return browser.tabs.sendMessage(tab.id, URL2);
-        }).then(url => {
-          browser.test.assertEq(URL2, url, "Should get response from expected content window");
+        // Repeat once just to be sure the first message was processed by all
+        // listeners before we exit the test.
+        url = await browser.tabs.sendMessage(tab.id, URL2);
+        browser.test.assertEq(URL2, url, "Should get response from expected content window");
 
-          return browser.tabs.remove(tab.id);
-        });
-      }).then(() => {
+        await browser.tabs.remove(tab.id);
+
         browser.test.notifyPass("contentscript-bfcache-window");
-      }).catch(error => {
+      } catch (error) {
         browser.test.fail(`Error: ${error} :: ${error.stack}`);
         browser.test.notifyFail("contentscript-bfcache-window");
-      });
+      }
     },
 
     files: {
       "content-script.js": function() {
         // Store this in a local variable to make sure we don't touch any
         // properties of the possibly-hidden content window.
         let href = window.location.href;
 
@@ -198,32 +196,30 @@ add_task(function* tabsSendHidden() {
 
 
 add_task(function* tabsSendMessageNoExceptionOnNonExistentTab() {
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["tabs"],
     },
 
-    background: function() {
+    async background() {
       let url = "http://example.com/mochitest/browser/browser/components/extensions/test/browser/file_dummy.html";
-      browser.tabs.create({url}, tab => {
-        let exception;
-        try {
-          browser.tabs.sendMessage(tab.id, "message");
-          browser.tabs.sendMessage(tab.id + 100, "message");
-        } catch (e) {
-          exception = e;
-        }
+      let tab = await browser.tabs.create({url});
 
-        browser.test.assertEq(undefined, exception, "no exception should be raised on tabs.sendMessage to nonexistent tabs");
-        browser.tabs.remove(tab.id, function() {
-          browser.test.notifyPass("tabs.sendMessage");
-        });
-      });
+      try {
+        browser.tabs.sendMessage(tab.id, "message");
+        browser.tabs.sendMessage(tab.id + 100, "message");
+      } catch (e) {
+        browser.test.fail("no exception should be raised on tabs.sendMessage to nonexistent tabs");
+      }
+
+      await browser.tabs.remove(tab.id);
+
+      browser.test.notifyPass("tabs.sendMessage");
     },
   });
 
   yield Promise.all([
     extension.startup(),
     extension.awaitFinish("tabs.sendMessage"),
   ]);
 
--- a/browser/components/extensions/test/browser/browser_ext_tabs_update_url.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_update_url.js
@@ -19,41 +19,31 @@ function* testTabsUpdateURL(existentTabU
             <h1>tab page</h1>
           </body>
         </html>
       `.trim(),
     },
     background: function() {
       browser.test.sendMessage("ready", browser.runtime.getURL("tab.html"));
 
-      browser.test.onMessage.addListener((msg, tabsUpdateURL, isErrorExpected) => {
-        let onTabsUpdated = (tab) => {
-          if (isErrorExpected) {
-            browser.test.fail(`tabs.update with URL ${tabsUpdateURL} should be rejected`);
-          } else {
-            browser.test.assertTrue(tab, "on success the tab should be defined");
-          }
-        };
+      browser.test.onMessage.addListener(async (msg, tabsUpdateURL, isErrorExpected) => {
+        let tabs = await browser.tabs.query({lastFocusedWindow: true});
+
+        try {
+          let tab = await browser.tabs.update(tabs[1].id, {url: tabsUpdateURL});
 
-        let onTabsUpdateError = (error) => {
-          if (!isErrorExpected) {
-            browser.test.fails(`tabs.update with URL ${tabsUpdateURL} should not be rejected`);
-          } else {
-            browser.test.assertTrue(/^Illegal URL/.test(error.message),
-                                    "tabs.update should be rejected with the expected error message");
-          }
-        };
+          browser.test.assertFalse(isErrorExpected, `tabs.update with URL ${tabsUpdateURL} should be rejected`);
+          browser.test.assertTrue(tab, "on success the tab should be defined");
+        } catch (error) {
+          browser.test.assertTrue(isErrorExpected, `tabs.update with URL ${tabsUpdateURL} should not be rejected`);
+          browser.test.assertTrue(/^Illegal URL/.test(error.message),
+                                  "tabs.update should be rejected with the expected error message");
+        }
 
-        let onTabsUpdateDone = () => browser.test.sendMessage("done");
-
-        browser.tabs.query({lastFocusedWindow: true}, (tabs) => {
-          browser.tabs.update(tabs[1].id, {url: tabsUpdateURL})
-                      .then(onTabsUpdated, onTabsUpdateError)
-                      .then(onTabsUpdateDone);
-        });
+        browser.test.sendMessage("done");
       });
     },
   });
 
   yield extension.startup();
 
   let mozExtTabURL = yield extension.awaitMessage("ready");
 
--- a/browser/components/extensions/test/browser/browser_ext_tabs_zoom.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_zoom.js
@@ -5,17 +5,17 @@
 const SITE_SPECIFIC_PREF = "browser.zoom.siteSpecific";
 
 add_task(function* () {
   let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
   let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.net/");
 
   gBrowser.selectedTab = tab1;
 
-  function background() {
+  async function background() {
     function promiseUpdated(tabId, attr) {
       return new Promise(resolve => {
         let onUpdated = (tabId_, changeInfo, tab) => {
           if (tabId == tabId_ && attr in changeInfo) {
             browser.tabs.onUpdated.removeListener(onUpdated);
 
             resolve({changeInfo, tab});
           }
@@ -45,156 +45,144 @@ add_task(function* () {
     let eventPromises = [];
     browser.tabs.onZoomChange.addListener(info => {
       zoomEvents.push(info);
       if (eventPromises.length) {
         eventPromises.shift().resolve();
       }
     });
 
-    let awaitZoom = (tabId, newValue) => {
+    let awaitZoom = async (tabId, newValue) => {
       let listener;
 
-      return new Promise(resolve => {
+      await new Promise(async resolve => {
         listener = info => {
           if (info.tabId == tabId && info.newZoomFactor == newValue) {
             resolve();
           }
         };
         browser.tabs.onZoomChange.addListener(listener);
 
-        browser.tabs.getZoom(tabId).then(zoomFactor => {
-          if (zoomFactor == newValue) {
-            resolve();
-          }
-        });
-      }).then(() => {
-        browser.tabs.onZoomChange.removeListener(listener);
+        let zoomFactor = await browser.tabs.getZoom(tabId);
+        if (zoomFactor == newValue) {
+          resolve();
+        }
       });
+
+      browser.tabs.onZoomChange.removeListener(listener);
     };
 
-    let checkZoom = (tabId, newValue, oldValue = null) => {
+    let checkZoom = async (tabId, newValue, oldValue = null) => {
       let awaitEvent;
       if (oldValue != null && !zoomEvents.length) {
         awaitEvent = new Promise(resolve => {
           eventPromises.push({resolve});
         });
       }
 
-      return Promise.all([
+      let [apiZoom, realZoom] = await Promise.all([
         browser.tabs.getZoom(tabId),
         msg("get-zoom", tabId),
         awaitEvent,
-      ]).then(([apiZoom, realZoom]) => {
-        browser.test.assertEq(newValue, apiZoom, `Got expected zoom value from API`);
-        browser.test.assertEq(newValue, realZoom, `Got expected zoom value from parent`);
+      ]);
+
+      browser.test.assertEq(newValue, apiZoom, `Got expected zoom value from API`);
+      browser.test.assertEq(newValue, realZoom, `Got expected zoom value from parent`);
 
-        if (oldValue != null) {
-          let event = zoomEvents.shift();
-          browser.test.assertEq(tabId, event.tabId, `Got expected zoom event tab ID`);
-          browser.test.assertEq(newValue, event.newZoomFactor, `Got expected zoom event zoom factor`);
-          browser.test.assertEq(oldValue, event.oldZoomFactor, `Got expected zoom event old zoom factor`);
+      if (oldValue != null) {
+        let event = zoomEvents.shift();
+        browser.test.assertEq(tabId, event.tabId, `Got expected zoom event tab ID`);
+        browser.test.assertEq(newValue, event.newZoomFactor, `Got expected zoom event zoom factor`);
+        browser.test.assertEq(oldValue, event.oldZoomFactor, `Got expected zoom event old zoom factor`);
 
-          browser.test.assertEq(3, Object.keys(event.zoomSettings).length, `Zoom settings should have 3 keys`);
-          browser.test.assertEq("automatic", event.zoomSettings.mode, `Mode should be "automatic"`);
-          browser.test.assertEq("per-origin", event.zoomSettings.scope, `Scope should be "per-origin"`);
-          browser.test.assertEq(1, event.zoomSettings.defaultZoomFactor, `Default zoom should be 1`);
-        }
-      });
+        browser.test.assertEq(3, Object.keys(event.zoomSettings).length, `Zoom settings should have 3 keys`);
+        browser.test.assertEq("automatic", event.zoomSettings.mode, `Mode should be "automatic"`);
+        browser.test.assertEq("per-origin", event.zoomSettings.scope, `Scope should be "per-origin"`);
+        browser.test.assertEq(1, event.zoomSettings.defaultZoomFactor, `Default zoom should be 1`);
+      }
     };
 
-    let tabIds;
-
-    browser.tabs.query({lastFocusedWindow: true}).then(tabs => {
+    try {
+      let tabs = await browser.tabs.query({lastFocusedWindow: true});
       browser.test.assertEq(tabs.length, 3, "We have three tabs");
 
-      tabIds = [tabs[1].id, tabs[2].id];
+      let tabIds = [tabs[1].id, tabs[2].id];
+      await checkZoom(tabIds[0], 1);
 
-      return checkZoom(tabIds[0], 1);
-    }).then(() => {
-      return browser.tabs.setZoom(tabIds[0], 2);
-    }).then(() => {
-      return checkZoom(tabIds[0], 2, 1);
-    }).then(() => {
-      return browser.tabs.getZoomSettings(tabIds[0]);
-    }).then(zoomSettings => {
+      await browser.tabs.setZoom(tabIds[0], 2);
+      await checkZoom(tabIds[0], 2, 1);
+
+      let zoomSettings = await browser.tabs.getZoomSettings(tabIds[0]);
       browser.test.assertEq(3, Object.keys(zoomSettings).length, `Zoom settings should have 3 keys`);
       browser.test.assertEq("automatic", zoomSettings.mode, `Mode should be "automatic"`);
       browser.test.assertEq("per-origin", zoomSettings.scope, `Scope should be "per-origin"`);
       browser.test.assertEq(1, zoomSettings.defaultZoomFactor, `Default zoom should be 1`);
 
+
       browser.test.log(`Switch to tab 2`);
-      return browser.tabs.update(tabIds[1], {active: true});
-    }).then(() => {
-      return checkZoom(tabIds[1], 1);
-    }).then(() => {
+      await browser.tabs.update(tabIds[1], {active: true});
+      await checkZoom(tabIds[1], 1);
+
+
       browser.test.log(`Navigate tab 2 to origin of tab 1`);
       browser.tabs.update(tabIds[1], {url: "http://example.com"});
+      await promiseUpdated(tabIds[1], "url");
+      await checkZoom(tabIds[1], 2, 1);
 
-      return promiseUpdated(tabIds[1], "url");
-    }).then(() => {
-      return checkZoom(tabIds[1], 2, 1);
-    }).then(() => {
+
       browser.test.log(`Update zoom in tab 2, expect changes in both tabs`);
-      return browser.tabs.setZoom(tabIds[1], 1.5);
-    }).then(() => {
-      return checkZoom(tabIds[1], 1.5, 2);
-    }).then(() => {
+      await browser.tabs.setZoom(tabIds[1], 1.5);
+      await checkZoom(tabIds[1], 1.5, 2);
+
+
       browser.test.log(`Switch to tab 1, expect asynchronous zoom change just after the switch`);
-      return Promise.all([
+      await Promise.all([
         awaitZoom(tabIds[0], 1.5),
         browser.tabs.update(tabIds[0], {active: true}),
       ]);
-    }).then(() => {
-      return checkZoom(tabIds[0], 1.5, 2);
-    }).then(() => {
+      await checkZoom(tabIds[0], 1.5, 2);
+
+
       browser.test.log("Set zoom to 0, expect it set to 1");
-      return browser.tabs.setZoom(tabIds[0], 0);
-    }).then(() => {
-      return checkZoom(tabIds[0], 1, 1.5);
-    }).then(() => {
+      await browser.tabs.setZoom(tabIds[0], 0);
+      await checkZoom(tabIds[0], 1, 1.5);
+
+
       browser.test.log("Change zoom externally, expect changes reflected");
-      return msg("enlarge");
-    }).then(() => {
-      return checkZoom(tabIds[0], 1.1, 1);
-    }).then(() => {
-      return Promise.all([
+      await msg("enlarge");
+      await checkZoom(tabIds[0], 1.1, 1);
+
+      await Promise.all([
         browser.tabs.setZoom(tabIds[0], 0),
         browser.tabs.setZoom(tabIds[1], 0),
       ]);
-    }).then(() => {
-      return Promise.all([
+      await Promise.all([
         checkZoom(tabIds[0], 1, 1.1),
         checkZoom(tabIds[1], 1, 1.5),
       ]);
-    }).then(() => {
+
+
       browser.test.log("Check that invalid zoom values throw an error");
-      return browser.tabs.setZoom(tabIds[0], 42).then(
-        () => {
-          browser.test.fail("Expected an error");
-        },
-        error => {
-          browser.test.assertTrue(error.message.includes("Zoom value 42 out of range"),
-                                  "Got expected error");
-        });
-    }).then(() => {
+      await browser.test.assertRejects(
+        browser.tabs.setZoom(tabIds[0], 42),
+        /Zoom value 42 out of range/,
+        "Expected an out of range error");
+
       browser.test.log("Disable site-specific zoom, expect correct scope");
-      return msg("site-specific", false);
-    }).then(() => {
-      return browser.tabs.getZoomSettings(tabIds[0]);
-    }).then(zoomSettings => {
+      await msg("site-specific", false);
+      zoomSettings = await browser.tabs.getZoomSettings(tabIds[0]);
+
       browser.test.assertEq("per-tab", zoomSettings.scope, `Scope should be "per-tab"`);
-    }).then(() => {
-      return msg("site-specific", null);
-    }).then(() => {
+      await msg("site-specific", null);
+
       browser.test.notifyPass("tab-zoom");
-    }).catch(e => {
+    } catch (e) {
       browser.test.fail(`Error: ${e} :: ${e.stack}`);
       browser.test.notifyFail("tab-zoom");
-    });
+    }
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["tabs"],
     },
 
     background,
--- a/browser/components/extensions/test/browser/browser_ext_webNavigation_frameId0.js
+++ b/browser/components/extensions/test/browser/browser_ext_webNavigation_frameId0.js
@@ -15,25 +15,23 @@ add_task(function* webNavigation_getFram
   // main process:
   // Cu.import("resource://gre/modules/ExtensionManagement.jsm", {});
   //
   // Or simply run the test again.
   const BASE = "http://mochi.test:8888/browser/browser/components/extensions/test/browser/";
   const DUMMY_URL = BASE + "file_dummy.html";
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, DUMMY_URL, true);
 
-  function background(DUMMY_URL) {
-    browser.tabs.query({active: true, currentWindow: true}).then(tabs => {
-      return browser.webNavigation.getAllFrames({tabId: tabs[0].id});
-    }).then(frames => {
-      browser.test.assertEq(1, frames.length, "The dummy page has one frame");
-      browser.test.assertEq(0, frames[0].frameId, "Main frame's ID must be 0");
-      browser.test.assertEq(DUMMY_URL, frames[0].url, "Main frame URL must match");
-      browser.test.notifyPass("frameId checked");
-    });
+  async function background(DUMMY_URL) {
+    let tabs = await browser.tabs.query({active: true, currentWindow: true});
+    let frames = await browser.webNavigation.getAllFrames({tabId: tabs[0].id});
+    browser.test.assertEq(1, frames.length, "The dummy page has one frame");
+    browser.test.assertEq(0, frames[0].frameId, "Main frame's ID must be 0");
+    browser.test.assertEq(DUMMY_URL, frames[0].url, "Main frame URL must match");
+    browser.test.notifyPass("frameId checked");
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["webNavigation"],
     },
 
     background: `(${background})(${JSON.stringify(DUMMY_URL)});`,
--- a/browser/components/extensions/test/browser/browser_ext_webNavigation_getFrames.js
+++ b/browser/components/extensions/test/browser/browser_ext_webNavigation_getFrames.js
@@ -1,38 +1,31 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 add_task(function* testWebNavigationGetNonExistentTab() {
   let extension = ExtensionTestUtils.loadExtension({
-    background: "(" + function() {
-      let results = [
-        // There is no "tabId = 0" because the id assigned by TabManager (defined in ext-utils.js)
-        // starts from 1.
-        browser.webNavigation.getAllFrames({tabId: 0}).then(() => {
-          browser.test.fail("getAllFrames Promise should be rejected on error");
-        }, (error) => {
-          browser.test.assertEq("Invalid tab ID: 0", error.message,
-                                "getAllFrames rejected Promise should pass the expected error");
-        }),
-        // There is no "tabId = 0" because the id assigned by TabManager (defined in ext-utils.js)
-        // starts from 1, processId is currently marked as optional and it is ignored.
-        browser.webNavigation.getFrame({tabId: 0, frameId: 15, processId: 20}).then(() => {
-          browser.test.fail("getFrame Promise should be rejected on error");
-        }, (error) => {
-          browser.test.assertEq("Invalid tab ID: 0", error.message,
-                                "getFrame rejected Promise should pass the expected error");
-        }),
-      ];
+    background: async function() {
+      // There is no "tabId = 0" because the id assigned by TabManager (defined in ext-utils.js)
+      // starts from 1.
+      await browser.test.assertRejects(
+        browser.webNavigation.getAllFrames({tabId: 0}),
+        "Invalid tab ID: 0",
+        "getAllFrames rejected Promise should pass the expected error");
 
-      Promise.all(results).then(() => {
-        browser.test.sendMessage("getNonExistentTab.done");
-      });
-    } + ")();",
+      // There is no "tabId = 0" because the id assigned by TabManager (defined in ext-utils.js)
+      // starts from 1, processId is currently marked as optional and it is ignored.
+      await browser.test.assertRejects(
+        browser.webNavigation.getFrame({tabId: 0, frameId: 15, processId: 20}),
+        "Invalid tab ID: 0",
+        "getFrame rejected Promise should pass the expected error");
+
+      browser.test.sendMessage("getNonExistentTab.done");
+    },
     manifest: {
       permissions: ["webNavigation"],
     },
   });
   info("load complete");
 
   yield extension.startup();
   info("startup complete");
@@ -40,68 +33,61 @@ add_task(function* testWebNavigationGetN
   yield extension.awaitMessage("getNonExistentTab.done");
 
   yield extension.unload();
   info("extension unloaded");
 });
 
 add_task(function* testWebNavigationFrames() {
   let extension = ExtensionTestUtils.loadExtension({
-    background: "(" + function() {
+    background: async function() {
       let tabId;
       let collectedDetails = [];
 
-      browser.webNavigation.onCompleted.addListener((details) => {
+      browser.webNavigation.onCompleted.addListener(async details => {
         collectedDetails.push(details);
 
         if (details.frameId !== 0) {
           // wait for the top level iframe to be complete
           return;
         }
 
-        browser.webNavigation.getAllFrames({tabId}).then((getAllFramesDetails) => {
-          let getFramePromises = getAllFramesDetails.map((frameDetail) => {
-            let {frameId} = frameDetail;
-            // processId is currently marked as optional and it is ignored.
-            return browser.webNavigation.getFrame({tabId, frameId, processId: 0});
-          });
+        let getAllFramesDetails = await browser.webNavigation.getAllFrames({tabId});
 
-          Promise.all(getFramePromises).then((getFrameResults) => {
-            browser.test.sendMessage("webNavigationFrames.done", {
-              collectedDetails, getAllFramesDetails, getFrameResults,
-            });
-          }, () => {
-            browser.test.assertTrue(false, "None of the getFrame promises should have been rejected");
-          });
+        let getFramePromises = getAllFramesDetails.map(({frameId}) => {
+          // processId is currently marked as optional and it is ignored.
+          return browser.webNavigation.getFrame({tabId, frameId, processId: 0});
+        });
+
+        let getFrameResults = await Promise.all(getFramePromises);
+        browser.test.sendMessage("webNavigationFrames.done", {
+          collectedDetails, getAllFramesDetails, getFrameResults,
+        });
 
-          // Pick a random frameId.
-          let nonExistentFrameId = Math.floor(Math.random() * 10000);
+        // Pick a random frameId.
+        let nonExistentFrameId = Math.floor(Math.random() * 10000);
 
-          // Increment the picked random nonExistentFrameId until it doesn't exists.
-          while (getAllFramesDetails.filter((details) => details.frameId == nonExistentFrameId).length > 0) {
-            nonExistentFrameId += 1;
-          }
+        // Increment the picked random nonExistentFrameId until it doesn't exists.
+        while (getAllFramesDetails.filter((details) => details.frameId == nonExistentFrameId).length > 0) {
+          nonExistentFrameId += 1;
+        }
 
-          // Check that getFrame Promise is rejected with the expected error message on nonexistent frameId.
-          browser.webNavigation.getFrame({tabId, frameId: nonExistentFrameId, processId: 20}).then(() => {
-            browser.test.fail("getFrame promise should be rejected for an unexistent frameId");
-          }, (error) => {
-            browser.test.assertEq(`No frame found with frameId: ${nonExistentFrameId}`, error.message,
-                                  "getFrame promise should be rejected with the expected error message on unexistent frameId");
-          }).then(() => {
-            browser.tabs.remove(tabId);
-            browser.test.sendMessage("webNavigationFrames.done");
-          });
-        });
+        // Check that getFrame Promise is rejected with the expected error message on nonexistent frameId.
+        await browser.test.assertRejects(
+          browser.webNavigation.getFrame({tabId, frameId: nonExistentFrameId, processId: 20}),
+          `No frame found with frameId: ${nonExistentFrameId}`,
+          "getFrame promise should be rejected with the expected error message on unexistent frameId");
+
+        await browser.tabs.remove(tabId);
+        browser.test.sendMessage("webNavigationFrames.done");
       });
 
-      browser.tabs.create({url: "tab.html"}, (tab) => {
-        tabId = tab.id;
-      });
-    } + ")();",
+      let tab = await browser.tabs.create({url: "tab.html"});
+      tabId = tab.id;
+    },
     manifest: {
       permissions: ["webNavigation", "tabs"],
     },
     files: {
       "tab.html": `
         <!DOCTYPE html>
         <html>
           <head>
--- a/browser/components/extensions/test/browser/browser_ext_windows_create.js
+++ b/browser/components/extensions/test/browser/browser_ext_windows_create.js
@@ -1,15 +1,15 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 add_task(function* testWindowCreate() {
   let extension = ExtensionTestUtils.loadExtension({
-    background() {
+    async background() {
       let _checkWindowPromise;
       browser.test.onMessage.addListener(msg => {
         if (msg == "checked-window") {
           _checkWindowPromise.resolve();
           _checkWindowPromise = null;
         }
       });
 
@@ -17,69 +17,71 @@ add_task(function* testWindowCreate() {
 
       function checkWindow(expected) {
         return new Promise(resolve => {
           _checkWindowPromise = {resolve};
           browser.test.sendMessage("check-window", expected);
         });
       }
 
-      function createWindow(params, expected, keep = false) {
-        return browser.windows.create(params).then(window => {
-          for (let key of Object.keys(params)) {
-            if (key == "state" && os == "mac" && params.state == "normal") {
-              // OS-X doesn't have a hard distinction between "normal" and
-              // "maximized" states.
-              browser.test.assertTrue(window.state == "normal" || window.state == "maximized",
-                                      `Expected window.state (currently ${window.state}) to be "normal" but will accept "maximized"`);
-            } else {
-              browser.test.assertEq(params[key], window[key], `Got expected value for window.${key}`);
-            }
+      async function createWindow(params, expected, keep = false) {
+        let window = await browser.windows.create(params);
+
+        for (let key of Object.keys(params)) {
+          if (key == "state" && os == "mac" && params.state == "normal") {
+            // OS-X doesn't have a hard distinction between "normal" and
+            // "maximized" states.
+            browser.test.assertTrue(window.state == "normal" || window.state == "maximized",
+                                    `Expected window.state (currently ${window.state}) to be "normal" but will accept "maximized"`);
+          } else {
+            browser.test.assertEq(params[key], window[key], `Got expected value for window.${key}`);
           }
+        }
 
-          browser.test.assertEq(1, window.tabs.length, "tabs property got populated");
-          return checkWindow(expected).then(() => {
-            if (keep) {
-              return window;
-            }
-            if (params.state == "fullscreen" && os == "win") {
-              // FIXME: Closing a fullscreen window causes a window leak in
-              // Windows tests.
-              return browser.windows.update(window.id, {state: "normal"}).then(() => {
-                return browser.windows.remove(window.id);
-              });
-            }
-            return browser.windows.remove(window.id);
-          });
-        });
+        browser.test.assertEq(1, window.tabs.length, "tabs property got populated");
+
+        await checkWindow(expected);
+        if (keep) {
+          return window;
+        }
+
+        if (params.state == "fullscreen" && os == "win") {
+          // FIXME: Closing a fullscreen window causes a window leak in
+          // Windows tests.
+          await browser.windows.update(window.id, {state: "normal"});
+        }
+        await browser.windows.remove(window.id);
       }
 
-      browser.runtime.getPlatformInfo().then(info => { os = info.os; })
-      .then(() => createWindow({state: "maximized"}, {state: "STATE_MAXIMIZED"}))
-      .then(() => createWindow({state: "minimized"}, {state: "STATE_MINIMIZED"}))
-      .then(() => createWindow({state: "normal"}, {state: "STATE_NORMAL", hiddenChrome: []}))
-      .then(() => createWindow({state: "fullscreen"}, {state: "STATE_FULLSCREEN"}))
-      .then(() => {
-        return createWindow({type: "popup"},
-                            {hiddenChrome: ["menubar", "toolbar", "location", "directories", "status", "extrachrome"],
-                             chromeFlags: ["CHROME_OPENAS_DIALOG"]},
-                            true);
-      }).then(window => {
-        return browser.tabs.query({windowType: "popup", active: true}).then(tabs => {
-          browser.test.assertEq(1, tabs.length, "Expected only one popup");
-          browser.test.assertEq(window.id, tabs[0].windowId, "Expected new window to be returned in query");
+      try {
+        ({os} = await browser.runtime.getPlatformInfo());
+
+        await createWindow({state: "maximized"}, {state: "STATE_MAXIMIZED"});
+        await createWindow({state: "minimized"}, {state: "STATE_MINIMIZED"});
+        await createWindow({state: "normal"}, {state: "STATE_NORMAL", hiddenChrome: []});
+        await createWindow({state: "fullscreen"}, {state: "STATE_FULLSCREEN"});
 
-          return browser.windows.remove(window.id);
-        });
-      }).then(() => {
+        let window = await createWindow(
+          {type: "popup"},
+          {hiddenChrome: ["menubar", "toolbar", "location", "directories", "status", "extrachrome"],
+           chromeFlags: ["CHROME_OPENAS_DIALOG"]},
+          true);
+
+        let tabs = await browser.tabs.query({windowType: "popup", active: true});
+
+        browser.test.assertEq(1, tabs.length, "Expected only one popup");
+        browser.test.assertEq(window.id, tabs[0].windowId, "Expected new window to be returned in query");
+
+        await browser.windows.remove(window.id);
+
         browser.test.notifyPass("window-create");
-      }).catch(e => {
+      } catch (e) {
         browser.test.fail(`${e} :: ${e.stack}`);
         browser.test.notifyFail("window-create");
-      });
+      }
     },
   });
 
   let latestWindow;
   let windowListener = (window, topic) => {
     if (topic == "domwindowopened") {
       latestWindow = window;
     }
--- a/browser/components/extensions/test/browser/browser_ext_windows_create_params.js
+++ b/browser/components/extensions/test/browser/browser_ext_windows_create_params.js
@@ -1,40 +1,33 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 
 // Tests that incompatible parameters can't be used together.
 add_task(function* testWindowCreateParams() {
   let extension = ExtensionTestUtils.loadExtension({
-    background() {
-      function* getCalls() {
+    async background() {
+      try {
         for (let state of ["minimized", "maximized", "fullscreen"]) {
           for (let param of ["left", "top", "width", "height"]) {
             let expected = `"state": "${state}" may not be combined with "left", "top", "width", or "height"`;
 
-            yield browser.windows.create({state, [param]: 100}).then(
-              val => {
-                browser.test.fail(`Expected error but got "${val}" instead`);
-              },
-              error => {
-                browser.test.assertTrue(
-                  error.message.includes(expected),
-                  `Got expected error (got: '${error.message}', expected: '${expected}'`);
-              });
+            await browser.test.assertRejects(
+              browser.windows.create({state, [param]: 100}),
+              RegExp(expected),
+              `Got expected error from create(${param}=100)`);
           }
         }
-      }
 
-      Promise.all(getCalls()).then(() => {
         browser.test.notifyPass("window-create-params");
-      }).catch(e => {
+      } catch (e) {
         browser.test.fail(`${e} :: ${e.stack}`);
         browser.test.notifyFail("window-create-params");
-      });
+      }
     },
   });
 
   yield extension.startup();
   yield extension.awaitFinish("window-create-params");
   yield extension.unload();
 });
--- a/browser/components/extensions/test/browser/browser_ext_windows_create_tabId.js
+++ b/browser/components/extensions/test/browser/browser_ext_windows_create_tabId.js
@@ -1,14 +1,14 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 add_task(function* testWindowCreate() {
-  function background() {
+  async function background() {
     let promiseTabAttached = () => {
       return new Promise(resolve => {
         browser.tabs.onAttached.addListener(function listener() {
           browser.tabs.onAttached.removeListener(listener);
           resolve();
         });
       });
     };
@@ -19,130 +19,113 @@ add_task(function* testWindowCreate() {
           if (changeInfo.url === expected) {
             browser.tabs.onUpdated.removeListener(listener);
             resolve();
           }
         });
       });
     };
 
-    let windowId, tabId;
-    browser.windows.getCurrent().then(window => {
-      windowId = window.id;
+    try {
+      let window = await browser.windows.getCurrent();
+      let windowId = window.id;
 
       browser.test.log("Create additional tab in window 1");
-      return browser.tabs.create({windowId, url: "about:blank"});
-    }).then(tab => {
-      tabId = tab.id;
+      let tab = await browser.tabs.create({windowId, url: "about:blank"});
+      let tabId = tab.id;
+
       browser.test.log("Create a new window, adopting the new tab");
 
       // Note that we want to check against actual boolean values for
       // all of the `incognito` property tests.
       browser.test.assertEq(false, tab.incognito, "Tab is not private");
 
-      return Promise.all([
-        promiseTabAttached(),
-        browser.windows.create({tabId: tabId}),
-      ]);
-    }).then(([, window]) => {
-      browser.test.assertEq(false, window.incognito, "New window is not private");
-      browser.test.assertEq(tabId, window.tabs[0].id, "tabs property populated correctly");
+      {
+        let [, window] = await Promise.all([
+          promiseTabAttached(),
+          browser.windows.create({tabId: tabId}),
+        ]);
+        browser.test.assertEq(false, window.incognito, "New window is not private");
+        browser.test.assertEq(tabId, window.tabs[0].id, "tabs property populated correctly");
 
-      browser.test.log("Close the new window");
-      return browser.windows.remove(window.id);
-    }).then(() => {
-      browser.test.log("Create a new private window");
+        browser.test.log("Close the new window");
+        await browser.windows.remove(window.id);
+      }
 
-      return browser.windows.create({incognito: true});
-    }).then(privateWindow => {
-      browser.test.assertEq(true, privateWindow.incognito, "Private window is private");
+      {
+        browser.test.log("Create a new private window");
+        let privateWindow = await browser.windows.create({incognito: true});
+        browser.test.assertEq(true, privateWindow.incognito, "Private window is private");
 
-      browser.test.log("Create additional tab in private window");
-      return browser.tabs.create({windowId: privateWindow.id}).then(privateTab => {
+        browser.test.log("Create additional tab in private window");
+        let privateTab = await browser.tabs.create({windowId: privateWindow.id});
         browser.test.assertEq(true, privateTab.incognito, "Private tab is private");
 
         browser.test.log("Create a new window, adopting the new private tab");
-
-        return Promise.all([
+        let [, newWindow] = await Promise.all([
           promiseTabAttached(),
           browser.windows.create({tabId: privateTab.id}),
         ]);
-      }).then(([, newWindow]) => {
         browser.test.assertEq(true, newWindow.incognito, "New private window is private");
 
         browser.test.log("Close the new private window");
-        return browser.windows.remove(newWindow.id);
-      }).then(() => {
+        await browser.windows.remove(newWindow.id);
+
         browser.test.log("Close the private window");
-        return browser.windows.remove(privateWindow.id);
-      });
-    }).then(() => {
-      return browser.tabs.query({windowId, active: true});
-    }).then(([tab]) => {
-      browser.test.log("Try to create a window with both a tab and a URL");
+        await browser.windows.remove(privateWindow.id);
+      }
+
 
-      return browser.windows.create({tabId: tab.id, url: "http://example.com/"}).then(
-        window => {
-          browser.test.fail("Create call should have failed");
-        },
-        error => {
-          browser.test.assertTrue(/`tabId` may not be used in conjunction with `url`/.test(error.message),
-                                  "Create call failed as expected");
-        }).then(() => {
-          browser.test.log("Try to create a window with both a tab and an invalid incognito setting");
+      browser.test.log("Try to create a window with both a tab and a URL");
+      [tab] = await browser.tabs.query({windowId, active: true});
+      await browser.test.assertRejects(
+        browser.windows.create({tabId: tab.id, url: "http://example.com/"}),
+        /`tabId` may not be used in conjunction with `url`/,
+        "Create call failed as expected");
 
-          return browser.windows.create({tabId: tab.id, incognito: true});
-        }).then(
-          window => {
-            browser.test.fail("Create call should have failed");
-          },
-          error => {
-            browser.test.assertTrue(/`incognito` property must match the incognito state of tab/.test(error.message),
-                                    "Create call failed as expected");
-          });
-    }).then(() => {
-      browser.test.log("Try to create a window with an invalid tabId");
+      browser.test.log("Try to create a window with both a tab and an invalid incognito setting");
+      await browser.test.assertRejects(
+        browser.windows.create({tabId: tab.id, incognito: true}),
+        /`incognito` property must match the incognito state of tab/,
+        "Create call failed as expected");
+
 
-      return browser.windows.create({tabId: 0}).then(
-        window => {
-          browser.test.fail("Create call should have failed");
-        },
-        error => {
-          browser.test.assertTrue(/Invalid tab ID: 0/.test(error.message),
-                                  "Create call failed as expected");
-        }
-      );
-    }).then(() => {
+      browser.test.log("Try to create a window with an invalid tabId");
+      await browser.test.assertRejects(
+        browser.windows.create({tabId: 0}),
+        /Invalid tab ID: 0/,
+        "Create call failed as expected");
+
+
       browser.test.log("Try to create a window with two URLs");
-
-      return Promise.all([
+      [, , window] = await Promise.all([
         // tabs.onUpdated can be invoked between the call of windows.create and
         // the invocation of its callback/promise, so set up the listeners
         // before creating the window.
         promiseTabUpdated("http://example.com/"),
         promiseTabUpdated("http://example.org/"),
         browser.windows.create({url: ["http://example.com/", "http://example.org/"]}),
       ]);
-    }).then(([, , window]) => {
       browser.test.assertEq(2, window.tabs.length, "2 tabs were opened in new window");
       browser.test.assertEq("about:blank", window.tabs[0].url, "about:blank, page not loaded yet");
       browser.test.assertEq("about:blank", window.tabs[1].url, "about:blank, page not loaded yet");
 
-      return browser.windows.get(window.id, {populate: true});
-    }).then(window => {
+      window = await browser.windows.get(window.id, {populate: true});
+
       browser.test.assertEq(2, window.tabs.length, "2 tabs were opened in new window");
       browser.test.assertEq("http://example.com/", window.tabs[0].url, "Correct URL was loaded in tab 1");
       browser.test.assertEq("http://example.org/", window.tabs[1].url, "Correct URL was loaded in tab 2");
-      return browser.windows.remove(window.id);
-    }).then(() => {
+
+      await browser.windows.remove(window.id);
+
       browser.test.notifyPass("window-create");
-    }).catch(e => {
+    } catch (e) {
       browser.test.fail(`${e} :: ${e.stack}`);
       browser.test.notifyFail("window-create");
-    });
+    }
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["tabs"],
     },
 
     background,
--- a/browser/components/extensions/test/browser/browser_ext_windows_create_url.js
+++ b/browser/components/extensions/test/browser/browser_ext_windows_create_url.js
@@ -3,17 +3,17 @@
 "use strict";
 
 add_task(function* testWindowCreate() {
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       permissions: ["tabs"],
     },
 
-    background() {
+    background: async function() {
       const EXTENSION_URL = browser.runtime.getURL("test.html");
       const REMOTE_URL = browser.runtime.getURL("test.html");
 
       let windows = new class extends Map { // eslint-disable-line new-parens
         get(id) {
           if (!this.has(id)) {
             let window = {
               tabs: new Map(),
@@ -35,47 +35,47 @@ add_task(function* testWindowCreate() {
           window.tabs.set(tab.index, tab);
 
           if (window.tabs.size === window.expectedTabs) {
             window.resolvePromise(window);
           }
         }
       });
 
-      function create(options) {
-        return browser.windows.create(options).then(window => {
-          let win = windows.get(window.id);
+      async function create(options) {
+        let window = await browser.windows.create(options);
+        let win = windows.get(window.id);
 
-          win.expectedTabs = Array.isArray(options.url) ? options.url.length : 1;
+        win.expectedTabs = Array.isArray(options.url) ? options.url.length : 1;
 
-          return win.promise;
-        });
+        return win.promise;
       }
 
-      Promise.all([
-        create({url: REMOTE_URL}),
-        create({url: "test.html"}),
-        create({url: EXTENSION_URL}),
-        create({url: [REMOTE_URL, "test.html", EXTENSION_URL]}),
-      ]).then(windows => {
+      try {
+        let windows = await Promise.all([
+          create({url: REMOTE_URL}),
+          create({url: "test.html"}),
+          create({url: EXTENSION_URL}),
+          create({url: [REMOTE_URL, "test.html", EXTENSION_URL]}),
+        ]);
         browser.test.assertEq(REMOTE_URL, windows[0].tabs.get(0).url, "Single, absolute, remote URL");
 
         browser.test.assertEq(REMOTE_URL, windows[1].tabs.get(0).url, "Single, relative URL");
 
         browser.test.assertEq(REMOTE_URL, windows[2].tabs.get(0).url, "Single, absolute, extension URL");
 
         browser.test.assertEq(REMOTE_URL, windows[3].tabs.get(0).url, "url[0]: Absolute, remote URL");
         browser.test.assertEq(EXTENSION_URL, windows[3].tabs.get(1).url, "url[1]: Relative URL");
         browser.test.assertEq(EXTENSION_URL, windows[3].tabs.get(2).url, "url[2]: Absolute, extension URL");
-      }).then(() => {
+
         browser.test.notifyPass("window-create-url");
-      }).catch(e => {
+      } catch (e) {
         browser.test.fail(`${e} :: ${e.stack}`);
         browser.test.notifyFail("window-create-url");
-      });
+      }
     },
 
     files: {
       "test.html": `<DOCTYPE html><html><head><meta charset="utf-8"></head></html>`,
     },
   });
 
   yield extension.startup();
--- a/browser/components/extensions/test/browser/browser_ext_windows_events.js
+++ b/browser/components/extensions/test/browser/browser_ext_windows_events.js
@@ -1,50 +1,50 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 SimpleTest.requestCompleteLog();
 
 add_task(function* testWindowsEvents() {
   function background() {
-    browser.windows.onCreated.addListener(function listener(window) {
+    browser.windows.onCreated.addListener(window => {
       browser.test.log(`onCreated: windowId=${window.id}`);
 
       browser.test.assertTrue(Number.isInteger(window.id),
                               "Window object's id is an integer");
       browser.test.assertEq("normal", window.type,
                             "Window object returned with the correct type");
       browser.test.sendMessage("window-created", window.id);
     });
 
     let lastWindowId, os;
-    browser.windows.onFocusChanged.addListener(function listener(windowId) {
+    browser.windows.onFocusChanged.addListener(async windowId => {
       browser.test.log(`onFocusChange: windowId=${windowId} lastWindowId=${lastWindowId}`);
 
       if (windowId === browser.windows.WINDOW_ID_NONE && os === "linux") {
         browser.test.log("Ignoring a superfluous WINDOW_ID_NONE (blur) event on Linux");
         return;
       }
 
       browser.test.assertTrue(lastWindowId !== windowId,
                               "onFocusChanged fired once for the given window");
       lastWindowId = windowId;
 
       browser.test.assertTrue(Number.isInteger(windowId),
                               "windowId is an integer");
 
-      browser.windows.getLastFocused().then(window => {
-        browser.test.assertEq(windowId, window.id,
-                              "Last focused window has the correct id");
-        browser.test.sendMessage(`window-focus-changed`, window.id);
-      });
+      let window = await browser.windows.getLastFocused();
+
+      browser.test.assertEq(windowId, window.id,
+                            "Last focused window has the correct id");
+      browser.test.sendMessage(`window-focus-changed`, window.id);
     });
 
-    browser.windows.onRemoved.addListener(function listener(windowId) {
+    browser.windows.onRemoved.addListener(windowId => {
       browser.test.log(`onRemoved: windowId=${windowId}`);
 
       browser.test.assertTrue(Number.isInteger(windowId),
                               "windowId is an integer");
       browser.test.sendMessage(`window-removed`, windowId);
       browser.test.notifyPass("windows.events");
     });
 
--- a/browser/components/extensions/test/browser/browser_ext_windows_size.js
+++ b/browser/components/extensions/test/browser/browser_ext_windows_size.js
@@ -1,15 +1,15 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 add_task(function* testWindowCreate() {
   let extension = ExtensionTestUtils.loadExtension({
-    background() {
+    async background() {
       let _checkWindowPromise;
       browser.test.onMessage.addListener((msg, arg) => {
         if (msg == "checked-window") {
           _checkWindowPromise.resolve(arg);
           _checkWindowPromise = null;
         }
       });
 
@@ -23,83 +23,73 @@ add_task(function* testWindowCreate() {
       const KEYS = ["left", "top", "width", "height"];
       function checkGeom(expected, actual) {
         for (let key of KEYS) {
           browser.test.assertEq(expected[key], actual[key], `Expected '${key}' value`);
         }
       }
 
       let windowId;
-      function checkWindow(expected, retries = 5) {
-        return getWindowSize().then(geom => {
-          if (retries && KEYS.some(key => expected[key] != geom[key])) {
-            browser.test.log(`Got mismatched size (${JSON.stringify(expected)} != ${JSON.stringify(geom)}). ` +
-                             `Retrying after a short delay.`);
+      async function checkWindow(expected, retries = 5) {
+        let geom = await getWindowSize();
+
+        if (retries && KEYS.some(key => expected[key] != geom[key])) {
+          browser.test.log(`Got mismatched size (${JSON.stringify(expected)} != ${JSON.stringify(geom)}). ` +
+                           `Retrying after a short delay.`);
+
+          await new Promise(resolve => setTimeout(resolve, 200));
 
-            return new Promise(resolve => {
-              setTimeout(resolve, 200);
-            }).then(() => {
-              return checkWindow(expected, retries - 1);
-            });
-          }
+          return checkWindow(expected, retries - 1);
+        }
+
+        browser.test.log(`Check actual window size`);
+        checkGeom(expected, geom);
 
-          browser.test.log(`Check actual window size`);
-          checkGeom(expected, geom);
+        browser.test.log("Check API-reported window size");
 
-          browser.test.log("Check API-reported window size");
-          return browser.windows.get(windowId).then(geom => {
-            checkGeom(expected, geom);
-          });
-        });
+        geom = await browser.windows.get(windowId);
+
+        checkGeom(expected, geom);
       }
 
-      let geom = {left: 100, top: 100, width: 500, height: 300};
+      try {
+        let geom = {left: 100, top: 100, width: 500, height: 300};
 
-      return browser.windows.create(geom).then(window => {
+        let window = await browser.windows.create(geom);
         windowId = window.id;
 
-        return checkWindow(geom);
-      }).then(() => {
+        await checkWindow(geom);
+
         let update = {left: 150, width: 600};
         Object.assign(geom, update);
-
-        return browser.windows.update(windowId, update);
-      }).then(() => {
-        return checkWindow(geom);
-      }).then(() => {
-        let update = {top: 150, height: 400};
-        Object.assign(geom, update);
+        await browser.windows.update(windowId, update);
+        await checkWindow(geom);
 
-        return browser.windows.update(windowId, update);
-      }).then(() => {
-        return checkWindow(geom);
-      }).then(() => {
-        geom = {left: 200, top: 200, width: 800, height: 600};
+        update = {top: 150, height: 400};
+        Object.assign(geom, update);
+        await browser.windows.update(windowId, update);
+        await checkWindow(geom);
 
-        return browser.windows.update(windowId, geom);
-      }).then(() => {
-        return checkWindow(geom);
-      }).then(() => {
-        return browser.runtime.getPlatformInfo();
-      }).then((platformInfo) => {
+        geom = {left: 200, top: 200, width: 800, height: 600};
+        await browser.windows.update(windowId, geom);
+        await checkWindow(geom);
+
+        let platformInfo = await browser.runtime.getPlatformInfo();
         if (platformInfo.os != "linux") {
           geom = {left: -50, top: -50, width: 800, height: 600};
-
-          return browser.windows.update(windowId, geom).then(() => {
-            return checkWindow(geom);
-          });
+          await browser.windows.update(windowId, geom);
+          await checkWindow(geom);
         }
-      }).then(() => {
-        return browser.windows.remove(windowId);
-      }).then(() => {
+
+        await browser.windows.remove(windowId);
         browser.test.notifyPass("window-size");
-      }).catch(e => {
+      } catch (e) {
         browser.test.fail(`${e} :: ${e.stack}`);
         browser.test.notifyFail("window-size");
-      });
+      }
     },
   });
 
   let latestWindow;
   let windowListener = (window, topic) => {
     if (topic == "domwindowopened") {
       latestWindow = window;
     }
--- a/browser/components/extensions/test/browser/browser_ext_windows_update.js
+++ b/browser/components/extensions/test/browser/browser_ext_windows_update.js
@@ -46,17 +46,17 @@ add_task(function* () {
   yield extension.unload();
 
   yield BrowserTestUtils.closeWindow(window2);
 });
 
 
 add_task(function* testWindowUpdate() {
   let extension = ExtensionTestUtils.loadExtension({
-    background() {
+    async background() {
       let _checkWindowPromise;
       browser.test.onMessage.addListener(msg => {
         if (msg == "checked-window") {
           _checkWindowPromise.resolve();
           _checkWindowPromise = null;
         }
       });
 
@@ -64,49 +64,53 @@ add_task(function* testWindowUpdate() {
       function checkWindow(expected) {
         return new Promise(resolve => {
           _checkWindowPromise = {resolve};
           browser.test.sendMessage("check-window", expected);
         });
       }
 
       let currentWindowId;
-      function updateWindow(windowId, params, expected) {
-        return browser.windows.update(windowId, params).then(window => {
-          browser.test.assertEq(currentWindowId, window.id, "Expected WINDOW_ID_CURRENT to refer to the same window");
-          for (let key of Object.keys(params)) {
-            if (key == "state" && os == "mac" && params.state == "normal") {
-              // OS-X doesn't have a hard distinction between "normal" and
-              // "maximized" states.
-              browser.test.assertTrue(window.state == "normal" || window.state == "maximized",
-                                      `Expected window.state (currently ${window.state}) to be "normal" but will accept "maximized"`);
-            } else {
-              browser.test.assertEq(params[key], window[key], `Got expected value for window.${key}`);
-            }
+      async function updateWindow(windowId, params, expected) {
+        let window = await browser.windows.update(windowId, params);
+
+        browser.test.assertEq(currentWindowId, window.id, "Expected WINDOW_ID_CURRENT to refer to the same window");
+        for (let key of Object.keys(params)) {
+          if (key == "state" && os == "mac" && params.state == "normal") {
+            // OS-X doesn't have a hard distinction between "normal" and
+            // "maximized" states.
+            browser.test.assertTrue(window.state == "normal" || window.state == "maximized",
+                                    `Expected window.state (currently ${window.state}) to be "normal" but will accept "maximized"`);
+          } else {
+            browser.test.assertEq(params[key], window[key], `Got expected value for window.${key}`);
           }
+        }
 
-          return checkWindow(expected);
-        });
+        return checkWindow(expected);
       }
 
-      let windowId = browser.windows.WINDOW_ID_CURRENT;
+      try {
+        let windowId = browser.windows.WINDOW_ID_CURRENT;
+
+        ({os} = await browser.runtime.getPlatformInfo());
+
+        let window = await browser.windows.getCurrent();
+        currentWindowId = window.id;
 
-      browser.runtime.getPlatformInfo().then(info => { os = info.os; })
-      .then(() => browser.windows.getCurrent().then(window => { currentWindowId = window.id; }))
-      .then(() => updateWindow(windowId, {state: "maximized"}, {state: "STATE_MAXIMIZED"}))
-      .then(() => updateWindow(windowId, {state: "minimized"}, {state: "STATE_MINIMIZED"}))
-      .then(() => updateWindow(windowId, {state: "normal"}, {state: "STATE_NORMAL"}))
-      .then(() => updateWindow(windowId, {state: "fullscreen"}, {state: "STATE_FULLSCREEN"}))
-      .then(() => updateWindow(windowId, {state: "normal"}, {state: "STATE_NORMAL"}))
-      .then(() => {
+        await updateWindow(windowId, {state: "maximized"}, {state: "STATE_MAXIMIZED"});
+        await updateWindow(windowId, {state: "minimized"}, {state: "STATE_MINIMIZED"});
+        await updateWindow(windowId, {state: "normal"}, {state: "STATE_NORMAL"});
+        await updateWindow(windowId, {state: "fullscreen"}, {state: "STATE_FULLSCREEN"});
+        await updateWindow(windowId, {state: "normal"}, {state: "STATE_NORMAL"});
+
         browser.test.notifyPass("window-update");
-      }).catch(e => {
+      } catch (e) {
         browser.test.fail(`${e} :: ${e.stack}`);
         browser.test.notifyFail("window-update");
-      });
+      }
     },
   });
 
   extension.onMessage("check-window", expected => {
     if (expected.state != null) {
       let {windowState} = window;
       if (window.fullScreen) {
         windowState = window.STATE_FULLSCREEN;
@@ -152,41 +156,34 @@ add_task(function* () {
 
   yield BrowserTestUtils.closeWindow(window2);
 });
 
 
 // Tests that incompatible parameters can't be used together.
 add_task(function* testWindowUpdateParams() {
   let extension = ExtensionTestUtils.loadExtension({
-    background() {
-      function* getCalls() {
+    async background() {
+      try {
         for (let state of ["minimized", "maximized", "fullscreen"]) {
           for (let param of ["left", "top", "width", "height"]) {
             let expected = `"state": "${state}" may not be combined with "left", "top", "width", or "height"`;
 
             let windowId = browser.windows.WINDOW_ID_CURRENT;
-            yield browser.windows.update(windowId, {state, [param]: 100}).then(
-              val => {
-                browser.test.fail(`Expected error but got "${val}" instead`);
-              },
-              error => {
-                browser.test.assertTrue(
-                  error.message.includes(expected),
-                  `Got expected error (got: '${error.message}', expected: '${expected}'`);
-              });
+            await browser.test.assertRejects(
+              browser.windows.update(windowId, {state, [param]: 100}),
+              RegExp(expected),
+              `Got expected error for create(${param}=100`);
           }
         }
-      }
 
-      Promise.all(getCalls()).then(() => {
         browser.test.notifyPass("window-update-params");
-      }).catch(e => {
+      } catch (e) {
         browser.test.fail(`${e} :: ${e.stack}`);
         browser.test.notifyFail("window-update-params");
-      });
+      }
     },
   });
 
   yield extension.startup();
   yield extension.awaitFinish("window-update-params");
   yield extension.unload();
 });
--- a/browser/components/extensions/test/browser/head_pageAction.js
+++ b/browser/components/extensions/test/browser/head_pageAction.js
@@ -7,71 +7,66 @@
 
 function* runTests(options) {
   function background(getTests) {
     let tabs;
     let tests;
 
     // Gets the current details of the page action, and returns a
     // promise that resolves to an object containing them.
-    function getDetails() {
-      return new Promise(resolve => {
-        return browser.tabs.query({active: true, currentWindow: true}, resolve);
-      }).then(([tab]) => {
-        let tabId = tab.id;
-        browser.test.log(`Get details: tab={id: ${tabId}, url: ${JSON.stringify(tab.url)}}`);
-        return Promise.all([
-          browser.pageAction.getTitle({tabId}),
-          browser.pageAction.getPopup({tabId})]);
-      }).then(details => {
-        return Promise.resolve({title: details[0],
-                                popup: details[1]});
-      });
+    async function getDetails() {
+      let [tab] = await browser.tabs.query({active: true, currentWindow: true});
+      let tabId = tab.id;
+
+      browser.test.log(`Get details: tab={id: ${tabId}, url: ${JSON.stringify(tab.url)}}`);
+
+      return {
+        title: await browser.pageAction.getTitle({tabId}),
+        popup: await browser.pageAction.getPopup({tabId}),
+      };
     }
 
 
     // Runs the next test in the `tests` array, checks the results,
     // and passes control back to the outer test scope.
     function nextTest() {
       let test = tests.shift();
 
-      test(expecting => {
+      test(async expecting => {
         function finish() {
           // Check that the actual icon has the expected values, then
           // run the next test.
           browser.test.sendMessage("nextTest", expecting, tests.length);
         }
 
         if (expecting) {
           // Check that the API returns the expected values, and then
           // run the next test.
-          getDetails().then(details => {
-            browser.test.assertEq(expecting.title, details.title,
-                                  "expected value from getTitle");
+          let details = await getDetails();
+
+          browser.test.assertEq(expecting.title, details.title,
+                                "expected value from getTitle");
 
-            browser.test.assertEq(expecting.popup, details.popup,
-                                  "expected value from getPopup");
+          browser.test.assertEq(expecting.popup, details.popup,
+                                "expected value from getPopup");
+        }
 
-            finish();
-          });
-        } else {
-          finish();
-        }
+        finish();
       });
     }
 
-    function runTests() {
+    async function runTests() {
       tabs = [];
       tests = getTests(tabs);
 
-      browser.tabs.query({active: true, currentWindow: true}, resultTabs => {
-        tabs[0] = resultTabs[0].id;
+      let resultTabs = await browser.tabs.query({active: true, currentWindow: true});
 
-        nextTest();
-      });
+      tabs[0] = resultTabs[0].id;
+
+      nextTest();
     }
 
     browser.test.onMessage.addListener((msg) => {
       if (msg == "runTests") {
         runTests();
       } else if (msg == "runNextTest") {
         nextTest();
       } else {
--- a/browser/components/originattributes/moz.build
+++ b/browser/components/originattributes/moz.build
@@ -3,10 +3,14 @@
 # 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/browser.ini',
 ]
 
+MOCHITEST_MANIFESTS += [
+    'test/mochitest/mochitest.ini'
+]
+
 with Files('**'):
     BUG_COMPONENT = ('Firefox', 'OriginAttributes')
--- a/browser/components/originattributes/test/browser/browser_cache.js
+++ b/browser/components/originattributes/test/browser/browser_cache.js
@@ -216,16 +216,24 @@ function* doTest(aBrowser) {
 
 // The check function, which checks the number of cache entries.
 function* doCheck(aShouldIsolate, aInputA, aInputB) {
   let expectedEntryCount = 1;
   let data = [];
   data = data.concat(yield cacheDataForContext(LoadContextInfo.default));
   data = data.concat(yield cacheDataForContext(LoadContextInfo.private));
   data = data.concat(yield cacheDataForContext(LoadContextInfo.custom(true, {})));
+  data = data.concat(yield cacheDataForContext(LoadContextInfo.custom(false, { userContextId: 1 })));
+  data = data.concat(yield cacheDataForContext(LoadContextInfo.custom(true, { userContextId: 1 })));
+  data = data.concat(yield cacheDataForContext(LoadContextInfo.custom(false, { userContextId: 2 })));
+  data = data.concat(yield cacheDataForContext(LoadContextInfo.custom(true, { userContextId: 2 })));
+  data = data.concat(yield cacheDataForContext(LoadContextInfo.custom(false, { firstPartyDomain: "example.com" })));
+  data = data.concat(yield cacheDataForContext(LoadContextInfo.custom(true, { firstPartyDomain: "example.com" })));
+  data = data.concat(yield cacheDataForContext(LoadContextInfo.custom(false, { firstPartyDomain: "example.org" })));
+  data = data.concat(yield cacheDataForContext(LoadContextInfo.custom(true, { firstPartyDomain: "example.org" })));
 
   if (aShouldIsolate) {
     expectedEntryCount = 2;
   }
 
   for (let suffix of suffixes) {
     let foundEntryCount = countMatchingCacheEntries(data, "example.net", suffix);
     let result = (expectedEntryCount === foundEntryCount);
new file mode 100644
--- /dev/null
+++ b/browser/components/originattributes/test/mochitest/file_empty.html
@@ -0,0 +1,2 @@
+<h1>I'm just a support file</h1>
+<p>I get loaded to do permission testing.</p>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/browser/components/originattributes/test/mochitest/mochitest.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+support-files =
+  file_empty.html
+
+[test_permissions_api.html]
new file mode 100644
--- /dev/null
+++ b/browser/components/originattributes/test/mochitest/test_permissions_api.html
@@ -0,0 +1,207 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+
+<head>
+  <meta charset="utf-8">
+  <title>Test for Permissions API</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+
+<body>
+  <pre id="test"></pre>
+  <script type="application/javascript;version=1.8">
+  /*globals SpecialPowers, SimpleTest, is, ok, */
+  'use strict';
+
+  const {
+    UNKNOWN_ACTION,
+    PROMPT_ACTION,
+    ALLOW_ACTION,
+    DENY_ACTION
+  } = SpecialPowers.Ci.nsIPermissionManager;
+
+  SimpleTest.waitForExplicitFinish();
+
+  const PERMISSIONS = [{
+    name: 'geolocation',
+    type: 'geo'
+  }, {
+    name: 'notifications',
+    type: 'desktop-notification'
+  }, {
+    name: 'push',
+    type: 'desktop-notification'
+  }, ];
+
+  const UNSUPPORTED_PERMISSIONS = [
+    'foobarbaz', // Not in spec, for testing only.
+    'midi',
+  ];
+
+  // Create a closure, so that tests are run on the correct window object.
+  function createPermissionTester(aWindow) {
+    return {
+      setPermissions(allow) {
+        const permissions = PERMISSIONS.map(({ type }) => {
+          return {
+            type,
+            allow,
+            'context': aWindow.document
+          };
+        });
+        return new Promise((resolve) => {
+          SpecialPowers.popPermissions(() => {
+            SpecialPowers.pushPermissions(permissions, resolve);
+          });
+        });
+      },
+      revokePermissions() {
+        const promisesToRevoke = PERMISSIONS.map(({ name })  => {
+          return aWindow.navigator.permissions
+            .revoke({ name })
+            .then(
+              ({ state }) => is(state, 'prompt', `correct state for '${name}'`),
+              () => ok(false, `revoke should not have rejected for '${name}'`)
+            );
+        });
+        return Promise.all(promisesToRevoke);
+      },
+      revokeUnsupportedPermissions() {
+        const promisesToRevoke = UNSUPPORTED_PERMISSIONS.map(({ name }) => {
+          return aWindow.navigator.permissions
+            .revoke({ name })
+            .then(
+              () => ok(false, `revoke should not have resolved for '${name}'`),
+              error => is(error.name, 'TypeError', `revoke should have thrown TypeError for '${name}'`)
+            );
+        });
+        return Promise.all(promisesToRevoke);
+      },
+      checkPermissions(state) {
+        const promisesToQuery = PERMISSIONS.map(({ name }) => {
+          return aWindow.navigator.permissions
+            .query({ name })
+            .then(
+              () => is(state, state, `correct state for '${name}'`),
+              () => ok(false, `query should not have rejected for '${name}'`)
+            );
+          });
+        return Promise.all(promisesToQuery);
+      },
+      checkUnsupportedPermissions() {
+        const promisesToQuery = UNSUPPORTED_PERMISSIONS.map(({ name }) => {
+          return aWindow.navigator.permissions
+            .query({ name })
+            .then(
+              () => ok(false, `query should not have resolved for '${name}'`),
+              error => {
+                is(error.name, 'TypeError',
+                  `query should have thrown TypeError for '${name}'`);
+              }
+            );
+          });
+        return Promise.all(promisesToQuery);
+      },
+      promiseStateChanged(name, state) {
+        return aWindow.navigator.permissions
+          .query({ name })
+          .then(status => {
+            return new Promise( resolve => {
+              status.onchange = () => {
+                status.onchange = null;
+                is(status.state, state, `state changed for '${name}'`);
+                resolve();
+              };
+            });
+          },
+          () => ok(false, `query should not have rejected for '${name}'`));
+      },
+      testStatusOnChange() {
+        return new Promise((resolve) => {
+          SpecialPowers.popPermissions(() => {
+            const permission = 'geolocation';
+            const promiseGranted = this.promiseStateChanged(permission, 'granted');
+            this.setPermissions(ALLOW_ACTION);
+            promiseGranted.then(() => {
+              const promisePrompt = this.promiseStateChanged(permission, 'prompt');
+              SpecialPowers.popPermissions();
+              return promisePrompt;
+            }).then(resolve);
+          });
+        });
+      },
+      testInvalidQuery() {
+        return aWindow.navigator.permissions
+          .query({ name: 'invalid' })
+          .then(
+            () => ok(false, 'invalid query should not have resolved'),
+            () => ok(true, 'invalid query should have rejected')
+          );
+      },
+      testInvalidRevoke() {
+        return aWindow.navigator.permissions
+          .revoke({ name: 'invalid' })
+          .then(
+            () => ok(false, 'invalid revoke should not have resolved'),
+            () => ok(true, 'invalid revoke should have rejected')
+          );
+      },
+    };
+  }
+
+  function enablePrefs() {
+    const ops = {
+      'set': [
+        ['dom.permissions.revoke.enable', true],
+        ['privacy.firstparty.isolate', true],
+      ],
+    };
+    return SpecialPowers.pushPrefEnv(ops);
+  }
+
+  function createIframe() {
+    return new Promise((resolve) => {
+      const iframe = document.createElement('iframe');
+      iframe.src = 'file_empty.html';
+      iframe.onload = () => resolve(iframe.contentWindow);
+      document.body.appendChild(iframe);
+    });
+  }
+  debugger;
+  window.onload = () => {
+    enablePrefs()
+      .then(createIframe)
+      .then(createPermissionTester)
+      .then((tester) => {
+        return tester
+          .checkUnsupportedPermissions()
+          .then(() => tester.setPermissions(UNKNOWN_ACTION))
+          .then(() => tester.checkPermissions('prompt'))
+          .then(() => tester.setPermissions(PROMPT_ACTION))
+          .then(() => tester.checkPermissions('prompt'))
+          .then(() => tester.setPermissions(ALLOW_ACTION))
+          .then(() => tester.checkPermissions('granted'))
+          .then(() => tester.setPermissions(DENY_ACTION))
+          .then(() => tester.checkPermissions('denied'))
+          .then(() => tester.testStatusOnChange())
+          .then(() => tester.testInvalidQuery())
+          .then(() => tester.revokeUnsupportedPermissions())
+          .then(() => tester.revokePermissions())
+          .then(() => tester.checkPermissions('prompt'))
+          .then(() => tester.testInvalidRevoke());
+      })
+      .then(SimpleTest.finish)
+      .catch((e) => {
+        ok(false, `Unexpected error ${e}`);
+        SimpleTest.finish();
+      });
+  };
+  </script>
+</body>
+
+</html>
--- a/browser/components/safebrowsing/content/test/head.js
+++ b/browser/components/safebrowsing/content/test/head.js
@@ -28,18 +28,16 @@ function promiseTabLoadEvent(tab, url, e
       info(`Skipping spurious load event for ${loadedUrl}`);
       return false;
     }
 
     info("Tab event received: load");
     return true;
   }
 
-  // Create two promises: one resolved from the content process when the page
-  // loads and one that is rejected if we take too long to load the url.
   let loaded;
   if (eventType === "load") {
     loaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, handle);
   } else {
     // No need to use handle.
     loaded =
       BrowserTestUtils.waitForContentEvent(tab.linkedBrowser, eventType,
                                            true, undefined, true);
--- a/browser/components/search/test/head.js
+++ b/browser/components/search/test/head.js
@@ -107,18 +107,16 @@ function promiseTabLoadEvent(tab, url)
       info(`Skipping spurious load event for ${loadedUrl}`);
       return false;
     }
 
     info("Tab event received: load");
     return true;
   }
 
-  // Create two promises: one resolved from the content process when the page
-  // loads and one that is rejected if we take too long to load the url.
   let loaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, handle);
 
   if (url)
     BrowserTestUtils.loadURI(tab.linkedBrowser, url);
 
   return loaded;
 }
 
--- a/browser/locales/en-US/chrome/browser/tabbrowser.properties
+++ b/browser/locales/en-US/chrome/browser/tabbrowser.properties
@@ -12,16 +12,17 @@
 # tabs.downloading = Firefox is downloading a file for a helper application (PDF)
 tabs.connecting=Connecting…
 tabs.encryptingConnection=Securing connection…
 tabs.searching=Searching…
 tabs.loading=Loading…
 tabs.waiting=Waiting…
 tabs.downloading=Downloading…
 
+tabs.restoreLastTabs=Restore Tabs From Last Time
 tabs.emptyTabTitle=New Tab
 tabs.closeTab=Close Tab
 tabs.close=Close
 tabs.closeWarningTitle=Confirm close
 # LOCALIZATION NOTE (tabs.closeWarningMultiple):
 # Semicolon-separated list of plural forms. See:
 # http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # The singular form is not considered since this string is used only for
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -142,22 +142,16 @@
 }
 
 /** End titlebar **/
 
 #main-window[chromehidden~="toolbar"][chromehidden~="location"][chromehidden~="directories"] {
   border-top: 1px solid rgba(0,0,0,0.65);
 }
 
-/* Because of -moz-box-align: center above, separators will be invisible unless
-   we set their min-height. See bug 583510 for more information. */
-toolbarseparator {
-  min-height: 22px;
-}
-
 #navigator-toolbox > toolbar:not(#TabsToolbar):not(#nav-bar):not(:-moz-lwtheme) {
   -moz-appearance: none;
   background: url(chrome://browser/skin/Toolbar-background-noise.png) hsl(0,0%,83%);
 }
 
 /* remove noise texture on Yosemite */
 @media (-moz-mac-yosemite-theme) {
   #navigator-toolbox > toolbar:not(#TabsToolbar):not(#nav-bar):not(:-moz-lwtheme) {
--- a/build/gecko_templates.mozbuild
+++ b/build/gecko_templates.mozbuild
@@ -53,22 +53,24 @@ def GeckoBinary(linkage='dependent', msv
         ]
     elif linkage != None:
         error('`linkage` must be "dependent", "standalone" or None')
 
     if mozglue:
         LDFLAGS += CONFIG['MOZ_GLUE_WRAP_LDFLAGS']
         if mozglue == 'program':
             USE_LIBS += ['mozglue']
+            DEFINES['MOZ_HAS_MOZGLUE'] = True
             if CONFIG['MOZ_GLUE_IN_PROGRAM']:
                 if CONFIG['GNU_CC']:
                     LDFLAGS += ['-rdynamic']
                 if CONFIG['MOZ_MEMORY']:
                     USE_LIBS += ['memory']
         elif mozglue == 'library':
+            LIBRARY_DEFINES['MOZ_HAS_MOZGLUE'] = True
             if not CONFIG['MOZ_GLUE_IN_PROGRAM']:
                 USE_LIBS += ['mozglue']
         else:
             error('`mozglue` must be "program" or "library"')
 
     if not CONFIG['JS_STANDALONE']:
         USE_LIBS += [
             'fallible',
--- a/config/external/fdlibm/moz.build
+++ b/config/external/fdlibm/moz.build
@@ -1,14 +1,12 @@
 # -*- 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/.
 
-Library('fdlibm')
-
 with Files('**'):
     BUG_COMPONENT = ('Core', 'JavaScript Engine')
 
 DIRS += [
     '../../../modules/fdlibm',
 ]
--- a/devtools/client/netmonitor/test/browser_net_content-type.js
+++ b/devtools/client/netmonitor/test/browser_net_content-type.js
@@ -19,17 +19,17 @@ function* content_type_test(isHTTPS) {
   let { tab, monitor } = yield initNetMonitor(pageURL);
   info("Starting test... ");
 
   let { document, Editor, NetMonitorView } = monitor.panelWin;
   let { RequestsMenu } = NetMonitorView;
 
   RequestsMenu.lazyUpdate = false;
 
-  let wait = waitForNetworkEvents(monitor, 8);
+  let wait = waitForNetworkEvents(monitor, CONTENT_TYPE_WITHOUT_CACHE_REQUESTS);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   let okStatus = isHTTPS ? "Connected" : "OK";
 
   verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
--- a/devtools/client/netmonitor/test/browser_net_copy_image_as_data_uri.js
+++ b/devtools/client/netmonitor/test/browser_net_copy_image_as_data_uri.js
@@ -11,17 +11,17 @@ add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CONTENT_TYPE_WITHOUT_CACHE_URL);
   info("Starting test... ");
 
   let { NetMonitorView } = monitor.panelWin;
   let { RequestsMenu } = NetMonitorView;
 
   RequestsMenu.lazyUpdate = false;
 
-  let wait = waitForNetworkEvents(monitor, 8);
+  let wait = waitForNetworkEvents(monitor, CONTENT_TYPE_WITHOUT_CACHE_REQUESTS);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   let requestItem = RequestsMenu.getItemAtIndex(5);
   RequestsMenu.selectedItem = requestItem;
 
--- a/devtools/client/netmonitor/test/browser_net_copy_response.js
+++ b/devtools/client/netmonitor/test/browser_net_copy_response.js
@@ -13,17 +13,17 @@ add_task(function* () {
 
   const EXPECTED_RESULT = '{ "greeting": "Hello JSON!" }';
 
   let { NetMonitorView } = monitor.panelWin;
   let { RequestsMenu } = NetMonitorView;
 
   RequestsMenu.lazyUpdate = false;
 
-  let wait = waitForNetworkEvents(monitor, 8);
+  let wait = waitForNetworkEvents(monitor, CONTENT_TYPE_WITHOUT_CACHE_REQUESTS);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   let requestItem = RequestsMenu.getItemAtIndex(3);
   RequestsMenu.selectedItem = requestItem;
 
--- a/devtools/client/netmonitor/test/browser_net_icon-preview.js
+++ b/devtools/client/netmonitor/test/browser_net_icon-preview.js
@@ -39,17 +39,17 @@ add_task(function* () {
 
   info("Checking the image thumbnail after a reload.");
   checkImageThumbnail();
 
   yield teardown(monitor);
 
   function waitForEvents() {
     return promise.all([
-      waitForNetworkEvents(monitor, 8),
+      waitForNetworkEvents(monitor, CONTENT_TYPE_WITHOUT_CACHE_REQUESTS),
       monitor.panelWin.once(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED)
     ]);
   }
 
   function performRequests() {
     return ContentTask.spawn(tab.linkedBrowser, {}, function* () {
       content.wrappedJSObject.performRequests();
     });
--- a/devtools/client/netmonitor/test/browser_net_image-tooltip.js
+++ b/devtools/client/netmonitor/test/browser_net_image-tooltip.js
@@ -10,28 +10,28 @@ add_task(function* test() {
   let { tab, monitor } = yield initNetMonitor(CONTENT_TYPE_WITHOUT_CACHE_URL);
   info("Starting test... ");
 
   let { $, EVENTS, ACTIVITY_TYPE, NetMonitorView, NetMonitorController } =
     monitor.panelWin;
   let { RequestsMenu } = NetMonitorView;
   RequestsMenu.lazyUpdate = true;
 
-  let onEvents = waitForNetworkEvents(monitor, 8);
+  let onEvents = waitForNetworkEvents(monitor, CONTENT_TYPE_WITHOUT_CACHE_REQUESTS);
   let onThumbnail = monitor.panelWin.once(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED);
 
   yield performRequests();
   yield onEvents;
   yield onThumbnail;
 
   info("Checking the image thumbnail after a few requests were made...");
   yield showTooltipAndVerify(RequestsMenu.tooltip, RequestsMenu.items[5]);
 
-  // 7 XHRs as before + 1 extra document reload
-  onEvents = waitForNetworkEvents(monitor, 8);
+  // +1 extra document reload
+  onEvents = waitForNetworkEvents(monitor, CONTENT_TYPE_WITHOUT_CACHE_REQUESTS + 1);
   onThumbnail = monitor.panelWin.once(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED);
 
   info("Reloading the debuggee and performing all requests again...");
   yield NetMonitorController.triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED);
   yield performRequests();
   yield onEvents;
   yield onThumbnail;
 
--- a/devtools/client/netmonitor/test/head.js
+++ b/devtools/client/netmonitor/test/head.js
@@ -16,16 +16,17 @@ const EXAMPLE_URL = "http://example.com/
 const HTTPS_EXAMPLE_URL = "https://example.com/browser/devtools/client/netmonitor/test/";
 
 const API_CALLS_URL = EXAMPLE_URL + "html_api-calls-test-page.html";
 const SIMPLE_URL = EXAMPLE_URL + "html_simple-test-page.html";
 const NAVIGATE_URL = EXAMPLE_URL + "html_navigate-test-page.html";
 const CONTENT_TYPE_URL = EXAMPLE_URL + "html_content-type-test-page.html";
 const CONTENT_TYPE_WITHOUT_CACHE_URL = EXAMPLE_URL + "html_content-type-without-cache-test-page.html";
 const HTTPS_CONTENT_TYPE_WITHOUT_CACHE_URL = HTTPS_EXAMPLE_URL + "html_content-type-without-cache-test-page.html";
+const CONTENT_TYPE_WITHOUT_CACHE_REQUESTS = 8;
 const CYRILLIC_URL = EXAMPLE_URL + "html_cyrillic-test-page.html";
 const STATUS_CODES_URL = EXAMPLE_URL + "html_status-codes-test-page.html";
 const POST_DATA_URL = EXAMPLE_URL + "html_post-data-test-page.html";
 const POST_JSON_URL = EXAMPLE_URL + "html_post-json-test-page.html";
 const POST_RAW_URL = EXAMPLE_URL + "html_post-raw-test-page.html";
 const POST_RAW_WITH_HEADERS_URL = EXAMPLE_URL + "html_post-raw-with-headers-test-page.html";
 const PARAMS_URL = EXAMPLE_URL + "html_params-test-page.html";
 const JSONP_URL = EXAMPLE_URL + "html_jsonp-test-page.html";
--- a/devtools/client/storage/test/storage-listings.html
+++ b/devtools/client/storage/test/storage-listings.html
@@ -14,17 +14,17 @@ Bug 970517 - Storage inspector front end
 "use strict";
 let partialHostname = location.hostname.match(/^[^.]+(\..*)$/)[1];
 let cookieExpiresTime1 = 2000000000000;
 let cookieExpiresTime2 = 2000000001000;
 // Setting up some cookies to eat.
 document.cookie = "c1=foobar; expires=" +
   new Date(cookieExpiresTime1).toGMTString() + "; path=/browser";
 document.cookie = "cs2=sessionCookie; path=/; domain=" + partialHostname;
-document.cookie = "c3=foobar-2; secure=true; expires=" +
+document.cookie = "c3=foobar-2; expires=" +
   new Date(cookieExpiresTime2).toGMTString() + "; path=/";
 // ... and some local storage items ..
 localStorage.setItem("ls1", "foobar");
 localStorage.setItem("ls2", "foobar-2");
 // ... and finally some session storage items too
 sessionStorage.setItem("ss1", "foobar-3");
 dump("added cookies and storage from main page\n");
 
--- a/devtools/client/storage/test/storage-unsecured-iframe.html
+++ b/devtools/client/storage/test/storage-unsecured-iframe.html
@@ -4,16 +4,16 @@
 Iframe for testing multiple host detetion in storage actor
 -->
 <head>
   <meta charset="utf-8">
 </head>
 <body>
 <script>
 "use strict";
-document.cookie = "uc1=foobar; domain=.example.org; path=/; secure=true";
+document.cookie = "uc1=foobar; domain=.example.org; path=/";
 localStorage.setItem("iframe-u-ls1", "foobar");
 sessionStorage.setItem("iframe-u-ss1", "foobar1");
 sessionStorage.setItem("iframe-u-ss2", "foobar2");
 dump("added cookies and storage from unsecured iframe\n");
 </script>
 </body>
 </html>
--- a/devtools/client/themes/computed.css
+++ b/devtools/client/themes/computed.css
@@ -31,22 +31,22 @@
   align-items: center;
 }
 
 #browser-style-checkbox {
   /* Bug 1200073 - extra space before the browser styles checkbox so
      they aren't squished together in a small window. Put also
      an extra space after. */
   margin-inline-start: 5px;
-  margin-inline-end: 5px;
-
+  margin-inline-end: 0;
 }
 
 #browser-style-checkbox-label {
-  margin-right: 5px;
+  padding-inline-start: 5px;
+  margin-inline-end: 5px;
 }
 
 #propertyContainer {
   -moz-user-select: text;
   overflow-y: auto;
   overflow-x: hidden;
   flex: auto;
 }
--- a/devtools/server/tests/browser/browser_storage_listings.js
+++ b/devtools/server/tests/browser/browser_storage_listings.js
@@ -30,17 +30,17 @@ const storeMap = {
       },
       {
         name: "c3",
         value: "foobar-2",
         expires: 2000000001000,
         path: "/",
         host: "test1.example.org",
         isDomain: false,
-        isSecure: true,
+        isSecure: false,
       },
       {
         name: "uc1",
         value: "foobar",
         host: ".example.org",
         path: "/",
         expires: 0,
         isDomain: true,
@@ -339,17 +339,24 @@ function* testStores(data) {
 function testCookies(cookiesActor) {
   is(Object.keys(cookiesActor.hosts).length, 2, "Correct number of host entries for cookies");
   return testCookiesObjects(0, cookiesActor.hosts, cookiesActor);
 }
 
 var testCookiesObjects = Task.async(function* (index, hosts, cookiesActor) {
   let host = Object.keys(hosts)[index];
   let matchItems = data => {
-    is(data.total, storeMap.cookies[host].length,
+    let cookiesLength = 0;
+    for (let secureCookie of storeMap.cookies[host]) {
+       if (secureCookie.isSecure) {
+          ++cookiesLength;
+       }
+    }
+    // Any secure cookies did not get stored in the database.
+    is(data.total, storeMap.cookies[host].length - cookiesLength,
        "Number of cookies in host " + host + " matches");
     for (let item of data.data) {
       let found = false;
       for (let toMatch of storeMap.cookies[host]) {
         if (item.name == toMatch.name) {
           found = true;
           ok(true, "Found cookie " + item.name + " in response");
           is(item.value.str, toMatch.value, "The value matches.");
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -12263,16 +12263,17 @@ nsIDocument::InlineScriptAllowedByCSP()
 {
   nsCOMPtr<nsIContentSecurityPolicy> csp;
   nsresult rv = NodePrincipal()->GetCsp(getter_AddRefs(csp));
   NS_ENSURE_SUCCESS(rv, true);
   bool allowsInlineScript = true;
   if (csp) {
     nsresult rv = csp->GetAllowsInline(nsIContentPolicy::TYPE_SCRIPT,
                                        EmptyString(), // aNonce
+                                       false,         // parserCreated
                                        EmptyString(), // FIXME get script sample (bug 1314567)
                                        0,             // aLineNumber
                                        &allowsInlineScript);
     NS_ENSURE_SUCCESS(rv, true);
   }
   return allowsInlineScript;
 }
 
--- a/dom/base/nsScriptLoader.cpp
+++ b/dom/base/nsScriptLoader.cpp
@@ -1377,24 +1377,25 @@ CSPAllowsInlineScript(nsIScriptElement *
     // no CSP --> allow
     return true;
   }
 
   // query the nonce
   nsCOMPtr<nsIContent> scriptContent = do_QueryInterface(aElement);
   nsAutoString nonce;
   scriptContent->GetAttr(kNameSpaceID_None, nsGkAtoms::nonce, nonce);
+  bool parserCreated = aElement->GetParserCreated() != mozilla::dom::NOT_FROM_PARSER;
 
   // query the scripttext
   nsAutoString scriptText;
   aElement->GetScriptText(scriptText);
 
   bool allowInlineScript = false;
   rv = csp->GetAllowsInline(nsIContentPolicy::TYPE_SCRIPT,
-                            nonce, scriptText,
+                            nonce, parserCreated, scriptText,
                             aElement->GetScriptLineNumber(),
                             &allowInlineScript);
   return allowInlineScript;
 }
 
 nsScriptLoadRequest*
 nsScriptLoader::CreateLoadRequest(nsScriptKind aKind,
                                   nsIScriptElement* aElement,
--- a/dom/cache/CacheOpParent.cpp
+++ b/dom/cache/CacheOpParent.cpp
@@ -49,30 +49,30 @@ CacheOpParent::~CacheOpParent()
 
 void
 CacheOpParent::Execute(ManagerId* aManagerId)
 {
   NS_ASSERT_OWNINGTHREAD(CacheOpParent);
   MOZ_ASSERT(!mManager);
   MOZ_ASSERT(!mVerifier);
 
-  RefPtr<Manager> manager;
-  nsresult rv = Manager::GetOrCreate(aManagerId, getter_AddRefs(manager));
+  RefPtr<cache::Manager> manager;
+  nsresult rv = cache::Manager::GetOrCreate(aManagerId, getter_AddRefs(manager));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     ErrorResult result(rv);
     Unused << Send__delete__(this, result, void_t());
     result.SuppressException();
     return;
   }
 
   Execute(manager);
 }
 
 void
-CacheOpParent::Execute(Manager* aManager)
+CacheOpParent::Execute(cache::Manager* aManager)
 {
   NS_ASSERT_OWNINGTHREAD(CacheOpParent);
   MOZ_ASSERT(!mManager);
   MOZ_ASSERT(!mVerifier);
 
   mManager = aManager;
 
   // Handle put op
--- a/dom/cache/CacheOpParent.h
+++ b/dom/cache/CacheOpParent.h
@@ -32,17 +32,17 @@ public:
   CacheOpParent(mozilla::ipc::PBackgroundParent* aIpcManager,
                 Namespace aNamespace, const CacheOpArgs& aOpArgs);
   ~CacheOpParent();
 
   void
   Execute(ManagerId* aManagerId);
 
   void
-  Execute(Manager* aManager);
+  Execute(cache::Manager* aManager);
 
   void
   WaitForVerification(PrincipalVerifier* aVerifier);
 
 private:
   // PCacheOpParent methods
   virtual void
   ActorDestroy(ActorDestroyReason aReason) override;
@@ -62,17 +62,17 @@ private:
   // utility methods
   already_AddRefed<nsIInputStream>
   DeserializeCacheStream(const CacheReadStreamOrVoid& aStreamOrVoid);
 
   mozilla::ipc::PBackgroundParent* mIpcManager;
   const CacheId mCacheId;
   const Namespace mNamespace;
   const CacheOpArgs mOpArgs;
-  RefPtr<Manager> mManager;
+  RefPtr<cache::Manager> mManager;
   RefPtr<PrincipalVerifier> mVerifier;
 
   NS_DECL_OWNINGTHREAD
 };
 
 } // namespace cache
 } // namespace dom
 } // namespace mozilla
--- a/dom/cache/CacheTypes.ipdlh
+++ b/dom/cache/CacheTypes.ipdlh
@@ -45,22 +45,22 @@ union CacheReadStreamOrVoid
   CacheReadStream;
 };
 
 struct HeadersEntry
 {
   nsCString name;
   nsCString value;
 };
-
 struct CacheRequest
 {
   nsCString method;
   nsCString urlWithoutQuery;
   nsCString urlQuery;
+  nsCString urlFragment;
   HeadersEntry[] headers;
   HeadersGuardEnum headersGuard;
   nsString referrer;
   ReferrerPolicy referrerPolicy;
   RequestMode mode;
   RequestCredentials credentials;
   CacheReadStreamOrVoid body;
   uint32_t contentPolicyType;
--- a/dom/cache/DBSchema.cpp
+++ b/dom/cache/DBSchema.cpp
@@ -27,30 +27,25 @@
 #include "nsNetCID.h"
 #include "nsPrintfCString.h"
 #include "nsTArray.h"
 
 namespace mozilla {
 namespace dom {
 namespace cache {
 namespace db {
-
 const int32_t kFirstShippedSchemaVersion = 15;
-
 namespace {
-
 // Update this whenever the DB schema is changed.
-const int32_t kLatestSchemaVersion = 23;
-
+const int32_t kLatestSchemaVersion = 24;
 // ---------
 // The following constants define the SQL schema.  These are defined in the
 // same order the SQL should be executed in CreateOrMigrateSchema().  They are
 // broken out as constants for convenient use in validation and migration.
 // ---------
-
 // The caches table is the single source of truth about what Cache
 // objects exist for the origin.  The contents of the Cache are stored
 // in the entries table that references back to caches.
 //
 // The caches table is also referenced from storage.  Rows in storage
 // represent named Cache objects.  There are cases, however, where
 // a Cache can still exist, but not be in a named Storage.  For example,
 // when content is still using the Cache after CacheStorage::Delete()
@@ -98,24 +93,23 @@ const char* const kTableEntries =
     "response_type INTEGER NOT NULL, "
     "response_status INTEGER NOT NULL, "
     "response_status_text TEXT NOT NULL, "
     "response_headers_guard INTEGER NOT NULL, "
     "response_body_id TEXT NULL, "
     "response_security_info_id INTEGER NULL REFERENCES security_info(id), "
     "response_principal_info TEXT NOT NULL, "
     "cache_id INTEGER NOT NULL REFERENCES caches(id) ON DELETE CASCADE, "
-
     "request_redirect INTEGER NOT NULL, "
     "request_referrer_policy INTEGER NOT NULL, "
-    "request_integrity TEXT NOT NULL"
+    "request_integrity TEXT NOT NULL, "
+    "request_url_fragment TEXT NOT NULL"
     // New columns must be added at the end of table to migrate and
     // validate properly.
   ")";
-
 // Create an index to support the QueryCache() matching algorithm.  This
 // needs to quickly find entries in a given Cache that match the request
 // URL.  The url query is separated in order to support the ignoreSearch
 // option.  Finally, we index hashes of the URL values instead of the
 // actual strings to avoid excessive disk bloat.  The index will duplicate
 // the contents of the columsn in the index.  The hash index will prune
 // the vast majority of values from the query result so that normal
 // scanning only has to be done on a few values to find an exact URL match.
@@ -1650,16 +1644,17 @@ InsertEntry(mozIStorageConnection* aConn
   nsCOMPtr<mozIStorageStatement> state;
   rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
     "INSERT INTO entries ("
       "request_method, "
       "request_url_no_query, "
       "request_url_no_query_hash, "
       "request_url_query, "
       "request_url_query_hash, "
+      "request_url_fragment, "
       "request_referrer, "
       "request_referrer_policy, "
       "request_headers_guard, "
       "request_mode, "
       "request_credentials, "
       "request_contentpolicytype, "
       "request_cache, "
       "request_redirect, "
@@ -1674,16 +1669,17 @@ InsertEntry(mozIStorageConnection* aConn
       "response_principal_info, "
       "cache_id "
     ") VALUES ("
       ":request_method, "
       ":request_url_no_query, "
       ":request_url_no_query_hash, "
       ":request_url_query, "
       ":request_url_query_hash, "
+      ":request_url_fragment, "
       ":request_referrer, "
       ":request_referrer_policy, "
       ":request_headers_guard, "
       ":request_mode, "
       ":request_credentials, "
       ":request_contentpolicytype, "
       ":request_cache, "
       ":request_redirect, "
@@ -1719,29 +1715,29 @@ InsertEntry(mozIStorageConnection* aConn
 
   rv = state->BindUTF8StringByName(NS_LITERAL_CSTRING("request_url_query"),
                                    aRequest.urlQuery());
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   nsAutoCString urlQueryHash;
   rv = HashCString(crypto, aRequest.urlQuery(), urlQueryHash);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
-
   rv = state->BindUTF8StringAsBlobByName(
     NS_LITERAL_CSTRING("request_url_query_hash"), urlQueryHash);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+  rv = state->BindUTF8StringByName(NS_LITERAL_CSTRING("request_url_fragment"),
+                                   aRequest.urlFragment());
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   rv = state->BindStringByName(NS_LITERAL_CSTRING("request_referrer"),
                                aRequest.referrer());
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
-
   rv = state->BindInt32ByName(NS_LITERAL_CSTRING("request_referrer_policy"),
                               static_cast<int32_t>(aRequest.referrerPolicy()));
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
-
   rv = state->BindInt32ByName(NS_LITERAL_CSTRING("request_headers_guard"),
     static_cast<int32_t>(aRequest.headersGuard()));
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   rv = state->BindInt32ByName(NS_LITERAL_CSTRING("request_mode"),
                               static_cast<int32_t>(aRequest.mode()));
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
@@ -2038,23 +2034,23 @@ ReadResponse(mozIStorageConnection* aCon
 
 nsresult
 ReadRequest(mozIStorageConnection* aConn, EntryId aEntryId,
             SavedRequest* aSavedRequestOut)
 {
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(aConn);
   MOZ_ASSERT(aSavedRequestOut);
-
   nsCOMPtr<mozIStorageStatement> state;
   nsresult rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
     "SELECT "
       "request_method, "
       "request_url_no_query, "
       "request_url_query, "
+      "request_url_fragment, "
       "request_referrer, "
       "request_referrer_policy, "
       "request_headers_guard, "
       "request_mode, "
       "request_credentials, "
       "request_contentpolicytype, "
       "request_cache, "
       "request_redirect, "
@@ -2069,80 +2065,69 @@ ReadRequest(mozIStorageConnection* aConn
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   bool hasMoreData = false;
   rv = state->ExecuteStep(&hasMoreData);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   rv = state->GetUTF8String(0, aSavedRequestOut->mValue.method());
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
-
   rv = state->GetUTF8String(1, aSavedRequestOut->mValue.urlWithoutQuery());
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
-
   rv = state->GetUTF8String(2, aSavedRequestOut->mValue.urlQuery());
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
-
-  rv = state->GetString(3, aSavedRequestOut->mValue.referrer());
+  rv = state->GetUTF8String(3, aSavedRequestOut->mValue.urlFragment());
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+  rv = state->GetString(4, aSavedRequestOut->mValue.referrer());
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   int32_t referrerPolicy;
-  rv = state->GetInt32(4, &referrerPolicy);
+  rv = state->GetInt32(5, &referrerPolicy);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
   aSavedRequestOut->mValue.referrerPolicy() =
     static_cast<ReferrerPolicy>(referrerPolicy);
-
   int32_t guard;
-  rv = state->GetInt32(5, &guard);
+  rv = state->GetInt32(6, &guard);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
   aSavedRequestOut->mValue.headersGuard() =
     static_cast<HeadersGuardEnum>(guard);
-
   int32_t mode;
-  rv = state->GetInt32(6, &mode);
+  rv = state->GetInt32(7, &mode);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
   aSavedRequestOut->mValue.mode() = static_cast<RequestMode>(mode);
-
   int32_t credentials;
-  rv = state->GetInt32(7, &credentials);
+  rv = state->GetInt32(8, &credentials);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
   aSavedRequestOut->mValue.credentials() =
     static_cast<RequestCredentials>(credentials);
-
   int32_t requestContentPolicyType;
-  rv = state->GetInt32(8, &requestContentPolicyType);
+  rv = state->GetInt32(9, &requestContentPolicyType);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
   aSavedRequestOut->mValue.contentPolicyType() =
     static_cast<nsContentPolicyType>(requestContentPolicyType);
-
   int32_t requestCache;
-  rv = state->GetInt32(9, &requestCache);
+  rv = state->GetInt32(10, &requestCache);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
   aSavedRequestOut->mValue.requestCache() =
     static_cast<RequestCache>(requestCache);
-
   int32_t requestRedirect;
-  rv = state->GetInt32(10, &requestRedirect);
+  rv = state->GetInt32(11, &requestRedirect);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
   aSavedRequestOut->mValue.requestRedirect() =
     static_cast<RequestRedirect>(requestRedirect);
-
-  rv = state->GetString(11, aSavedRequestOut->mValue.integrity());
+  rv = state->GetString(12, aSavedRequestOut->mValue.integrity());
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
-
   bool nullBody = false;
-  rv = state->GetIsNull(12, &nullBody);
+  rv = state->GetIsNull(13, &nullBody);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
   aSavedRequestOut->mHasBodyId = !nullBody;
-
   if (aSavedRequestOut->mHasBodyId) {
-    rv = ExtractId(state, 12, &aSavedRequestOut->mBodyId);
+    rv = ExtractId(state, 13, &aSavedRequestOut->mBodyId);
     if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
   }
-
   rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
     "SELECT "
       "name, "
       "value "
     "FROM request_headers "
     "WHERE entry_id=:entry_id;"
   ), getter_AddRefs(state));
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
@@ -2487,31 +2472,30 @@ struct Migration
 nsresult MigrateFrom15To16(mozIStorageConnection* aConn, bool& aRewriteSchema);
 nsresult MigrateFrom16To17(mozIStorageConnection* aConn, bool& aRewriteSchema);
 nsresult MigrateFrom17To18(mozIStorageConnection* aConn, bool& aRewriteSchema);
 nsresult MigrateFrom18To19(mozIStorageConnection* aConn, bool& aRewriteSchema);
 nsresult MigrateFrom19To20(mozIStorageConnection* aConn, bool& aRewriteSchema);
 nsresult MigrateFrom20To21(mozIStorageConnection* aConn, bool& aRewriteSchema);
 nsresult MigrateFrom21To22(mozIStorageConnection* aConn, bool& aRewriteSchema);
 nsresult MigrateFrom22To23(mozIStorageConnection* aConn, bool& aRewriteSchema);
-
+nsresult MigrateFrom23To24(mozIStorageConnection* aConn, bool& aRewriteSchema);
 // Configure migration functions to run for the given starting version.
 Migration sMigrationList[] = {
   Migration(15, MigrateFrom15To16),
   Migration(16, MigrateFrom16To17),
   Migration(17, MigrateFrom17To18),
   Migration(18, MigrateFrom18To19),
   Migration(19, MigrateFrom19To20),
   Migration(20, MigrateFrom20To21),
   Migration(21, MigrateFrom21To22),
   Migration(22, MigrateFrom22To23),
+  Migration(23, MigrateFrom23To24),
 };
-
 uint32_t sMigrationListLength = sizeof(sMigrationList) / sizeof(Migration);
-
 nsresult
 RewriteEntriesSchema(mozIStorageConnection* aConn)
 {
   nsresult rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
     "PRAGMA writable_schema = ON"
   ));
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
@@ -2996,21 +2980,37 @@ nsresult MigrateFrom21To22(mozIStorageCo
 
 nsresult MigrateFrom22To23(mozIStorageConnection* aConn, bool& aRewriteSchema)
 {
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(aConn);
 
   // The only change between 22 and 23 was a different snappy compression
   // format, but it's backwards-compatible.
-
   nsresult rv = aConn->SetSchemaVersion(23);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+  return rv;
+}
+nsresult MigrateFrom23To24(mozIStorageConnection* aConn, bool& aRewriteSchema)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aConn);
+
+  // Add the request_url_fragment column.
+  nsresult rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "ALTER TABLE entries "
+    "ADD COLUMN request_url_fragment TEXT NOT NULL DEFAULT ''"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = aConn->SetSchemaVersion(24);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  aRewriteSchema = true;
 
   return rv;
 }
 
 } // anonymous namespace
-
 } // namespace db
 } // namespace cache
 } // namespace dom
 } // namespace mozilla
--- a/dom/cache/TypeUtils.cpp
+++ b/dom/cache/TypeUtils.cpp
@@ -117,40 +117,35 @@ TypeUtils::ToInternalRequest(const Ownin
 
 void
 TypeUtils::ToCacheRequest(CacheRequest& aOut, InternalRequest* aIn,
                           BodyAction aBodyAction, SchemeAction aSchemeAction,
                           nsTArray<UniquePtr<AutoIPCStream>>& aStreamCleanupList,
                           ErrorResult& aRv)
 {
   MOZ_ASSERT(aIn);
-
   aIn->GetMethod(aOut.method());
-
-  nsAutoCString url;
-  aIn->GetURL(url);
-
+  nsCString url(aIn->GetURLWithoutFragment());
   bool schemeValid;
   ProcessURL(url, &schemeValid, &aOut.urlWithoutQuery(), &aOut.urlQuery(), aRv);
   if (aRv.Failed()) {
     return;
   }
-
   if (!schemeValid) {
     if (aSchemeAction == TypeErrorOnInvalidScheme) {
       NS_ConvertUTF8toUTF16 urlUTF16(url);
       aRv.ThrowTypeError<MSG_INVALID_URL_SCHEME>(NS_LITERAL_STRING("Request"),
                                                  urlUTF16);
       return;
     }
   }
+  aOut.urlFragment() = aIn->GetFragment();
 
   aIn->GetReferrer(aOut.referrer());
   aOut.referrerPolicy() = aIn->ReferrerPolicy_();
-
   RefPtr<InternalHeaders> headers = aIn->Headers();
   MOZ_ASSERT(headers);
   ToHeadersEntryList(aOut.headers(), headers);
   aOut.headersGuard() = headers->Guard();
   aOut.mode() = aIn->Mode();
   aOut.credentials() = aIn->GetCredentialsMode();
   aOut.contentPolicyType() = aIn->ContentPolicyType();
   aOut.requestCache() = aIn->GetCacheMode();
@@ -301,27 +296,24 @@ TypeUtils::ToResponse(const CacheRespons
     default:
       MOZ_CRASH("Unexpected ResponseType!");
   }
   MOZ_ASSERT(ir);
 
   RefPtr<Response> ref = new Response(GetGlobalObject(), ir);
   return ref.forget();
 }
-
 already_AddRefed<InternalRequest>
 TypeUtils::ToInternalRequest(const CacheRequest& aIn)
 {
   nsAutoCString url(aIn.urlWithoutQuery());
   url.Append(aIn.urlQuery());
-
-  RefPtr<InternalRequest> internalRequest = new InternalRequest(url);
-
+  RefPtr<InternalRequest> internalRequest =
+    new InternalRequest(url, aIn.urlFragment());
   internalRequest->SetMethod(aIn.method());
-
   internalRequest->SetReferrer(aIn.referrer());
   internalRequest->SetReferrerPolicy(aIn.referrerPolicy());
   internalRequest->SetMode(aIn.mode());
   internalRequest->SetCredentialsMode(aIn.credentials());
   internalRequest->SetContentPolicyType(aIn.contentPolicyType());
   internalRequest->SetCacheMode(aIn.requestCache());
   internalRequest->SetRedirectMode(aIn.requestRedirect());
   internalRequest->SetIntegrity(aIn.integrity());
--- a/dom/cache/test/mochitest/test_cache_keys.js
+++ b/dom/cache/test/mochitest/test_cache_keys.js
@@ -10,16 +10,21 @@ caches.open(name).then(function(cache) {
   c = cache;
   return c.addAll(tests);
 }).then(function() {
   // Add another cache entry using Cache.add
   var another = "//mochi.test:8888/?yetanother" + context;
   tests.push(another);
   return c.add(another);
 }).then(function() {
+  // Add another cache entry with URL fragment using Cache.add
+  var anotherWithFragment = "//mochi.test:8888/?fragment" + context + "#fragment";
+  tests.push(anotherWithFragment);
+  return c.add(anotherWithFragment);
+}).then(function() {
   return c.keys();
 }).then(function(keys) {
   is(keys.length, tests.length, "Same number of elements");
   // Verify both the insertion order of the requests and their validity.
   keys.forEach(function(r, i) {
     ok(r instanceof Request, "Valid request object");
     ok(r.url.indexOf(tests[i]) >= 0, "Valid URL");
   });
--- a/dom/cache/test/mochitest/test_cache_match_request.js
+++ b/dom/cache/test/mochitest/test_cache_match_request.js
@@ -20,31 +20,29 @@ function checkResponse(r, expectedBody) 
      "Both responses should have the same status text");
   return r.text().then(function(text) {
     // Avoid dumping out the large response text to the log if they're equal.
     if (text !== expectedBody) {
       is(text, responseText, "The response body should be correct");
     }
   });
 }
-
 fetch(new Request(request)).then(function(r) {
   response = r;
   return response.text();
 }).then(function(text) {
   responseText = text;
   return testRequest(request, unknownRequest, requestWithAltQS,
                      request.url.replace("#fragment", "#other"));
 }).then(function() {
   return testRequest(request.url, unknownRequest.url, requestWithAltQS.url,
                      request.url.replace("#fragment", "#other"));
 }).then(function() {
   testDone();
 });
-
 // The request argument can either be a URL string, or a Request object.
 function testRequest(request, unknownRequest, requestWithAlternateQueryString,
                      requestWithDifferentFragment) {
   return caches.open(name).then(function(cache) {
     c = cache;
     return c.add(request);
   }).then(function() {
     return Promise.all(
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -867,16 +867,17 @@ EventListenerManager::SetEventHandler(ns
       scriptSample.Assign(attr);
       scriptSample.AppendLiteral(" attribute on ");
       scriptSample.Append(tagName);
       scriptSample.AppendLiteral(" element");
 
       bool allowsInlineScript = true;
       rv = csp->GetAllowsInline(nsIContentPolicy::TYPE_SCRIPT,
                                 EmptyString(), // aNonce
+                                false, // aParserCreated
                                 scriptSample,
                                 0,             // aLineNumber
                                 &allowsInlineScript);
       NS_ENSURE_SUCCESS(rv, rv);
 
       // return early if CSP wants us to block inline scripts
       if (!allowsInlineScript) {
         return NS_OK;
--- a/dom/fetch/FetchDriver.cpp
+++ b/dom/fetch/FetchDriver.cpp
@@ -370,29 +370,25 @@ FetchDriver::HttpFetch()
   }
 
   rv = chan->AsyncOpen2(this);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Step 4 onwards of "HTTP Fetch" is handled internally by Necko.
   return NS_OK;
 }
-
 already_AddRefed<InternalResponse>
 FetchDriver::BeginAndGetFilteredResponse(InternalResponse* aResponse,
                                          bool aFoundOpaqueRedirect)
 {
   MOZ_ASSERT(aResponse);
-
   AutoTArray<nsCString, 4> reqURLList;
-  mRequest->GetURLList(reqURLList);
-
+  mRequest->GetURLListWithoutFragment(reqURLList);
   MOZ_ASSERT(!reqURLList.IsEmpty());
   aResponse->SetURLList(reqURLList);
-
   RefPtr<InternalResponse> filteredResponse;
   if (aFoundOpaqueRedirect) {
     filteredResponse = aResponse->OpaqueRedirectResponse();
   } else {
     switch (mRequest->GetResponseTainting()) {
       case LoadTainting::Basic:
         filteredResponse = aResponse->BasicResponse();
         break;
@@ -803,25 +799,28 @@ FetchDriver::AsyncOnChannelRedirect(nsIC
   nsCOMPtr<nsIURI> uri;
   MOZ_ALWAYS_SUCCEEDS(aNewChannel->GetURI(getter_AddRefs(uri)));
 
   nsCOMPtr<nsIURI> uriClone;
   nsresult rv = uri->CloneIgnoringRef(getter_AddRefs(uriClone));
   if(NS_WARN_IF(NS_FAILED(rv))){
     return rv;
   }
-
   nsCString spec;
   rv = uriClone->GetSpec(spec);
   if(NS_WARN_IF(NS_FAILED(rv))){
     return rv;
   }
+  nsCString fragment;
+  rv = uri->GetRef(fragment);
+  if(NS_WARN_IF(NS_FAILED(rv))){
+    return rv;
+  }
 
-  mRequest->AddURL(spec);
-
+  mRequest->AddURL(spec, fragment);
   NS_ConvertUTF8toUTF16 tRPHeaderValue(tRPHeaderCValue);
   // updates request’s associated referrer policy according to the
   // Referrer-Policy header (if any).
   if (!tRPHeaderValue.IsEmpty()) {
     net::ReferrerPolicy net_referrerPolicy =
       nsContentUtils::GetReferrerPolicyFromHeader(tRPHeaderValue);
     if (net_referrerPolicy != net::RP_Unset) {
       ReferrerPolicy referrerPolicy = mRequest->ReferrerPolicy_();
--- a/dom/fetch/InternalRequest.cpp
+++ b/dom/fetch/InternalRequest.cpp
@@ -14,28 +14,26 @@
 #include "mozilla/dom/FetchTypes.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/workers/Workers.h"
 
 #include "WorkerPrivate.h"
 
 namespace mozilla {
 namespace dom {
-
 // The global is used to extract the principal.
 already_AddRefed<InternalRequest>
 InternalRequest::GetRequestConstructorCopy(nsIGlobalObject* aGlobal, ErrorResult& aRv) const
 {
   MOZ_RELEASE_ASSERT(!mURLList.IsEmpty(), "Internal Request's urlList should not be empty when copied from constructor.");
-
-  RefPtr<InternalRequest> copy = new InternalRequest(mURLList.LastElement());
+  RefPtr<InternalRequest> copy = new InternalRequest(mURLList.LastElement(),
+                                                     mFragment);
   copy->SetMethod(mMethod);
   copy->mHeaders = new InternalHeaders(*mHeaders);
   copy->SetUnsafeRequest();
-
   copy->mBodyStream = mBodyStream;
   copy->mForceOriginHeader = true;
   // The "client" is not stored in our implementation. Fetch API users should
   // use the appropriate window/document/principal and other Gecko security
   // mechanisms as appropriate.
   copy->mSameOriginDataURL = true;
   copy->mPreserveContentCodings = true;
   copy->mReferrer = mReferrer;
@@ -70,21 +68,20 @@ InternalRequest::Clone()
   nsresult rv = NS_CloneInputStream(mBodyStream, getter_AddRefs(clonedBody),
                                     getter_AddRefs(replacementBody));
   if (NS_WARN_IF(NS_FAILED(rv))) { return nullptr; }
 
   clone->mBodyStream.swap(clonedBody);
   if (replacementBody) {
     mBodyStream.swap(replacementBody);
   }
-
   return clone.forget();
 }
-
-InternalRequest::InternalRequest(const nsACString& aURL)
+InternalRequest::InternalRequest(const nsACString& aURL,
+                                 const nsACString& aFragment)
   : mMethod("GET")
   , mHeaders(new InternalHeaders(HeadersGuardEnum::None))
   , mContentPolicyType(nsIContentPolicy::TYPE_FETCH)
   , mReferrer(NS_LITERAL_STRING(kFETCH_CLIENT_REFERRER_STR))
   , mReferrerPolicy(ReferrerPolicy::_empty)
   , mEnvironmentReferrerPolicy(net::RP_Default)
   , mMode(RequestMode::No_cors)
   , mCredentialsMode(RequestCredentials::Omit)
@@ -100,20 +97,20 @@ InternalRequest::InternalRequest(const n
     // specification does not handle this yet.
   , mSameOriginDataURL(true)
   , mSkipServiceWorker(false)
   , mSynchronous(false)
   , mUnsafeRequest(false)
   , mUseURLCredentials(false)
 {
   MOZ_ASSERT(!aURL.IsEmpty());
-  AddURL(aURL);
+  AddURL(aURL, aFragment);
 }
-
 InternalRequest::InternalRequest(const nsACString& aURL,
+                                 const nsACString& aFragment,
                                  const nsACString& aMethod,
                                  already_AddRefed<InternalHeaders> aHeaders,
                                  RequestCache aCacheMode,
                                  RequestMode aMode,
                                  RequestRedirect aRequestRedirect,
                                  RequestCredentials aRequestCredentials,
                                  const nsAString& aReferrer,
                                  ReferrerPolicy aReferrerPolicy,
@@ -137,33 +134,33 @@ InternalRequest::InternalRequest(const n
     // FIXME See the above comment in the default constructor.
   , mSameOriginDataURL(true)
   , mSkipServiceWorker(false)
   , mSynchronous(false)
   , mUnsafeRequest(false)
   , mUseURLCredentials(false)
 {
   MOZ_ASSERT(!aURL.IsEmpty());
-  AddURL(aURL);
+  AddURL(aURL, aFragment);
 }
-
 InternalRequest::InternalRequest(const InternalRequest& aOther)
   : mMethod(aOther.mMethod)
   , mURLList(aOther.mURLList)
   , mHeaders(new InternalHeaders(*aOther.mHeaders))
   , mContentPolicyType(aOther.mContentPolicyType)
   , mReferrer(aOther.mReferrer)
   , mReferrerPolicy(aOther.mReferrerPolicy)
   , mEnvironmentReferrerPolicy(aOther.mEnvironmentReferrerPolicy)
   , mMode(aOther.mMode)
   , mCredentialsMode(aOther.mCredentialsMode)
   , mResponseTainting(aOther.mResponseTainting)
   , mCacheMode(aOther.mCacheMode)
   , mRedirectMode(aOther.mRedirectMode)
   , mIntegrity(aOther.mIntegrity)
+  , mFragment(aOther.mFragment)
   , mAuthenticationFlag(aOther.mAuthenticationFlag)
   , mForceOriginHeader(aOther.mForceOriginHeader)
   , mPreserveContentCodings(aOther.mPreserveContentCodings)
   , mSameOriginDataURL(aOther.mSameOriginDataURL)
   , mSkipServiceWorker(aOther.mSkipServiceWorker)
   , mSynchronous(aOther.mSynchronous)
   , mUnsafeRequest(aOther.mUnsafeRequest)
   , mUseURLCredentials(aOther.mUseURLCredentials)
--- a/dom/fetch/InternalRequest.h
+++ b/dom/fetch/InternalRequest.h
@@ -82,27 +82,24 @@ namespace dom {
  * TODO: Add a content type for favicon
  * TODO: Add a content type for download
  */
 
 class Request;
 class IPCInternalRequest;
 
 #define kFETCH_CLIENT_REFERRER_STR "about:client"
-
 class InternalRequest final
 {
   friend class Request;
-
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(InternalRequest)
-
-  explicit InternalRequest(const nsACString& aURL);
-
+  InternalRequest(const nsACString& aURL, const nsACString& aFragment);
   InternalRequest(const nsACString& aURL,
+                  const nsACString& aFragment,
                   const nsACString& aMethod,
                   already_AddRefed<InternalHeaders> aHeaders,
                   RequestCache aCacheMode,
                   RequestMode aMode,
                   RequestRedirect aRequestRedirect,
                   RequestCredentials aRequestCredentials,
                   const nsAString& aReferrer,
                   ReferrerPolicy aReferrerPolicy,
@@ -129,47 +126,59 @@ public:
 
   bool
   HasSimpleMethod() const
   {
     return mMethod.LowerCaseEqualsASCII("get") ||
            mMethod.LowerCaseEqualsASCII("post") ||
            mMethod.LowerCaseEqualsASCII("head");
   }
-
-  // GetURL should get the request's current url. A request has an associated
-  // current url. It is a pointer to the last fetch URL in request's url list.
+  // GetURL should get the request's current url with fragment. A request has
+  // an associated current url. It is a pointer to the last fetch URL in
+  // request's url list.
   void
   GetURL(nsACString& aURL) const
   {
-    MOZ_RELEASE_ASSERT(!mURLList.IsEmpty(), "Internal Request's urlList should not be empty.");
-
-    aURL.Assign(mURLList.LastElement());
+    aURL.Assign(GetURLWithoutFragment());
+    if (GetFragment().IsEmpty()) {
+      return;
+    }
+    aURL.Append(NS_LITERAL_CSTRING("#"));
+    aURL.Append(GetFragment());
   }
 
+  const nsCString&
+  GetURLWithoutFragment() const
+  {
+    MOZ_RELEASE_ASSERT(!mURLList.IsEmpty(),
+                       "Internal Request's urlList should not be empty.");
+
+    return mURLList.LastElement();
+  }
   // AddURL should append the url into url list.
-  // Normally we strip the fragment from the URL in Request::Constructor.
-  // If internal code is directly constructing this object they must
-  // strip the fragment first.  Since these should be well formed URLs we
-  // can use a simple check for a fragment here.  The full parser is
-  // difficult to use off the main thread.
+  // Normally we strip the fragment from the URL in Request::Constructor and
+  // pass the fragment as the second argument into it.
+  // If a fragment is present in the URL it must be stripped and passed in
+  // separately.
   void
-  AddURL(const nsACString& aURL)
+  AddURL(const nsACString& aURL, const nsACString& aFragment)
   {
     MOZ_ASSERT(!aURL.IsEmpty());
+    MOZ_ASSERT(!aURL.Contains('#'));
+
     mURLList.AppendElement(aURL);
-    MOZ_ASSERT(mURLList.LastElement().Find(NS_LITERAL_CSTRING("#")) == kNotFound);
+
+    mFragment.Assign(aFragment);
   }
-
+  // Get the URL list without their fragments.
   void
-  GetURLList(nsTArray<nsCString>& aURLList)
+  GetURLListWithoutFragment(nsTArray<nsCString>& aURLList)
   {
     aURLList.Assign(mURLList);
   }
-
   void
   GetReferrer(nsAString& aReferrer) const
   {
     aReferrer.Assign(mReferrer);
   }
 
   void
   SetReferrer(const nsAString& aReferrer)
@@ -316,30 +325,33 @@ public:
     mRedirectMode = aRedirectMode;
   }
 
   const nsString&
   GetIntegrity() const
   {
     return mIntegrity;
   }
-
   void
   SetIntegrity(const nsAString& aIntegrity)
   {
     MOZ_ASSERT(mIntegrity.IsEmpty());
     mIntegrity.Assign(aIntegrity);
   }
+  const nsCString&
+  GetFragment() const
+  {
+    return mFragment;
+  }
 
   nsContentPolicyType
   ContentPolicyType() const
   {
     return mContentPolicyType;
   }
-
   void
   SetContentPolicyType(nsContentPolicyType aContentPolicyType);
 
   void
   OverrideContentPolicyType(nsContentPolicyType aContentPolicyType);
 
   RequestContext
   Context() const
@@ -486,25 +498,23 @@ private:
   ReferrerPolicy mReferrerPolicy;
 
   // This will be used for request created from Window or Worker contexts
   // In case there's no Referrer Policy in Request, this will be passed to
   // channel.
   // The Environment Referrer Policy should be net::ReferrerPolicy so that it
   // could be associated with nsIHttpChannel.
   net::ReferrerPolicy mEnvironmentReferrerPolicy;
-
   RequestMode mMode;
   RequestCredentials mCredentialsMode;
   MOZ_INIT_OUTSIDE_CTOR LoadTainting mResponseTainting;
   RequestCache mCacheMode;
   RequestRedirect mRedirectMode;
-
   nsString mIntegrity;
-
+  nsCString mFragment;
   MOZ_INIT_OUTSIDE_CTOR bool mAuthenticationFlag;
   MOZ_INIT_OUTSIDE_CTOR bool mForceOriginHeader;
   MOZ_INIT_OUTSIDE_CTOR bool mPreserveContentCodings;
   MOZ_INIT_OUTSIDE_CTOR bool mSameOriginDataURL;
   MOZ_INIT_OUTSIDE_CTOR bool mSkipServiceWorker;
   MOZ_INIT_OUTSIDE_CTOR bool mSynchronous;
   MOZ_INIT_OUTSIDE_CTOR bool mUnsafeRequest;
   MOZ_INIT_OUTSIDE_CTOR bool mUseURLCredentials;
--- a/dom/fetch/InternalResponse.h
+++ b/dom/fetch/InternalResponse.h
@@ -80,47 +80,40 @@ public:
     return mType;
   }
 
   bool
   IsError() const
   {
     return Type() == ResponseType::Error;
   }
-
   // GetUrl should return last fetch URL in response's url list and null if
   // response's url list is the empty list.
-  void
-  GetURL(nsCString& aURL) const
+  const nsCString&
+  GetURL() const
   {
     // Empty urlList when response is a synthetic response.
     if (mURLList.IsEmpty()) {
-      aURL.Truncate();
-      return;
+      return EmptyCString();
     }
-
-    aURL.Assign(mURLList.LastElement());
+    return mURLList.LastElement();
   }
-
   void
   GetURLList(nsTArray<nsCString>& aURLList) const
   {
     aURLList.Assign(mURLList);
   }
-
-  void
-  GetUnfilteredURL(nsCString& aURL) const
+  const nsCString&
+  GetUnfilteredURL() const
   {
     if (mWrappedResponse) {
-      return mWrappedResponse->GetURL(aURL);
+      return mWrappedResponse->GetURL();
     }
-
-    return GetURL(aURL);
+    return GetURL();
   }
-
   void
   GetUnfilteredURLList(nsTArray<nsCString>& aURLList) const
   {
     if (mWrappedResponse) {
       return mWrappedResponse->GetURLList(aURLList);
     }
 
     return GetURLList(aURLList);
--- a/dom/fetch/Request.cpp
+++ b/dom/fetch/Request.cpp
@@ -84,147 +84,158 @@ ParseURLFromDocument(nsIDocument* aDocum
   nsCOMPtr<nsIURI> baseURI = aDocument->GetBaseURI();
   nsCOMPtr<nsIURI> resolvedURI;
   aRv = NS_NewURI(getter_AddRefs(resolvedURI), aInput, nullptr, baseURI);
   if (NS_WARN_IF(aRv.Failed())) {
     aRv.ThrowTypeError<MSG_INVALID_URL>(aInput);
   }
   return resolvedURI.forget();
 }
-
 void
 GetRequestURLFromDocument(nsIDocument* aDocument, const nsAString& aInput,
-                          nsAString& aRequestURL, ErrorResult& aRv)
+                          nsAString& aRequestURL, nsACString& aURLfragment,
+                          ErrorResult& aRv)
 {
   nsCOMPtr<nsIURI> resolvedURI = ParseURLFromDocument(aDocument, aInput, aRv);
   if (aRv.Failed()) {
     return;
   }
-
   // This fails with URIs with weird protocols, even when they are valid,
   // so we ignore the failure
   nsAutoCString credentials;
   Unused << resolvedURI->GetUserPass(credentials);
   if (!credentials.IsEmpty()) {
     aRv.ThrowTypeError<MSG_URL_HAS_CREDENTIALS>(aInput);
     return;
   }
 
   nsCOMPtr<nsIURI> resolvedURIClone;
   // We use CloneIgnoringRef to strip away the fragment even if the original URI
   // is immutable.
   aRv = resolvedURI->CloneIgnoringRef(getter_AddRefs(resolvedURIClone));
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
-
   nsAutoCString spec;
   aRv = resolvedURIClone->GetSpec(spec);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
+  CopyUTF8toUTF16(spec, aRequestURL);
 
-  CopyUTF8toUTF16(spec, aRequestURL);
+  // Get the fragment from nsIURI.
+  aRv = resolvedURI->GetRef(aURLfragment);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return;
+  }
 }
-
 already_AddRefed<nsIURI>
 ParseURLFromChrome(const nsAString& aInput, ErrorResult& aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
-
   nsCOMPtr<nsIURI> uri;
   aRv = NS_NewURI(getter_AddRefs(uri), aInput, nullptr, nullptr);
   if (NS_WARN_IF(aRv.Failed())) {
     aRv.ThrowTypeError<MSG_INVALID_URL>(aInput);
   }
   return uri.forget();
 }
-
 void
 GetRequestURLFromChrome(const nsAString& aInput, nsAString& aRequestURL,
-                        ErrorResult& aRv)
+                        nsACString& aURLfragment, ErrorResult& aRv)
 {
   nsCOMPtr<nsIURI> uri = ParseURLFromChrome(aInput, aRv);
   if (aRv.Failed()) {
     return;
   }
-
   // This fails with URIs with weird protocols, even when they are valid,
   // so we ignore the failure
   nsAutoCString credentials;
   Unused << uri->GetUserPass(credentials);
   if (!credentials.IsEmpty()) {
     aRv.ThrowTypeError<MSG_URL_HAS_CREDENTIALS>(aInput);
     return;
   }
 
   nsCOMPtr<nsIURI> uriClone;
   // We use CloneIgnoringRef to strip away the fragment even if the original URI
   // is immutable.
   aRv = uri->CloneIgnoringRef(getter_AddRefs(uriClone));
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
-
   nsAutoCString spec;
   aRv = uriClone->GetSpec(spec);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
+  CopyUTF8toUTF16(spec, aRequestURL);
 
-  CopyUTF8toUTF16(spec, aRequestURL);
+  // Get the fragment from nsIURI.
+  aRv = uri->GetRef(aURLfragment);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return;
+  }
 }
-
 already_AddRefed<URL>
 ParseURLFromWorker(const GlobalObject& aGlobal, const nsAString& aInput,
                    ErrorResult& aRv)
 {
   workers::WorkerPrivate* worker = workers::GetCurrentThreadWorkerPrivate();
   MOZ_ASSERT(worker);
   worker->AssertIsOnWorkerThread();
 
   NS_ConvertUTF8toUTF16 baseURL(worker->GetLocationInfo().mHref);
   RefPtr<URL> url = URL::WorkerConstructor(aGlobal, aInput, baseURL, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     aRv.ThrowTypeError<MSG_INVALID_URL>(aInput);
   }
   return url.forget();
 }
-
 void
 GetRequestURLFromWorker(const GlobalObject& aGlobal, const nsAString& aInput,
-                        nsAString& aRequestURL, ErrorResult& aRv)
+                        nsAString& aRequestURL, nsACString& aURLfragment,
+                        ErrorResult& aRv)
 {
   RefPtr<URL> url = ParseURLFromWorker(aGlobal, aInput, aRv);
   if (aRv.Failed()) {
     return;
   }
-
   nsString username;
   url->GetUsername(username, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
   nsString password;
   url->GetPassword(password, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
-
   if (!username.IsEmpty() || !password.IsEmpty()) {
     aRv.ThrowTypeError<MSG_URL_HAS_CREDENTIALS>(aInput);
     return;
   }
+  // Get the fragment from URL.
+  nsAutoString fragment;
+  url->GetHash(fragment, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return;
+  }
+
+  // Note: URL::GetHash() includes the "#" and we want the fragment with out
+  // the hash symbol.
+  if (!fragment.IsEmpty()) {
+    CopyUTF16toUTF8(Substring(fragment, 1), aURLfragment);
+  }
 
   url->SetHash(EmptyString(), aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
-
   url->Stringify(aRequestURL, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 }
 
 class ReferrerSameOriginChecker final : public workers::WorkerMainThreadRunnable
 {
@@ -279,48 +290,43 @@ Request::Constructor(const GlobalObject&
       aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
       return nullptr;
     }
     if (body) {
       temporaryBody = body;
     }
 
     request = inputReq->GetInternalRequest();
-
   } else {
     // aInput is USVString.
     // We need to get url before we create a InternalRequest.
     nsAutoString input;
     input.Assign(aInput.GetAsUSVString());
-
     nsAutoString requestURL;
+    nsCString fragment;
     if (NS_IsMainThread()) {
       nsIDocument* doc = GetEntryDocument();
       if (doc) {
-        GetRequestURLFromDocument(doc, input, requestURL, aRv);
+        GetRequestURLFromDocument(doc, input, requestURL, fragment, aRv);
       } else {
         // If we don't have a document, we must assume that this is a full URL.
-        GetRequestURLFromChrome(input, requestURL, aRv);
+        GetRequestURLFromChrome(input, requestURL, fragment, aRv);
       }
     } else {
-      GetRequestURLFromWorker(aGlobal, input, requestURL, aRv);
+      GetRequestURLFromWorker(aGlobal, input, requestURL, fragment, aRv);
     }
-
     if (NS_WARN_IF(aRv.Failed())) {
       return nullptr;
     }
-
-    request = new InternalRequest(NS_ConvertUTF16toUTF8(requestURL));
+    request = new InternalRequest(NS_ConvertUTF16toUTF8(requestURL), fragment);
   }
-
   request = request->GetRequestConstructorCopy(global, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
-
   RequestMode fallbackMode = RequestMode::EndGuard_;
   RequestCredentials fallbackCredentials = RequestCredentials::EndGuard_;
   RequestCache fallbackCache = RequestCache::EndGuard_;
   if (aInput.IsUSVString()) {
     fallbackMode = RequestMode::Cors;
     fallbackCredentials = RequestCredentials::Omit;
     fallbackCache = RequestCache::Default;
   }
--- a/dom/fetch/Response.h
+++ b/dom/fetch/Response.h
@@ -43,31 +43,26 @@ public:
     return ResponseBinding::Wrap(aCx, this, aGivenProto);
   }
 
   ResponseType
   Type() const
   {
     return mInternalResponse->Type();
   }
-
   void
   GetUrl(nsAString& aUrl) const
   {
-    nsCString url;
-    mInternalResponse->GetURL(url);
-    CopyUTF8toUTF16(url, aUrl);
+    CopyUTF8toUTF16(mInternalResponse->GetURL(), aUrl);
   }
-
   bool
   Redirected() const
   {
     return mInternalResponse->IsRedirected();
   }
-
   uint16_t
   Status() const
   {
     return mInternalResponse->GetStatus();
   }
 
   bool
   Ok() const
--- a/dom/flyweb/HttpServer.cpp
+++ b/dom/flyweb/HttpServer.cpp
@@ -580,26 +580,23 @@ HttpServer::Connection::ConsumeLine(cons
     MOZ_ASSERT(!mPendingReq);
 
     // Process request line
     nsCWhitespaceTokenizer tokens(Substring(aBuffer, aLength));
 
     NS_ENSURE_TRUE(tokens.hasMoreTokens(), NS_ERROR_UNEXPECTED);
     nsDependentCSubstring method = tokens.nextToken();
     NS_ENSURE_TRUE(NS_IsValidHTTPToken(method), NS_ERROR_UNEXPECTED);
-
     NS_ENSURE_TRUE(tokens.hasMoreTokens(), NS_ERROR_UNEXPECTED);
     nsDependentCSubstring url = tokens.nextToken();
     // Seems like it's also allowed to pass full urls with scheme+host+port.
     // May need to support that.
     NS_ENSURE_TRUE(url.First() == '/', NS_ERROR_UNEXPECTED);
-
-    mPendingReq = new InternalRequest(url);
+    mPendingReq = new InternalRequest(url, /* aURLFragment */ EmptyCString());
     mPendingReq->SetMethod(method);
-
     NS_ENSURE_TRUE(tokens.hasMoreTokens(), NS_ERROR_UNEXPECTED);
     nsDependentCSubstring version = tokens.nextToken();
     NS_ENSURE_TRUE(StringBeginsWith(version, NS_LITERAL_CSTRING("HTTP/1.")),
                    NS_ERROR_UNEXPECTED);
     nsresult rv;
     // This integer parsing is likely not strict enough.
     nsCString reqVersion;
     reqVersion = Substring(version, MOZ_ARRAY_LENGTH("HTTP/1.") - 1);
--- a/dom/interfaces/security/nsIContentSecurityPolicy.idl
+++ b/dom/interfaces/security/nsIContentSecurityPolicy.idl
@@ -126,26 +126,28 @@ interface nsIContentSecurityPolicy : nsI
   void appendPolicy(in AString policyString,
                     in boolean reportOnly,
                     in boolean deliveredViaMetaTag);
 
   /*
    * Whether this policy allows inline script or style.
    * @param aContentPolicyType Either TYPE_SCRIPT or TYPE_STYLESHEET
    * @param aNonce The nonce string to check against the policy
+   * @param aParserCreated If the script element was created by the HTML Parser
    * @param aContent  The content of the inline resource to hash
    *        (and compare to the hashes listed in the policy)
    * @param aLineNumber The line number of the inline resource
    *        (used for reporting)
    * @return
    *     Whether or not the effects of the inline style should be allowed
    *     (block the rules if false).
    */
   boolean getAllowsInline(in nsContentPolicyType aContentPolicyType,
                           in AString aNonce,
+                          in boolean aParserCreated,
                           in AString aContent,
                           in unsigned long aLineNumber);
 
   /**
    * whether this policy allows eval and eval-like functions
    * such as setTimeout("code string", time).
    * @param shouldReportViolations
    *     Whether or not the use of eval should be reported.
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -20,16 +20,17 @@
 #include "mozilla/LookAndFeel.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ProcessHangMonitorIPC.h"
 #include "mozilla/Unused.h"
 #include "mozilla/devtools/HeapSnapshotTempFileHelperChild.h"
 #include "mozilla/docshell/OfflineCacheUpdateChild.h"
 #include "mozilla/dom/ContentBridgeChild.h"
 #include "mozilla/dom/ContentBridgeParent.h"
+#include "mozilla/dom/VideoDecoderManagerChild.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/DataTransfer.h"
 #include "mozilla/dom/DOMStorageIPC.h"
 #include "mozilla/dom/ExternalHelperAppChild.h"
 #include "mozilla/dom/FlyWebPublishedServerIPC.h"
 #include "mozilla/dom/GetFilesHelper.h"
 #include "mozilla/dom/PCrashReporterChild.h"
 #include "mozilla/dom/ProcessGlobal.h"
@@ -1176,34 +1177,37 @@ ContentChild::RecvGMPsChanged(nsTArray<G
 {
   GeckoMediaPluginServiceChild::UpdateGMPCapabilities(Move(capabilities));
   return true;
 }
 
 bool
 ContentChild::RecvInitRendering(Endpoint<PCompositorBridgeChild>&& aCompositor,
                                 Endpoint<PImageBridgeChild>&& aImageBridge,
-                                Endpoint<PVRManagerChild>&& aVRBridge)
+                                Endpoint<PVRManagerChild>&& aVRBridge,
+                                Endpoint<PVideoDecoderManagerChild>&& aVideoManager)
 {
   if (!CompositorBridgeChild::InitForContent(Move(aCompositor))) {
     return false;
   }
   if (!ImageBridgeChild::InitForContent(Move(aImageBridge))) {
     return false;
   }
   if (!gfx::VRManagerChild::InitForContent(Move(aVRBridge))) {
     return false;
   }
+  VideoDecoderManagerChild::InitForContent(Move(aVideoManager));
   return true;
 }
 
 bool
 ContentChild::RecvReinitRendering(Endpoint<PCompositorBridgeChild>&& aCompositor,
                                   Endpoint<PImageBridgeChild>&& aImageBridge,
-                                  Endpoint<PVRManagerChild>&& aVRBridge)
+                                  Endpoint<PVRManagerChild>&& aVRBridge,
+                                  Endpoint<PVideoDecoderManagerChild>&& aVideoManager)
 {
   nsTArray<RefPtr<TabChild>> tabs = TabChild::GetAll();
 
   // Zap all the old layer managers we have lying around.
   for (const auto& tabChild : tabs) {
     if (tabChild->LayersId()) {
       tabChild->InvalidateLayers();
     }
@@ -1221,16 +1225,18 @@ ContentChild::RecvReinitRendering(Endpoi
   }
 
   // Establish new PLayerTransactions.
   for (const auto& tabChild : tabs) {
     if (tabChild->LayersId()) {
       tabChild->ReinitRendering();
     }
   }
+
+  VideoDecoderManagerChild::InitForContent(Move(aVideoManager));
   return true;
 }
 
 PBackgroundChild*
 ContentChild::AllocPBackgroundChild(Transport* aTransport,
                                     ProcessId aOtherProcess)
 {
   return BackgroundChild::Alloc(aTransport, aOtherProcess);
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -164,23 +164,25 @@ public:
 
   bool
   RecvGMPsChanged(nsTArray<GMPCapabilityData>&& capabilities) override;
 
   bool
   RecvInitRendering(
     Endpoint<PCompositorBridgeChild>&& aCompositor,
     Endpoint<PImageBridgeChild>&& aImageBridge,
-    Endpoint<PVRManagerChild>&& aVRBridge) override;
+    Endpoint<PVRManagerChild>&& aVRBridge,
+    Endpoint<PVideoDecoderManagerChild>&& aVideoManager) override;
 
   bool
   RecvReinitRendering(
     Endpoint<PCompositorBridgeChild>&& aCompositor,
     Endpoint<PImageBridgeChild>&& aImageBridge,
-    Endpoint<PVRManagerChild>&& aVRBridge) override;
+    Endpoint<PVRManagerChild>&& aVRBridge,
+    Endpoint<PVideoDecoderManagerChild>&& aVideoManager) override;
 
   PProcessHangMonitorChild*
   AllocPProcessHangMonitorChild(Transport* aTransport,
                                 ProcessId aOtherProcess) override;
 
   virtual bool RecvSetProcessSandbox(const MaybeFileDesc& aBroker) override;
 
   PBackgroundChild*
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -1022,23 +1022,16 @@ ContentParent::RecvFindPlugins(const uin
                                nsresult* aRv,
                                nsTArray<PluginTag>* aPlugins,
                                uint32_t* aNewPluginEpoch)
 {
   *aRv = mozilla::plugins::FindPluginsForContent(aPluginEpoch, aPlugins, aNewPluginEpoch);
   return true;
 }
 
-bool
-ContentParent::RecvInitVideoDecoderManager(Endpoint<PVideoDecoderManagerChild>* aEndpoint)
-{
-  GPUProcessManager::Get()->CreateContentVideoDecoderManager(OtherPid(), aEndpoint);
-  return true;
-}
-
 /*static*/ TabParent*
 ContentParent::CreateBrowserOrApp(const TabContext& aContext,
                                   Element* aFrameElement,
                                   ContentParent* aOpenerContentParent,
                                   bool aFreshProcess)
 {
   PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER);
 
@@ -2222,28 +2215,31 @@ ContentParent::InitInternal(ProcessPrior
     // on demand.)
     bool useOffMainThreadCompositing = !!CompositorThreadHolder::Loop();
     if (useOffMainThreadCompositing) {
       GPUProcessManager* gpm = GPUProcessManager::Get();
 
       Endpoint<PCompositorBridgeChild> compositor;
       Endpoint<PImageBridgeChild> imageBridge;
       Endpoint<PVRManagerChild> vrBridge;
+      Endpoint<PVideoDecoderManagerChild> videoManager;
 
       DebugOnly<bool> opened = gpm->CreateContentBridges(
         OtherPid(),
         &compositor,
         &imageBridge,
-        &vrBridge);
+        &vrBridge,
+        &videoManager);
       MOZ_ASSERT(opened);
 
       Unused << SendInitRendering(
         Move(compositor),
         Move(imageBridge),
-        Move(vrBridge));
+        Move(vrBridge),
+        Move(videoManager));
 
       gpm->AddListener(this);
     }
   }
 
   if (gAppData) {
     // Sending all information to content process.
     Unused << SendAppInit();
@@ -2378,28 +2374,31 @@ ContentParent::RecvGetGfxVars(Infallible
 void
 ContentParent::OnCompositorUnexpectedShutdown()
 {
   GPUProcessManager* gpm = GPUProcessManager::Get();
 
   Endpoint<PCompositorBridgeChild> compositor;
   Endpoint<PImageBridgeChild> imageBridge;
   Endpoint<PVRManagerChild> vrBridge;
+  Endpoint<PVideoDecoderManagerChild> videoManager;
 
   DebugOnly<bool> opened = gpm->CreateContentBridges(
     OtherPid(),
     &compositor,
     &imageBridge,
-    &vrBridge);
+    &vrBridge,
+    &videoManager);
   MOZ_ASSERT(opened);
 
   Unused << SendReinitRendering(
     Move(compositor),
     Move(imageBridge),
-    Move(vrBridge));
+    Move(vrBridge),
+    Move(videoManager));
 }
 
 void
 ContentParent::OnVarChanged(const GfxVarUpdate& aVar)
 {
   if (!mIPCOpen) {
     return;
   }
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -257,18 +257,16 @@ public:
   virtual bool RecvGetBlocklistState(const uint32_t& aPluginId,
                                      uint32_t* aIsBlocklisted) override;
 
   virtual bool RecvFindPlugins(const uint32_t& aPluginEpoch,
                                nsresult* aRv,
                                nsTArray<PluginTag>* aPlugins,
                                uint32_t* aNewPluginEpoch) override;
 
-  virtual bool RecvInitVideoDecoderManager(Endpoint<PVideoDecoderManagerChild>* endpoint) override;
-
   virtual bool RecvUngrabPointer(const uint32_t& aTime) override;
 
   virtual bool RecvRemovePermission(const IPC::Principal& aPrincipal,
                                     const nsCString& aPermissionType,
                                     nsresult* aRv) override;
 
   NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(ContentParent, nsIObserver)
 
--- a/dom/ipc/CrashReporterParent.cpp
+++ b/dom/ipc/CrashReporterParent.cpp
@@ -42,44 +42,16 @@ CrashReporterParent::ActorDestroy(ActorD
 
 bool
 CrashReporterParent::RecvAppendAppNotes(const nsCString& data)
 {
   mAppNotes.Append(data);
   return true;
 }
 
-mozilla::ipc::IProtocol*
-CrashReporterParent::CloneProtocol(Channel* aChannel,
-                                   mozilla::ipc::ProtocolCloneContext* aCtx)
-{
-#ifdef MOZ_CRASHREPORTER
-  ContentParent* contentParent = aCtx->GetContentParent();
-  CrashReporter::ThreadId childThreadId = contentParent->Pid();
-  GeckoProcessType childProcessType =
-    contentParent->Process()->GetProcessType();
-
-  nsAutoPtr<PCrashReporterParent> actor(
-    contentParent->AllocPCrashReporterParent(childThreadId,
-                                             childProcessType)
-  );
-  if (!actor ||
-      !contentParent->RecvPCrashReporterConstructor(actor,
-                                                    childThreadId,
-                                                    childThreadId)) {
-    return nullptr;
-  }
-
-  return actor.forget();
-#else
-  MOZ_CRASH("Not Implemented");
-  return nullptr;
-#endif
-}
-
 CrashReporterParent::CrashReporterParent()
   :
 #ifdef MOZ_CRASHREPORTER
     mNotes(4),
 #endif
     mStartTime(::time(nullptr))
   , mInitialized(false)
 {
--- a/dom/ipc/CrashReporterParent.h
+++ b/dom/ipc/CrashReporterParent.h
@@ -155,20 +155,16 @@ public:
                                        const nsCString& aData) override
   {
     AnnotateCrashReport(aKey, aData);
     return true;
   }
 
   virtual bool RecvAppendAppNotes(const nsCString& aData) override;
 
-  virtual mozilla::ipc::IProtocol*
-  CloneProtocol(Channel* aChannel,
-                mozilla::ipc::ProtocolCloneContext *aCtx) override;
-
 #ifdef MOZ_CRASHREPORTER
   void
   NotifyCrashService();
 #endif
 
 #ifdef MOZ_CRASHREPORTER
   AnnotationTable mNotes;
 #endif
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -424,25 +424,27 @@ both:
     async PWebBrowserPersistDocument(nullable PBrowser aBrowser,
                                      uint64_t aOuterWindowID);
 
 child:
     // Give the content process its endpoints to the compositor.
     async InitRendering(
       Endpoint<PCompositorBridgeChild> compositor,
       Endpoint<PImageBridgeChild> imageBridge,
-      Endpoint<PVRManagerChild> vr);
+      Endpoint<PVRManagerChild> vr,
+      Endpoint<PVideoDecoderManagerChild> video);
 
     // Re-create the rendering stack using the given endpoints. This is sent
     // after the compositor process has crashed. The new endpoints may be to a
     // newly launched GPU process, or the compositor thread of the UI process.
     async ReinitRendering(
       Endpoint<PCompositorBridgeChild> compositor,
       Endpoint<PImageBridgeChild> bridge,
-      Endpoint<PVRManagerChild> vr);
+      Endpoint<PVRManagerChild> vr,
+      Endpoint<PVideoDecoderManagerChild> video);
 
     /**
      * Enable system-level sandboxing features, if available.  Can
      * usually only be performed zero or one times.  The child may
      * abnormally exit if this fails; the details are OS-specific.
      */
     async SetProcessSandbox(MaybeFileDesc aBroker);
 
@@ -737,18 +739,16 @@ parent:
 
     async PJavaScript();
 
     async PRemoteSpellcheckEngine();
     async PDeviceStorageRequest(DeviceStorageParams params);
 
     sync PCrashReporter(NativeThreadId tid, uint32_t processType);
 
-    sync InitVideoDecoderManager() returns (Endpoint<PVideoDecoderManagerChild> endpoint);
-
     /**
      * Is this token compatible with the provided version?
      *
      * |version| The offered version to test
      * Returns |True| if the offered version is compatible
      */
     sync NSSU2FTokenIsCompatibleVersion(nsString version)
         returns (bool result);
--- a/dom/jsurl/nsJSProtocolHandler.cpp
+++ b/dom/jsurl/nsJSProtocolHandler.cpp
@@ -178,16 +178,17 @@ nsresult nsJSThunk::EvaluateScript(nsICh
     // allowed.
     nsCOMPtr<nsIContentSecurityPolicy> csp;
     rv = principal->GetCsp(getter_AddRefs(csp));
     NS_ENSURE_SUCCESS(rv, rv);
     if (csp) {
         bool allowsInlineScript = true;
         rv = csp->GetAllowsInline(nsIContentPolicy::TYPE_SCRIPT,
                                   EmptyString(), // aNonce
+                                  false,         // aParserCreated
                                   EmptyString(), // aContent
                                   0,             // aLineNumber
                                   &allowsInlineScript);
 
         //return early if inline scripts are not allowed
         if (!allowsInlineScript) {
           return NS_ERROR_DOM_RETVAL_UNDEFINED;
         }
--- a/dom/locales/en-US/chrome/security/csp.properties
+++ b/dom/locales/en-US/chrome/security/csp.properties
@@ -34,16 +34,27 @@ ignoringUnknownOption = Ignoring unknown
 ignoringDuplicateSrc = Ignoring duplicate source %1$S
 # LOCALIZATION NOTE (ignoringSrcFromMetaCSP):
 # %1$S defines the ignored src
 ignoringSrcFromMetaCSP = Ignoring source ‘%1$S’ (Not supported when delivered via meta element).
 # LOCALIZATION NOTE (ignoringSrcWithinScriptStyleSrc):
 # %1$S is the ignored src
 # script-src and style-src are directive names and should not be localized
 ignoringSrcWithinScriptStyleSrc = Ignoring “%1$S” within script-src or style-src: nonce-source or hash-source specified
+# LOCALIZATION NOTE (ignoringSrcForStrictDynamic):
+# %1$S is the ignored src
+# script-src, as well as 'strict-dynamic' should not be localized
+ignoringSrcForStrictDynamic = Ignoring “%1$S” within script-src: ‘strict-dynamic’ specified
+# LOCALIZATION NOTE (ignoringStrictDynamic):
+# %1$S is the ignored src
+ignoringStrictDynamic = Ignoring source “%1$S” (Only supported within script-src). 
+# LOCALIZATION NOTE (strictDynamicButNoHashOrNonce):
+# %1$S is the csp directive that contains 'strict-dynamic'
+# 'strict-dynamic' should not be localized
+strictDynamicButNoHashOrNonce = Keyword ‘strict-dynamic’ within “%1$S” with no valid nonce or hash might block all scripts from loading
 # LOCALIZATION NOTE (reportURInotHttpsOrHttp2):
 # %1$S is the ETLD of the report URI that is not HTTP or HTTPS
 reportURInotHttpsOrHttp2 = The report URI (%1$S) should be an HTTP or HTTPS URI.
 # LOCALIZATION NOTE (reportURInotInReportOnlyHeader):
 # %1$S is the ETLD of the page with the policy
 reportURInotInReportOnlyHeader = This site (%1$S) has a Report-Only policy without a report URI. CSP will not block and cannot report violations of this policy.
 # LOCALIZATION NOTE (failedToParseUnrecognizedSource):
 # %1$S is the CSP Source that could not be parsed
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -1507,26 +1507,30 @@ MediaFormatReader::Update(TrackType aTra
 
   if (decoder.mNeedDraining) {
     DrainDecoder(aTrack);
     return;
   }
 
   if (decoder.mError && !decoder.HasFatalError()) {
     decoder.mDecodePending = false;
-    if (++decoder.mNumOfConsecutiveError > decoder.mMaxConsecutiveError) {
+    bool needsNewDecoder = decoder.mError.ref() == NS_ERROR_DOM_MEDIA_NEED_NEW_DECODER;
+    if (!needsNewDecoder && ++decoder.mNumOfConsecutiveError > decoder.mMaxConsecutiveError) {
       NotifyError(aTrack, decoder.mError.ref());
       return;
     }
     decoder.mError.reset();
     LOG("%s decoded error count %d", TrackTypeToStr(aTrack),
                                      decoder.mNumOfConsecutiveError);
     media::TimeUnit nextKeyframe;
     if (aTrack == TrackType::kVideoTrack && !decoder.HasInternalSeekPending() &&
         NS_SUCCEEDED(decoder.mTrackDemuxer->GetNextRandomAccessPoint(&nextKeyframe))) {
+      if (needsNewDecoder) {
+        decoder.ShutdownDecoder();
+      }
       SkipVideoDemuxToNextKeyFrame(decoder.mLastSampleTime.refOr(TimeInterval()).Length());
       return;
     } else if (aTrack == TrackType::kAudioTrack) {
       decoder.Flush();
     }
   }
 
   bool needInput = NeedInput(decoder);
--- a/dom/media/MediaFormatReader.h
+++ b/dom/media/MediaFormatReader.h
@@ -323,19 +323,31 @@ private:
     }
 
     uint32_t mNumOfConsecutiveError;
     uint32_t mMaxConsecutiveError;
 
     Maybe<MediaResult> mError;
     bool HasFatalError() const
     {
-      return mError.isSome() &&
-             (mError.ref() != NS_ERROR_DOM_MEDIA_DECODE_ERR ||
-              mNumOfConsecutiveError > mMaxConsecutiveError);
+      if (!mError.isSome()) {
+        return false;
+      }
+      if (mError.ref() == NS_ERROR_DOM_MEDIA_DECODE_ERR) {
+        // Allow decode errors to be non-fatal, but give up
+        // if we have too many.
+        return mNumOfConsecutiveError > mMaxConsecutiveError;
+      } else if (mError.ref() == NS_ERROR_DOM_MEDIA_NEED_NEW_DECODER) {
+        // If the caller asked for a new decoder we shouldn't treat
+        // it as fatal.
+        return false;
+      } else {
+        // All other error types are fatal
+        return true;
+      }
     }
 
     // If set, all decoded samples prior mTimeThreshold will be dropped.
     // Used for internal seeking when a change of stream is detected or when
     // encountering data discontinuity.
     Maybe<InternalSeekTarget> mTimeThreshold;
     // Time of last sample returned.
     Maybe<media::TimeInterval> mLastSampleTime;
deleted file mode 100644
--- a/dom/media/compiledtest/moz.build
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- 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/.
-
-GeckoCppUnitTests([
-    'TestAudioBuffers',
-    'TestAudioMixer',
-    'TestAudioPacketizer',
-    'TestAudioSegment'
-])
-
-LOCAL_INCLUDES += [
-    '..',
-]
-
-USE_LIBS += [
-    'lgpllibs',
-]
rename from dom/media/compiledtest/TestAudioBuffers.cpp
rename to dom/media/gtest/TestAudioBuffers.cpp
--- a/dom/media/compiledtest/TestAudioBuffers.cpp
+++ b/dom/media/gtest/TestAudioBuffers.cpp
@@ -1,59 +1,57 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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/. */
 
 #include <stdint.h>
 #include "AudioBufferUtils.h"
-#include <mozilla/Assertions.h>
+#include "gtest/gtest.h"
 
 const uint32_t FRAMES = 256;
 const uint32_t CHANNELS = 2;
 const uint32_t SAMPLES = CHANNELS * FRAMES;
 
-int main() {
+TEST(AudioBuffers, Test)
+{
   mozilla::AudioCallbackBufferWrapper<float, CHANNELS> mBuffer;
   mozilla::SpillBuffer<float, 128, CHANNELS> b;
   float fromCallback[SAMPLES];
   float other[SAMPLES];
 
   for (uint32_t i = 0; i < SAMPLES; i++) {
     other[i] = 1.0;
     fromCallback[i] = 0.0;
   }
 
   // Set the buffer in the wrapper from the callback
   mBuffer.SetBuffer(fromCallback, FRAMES);
 
   // Fill the SpillBuffer with data.
-  MOZ_RELEASE_ASSERT(b.Fill(other, 15) == 15);
-  MOZ_RELEASE_ASSERT(b.Fill(other, 17) == 17);
+  ASSERT_TRUE(b.Fill(other, 15) == 15);
+  ASSERT_TRUE(b.Fill(other, 17) == 17);
   for (uint32_t i = 0; i < 32 * CHANNELS; i++) {
     other[i] = 0.0;
   }
 
   // Empty it in the AudioCallbackBufferWrapper
-  MOZ_RELEASE_ASSERT(b.Empty(mBuffer) == 32);
+  ASSERT_TRUE(b.Empty(mBuffer) == 32);
 
   // Check available return something reasonnable
-  MOZ_RELEASE_ASSERT(mBuffer.Available() == FRAMES - 32);
+  ASSERT_TRUE(mBuffer.Available() == FRAMES - 32);
 
   // Fill the buffer with the rest of the data
   mBuffer.WriteFrames(other + 32 * CHANNELS, FRAMES - 32);
 
   // Check the buffer is now full
-  MOZ_RELEASE_ASSERT(mBuffer.Available() == 0);
+  ASSERT_TRUE(mBuffer.Available() == 0);
 
   for (uint32_t i = 0 ; i < SAMPLES; i++) {
-    if (fromCallback[i] != 1.0) {
-      fprintf(stderr, "Difference at %d (%f != %f)\n", i, fromCallback[i], 1.0);
-      MOZ_CRASH("Samples differ");
-    }
+    ASSERT_TRUE(fromCallback[i] == 1.0) <<
+      "Difference at " << i << " (" << fromCallback[i] << " != " << 1.0 <<
+      ")\n";
   }
 
-  MOZ_RELEASE_ASSERT(b.Fill(other, FRAMES) == 128);
-  MOZ_RELEASE_ASSERT(b.Fill(other, FRAMES) == 0);
-  MOZ_RELEASE_ASSERT(b.Empty(mBuffer) == 0);
-
-  return 0;
+  ASSERT_TRUE(b.Fill(other, FRAMES) == 128);
+  ASSERT_TRUE(b.Fill(other, FRAMES) == 0);
+  ASSERT_TRUE(b.Empty(mBuffer) == 0);
 }
rename from dom/media/compiledtest/TestAudioMixer.cpp
rename to dom/media/gtest/TestAudioMixer.cpp
--- a/dom/media/compiledtest/TestAudioMixer.cpp
+++ b/dom/media/gtest/TestAudioMixer.cpp
@@ -1,18 +1,21 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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/. */
 
 #include "AudioMixer.h"
+#include "gtest/gtest.h"
 
 using mozilla::AudioDataValue;
 using mozilla::AudioSampleFormat;
 
+namespace audio_mixer {
+
 struct MixerConsumer : public mozilla::MixerCallbackReceiver
 {
 /* In this test, the different audio stream and channels are always created to
  * cancel each other. */
   void MixerCallback(AudioDataValue* aData, AudioSampleFormat aFormat, uint32_t aChannels, uint32_t aFrames, uint32_t aSampleRate)
   {
     bool silent = true;
     for (uint32_t i = 0; i < aChannels * aFrames; i++) {
@@ -20,19 +23,17 @@ struct MixerConsumer : public mozilla::M
         if (aFormat == mozilla::AUDIO_FORMAT_S16) {
           fprintf(stderr, "Sample at %d is not silent: %d\n", i, (short)aData[i]);
         } else {
           fprintf(stderr, "Sample at %d is not silent: %f\n", i, (float)aData[i]);
         }
         silent = false;
       }
     }
-    if (!silent) {
-      MOZ_CRASH();
-    }
+    ASSERT_TRUE(silent);
   }
 };
 
 /* Helper function to give us the maximum and minimum value that don't clip,
  * for a given sample format (integer or floating-point). */
 template<typename T>
 T GetLowValue();
 
@@ -62,17 +63,18 @@ short GetHighValue<short>() {
 void FillBuffer(AudioDataValue* aBuffer, uint32_t aLength, AudioDataValue aValue)
 {
   AudioDataValue* end = aBuffer + aLength;
   while (aBuffer != end) {
     *aBuffer++ = aValue;
   }
 }
 
-int main(int argc, char* argv[]) {
+TEST(AudioMixer, Test)
+{
   const uint32_t CHANNEL_LENGTH = 256;
   const uint32_t AUDIO_RATE = 44100;
   MixerConsumer consumer;
   AudioDataValue a[CHANNEL_LENGTH * 2];
   AudioDataValue b[CHANNEL_LENGTH * 2];
   FillBuffer(a, CHANNEL_LENGTH, GetLowValue<AudioDataValue>());
   FillBuffer(a + CHANNEL_LENGTH, CHANNEL_LENGTH, GetHighValue<AudioDataValue>());
   FillBuffer(b, CHANNEL_LENGTH, GetHighValue<AudioDataValue>());
@@ -153,11 +155,11 @@ int main(int argc, char* argv[]) {
     mixer.Mix(b, 2, CHANNEL_LENGTH, AUDIO_RATE);
     mixer.Mix(a, 2, CHANNEL_LENGTH, AUDIO_RATE);
     mixer.Mix(b, 2, CHANNEL_LENGTH, AUDIO_RATE);
     mixer.FinishMixing();
     mixer.Mix(a, 2, CHANNEL_LENGTH, AUDIO_RATE);
     mixer.Mix(b, 2, CHANNEL_LENGTH, AUDIO_RATE);
     mixer.FinishMixing();
   }
+}
 
-  return 0;
-}
+} // namespace audio_mixer
rename from dom/media/compiledtest/TestAudioPacketizer.cpp
rename to dom/media/gtest/TestAudioPacketizer.cpp
--- a/dom/media/compiledtest/TestAudioPacketizer.cpp
+++ b/dom/media/gtest/TestAudioPacketizer.cpp
@@ -1,17 +1,17 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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/. */
 
 #include <stdint.h>
 #include <math.h>
 #include "../AudioPacketizer.h"
-#include <mozilla/Assertions.h>
+#include "gtest/gtest.h"
 
 using namespace mozilla;
 
 template<typename T>
 class AutoBuffer
 {
 public:
   explicit AutoBuffer(size_t aLength)
@@ -35,39 +35,36 @@ int16_t Sequence(int16_t* aBuffer, uint3
     aBuffer[i] = aStart + i;
   }
   return aStart + i;
 }
 
 void IsSequence(int16_t* aBuffer, uint32_t aSize, uint32_t aStart = 0)
 {
   for (uint32_t i = 0; i < aSize; i++) {
-    if (aBuffer[i] != static_cast<int64_t>(aStart + i)) {
-      fprintf(stderr, "Buffer is not a sequence at offset %u\n", i);
-      MOZ_CRASH("Buffer is not a sequence");
-    }
+    ASSERT_TRUE(aBuffer[i] == static_cast<int64_t>(aStart + i)) <<
+      "Buffer is not a sequence at offset " << i << std::endl;
   }
   // Buffer is a sequence.
 }
 
 void Zero(int16_t* aBuffer, uint32_t aSize)
 {
   for (uint32_t i = 0; i < aSize; i++) {
-    if (aBuffer[i] != 0) {
-      fprintf(stderr, "Buffer is not null at offset %u\n", i);
-      MOZ_CRASH("Buffer is not null");
-    }
+    ASSERT_TRUE(aBuffer[i] == 0) <<
+      "Buffer is not null at offset " << i << std::endl;
   }
 }
 
 double sine(uint32_t aPhase) {
- return sin(aPhase * 2 * M_PI * 440 / 44100);
+  return sin(aPhase * 2 * M_PI * 440 / 44100);
 }
 
-int main() {
+TEST(AudioPacketizer, Test)
+{
   for (int16_t channels = 1; channels < 2; channels++) {
     // Test that the packetizer returns zero on underrun
     {
       AudioPacketizer<int16_t, int16_t> ap(441, channels);
       for (int16_t i = 0; i < 10; i++) {
         int16_t* out = ap.Output();
         Zero(out, 441);
         delete[] out;
@@ -152,22 +149,19 @@ int main() {
           }
           phase++;
         }
         ap.Input(b.Get(), 128);
         while (ap.PacketsAvailable()) {
           int16_t* packet = ap.Output();
           for (uint32_t k = 0; k < ap.PacketSize(); k++) {
             for (int32_t c = 0; c < channels; c++) {
-              MOZ_RELEASE_ASSERT(packet[k * channels + c] ==
-                                 static_cast<int16_t>(((2 << 14) * sine(outPhase))));
+              ASSERT_TRUE(packet[k * channels + c] ==
+                          static_cast<int16_t>(((2 << 14) * sine(outPhase))));
             }
             outPhase++;
           }
           delete [] packet;
         }
       }
     }
   }
-
-  printf("OK\n");
-  return 0;
 }
rename from dom/media/compiledtest/TestAudioSegment.cpp
rename to dom/media/gtest/TestAudioSegment.cpp
--- a/dom/media/compiledtest/TestAudioSegment.cpp
+++ b/dom/media/gtest/TestAudioSegment.cpp
@@ -1,26 +1,20 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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/. */
 
 #include "AudioSegment.h"
 #include <iostream>
-#include <mozilla/Assertions.h>
+#include "gtest/gtest.h"
 
 using namespace mozilla;
 
-namespace mozilla {
-uint32_t
-GetAudioChannelsSuperset(uint32_t aChannels1, uint32_t aChannels2)
-{
-  return std::max(aChannels1, aChannels2);
-}
-}
+namespace audio_segment {
 
 /* Helper function to give us the maximum and minimum value that don't clip,
  * for a given sample format (integer or floating-point). */
 template<typename T>
 T GetLowValue();
 
 template<typename T>
 T GetHighValue();
@@ -137,18 +131,18 @@ void TestInterleaveAndConvert()
   for (uint32_t channels = 1; channels < maxChannels; channels++) {
     const SrcT* const* src = GetPlanarChannelArray<SrcT>(channels, arraySize);
     DstT* dst = new DstT[channels * arraySize];
 
     InterleaveAndConvertBuffer(src, arraySize, 1.0, channels, dst);
 
     uint32_t channelIndex = 0;
     for (size_t i = 0; i < arraySize * channels; i++) {
-      MOZ_RELEASE_ASSERT(FuzzyEqual(dst[i],
-                         FloatToAudioSample<DstT>(1. / (channelIndex + 1))));
+      ASSERT_TRUE(FuzzyEqual(dst[i],
+                  FloatToAudioSample<DstT>(1. / (channelIndex + 1))));
       channelIndex++;
       channelIndex %= channels;
     }
 
     DeletePlanarChannelsArray(src, channels);
     delete [] dst;
   }
 }
@@ -161,18 +155,18 @@ void TestDeinterleaveAndConvert()
   for (uint32_t channels = 1; channels < maxChannels; channels++) {
     const SrcT* src = GetInterleavedChannelArray<SrcT>(channels, arraySize);
     DstT** dst = GetPlanarArray<DstT>(channels, arraySize);
 
     DeinterleaveAndConvertBuffer(src, arraySize, channels, dst);
 
     for (size_t channel = 0; channel < channels; channel++) {
       for (size_t i = 0; i < arraySize; i++) {
-        MOZ_RELEASE_ASSERT(FuzzyEqual(dst[channel][i],
-                           FloatToAudioSample<DstT>(1. / (channel + 1))));
+        ASSERT_TRUE(FuzzyEqual(dst[channel][i],
+                    FloatToAudioSample<DstT>(1. / (channel + 1))));
       }
     }
 
     DeleteInterleavedChannelArray(src);
     DeletePlanarArray(dst, channels);
   }
 }
 
@@ -196,21 +190,21 @@ void TestUpmixStereo()
 
   channels[0] = new T[arraySize];
 
   for (size_t i = 0; i < arraySize; i++) {
     channels[0][i] = GetHighValue<T>();
   }
   channelsptr[0] = channels[0];
 
-  AudioChannelsUpMix(&channelsptr, 2, ::SilentChannel<T>());
+  AudioChannelsUpMix(&channelsptr, 2, SilentChannel<T>());
 
   for (size_t channel = 0; channel < 2; channel++) {
     for (size_t i = 0; i < arraySize; i++) {
-      MOZ_RELEASE_ASSERT(channelsptr[channel][i] == GetHighValue<T>());
+      ASSERT_TRUE(channelsptr[channel][i] == GetHighValue<T>());
     }
   }
   delete channels[0];
 }
 
 template<typename T>
 void TestDownmixStereo()
 {
@@ -231,32 +225,33 @@ void TestDownmixStereo()
       input[channel][i] = channel == 0 ? GetLowValue<T>() : GetHighValue<T>();
     }
     inputptr[channel] = input[channel];
   }
 
   AudioChannelsDownMix(inputptr, output, 1, arraySize);
 
   for (size_t i = 0; i < arraySize; i++) {
-    MOZ_RELEASE_ASSERT(output[0][i] == GetSilentValue<T>());
-    MOZ_RELEASE_ASSERT(output[0][i] == GetSilentValue<T>());
+    ASSERT_TRUE(output[0][i] == GetSilentValue<T>());
+    ASSERT_TRUE(output[0][i] == GetSilentValue<T>());
   }
 
   delete output[0];
   delete output;
 }
 
-int main(int argc, char* argv[]) {
+TEST(AudioSegment, Test)
+{
   TestInterleaveAndConvert<float, float>();
   TestInterleaveAndConvert<float, int16_t>();
   TestInterleaveAndConvert<int16_t, float>();
   TestInterleaveAndConvert<int16_t, int16_t>();
   TestDeinterleaveAndConvert<float, float>();
   TestDeinterleaveAndConvert<float, int16_t>();
   TestDeinterleaveAndConvert<int16_t, float>();
   TestDeinterleaveAndConvert<int16_t, int16_t>();
   TestUpmixStereo<float>();
   TestUpmixStereo<int16_t>();
   TestDownmixStereo<float>();
   TestDownmixStereo<int16_t>();
+}
 
-  return 0;
-}
+} // namespace audio_segment
--- a/dom/media/gtest/TestRust.cpp
+++ b/dom/media/gtest/TestRust.cpp
@@ -1,8 +1,9 @@
 #include <stdint.h>
+#include "gtest/gtest.h"
 
 extern "C" uint8_t* test_rust();
 
 TEST(rust, CallFromCpp) {
   auto greeting = test_rust();
   EXPECT_STREQ(reinterpret_cast<char*>(greeting), "hello from rust.");
 }
--- a/dom/media/gtest/moz.build
+++ b/dom/media/gtest/moz.build
@@ -1,17 +1,21 @@
 # -*- 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/.
 
 UNIFIED_SOURCES += [
     'MockMediaResource.cpp',
+    'TestAudioBuffers.cpp',
     'TestAudioCompactor.cpp',
+    'TestAudioMixer.cpp',
+    'TestAudioPacketizer.cpp',
+    'TestAudioSegment.cpp',
     'TestGMPCrossOrigin.cpp',
     'TestGMPRemoveAndDelete.cpp',
     'TestGMPUtils.cpp',
     'TestIntervalSet.cpp',
     'TestMediaDataDecoder.cpp',
     'TestMediaEventSource.cpp',
     'TestMediaFormatReader.cpp',
     'TestMozPromise.cpp',
--- a/dom/media/ipc/RemoteVideoDecoder.cpp
+++ b/dom/media/ipc/RemoteVideoDecoder.cpp
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #include "RemoteVideoDecoder.h"
 #include "VideoDecoderChild.h"
 #include "VideoDecoderManagerChild.h"
 #include "mozilla/layers/TextureClient.h"
 #include "base/thread.h"
 #include "MediaInfo.h"
+#include "MediaPrefs.h"
 #include "ImageContainer.h"
 
 namespace mozilla {
 namespace dom {
 
 using base::Thread;
 using namespace ipc;
 using namespace layers;
@@ -142,17 +143,18 @@ PlatformDecoderModule::ConversionRequire
 RemoteDecoderModule::DecoderNeedsConversion(const TrackInfo& aConfig) const
 {
   return mWrapped->DecoderNeedsConversion(aConfig);
 }
 
 already_AddRefed<MediaDataDecoder>
 RemoteDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams)
 {
-  if (!aParams.mKnowsCompositor ||
+  if (!MediaPrefs::PDMUseGPUDecoder() ||
+      !aParams.mKnowsCompositor ||
       aParams.mKnowsCompositor->GetTextureFactoryIdentifier().mParentProcessType != GeckoProcessType_GPU) {
     return nullptr;
   }
 
   MediaDataDecoderCallback* callback = aParams.mCallback;
   MOZ_ASSERT(callback->OnReaderTaskQueue());
   RefPtr<RemoteVideoDecoder> object = new RemoteVideoDecoder(callback);
 
--- a/dom/media/ipc/VideoDecoderChild.cpp
+++ b/dom/media/ipc/VideoDecoderChild.cpp
@@ -98,34 +98,45 @@ VideoDecoderChild::RecvInitFailed(const 
   mInitPromise.Reject(aReason, __func__);
   return true;
 }
 
 void
 VideoDecoderChild::ActorDestroy(ActorDestroyReason aWhy)
 {
   if (aWhy == AbnormalShutdown) {
-    if (mInitialized) {
-      mCallback->Error(NS_ERROR_DOM_MEDIA_FATAL_ERR);
-    } else {
-      mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
-    }
+    // Defer reporting an error until we've recreated the manager so that
+    // it'll be safe for MediaFormatReader to recreate decoders
+    RefPtr<VideoDecoderChild> ref = this;
+    GetManager()->RunWhenRecreated(NS_NewRunnableFunction([=]() {
+      if (ref->mInitialized) {
+        ref->mCallback->Error(NS_ERROR_DOM_MEDIA_NEED_NEW_DECODER);
+      } else {
+        ref->mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_NEED_NEW_DECODER, __func__);
+      }
+    }));
   }
   mCanSend = false;
 }
 
 void
 VideoDecoderChild::InitIPDL(MediaDataDecoderCallback* aCallback,
                             const VideoInfo& aVideoInfo,
                             layers::KnowsCompositor* aKnowsCompositor)
 {
   RefPtr<VideoDecoderManagerChild> manager = VideoDecoderManagerChild::GetSingleton();
-  if (!manager) {
+  // If the manager isn't available, then don't initialize mIPDLSelfRef and leave
+  // us in an error state. We'll then immediately reject the promise when Init()
+  // is called and the caller can try again. Hopefully by then the new manager is
+  // ready, or we've notified the caller of it being no longer available.
+  // If not, then the cycle repeats until we're ready.
+  if (!manager || !manager->CanSend()) {
     return;
   }
+
   mIPDLSelfRef = this;
   mCallback = aCallback;
   mVideoInfo = aVideoInfo;
   mKnowsCompositor = aKnowsCompositor;
   if (manager->SendPVideoDecoderConstructor(this)) {
     mCanSend = true;
   }
 }
@@ -145,96 +156,99 @@ VideoDecoderChild::IPDLActorDestroyed()
 }
 
 // MediaDataDecoder methods
 
 RefPtr<MediaDataDecoder::InitPromise>
 VideoDecoderChild::Init()
 {
   AssertOnManagerThread();
-  if (!mCanSend || !SendInit(mVideoInfo, mKnowsCompositor->GetTextureFactoryIdentifier())) {
+
+  if (!mIPDLSelfRef) {
     return MediaDataDecoder::InitPromise::CreateAndReject(
-      NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+      NS_ERROR_DOM_MEDIA_DECODE_ERR, __func__);
+  }
+  // If we failed to send this, then we'll still resolve the Init promise
+  // as ActorDestroy handles it.
+  if (mCanSend) {
+    SendInit(mVideoInfo, mKnowsCompositor->GetTextureFactoryIdentifier());
   }
   return mInitPromise.Ensure(__func__);
 }
 
 void
 VideoDecoderChild::Input(MediaRawData* aSample)
 {
   AssertOnManagerThread();
   if (!mCanSend) {
-    mCallback->Error(NS_ERROR_DOM_MEDIA_FATAL_ERR);
     return;
   }
 
   // TODO: It would be nice to add an allocator method to
   // MediaDataDecoder so that the demuxer could write directly
   // into shmem rather than requiring a copy here.
   Shmem buffer;
   if (!AllocShmem(aSample->Size(), Shmem::SharedMemory::TYPE_BASIC, &buffer)) {
-    mCallback->Error(NS_ERROR_DOM_MEDIA_FATAL_ERR);
+    mCallback->Error(NS_ERROR_DOM_MEDIA_DECODE_ERR);
     return;
   }
 
   memcpy(buffer.get<uint8_t>(), aSample->Data(), aSample->Size());
 
   MediaRawDataIPDL sample(MediaDataIPDL(aSample->mOffset,
                                         aSample->mTime,
                                         aSample->mTimecode,
                                         aSample->mDuration,
                                         aSample->mFrames,
                                         aSample->mKeyframe),
                           buffer);
-  if (!SendInput(sample)) {
-    mCallback->Error(NS_ERROR_DOM_MEDIA_FATAL_ERR);
-  }
+  SendInput(sample);
 }
 
 void
 VideoDecoderChild::Flush()
 {
   AssertOnManagerThread();
-  if (!mCanSend || !SendFlush()) {
-    mCallback->Error(NS_ERROR_DOM_MEDIA_FATAL_ERR);
+  if (mCanSend) {
+    SendFlush();
   }
 }
 
 void
 VideoDecoderChild::Drain()
 {
   AssertOnManagerThread();
-  if (!mCanSend || !SendDrain()) {
-    mCallback->Error(NS_ERROR_DOM_MEDIA_FATAL_ERR);
+  if (mCanSend) {
+    SendDrain();
   }
 }
 
 void
 VideoDecoderChild::Shutdown()
 {
   AssertOnManagerThread();
-  if (!mCanSend || !SendShutdown()) {
-    mCallback->Error(NS_ERROR_DOM_MEDIA_FATAL_ERR);
+  if (mCanSend) {
+    SendShutdown();
   }
   mInitialized = false;
 }
 
 bool
 VideoDecoderChild::IsHardwareAccelerated(nsACString& aFailureReason) const
 {
   aFailureReason = mHardwareAcceleratedReason;
   return mIsHardwareAccelerated;
 }
 
 void
 VideoDecoderChild::SetSeekThreshold(const media::TimeUnit& aTime)
 {
   AssertOnManagerThread();
-  if (!mCanSend || !SendSetSeekThreshold(aTime.ToMicroseconds())) {
-    mCallback->Error(NS_ERROR_DOM_MEDIA_FATAL_ERR);
+  if (mCanSend) {
+    SendSetSeekThreshold(aTime.ToMicroseconds());
   }
 }
 
 void
 VideoDecoderChild::AssertOnManagerThread()
 {
   MOZ_ASSERT(NS_GetCurrentThread() == mThread);
 }
--- a/dom/media/ipc/VideoDecoderManagerChild.cpp
+++ b/dom/media/ipc/VideoDecoderManagerChild.cpp
@@ -8,109 +8,98 @@
 #include "mozilla/dom/ContentChild.h"
 #include "MediaPrefs.h"
 #include "nsThreadUtils.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/ipc/ProtocolUtils.h"
 #include "mozilla/layers/SynchronousTask.h"
 #include "mozilla/gfx/DataSurfaceHelpers.h"
 #include "mozilla/layers/ISurfaceAllocator.h"
+#include "base/task.h"
 
 namespace mozilla {
 namespace dom {
 
 using namespace ipc;
 using namespace layers;
 using namespace gfx;
 
 // Only modified on the main-thread
 StaticRefPtr<nsIThread> sVideoDecoderChildThread;
 StaticRefPtr<AbstractThread> sVideoDecoderChildAbstractThread;
 
 // Only accessed from sVideoDecoderChildThread
 static StaticRefPtr<VideoDecoderManagerChild> sDecoderManager;
+static UniquePtr<nsTArray<RefPtr<Runnable>>> sRecreateTasks;
 
 /* static */ void
-VideoDecoderManagerChild::Initialize()
+VideoDecoderManagerChild::InitializeThread()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  MediaPrefs::GetSingleton();
-
-#ifdef XP_WIN
-  if (!MediaPrefs::PDMUseGPUDecoder()) {
-    return;
-  }
-
-  // Can't run remote video decoding in the parent process.
-  if (!ContentChild::GetSingleton()) {
-    return;
-  }
-
   if (!sVideoDecoderChildThread) {
     RefPtr<nsIThread> childThread;
     nsresult rv = NS_NewNamedThread("VideoChild", getter_AddRefs(childThread));
     NS_ENSURE_SUCCESS_VOID(rv);
     sVideoDecoderChildThread = childThread;
 
     sVideoDecoderChildAbstractThread =
       AbstractThread::CreateXPCOMThreadWrapper(childThread, false);
+
+    sRecreateTasks = MakeUnique<nsTArray<RefPtr<Runnable>>>();
   }
-#else
-  return;
-#endif
+}
 
+/* static */ void
+VideoDecoderManagerChild::InitForContent(Endpoint<PVideoDecoderManagerChild>&& aVideoManager)
+{
+  InitializeThread();
+  sVideoDecoderChildThread->Dispatch(NewRunnableFunction(&Open, Move(aVideoManager)), NS_DISPATCH_NORMAL);
 }
 
 /* static */ void
 VideoDecoderManagerChild::Shutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (sVideoDecoderChildThread) {
     sVideoDecoderChildThread->Dispatch(NS_NewRunnableFunction([]() {
-      if (sDecoderManager) {
+      if (sDecoderManager && sDecoderManager->CanSend()) {
         sDecoderManager->Close();
         sDecoderManager = nullptr;
       }
     }), NS_DISPATCH_NORMAL);
 
     sVideoDecoderChildAbstractThread = nullptr;
     sVideoDecoderChildThread->Shutdown();
     sVideoDecoderChildThread = nullptr;
+
+    sRecreateTasks = nullptr;
   }
 }
 
+void
+VideoDecoderManagerChild::RunWhenRecreated(already_AddRefed<Runnable> aTask)
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == GetManagerThread());
+
+  // If we've already been recreated, then run the task immediately.
+  if (sDecoderManager && sDecoderManager != this && sDecoderManager->CanSend()) {
+    RefPtr<Runnable> task = aTask;
+    task->Run();
+  } else {
+    sRecreateTasks->AppendElement(aTask);
+  }
+}
+
+
 /* static */ VideoDecoderManagerChild*
 VideoDecoderManagerChild::GetSingleton()
 {
   MOZ_ASSERT(NS_GetCurrentThread() == GetManagerThread());
-
-  if (!sDecoderManager || !sDecoderManager->mCanSend) {
-    RefPtr<VideoDecoderManagerChild> manager;
-      
-    NS_DispatchToMainThread(NS_NewRunnableFunction([&]() {
-      Endpoint<PVideoDecoderManagerChild> endpoint;
-      if (!ContentChild::GetSingleton()->SendInitVideoDecoderManager(&endpoint)) {
-        return;
-      }
-
-      if (!endpoint.IsValid()) {
-        return;
-      }
-
-      manager = new VideoDecoderManagerChild();
-
-      RefPtr<Runnable> task = NewRunnableMethod<Endpoint<PVideoDecoderManagerChild>&&>(
-        manager, &VideoDecoderManagerChild::Open, Move(endpoint));
-      sVideoDecoderChildThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
-    }), NS_DISPATCH_SYNC);
-
-    sDecoderManager = manager;
-  }
   return sDecoderManager;
 }
 
 /* static */ nsIThread*
 VideoDecoderManagerChild::GetManagerThread()
 {
   return sVideoDecoderChildThread;
 }
@@ -133,43 +122,66 @@ VideoDecoderManagerChild::DeallocPVideoD
   VideoDecoderChild* child = static_cast<VideoDecoderChild*>(actor);
   child->IPDLActorDestroyed();
   return true;
 }
 
 void
 VideoDecoderManagerChild::Open(Endpoint<PVideoDecoderManagerChild>&& aEndpoint)
 {
-  if (!aEndpoint.Bind(this)) {
-    return;
+  // Make sure we always dispatch everything in sRecreateTasks, even if we
+  // fail since this is as close to being recreated as we will ever be.
+  sDecoderManager = nullptr;
+  if (aEndpoint.IsValid()) {
+    RefPtr<VideoDecoderManagerChild> manager = new VideoDecoderManagerChild();
+    if (aEndpoint.Bind(manager)) {
+      sDecoderManager = manager;
+      manager->InitIPDL();
+    }
   }
-  AddRef();
+  for (Runnable* task : *sRecreateTasks) {
+    task->Run();
+  }
+  sRecreateTasks->Clear();
+}
+
+void
+VideoDecoderManagerChild::InitIPDL()
+{
   mCanSend = true;
+  mIPDLSelfRef = this;
 }
 
 void
 VideoDecoderManagerChild::ActorDestroy(ActorDestroyReason aWhy)
 {
   mCanSend = false;
 }
 
 void
 VideoDecoderManagerChild::DeallocPVideoDecoderManagerChild()
 {
-  Release();
+  mIPDLSelfRef = nullptr;
+}
+
+bool
+VideoDecoderManagerChild::CanSend()
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == GetManagerThread());
+  return mCanSend;
 }
 
 bool
 VideoDecoderManagerChild::DeallocShmem(mozilla::ipc::Shmem& aShmem)
 {
   if (NS_GetCurrentThread() != sVideoDecoderChildThread) {
     RefPtr<VideoDecoderManagerChild> self = this;
     mozilla::ipc::Shmem shmem = aShmem;
     sVideoDecoderChildThread->Dispatch(NS_NewRunnableFunction([self, shmem]() {
-      if (self->mCanSend) {
+      if (self->CanSend()) {
         mozilla::ipc::Shmem shmemCopy = shmem;
         self->DeallocShmem(shmemCopy);
       }
     }), NS_DISPATCH_NORMAL);
     return true;
   }
   return PVideoDecoderManagerChild::DeallocShmem(aShmem);
 }
@@ -202,17 +214,17 @@ VideoDecoderManagerChild::Readback(const
   // loop while it waits. This function can be called from JS and we
   // don't want that to happen.
   SynchronousTask task("Readback sync");
 
   RefPtr<VideoDecoderManagerChild> ref = this;
   SurfaceDescriptor sd;
   sVideoDecoderChildThread->Dispatch(NS_NewRunnableFunction([&]() {
     AutoCompleteTask complete(&task);
-    if (ref->mCanSend) {
+    if (ref->CanSend()) {
       ref->SendReadback(aSD, &sd);
     }
   }), NS_DISPATCH_NORMAL);
 
   task.Wait();
 
   if (!IsSurfaceDescriptorValid(sd)) {
     return nullptr;
@@ -234,22 +246,22 @@ VideoDecoderManagerChild::Readback(const
 }
 
 void
 VideoDecoderManagerChild::DeallocateSurfaceDescriptorGPUVideo(const SurfaceDescriptorGPUVideo& aSD)
 {
   RefPtr<VideoDecoderManagerChild> ref = this;
   SurfaceDescriptorGPUVideo sd = Move(aSD);
   sVideoDecoderChildThread->Dispatch(NS_NewRunnableFunction([ref, sd]() {
-    if (ref->mCanSend) {
+    if (ref->CanSend()) {
       ref->SendDeallocateSurfaceDescriptorGPUVideo(sd);
     }
   }), NS_DISPATCH_NORMAL);
 }
 
 void
-VideoDecoderManagerChild::FatalError(const char* const aName, const char* const aMsg) const
+VideoDecoderManagerChild::HandleFatalError(const char* aName, const char* aMsg) const
 {
   dom::ContentChild::FatalErrorIfNotUsingGPUProcess(aName, aMsg, OtherPid());
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/media/ipc/VideoDecoderManagerChild.h
+++ b/dom/media/ipc/VideoDecoderManagerChild.h
@@ -46,35 +46,50 @@ public:
     return PVideoDecoderManagerChild::AllocUnsafeShmem(aSize, aShmType, aShmem);
   }
 
   // Can be called from any thread, dispatches the request to the IPDL thread internally
   // and will be ignored if the IPDL actor has been destroyed.
   bool DeallocShmem(mozilla::ipc::Shmem& aShmem) override;
 
   // Main thread only
-  static void Initialize();
+  static void InitForContent(Endpoint<PVideoDecoderManagerChild>&& aVideoManager);
   static void Shutdown();
 
+  // Run aTask (on the manager thread) when we next attempt to create a new manager
+  // (even if creation fails). Intended to be called from ActorDestroy when we get
+  // notified that the old manager is being destroyed.
+  // Can only be called from the manager thread.
+  void RunWhenRecreated(already_AddRefed<Runnable> aTask);
+
+  bool CanSend();
+
 protected:
+  void InitIPDL();
+
   void ActorDestroy(ActorDestroyReason aWhy) override;
   void DeallocPVideoDecoderManagerChild() override;
 
-  void FatalError(const char* const aName, const char* const aMsg) const override;
+  void HandleFatalError(const char* aName, const char* aMsg) const override;
 
   PVideoDecoderChild* AllocPVideoDecoderChild() override;
   bool DeallocPVideoDecoderChild(PVideoDecoderChild* actor) override;
 
 private:
+  // Main thread only
+  static void InitializeThread();
+
   VideoDecoderManagerChild()
     : mCanSend(false)
   {}
   ~VideoDecoderManagerChild() {}
 
-  void Open(Endpoint<PVideoDecoderManagerChild>&& aEndpoint);
+  static void Open(Endpoint<PVideoDecoderManagerChild>&& aEndpoint);
+
+  RefPtr<VideoDecoderManagerChild> mIPDLSelfRef;
 
   // Should only ever be accessed on the manager thread.
   bool mCanSend;
 };
 
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/media/ipc/VideoDecoderManagerParent.h
+++ b/dom/media/ipc/VideoDecoderManagerParent.h
@@ -28,17 +28,17 @@ public:
 
 protected:
   PVideoDecoderParent* AllocPVideoDecoderParent() override;
   bool DeallocPVideoDecoderParent(PVideoDecoderParent* actor) override;
 
   bool RecvReadback(const SurfaceDescriptorGPUVideo& aSD, SurfaceDescriptor* aResult) override;
   bool RecvDeallocateSurfaceDescriptorGPUVideo(const SurfaceDescriptorGPUVideo& aSD) override;
 
-  void ActorDestroy(mozilla::ipc::IProtocolManager<mozilla::ipc::IProtocol>::ActorDestroyReason) override {}
+  void ActorDestroy(mozilla::ipc::IProtocol::ActorDestroyReason) override {}
 
   void DeallocPVideoDecoderManagerParent() override;
 
  private:
   VideoDecoderManagerParent();
   ~VideoDecoderManagerParent();
 
   void Open(Endpoint<PVideoDecoderManagerParent>&& aEndpoint);
--- a/dom/media/moz.build
+++ b/dom/media/moz.build
@@ -50,17 +50,16 @@ if CONFIG['MOZ_ANDROID_OMX']:
 
 if CONFIG['MOZ_FMP4']:
     DIRS += ['fmp4']
 
 if CONFIG['MOZ_WEBRTC']:
     DIRS += ['bridge']
 
 TEST_DIRS += [
-    'compiledtest',
     'gtest',
 ]
 
 MOCHITEST_MANIFESTS += [
     'test/mochitest.ini',
     'tests/mochitest/identity/mochitest.ini',
 ]
 
--- a/dom/media/systemservices/MediaParent.h
+++ b/dom/media/systemservices/MediaParent.h
@@ -17,17 +17,17 @@ namespace media {
 
 // media::Parent implements the chrome-process side of ipc for media::Child APIs
 // A same-process version may also be created to service non-e10s calls.
 
 class OriginKeyStore;
 
 class NonE10s
 {
-  typedef mozilla::ipc::IProtocolManager<mozilla::ipc::IProtocol>::ActorDestroyReason
+  typedef mozilla::ipc::IProtocol::ActorDestroyReason
       ActorDestroyReason;
 public:
   virtual ~NonE10s() {}
 protected:
   virtual bool RecvGetOriginKey(const uint32_t& aRequestId,
                                 const nsCString& aOrigin,
                                 const bool& aPrivateBrowsing,
                                 const bool& aPersist) = 0;
@@ -40,17 +40,17 @@ protected:
                                 nsCString aKey);
 };
 
 // Super = PMediaParent or NonE10s
 
 template<class Super>
 class Parent : public Super
 {
-  typedef mozilla::ipc::IProtocolManager<mozilla::ipc::IProtocol>::ActorDestroyReason
+  typedef mozilla::ipc::IProtocol::ActorDestroyReason
       ActorDestroyReason;
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Parent<Super>)
 
   virtual bool RecvGetOriginKey(const uint32_t& aRequestId,
                                 const nsCString& aOrigin,
                                 const bool& aPrivateBrowsing,
                                 const bool& aPersist) override;
--- a/dom/permission/PermissionObserver.cpp
+++ b/dom/permission/PermissionObserver.cpp
@@ -81,17 +81,17 @@ PermissionObserver::RemoveSink(Permissio
 void
 PermissionObserver::Notify(PermissionName aName, nsIPrincipal& aPrincipal)
 {
   for (auto* sink : mSinks) {
     if (sink->mName != aName) {
       continue;
     }
 
-    nsIPrincipal* sinkPrincipal = sink->GetPrincipal();
+    nsCOMPtr<nsIPrincipal> sinkPrincipal = sink->GetPrincipal();
     if (NS_WARN_IF(!sinkPrincipal) || !aPrincipal.Equals(sinkPrincipal)) {
       continue;
     }
 
     sink->PermissionChanged();
   }
 }
 
--- a/dom/permission/PermissionStatus.cpp
+++ b/dom/permission/PermissionStatus.cpp
@@ -88,30 +88,34 @@ PermissionStatus::UpdateState()
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   mState = ActionToPermissionState(action);
   return NS_OK;
 }
 
-nsIPrincipal*
+already_AddRefed<nsIPrincipal>
 PermissionStatus::GetPrincipal() const
 {
   nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
   if (NS_WARN_IF(!window)) {
     return nullptr;
   }
 
   nsIDocument* doc = window->GetExtantDoc();
   if (NS_WARN_IF(!doc)) {
     return nullptr;
   }
 
-  return doc->NodePrincipal();
+  nsCOMPtr<nsIPrincipal> principal =
+    mozilla::BasePrincipal::Cast(doc->NodePrincipal())->CloneStrippingUserContextIdAndFirstPartyDomain();
+  NS_ENSURE_TRUE(principal, nullptr);
+
+  return principal.forget();
 }
 
 void
 PermissionStatus::PermissionChanged()
 {
   auto oldState = mState;
   UpdateState();
   if (mState != oldState) {
--- a/dom/permission/PermissionStatus.h
+++ b/dom/permission/PermissionStatus.h
@@ -37,17 +37,17 @@ private:
   ~PermissionStatus();
 
   PermissionStatus(nsPIDOMWindowInner* aWindow, PermissionName aName);
 
   nsresult Init();
 
   nsresult UpdateState();
 
-  nsIPrincipal* GetPrincipal() const;
+  already_AddRefed<nsIPrincipal> GetPrincipal() const;
 
   void PermissionChanged();
 
   PermissionName mName;
   PermissionState mState;
 
   RefPtr<PermissionObserver> mObserver;
 };
--- a/dom/plugins/ipc/PluginMessageUtils.h
+++ b/dom/plugins/ipc/PluginMessageUtils.h
@@ -5,18 +5,19 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef DOM_PLUGINS_PLUGINMESSAGEUTILS_H
 #define DOM_PLUGINS_PLUGINMESSAGEUTILS_H
 
 #include "ipc/IPCMessageUtils.h"
 #include "base/message_loop.h"
 
+#include "mozilla/ipc/CrossProcessMutex.h"
 #include "mozilla/ipc/MessageChannel.h"
-#include "mozilla/ipc/CrossProcessMutex.h"
+#include "mozilla/ipc/ProtocolUtils.h"
 #include "mozilla/UniquePtr.h"
 #include "gfxipc/ShadowLayerUtils.h"
 
 #include "npapi.h"
 #include "npruntime.h"
 #include "npfunctions.h"
 #include "nsString.h"
 #include "nsTArray.h"
--- a/dom/plugins/ipc/PluginModuleParent.cpp
+++ b/dom/plugins/ipc/PluginModuleParent.cpp
@@ -1031,18 +1031,17 @@ PluginModuleChromeParent::GetInvokingPro
  *
  * This function needs to be updated if the subprotocols are modified in
  * PPluginInstance.ipdl.
  */
 PluginInstanceParent*
 PluginModuleChromeParent::GetManagingInstance(mozilla::ipc::IProtocol* aProtocol)
 {
     MOZ_ASSERT(aProtocol);
-    mozilla::ipc::MessageListener* listener =
-        static_cast<mozilla::ipc::MessageListener*>(aProtocol);
+    mozilla::ipc::IProtocol* listener = aProtocol;
     switch (listener->GetProtocolTypeId()) {
         case PPluginInstanceMsgStart:
             // In this case, aProtocol is the instance itself. Just cast it.
             return static_cast<PluginInstanceParent*>(aProtocol);
         case PPluginBackgroundDestroyerMsgStart: {
             PPluginBackgroundDestroyerParent* actor =
                 static_cast<PPluginBackgroundDestroyerParent*>(aProtocol);
             return static_cast<PluginInstanceParent*>(actor->Manager());
--- a/dom/presentation/PresentationAvailability.cpp
+++ b/dom/presentation/PresentationAvailability.cpp
@@ -48,33 +48,36 @@ PresentationAvailability::Create(nsPIDOM
 }
 
 PresentationAvailability::PresentationAvailability(nsPIDOMWindowInner* aWindow,
                                                    const nsTArray<nsString>& aUrls)
   : DOMEventTargetHelper(aWindow)
   , mIsAvailable(false)
   , mUrls(aUrls)
 {
+  for (uint32_t i = 0; i < mUrls.Length(); ++i) {
+    mAvailabilityOfUrl.AppendElement(false);
+  }
 }
 
 PresentationAvailability::~PresentationAvailability()
 {
   Shutdown();
 }
 
 bool
 PresentationAvailability::Init(RefPtr<Promise>& aPromise)
 {
   nsCOMPtr<nsIPresentationService> service =
     do_GetService(PRESENTATION_SERVICE_CONTRACTID);
   if (NS_WARN_IF(!service)) {
     return false;
   }
 
-  nsresult rv = service->RegisterAvailabilityListener(this);
+  nsresult rv = service->RegisterAvailabilityListener(mUrls, this);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     // If the user agent is unable to monitor available device,
     // Resolve promise with |value| set to false.
     mIsAvailable = false;
     aPromise->MaybeResolve(this);
     return true;
   }
 
@@ -97,17 +100,18 @@ void PresentationAvailability::Shutdown(
 
   nsCOMPtr<nsIPresentationService> service =
     do_GetService(PRESENTATION_SERVICE_CONTRACTID);
   if (NS_WARN_IF(!service)) {
     return;
   }
 
   Unused <<
-    NS_WARN_IF(NS_FAILED(service->UnregisterAvailabilityListener(this)));
+    NS_WARN_IF(NS_FAILED(service->UnregisterAvailabilityListener(mUrls,
+                                                                 this)));
 }
 
 /* virtual */ void
 PresentationAvailability::DisconnectFromOwner()
 {
   Shutdown();
   DOMEventTargetHelper::DisconnectFromOwner();
 }
@@ -152,22 +156,31 @@ PresentationAvailability::EnqueuePromise
 
 bool
 PresentationAvailability::Value() const
 {
   return mIsAvailable;
 }
 
 NS_IMETHODIMP
-PresentationAvailability::NotifyAvailableChange(bool aIsAvailable)
+PresentationAvailability::NotifyAvailableChange(const nsTArray<nsString>& aAvailabilityUrls,
+                                                bool aIsAvailable)
 {
+  bool available = false;
+  for (uint32_t i = 0; i < mUrls.Length(); ++i) {
+    if (aAvailabilityUrls.Contains(mUrls[i])) {
+      mAvailabilityOfUrl[i] = aIsAvailable;
+    }
+    available |= mAvailabilityOfUrl[i];
+  }
+
   return NS_DispatchToCurrentThread(NewRunnableMethod
                                     <bool>(this,
                                            &PresentationAvailability::UpdateAvailabilityAndDispatchEvent,
-                                           aIsAvailable));
+                                           available));
 }
 
 void
 PresentationAvailability::UpdateAvailabilityAndDispatchEvent(bool aIsAvailable)
 {
   PRES_DEBUG("%s\n", __func__);
   bool isChanged = (aIsAvailable != mIsAvailable);
 
--- a/dom/presentation/PresentationAvailability.h
+++ b/dom/presentation/PresentationAvailability.h
@@ -60,14 +60,15 @@ private:
 
   void UpdateAvailabilityAndDispatchEvent(bool aIsAvailable);
 
   bool mIsAvailable;
 
   nsTArray<RefPtr<Promise>> mPromises;
 
   nsTArray<nsString> mUrls;
+  nsTArray<bool> mAvailabilityOfUrl;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_PresentationAvailability_h
--- a/dom/presentation/PresentationService.cpp
+++ b/dom/presentation/PresentationService.cpp
@@ -21,19 +21,16 @@
 #include "nsISupportsPrimitives.h"
 #include "nsNetUtil.h"
 #include "nsServiceManagerUtils.h"
 #include "nsThreadUtils.h"
 #include "nsXPCOMCID.h"
 #include "nsXULAppAPI.h"
 #include "PresentationLog.h"
 
-using namespace mozilla;
-using namespace mozilla::dom;
-
 namespace mozilla {
 namespace dom {
 
 static bool
 IsSameDevice(nsIPresentationDevice* aDevice, nsIPresentationDevice* aDeviceAnother) {
   if (!aDevice || !aDeviceAnother) {
     return false;
   }
@@ -127,19 +124,16 @@ private:
   nsWeakPtr mChromeEventHandler;
   nsCOMPtr<nsIPrincipal> mPrincipal;
   nsCOMPtr<nsIPresentationServiceCallback> mCallback;
   nsCOMPtr<nsIPresentationTransportBuilderConstructor> mBuilderConstructor;
 };
 
 LazyLogModule gPresentationLog("Presentation");
 
-} // namespace dom
-} // namespace mozilla
-
 NS_IMPL_ISUPPORTS(PresentationDeviceRequest, nsIPresentationDeviceRequest)
 
 PresentationDeviceRequest::PresentationDeviceRequest(
                const nsTArray<nsString>& aUrls,
                const nsAString& aId,
                const nsAString& aOrigin,
                uint64_t aWindowId,
                nsIDOMEventTarget* aEventTarget,
@@ -271,17 +265,16 @@ PresentationDeviceRequest::Cancel(nsresu
  * Implementation of PresentationService
  */
 
 NS_IMPL_ISUPPORTS(PresentationService,
                   nsIPresentationService,
                   nsIObserver)
 
 PresentationService::PresentationService()
-  : mIsAvailable(false)
 {
 }
 
 PresentationService::~PresentationService()
 {
   HandleShutdown();
 }
 
@@ -311,36 +304,42 @@ PresentationService::Init()
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return false;
   }
   rv = obs->AddObserver(this, PRESENTATION_RECONNECT_REQUEST_TOPIC, false);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return false;
   }
 
-  nsCOMPtr<nsIPresentationDeviceManager> deviceManager =
-    do_GetService(PRESENTATION_DEVICE_MANAGER_CONTRACTID);
-  if (NS_WARN_IF(!deviceManager)) {
-    return false;
-  }
-
-  rv = deviceManager->GetDeviceAvailable(&mIsAvailable);
   return !NS_WARN_IF(NS_FAILED(rv));
 }
 
 NS_IMETHODIMP
 PresentationService::Observe(nsISupports* aSubject,
                              const char* aTopic,
                              const char16_t* aData)
 {
   if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
     HandleShutdown();
     return NS_OK;
   } else if (!strcmp(aTopic, PRESENTATION_DEVICE_CHANGE_TOPIC)) {
-    return HandleDeviceChange();
+    // Ignore the "update" case here, since we only care about the arrival and
+    // removal of the device.
+    if (!NS_strcmp(aData, u"add")) {
+      nsCOMPtr<nsIPresentationDevice> device = do_QueryInterface(aSubject);
+      if (NS_WARN_IF(!device)) {
+        return NS_ERROR_FAILURE;
+      }
+
+      return HandleDeviceAdded(device);
+    } else if(!NS_strcmp(aData, u"remove")) {
+      return HandleDeviceRemoved();
+    }
+
+    return NS_OK;
   } else if (!strcmp(aTopic, PRESENTATION_SESSION_REQUEST_TOPIC)) {
     nsCOMPtr<nsIPresentationSessionRequest> request(do_QueryInterface(aSubject));
     if (NS_WARN_IF(!request)) {
       return NS_ERROR_FAILURE;
     }
 
     return HandleSessionRequest(request);
   } else if (!strcmp(aTopic, PRESENTATION_TERMINATE_REQUEST_TOPIC)) {
@@ -369,53 +368,114 @@ PresentationService::Observe(nsISupports
 
 void
 PresentationService::HandleShutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   Shutdown();
 
-  mAvailabilityListeners.Clear();
+  mAvailabilityManager.Clear();
   mSessionInfoAtController.Clear();
   mSessionInfoAtReceiver.Clear();
 
   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   if (obs) {
     obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
     obs->RemoveObserver(this, PRESENTATION_DEVICE_CHANGE_TOPIC);
     obs->RemoveObserver(this, PRESENTATION_SESSION_REQUEST_TOPIC);
     obs->RemoveObserver(this, PRESENTATION_TERMINATE_REQUEST_TOPIC);
     obs->RemoveObserver(this, PRESENTATION_RECONNECT_REQUEST_TOPIC);
   }
 }
 
 nsresult
-PresentationService::HandleDeviceChange()
+PresentationService::HandleDeviceAdded(nsIPresentationDevice* aDevice)
+{
+  PRES_DEBUG("%s\n", __func__);
+  if (!aDevice) {
+    MOZ_ASSERT(false, "aDevice shoud no be null.");
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  // Query for only unavailable URLs while device added.
+  nsTArray<nsString> unavailableUrls;
+  mAvailabilityManager.GetAvailbilityUrlByAvailability(unavailableUrls, false);
+
+  nsTArray<nsString> supportedAvailabilityUrl;
+  for (const auto& url : unavailableUrls) {
+     bool isSupported;
+    if (NS_SUCCEEDED(aDevice->IsRequestedUrlSupported(url, &isSupported)) &&
+        isSupported) {
+      supportedAvailabilityUrl.AppendElement(url);
+    }
+  }
+
+  if (!supportedAvailabilityUrl.IsEmpty()) {
+    return mAvailabilityManager.DoNotifyAvailableChange(supportedAvailabilityUrl,
+                                                        true);
+  }
+
+  return NS_OK;
+}
+
+nsresult
+PresentationService::HandleDeviceRemoved()
 {
   PRES_DEBUG("%s\n", __func__);
 
+  // Query for only available URLs while device removed.
+  nsTArray<nsString> availabilityUrls;
+  mAvailabilityManager.GetAvailbilityUrlByAvailability(availabilityUrls, true);
+
+  return UpdateAvailabilityUrlChange(availabilityUrls);
+}
+
+nsresult
+PresentationService::UpdateAvailabilityUrlChange(
+                                   const nsTArray<nsString>& aAvailabilityUrls)
+{
   nsCOMPtr<nsIPresentationDeviceManager> deviceManager =
     do_GetService(PRESENTATION_DEVICE_MANAGER_CONTRACTID);
   if (NS_WARN_IF(!deviceManager)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
-  bool isAvailable;
-  nsresult rv = deviceManager->GetDeviceAvailable(&isAvailable);
+  nsCOMPtr<nsIArray> devices;
+  nsresult rv = deviceManager->GetAvailableDevices(nullptr,
+                                                   getter_AddRefs(devices));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  if (isAvailable != mIsAvailable) {
-    mIsAvailable = isAvailable;
-    NotifyAvailableChange(mIsAvailable);
+  uint32_t numOfDevices;
+  devices->GetLength(&numOfDevices);
+
+  nsTArray<nsString> supportedAvailabilityUrl;
+  for (const auto& url : aAvailabilityUrls) {
+    for (uint32_t i = 0; i < numOfDevices; ++i) {
+      nsCOMPtr<nsIPresentationDevice> device = do_QueryElementAt(devices, i);
+      if (device) {
+        bool isSupported;
+        if (NS_SUCCEEDED(device->IsRequestedUrlSupported(url, &isSupported)) &&
+            isSupported) {
+          supportedAvailabilityUrl.AppendElement(url);
+          break;
+        }
+      }
+    }
   }
 
-  return NS_OK;
+  if (supportedAvailabilityUrl.IsEmpty()) {
+    return mAvailabilityManager.DoNotifyAvailableChange(aAvailabilityUrls,
+                                                        false);
+  }
+
+  return mAvailabilityManager.DoNotifyAvailableChange(supportedAvailabilityUrl,
+                                                      true);
 }
 
 nsresult
 PresentationService::HandleSessionRequest(nsIPresentationSessionRequest* aRequest)
 {
   nsCOMPtr<nsIPresentationControlChannel> ctrlChannel;
   nsresult rv = aRequest->GetControlChannel(getter_AddRefs(ctrlChannel));
   if (NS_WARN_IF(NS_FAILED(rv) || !ctrlChannel)) {
@@ -592,27 +652,16 @@ PresentationService::HandleReconnectRequ
   if (NS_WARN_IF(!info->GetUrl().Equals(url))) {
     ctrlChannel->Disconnect(rv);
     return rv;
   }
 
   return HandleSessionRequest(aRequest);
 }
 
-void
-PresentationService::NotifyAvailableChange(bool aIsAvailable)
-{
-  nsTObserverArray<nsCOMPtr<nsIPresentationAvailabilityListener>>::ForwardIterator iter(mAvailabilityListeners);
-  while (iter.HasMore()) {
-    nsCOMPtr<nsIPresentationAvailabilityListener> listener = iter.GetNext();
-    Unused <<
-      NS_WARN_IF(NS_FAILED(listener->NotifyAvailableChange(aIsAvailable)));
-  }
-}
-
 NS_IMETHODIMP
 PresentationService::StartSession(
                const nsTArray<nsString>& aUrls,
                const nsAString& aSessionId,
                const nsAString& aOrigin,
                const nsAString& aDeviceId,
                uint64_t aWindowId,
                nsIDOMEventTarget* aEventTarget,
@@ -873,38 +922,36 @@ PresentationService::BuildTransport(cons
   if (NS_WARN_IF(!info)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   return static_cast<PresentationControllingInfo*>(info.get())->BuildTransport();
 }
 
 NS_IMETHODIMP
-PresentationService::RegisterAvailabilityListener(nsIPresentationAvailabilityListener* aListener)
+PresentationService::RegisterAvailabilityListener(
+                                const nsTArray<nsString>& aAvailabilityUrls,
+                                nsIPresentationAvailabilityListener* aListener)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!aAvailabilityUrls.IsEmpty());
+  MOZ_ASSERT(aListener);
+
+  mAvailabilityManager.AddAvailabilityListener(aAvailabilityUrls, aListener);
+  return UpdateAvailabilityUrlChange(aAvailabilityUrls);
+}
+
+NS_IMETHODIMP
+PresentationService::UnregisterAvailabilityListener(
+                                const nsTArray<nsString>& aAvailabilityUrls,
+                                nsIPresentationAvailabilityListener* aListener)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (!mAvailabilityListeners.Contains(aListener)) {
-    mAvailabilityListeners.AppendElement(aListener);
-  }
-
-  // Leverage availablility change notification to assign
-  // the initial value of availability object.
-  Unused <<
-    NS_WARN_IF(NS_FAILED(aListener->NotifyAvailableChange(mIsAvailable)));
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-PresentationService::UnregisterAvailabilityListener(nsIPresentationAvailabilityListener* aListener)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  mAvailabilityListeners.RemoveElement(aListener);
+  mAvailabilityManager.RemoveAvailabilityListener(aAvailabilityUrls, aListener);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationService::RegisterSessionListener(const nsAString& aSessionId,
                                              uint8_t aRole,
                                              nsIPresentationSessionListener* aListener)
 {
@@ -1114,16 +1161,19 @@ PresentationService::IsSessionAccessible
              aRole == nsIPresentationService::ROLE_RECEIVER);
   RefPtr<PresentationSessionInfo> info = GetSessionInfo(aSessionId, aRole);
   if (NS_WARN_IF(!info)) {
     return false;
   }
   return info->IsAccessible(aProcessId);
 }
 
+} // namespace dom
+} // namespace mozilla
+
 already_AddRefed<nsIPresentationService>
 NS_CreatePresentationService()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsCOMPtr<nsIPresentationService> service;
   if (XRE_GetProcessType() == GeckoProcessType_Content) {
     service = new mozilla::dom::PresentationIPCService();
--- a/dom/presentation/PresentationService.h
+++ b/dom/presentation/PresentationService.h
@@ -4,17 +4,16 @@
  * 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_PresentationService_h
 #define mozilla_dom_PresentationService_h
 
 #include "nsCOMPtr.h"
 #include "nsIObserver.h"
-#include "nsTObserverArray.h"
 #include "PresentationServiceBase.h"
 #include "PresentationSessionInfo.h"
 
 class nsIPresentationSessionRequest;
 class nsIPresentationTerminateRequest;
 class nsIURI;
 class nsIPresentationSessionTransportBuilder;
 
@@ -41,28 +40,29 @@ public:
                            const uint8_t aRole,
                            base::ProcessId aProcessId);
 
 private:
   friend class PresentationDeviceRequest;
 
   virtual ~PresentationService();
   void HandleShutdown();
-  nsresult HandleDeviceChange();
+  nsresult HandleDeviceAdded(nsIPresentationDevice* aDevice);
+  nsresult HandleDeviceRemoved();
   nsresult HandleSessionRequest(nsIPresentationSessionRequest* aRequest);
   nsresult HandleTerminateRequest(nsIPresentationTerminateRequest* aRequest);
   nsresult HandleReconnectRequest(nsIPresentationSessionRequest* aRequest);
-  void NotifyAvailableChange(bool aIsAvailable);
 
   // This is meant to be called by PresentationDeviceRequest.
   already_AddRefed<PresentationSessionInfo>
   CreateControllingSessionInfo(const nsAString& aUrl,
                                const nsAString& aSessionId,
                                uint64_t aWindowId);
 
-  bool mIsAvailable;
-  nsTObserverArray<nsCOMPtr<nsIPresentationAvailabilityListener>> mAvailabilityListeners;
+  // Emumerate all devices to get the availability of each input Urls.
+  nsresult UpdateAvailabilityUrlChange(
+                                  const nsTArray<nsString>& aAvailabilityUrls);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_PresentationService_h
--- a/dom/presentation/PresentationServiceBase.h
+++ b/dom/presentation/PresentationServiceBase.h
@@ -2,24 +2,25 @@
 /* vim: set sw=2 ts=8 et ft=cpp : */
 /* 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_PresentationServiceBase_h
 #define mozilla_dom_PresentationServiceBase_h
 
+#include "mozilla/Unused.h"
 #include "nsClassHashtable.h"
+#include "nsCOMArray.h"
+#include "nsIPresentationListener.h"
 #include "nsIPresentationService.h"
 #include "nsRefPtrHashtable.h"
 #include "nsString.h"
 #include "nsTArray.h"
 
-class nsIPresentationRespondingListener;
-
 namespace mozilla {
 namespace dom {
 
 template<class T>
 class PresentationServiceBase
 {
 public:
   PresentationServiceBase() = default;
@@ -127,16 +128,191 @@ protected:
       mRespondingWindowIds.Clear();
     }
 
   private:
     nsClassHashtable<nsUint64HashKey, nsTArray<nsString>> mRespondingSessionIds;
     nsDataHashtable<nsStringHashKey, uint64_t> mRespondingWindowIds;
   };
 
+  class AvailabilityManager final
+  {
+  public:
+    explicit AvailabilityManager()
+    {
+      MOZ_COUNT_CTOR(AvailabilityManager);
+    }
+
+    ~AvailabilityManager()
+    {
+      MOZ_COUNT_DTOR(AvailabilityManager);
+    }
+
+    void AddAvailabilityListener(
+                               const nsTArray<nsString>& aAvailabilityUrls,
+                               nsIPresentationAvailabilityListener* aListener)
+    {
+      nsTArray<nsString> dummy;
+      AddAvailabilityListener(aAvailabilityUrls, aListener, dummy);
+    }
+
+    void AddAvailabilityListener(
+                               const nsTArray<nsString>& aAvailabilityUrls,
+                               nsIPresentationAvailabilityListener* aListener,
+                               nsTArray<nsString>& aAddedUrls)
+    {
+      if (!aListener) {
+        MOZ_ASSERT(false, "aListener should not be null.");
+        return;
+      }
+
+      if (aAvailabilityUrls.IsEmpty()) {
+        MOZ_ASSERT(false, "aAvailabilityUrls should not be empty.");
+        return;
+      }
+
+      aAddedUrls.Clear();
+      nsTArray<nsString> knownAvailableUrls;
+      for (const auto& url : aAvailabilityUrls) {
+        AvailabilityEntry* entry;
+        if (!mAvailabilityUrlTable.Get(url, &entry)) {
+          entry = new AvailabilityEntry();
+          mAvailabilityUrlTable.Put(url, entry);
+          aAddedUrls.AppendElement(url);
+        }
+        if (!entry->mListeners.Contains(aListener)) {
+          entry->mListeners.AppendElement(aListener);
+        }
+        if (entry->mAvailable) {
+          knownAvailableUrls.AppendElement(url);
+        }
+      }
+
+      if (!knownAvailableUrls.IsEmpty()) {
+        Unused <<
+          NS_WARN_IF(
+            NS_FAILED(aListener->NotifyAvailableChange(knownAvailableUrls,
+                                                       true)));
+      } else {
+        // If we can't find any known available url and there is no newly
+        // added url, we still need to notify the listener of the result.
+        // So, the promise returned by |getAvailability| can be resolved.
+        if (aAddedUrls.IsEmpty()) {
+          Unused <<
+            NS_WARN_IF(
+              NS_FAILED(aListener->NotifyAvailableChange(aAvailabilityUrls,
+                                                         false)));
+        }
+      }
+    }
+
+    void RemoveAvailabilityListener(
+                               const nsTArray<nsString>& aAvailabilityUrls,
+                               nsIPresentationAvailabilityListener* aListener)
+    {
+      nsTArray<nsString> dummy;
+      RemoveAvailabilityListener(aAvailabilityUrls, aListener, dummy);
+    }
+
+    void RemoveAvailabilityListener(
+                               const nsTArray<nsString>& aAvailabilityUrls,
+                               nsIPresentationAvailabilityListener* aListener,
+                               nsTArray<nsString>& aRemovedUrls)
+    {
+      if (!aListener) {
+        MOZ_ASSERT(false, "aListener should not be null.");
+        return;
+      }
+
+      if (aAvailabilityUrls.IsEmpty()) {
+        MOZ_ASSERT(false, "aAvailabilityUrls should not be empty.");
+        return;
+      }
+
+      aRemovedUrls.Clear();
+      for (const auto& url : aAvailabilityUrls) {
+        AvailabilityEntry* entry;
+        if (mAvailabilityUrlTable.Get(url, &entry)) {
+          entry->mListeners.RemoveElement(aListener);
+          if (entry->mListeners.IsEmpty()) {
+            mAvailabilityUrlTable.Remove(url);
+            aRemovedUrls.AppendElement(url);
+          }
+        }
+      }
+    }
+
+    nsresult DoNotifyAvailableChange(const nsTArray<nsString>& aAvailabilityUrls,
+                                     bool aAvailable)
+    {
+      typedef nsClassHashtable<nsISupportsHashKey,
+                               nsTArray<nsString>> ListenerToUrlsMap;
+      ListenerToUrlsMap availabilityListenerTable;
+      // Create a mapping from nsIPresentationAvailabilityListener to
+      // availabilityUrls.
+      for (auto it = mAvailabilityUrlTable.ConstIter(); !it.Done(); it.Next()) {
+        if (aAvailabilityUrls.Contains(it.Key())) {
+          AvailabilityEntry* entry = it.UserData();
+          entry->mAvailable = aAvailable;
+
+          for (uint32_t i = 0; i < entry->mListeners.Length(); ++i) {
+            nsIPresentationAvailabilityListener* listener =
+              entry->mListeners.ObjectAt(i);
+            nsTArray<nsString>* urlArray;
+            if (!availabilityListenerTable.Get(listener, &urlArray)) {
+              urlArray = new nsTArray<nsString>();
+              availabilityListenerTable.Put(listener, urlArray);
+            }
+            urlArray->AppendElement(it.Key());
+          }
+        }
+      }
+
+      for (auto it = availabilityListenerTable.Iter(); !it.Done(); it.Next()) {
+        auto listener =
+          static_cast<nsIPresentationAvailabilityListener*>(it.Key());
+
+        Unused <<
+          NS_WARN_IF(NS_FAILED(listener->NotifyAvailableChange(*it.UserData(),
+                                                               aAvailable)));
+      }
+      return NS_OK;
+    }
+
+    void GetAvailbilityUrlByAvailability(nsTArray<nsString>& aOutArray,
+                                         bool aAvailable)
+    {
+      aOutArray.Clear();
+
+      for (auto it = mAvailabilityUrlTable.ConstIter(); !it.Done(); it.Next()) {
+        if (it.UserData()->mAvailable == aAvailable) {
+          aOutArray.AppendElement(it.Key());
+        }
+      }
+    }
+
+    void Clear()
+    {
+      mAvailabilityUrlTable.Clear();
+    }
+
+  private:
+    struct AvailabilityEntry
+    {
+      explicit AvailabilityEntry()
+        : mAvailable(false)
+      {}
+
+      bool mAvailable;
+      nsCOMArray<nsIPresentationAvailabilityListener> mListeners;
+    };
+
+    nsClassHashtable<nsStringHashKey, AvailabilityEntry> mAvailabilityUrlTable;
+  };
+
   virtual ~PresentationServiceBase() = default;
 
   void Shutdown()
   {
     mRespondingListeners.Clear();
     mControllerSessionIdManager.Clear();
     mReceiverSessionIdManager.Clear();
   }
@@ -210,14 +386,16 @@ protected:
   // to retrieve the correspondent session ID. Besides, also keep the mapping
   // between the responding session ID and the window ID to help look up the
   // window ID.
   SessionIdManager mControllerSessionIdManager;
   SessionIdManager mReceiverSessionIdManager;
 
   nsRefPtrHashtable<nsStringHashKey, T> mSessionInfoAtController;
   nsRefPtrHashtable<nsStringHashKey, T> mSessionInfoAtReceiver;
+
+  AvailabilityManager mAvailabilityManager;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_PresentationServiceBase_h
--- a/dom/presentation/interfaces/nsIPresentationListener.idl
+++ b/dom/presentation/interfaces/nsIPresentationListener.idl
@@ -1,21 +1,24 @@
 /* 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/. */
 
 #include "nsISupports.idl"
 
-[scriptable, uuid(0105f837-4279-4715-9d5b-2dc3f8b65353)]
+[ref] native URLArrayRef(const nsTArray<nsString>);
+
+[uuid(0105f837-4279-4715-9d5b-2dc3f8b65353)]
 interface nsIPresentationAvailabilityListener : nsISupports
 {
   /*
    * Called when device availability changes.
    */
-  void notifyAvailableChange(in bool available);
+  [noscript] void notifyAvailableChange(in URLArrayRef urls,
+                                        in bool available);
 };
 
 [scriptable, uuid(7dd48df8-8f8c-48c7-ac37-7b9fd1acf2f8)]
 interface nsIPresentationSessionListener : nsISupports
 {
   const unsigned short STATE_CONNECTING = 0;
   const unsigned short STATE_CONNECTED = 1;
   const unsigned short STATE_CLOSED = 2;
--- a/dom/presentation/interfaces/nsIPresentationService.idl
+++ b/dom/presentation/interfaces/nsIPresentationService.idl
@@ -153,25 +153,32 @@ interface nsIPresentationService : nsISu
   [noscript] void reconnectSession(in URLArrayRef urls,
                                    in DOMString sessionId,
                                    in uint8_t role,
                                    in nsIPresentationServiceCallback callback);
 
   /*
    * Register an availability listener. Must be called from the main thread.
    *
+   * @param availabilityUrls: The Urls that this listener is interested in.
    * @param listener: The listener to register.
    */
-  void registerAvailabilityListener(in nsIPresentationAvailabilityListener listener);
+  [noscript] void registerAvailabilityListener(
+                              in URLArrayRef availabilityUrls,
+                              in nsIPresentationAvailabilityListener listener);
 
   /*
    * Unregister an availability listener. Must be called from the main thread.
+   *
+   * @param availabilityUrls: The Urls that are registered before.
    * @param listener: The listener to unregister.
    */
-  void unregisterAvailabilityListener(in nsIPresentationAvailabilityListener listener);
+  [noscript] void unregisterAvailabilityListener(
+                              in URLArrayRef availabilityUrls,
+                              in nsIPresentationAvailabilityListener listener);
 
   /*
    * Register a session listener. Must be called from the main thread.
    *
    * @param sessionId: An ID to identify presentation session.
    * @param role: Identify the function called by controller or receiver.
    * @param listener: The listener to register.
    */
--- a/dom/presentation/ipc/PPresentation.ipdl
+++ b/dom/presentation/ipc/PPresentation.ipdl
@@ -72,33 +72,34 @@ union PresentationIPCRequest
 
 sync protocol PPresentation
 {
   manager PContent;
   manages PPresentationBuilder;
   manages PPresentationRequest;
 
 child:
-  async NotifyAvailableChange(bool aAvailable);
+  async NotifyAvailableChange(nsString[] aAvailabilityUrls,
+                              bool aAvailable);
   async NotifySessionStateChange(nsString aSessionId,
                                  uint16_t aState,
                                  nsresult aReason);
   async NotifyMessage(nsString aSessionId, nsCString aData, bool aIsBinary);
   async NotifySessionConnect(uint64_t aWindowId, nsString aSessionId);
   async NotifyCloseSessionTransport(nsString aSessionId,
                                     uint8_t aRole,
                                     nsresult aReason);
 
   async PPresentationBuilder(nsString aSessionId, uint8_t aRole);
 
 parent:
   async __delete__();
 
-  async RegisterAvailabilityHandler();
-  async UnregisterAvailabilityHandler();
+  async RegisterAvailabilityHandler(nsString[] aAvailabilityUrls);
+  async UnregisterAvailabilityHandler(nsString[] aAvailabilityUrls);
 
   async RegisterSessionHandler(nsString aSessionId, uint8_t aRole);
   async UnregisterSessionHandler(nsString aSessionId, uint8_t aRole);
 
   async RegisterRespondingHandler(uint64_t aWindowId);
   async UnregisterRespondingHandler(uint64_t aWindowId);
 
   async PPresentationRequest(PresentationIPCRequest aRequest);
--- a/dom/presentation/ipc/PresentationChild.cpp
+++ b/dom/presentation/ipc/PresentationChild.cpp
@@ -84,20 +84,24 @@ PresentationChild::DeallocPPresentationB
 {
   RefPtr<PresentationBuilderChild> actor =
     dont_AddRef(static_cast<PresentationBuilderChild*>(aActor));
   return true;
 }
 
 
 bool
-PresentationChild::RecvNotifyAvailableChange(const bool& aAvailable)
+PresentationChild::RecvNotifyAvailableChange(
+                                        nsTArray<nsString>&& aAvailabilityUrls,
+                                        const bool& aAvailable)
 {
   if (mService) {
-    Unused << NS_WARN_IF(NS_FAILED(mService->NotifyAvailableChange(aAvailable)));
+    Unused <<
+      NS_WARN_IF(NS_FAILED(mService->NotifyAvailableChange(aAvailabilityUrls,
+                                                           aAvailable)));
   }
   return true;
 }
 
 bool
 PresentationChild::RecvNotifySessionStateChange(const nsString& aSessionId,
                                                 const uint16_t& aState,
                                                 const nsresult& aReason)
--- a/dom/presentation/ipc/PresentationChild.h
+++ b/dom/presentation/ipc/PresentationChild.h
@@ -38,17 +38,18 @@ public:
 
   virtual PPresentationBuilderChild*
   AllocPPresentationBuilderChild(const nsString& aSessionId, const uint8_t& aRole) override;
 
   virtual bool
   DeallocPPresentationBuilderChild(PPresentationBuilderChild* aActor) override;
 
   virtual bool
-  RecvNotifyAvailableChange(const bool& aAvailable) override;
+  RecvNotifyAvailableChange(nsTArray<nsString>&& aAvailabilityUrls,
+                            const bool& aAvailable) override;
 
   virtual bool
   RecvNotifySessionStateChange(const nsString& aSessionId,
                                const uint16_t& aState,
                                const nsresult& aReason) override;
 
   virtual bool
   RecvNotifyMessage(const nsString& aSessionId,
--- a/dom/presentation/ipc/PresentationIPCService.cpp
+++ b/dom/presentation/ipc/PresentationIPCService.cpp
@@ -23,17 +23,19 @@ using namespace mozilla::dom;
 using namespace mozilla::ipc;
 
 namespace {
 
 PresentationChild* sPresentationChild;
 
 } // anonymous
 
-NS_IMPL_ISUPPORTS(PresentationIPCService, nsIPresentationService)
+NS_IMPL_ISUPPORTS(PresentationIPCService,
+                  nsIPresentationService,
+                  nsIPresentationAvailabilityListener)
 
 PresentationIPCService::PresentationIPCService()
 {
   ContentChild* contentChild = ContentChild::GetSingleton();
   if (NS_WARN_IF(!contentChild)) {
     return;
   }
   sPresentationChild = new PresentationChild(this);
@@ -41,17 +43,16 @@ PresentationIPCService::PresentationIPCS
     NS_WARN_IF(!contentChild->SendPPresentationConstructor(sPresentationChild));
 }
 
 /* virtual */
 PresentationIPCService::~PresentationIPCService()
 {
   Shutdown();
 
-  mAvailabilityListeners.Clear();
   mSessionListeners.Clear();
   mSessionInfoAtController.Clear();
   mSessionInfoAtReceiver.Clear();
   sPresentationChild = nullptr;
 }
 
 NS_IMETHODIMP
 PresentationIPCService::StartSession(
@@ -229,39 +230,53 @@ PresentationIPCService::SendRequest(nsIP
   if (sPresentationChild) {
     PresentationRequestChild* actor = new PresentationRequestChild(aCallback);
     Unused << NS_WARN_IF(!sPresentationChild->SendPPresentationRequestConstructor(actor, aRequest));
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
-PresentationIPCService::RegisterAvailabilityListener(nsIPresentationAvailabilityListener* aListener)
+PresentationIPCService::RegisterAvailabilityListener(
+                                const nsTArray<nsString>& aAvailabilityUrls,
+                                nsIPresentationAvailabilityListener* aListener)
 {
   MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!aAvailabilityUrls.IsEmpty());
   MOZ_ASSERT(aListener);
 
-  mAvailabilityListeners.AppendElement(aListener);
-