Merge autoland to mozilla-central r=merge a=merge
authorMargareta Eliza Balazs <ebalazs@mozilla.com>
Mon, 27 Nov 2017 23:55:26 +0200
changeset 438362 5441160857a6bb70b5b29a7b00d647fc88951b7a
parent 438282 895e1f5cc6170e8bf4497a533aaed00d37797aa0 (current diff)
parent 438361 f9be9bd0f9e255bcb698654505ce42a10511e5d9 (diff)
child 438363 f5f03ee9e6abf77964f8dc1b9d69c6ccd3f655fd
push id117
push userfmarier@mozilla.com
push dateTue, 28 Nov 2017 20:17:16 +0000
reviewersmerge, merge
milestone59.0a1
Merge autoland to mozilla-central r=merge a=merge
devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_history.js
testing/web-platform/meta/webvtt/api/VTTRegion/constructor.html.ini
testing/web-platform/meta/webvtt/api/VTTRegion/id.html.ini
testing/web-platform/meta/webvtt/api/VTTRegion/lines.html.ini
testing/web-platform/meta/webvtt/api/VTTRegion/regionAnchorX.html.ini
testing/web-platform/meta/webvtt/api/VTTRegion/regionAnchorY.html.ini
testing/web-platform/meta/webvtt/api/VTTRegion/scroll.html.ini
testing/web-platform/meta/webvtt/api/VTTRegion/viewportAnchorX.html.ini
testing/web-platform/meta/webvtt/api/VTTRegion/viewportAnchorY.html.ini
testing/web-platform/meta/webvtt/api/VTTRegion/width.html.ini
testing/web-platform/meta/webvtt/api/historical.html.ini
testing/web-platform/meta/webvtt/api/interfaces.html.ini
toolkit/components/.eslintrc.js
--- a/.taskcluster.yml
+++ b/.taskcluster.yml
@@ -60,17 +60,17 @@ tasks:
           - "tc-treeherder.v2.${repository.project}.${push.revision}.${push.pushlog_id}"
           - "tc-treeherder-stage.v2.${repository.project}.${push.revision}.${push.pushlog_id}"
           - "notify.email.${ownerEmail}.on-failed"
           - "notify.email.${ownerEmail}.on-exception"
         else:
           - "tc-treeherder.v2.${repository.project}.${push.revision}.${push.pushlog_id}"
           - "tc-treeherder-stage.v2.${repository.project}.${push.revision}.${push.pushlog_id}"
           - $if: 'tasks_for == "action"'
-            then: "index.gecko.v2.${repository.project}.pushlog-id.${push.pushlog_id}.actions.$ownTaskId"
+            then: "index.gecko.v2.${repository.project}.pushlog-id.${push.pushlog_id}.actions.${ownTaskId}"
             else: "index.gecko.v2.${repository.project}.latest.firefox.decision-${cron.job_name}"
 
       scopes:
         $if: 'tasks_for == "hg-push"'
         then:
           - 'assume:repo:${repoUrl[8:]}:*'
           - 'queue:route:notify.email.${ownerEmail}.*'
         else:
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1669,17 +1669,17 @@ pref("signon.schemeUpgrades", true);
 pref("print.use_simplify_page", true);
 #endif
 #else
 pref("print.use_simplify_page", true);
 #endif
 
 // Space separated list of URLS that are allowed to send objects (instead of
 // only strings) through webchannels. This list is duplicated in mobile/android/app/mobile.js
-pref("webchannel.allowObject.urlWhitelist", "https://accounts.firefox.com https://content.cdn.mozilla.net https://input.mozilla.org https://support.mozilla.org https://install.mozilla.org");
+pref("webchannel.allowObject.urlWhitelist", "https://content.cdn.mozilla.net https://input.mozilla.org https://support.mozilla.org https://install.mozilla.org");
 
 // Whether or not the browser should scan for unsubmitted
 // crash reports, and then show a notification for submitting
 // those reports.
 #ifdef EARLY_BETA_OR_EARLIER
 pref("browser.crashReports.unsubmittedCheck.enabled", true);
 #else
 pref("browser.crashReports.unsubmittedCheck.enabled", false);
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -5858,32 +5858,24 @@
               break;
             }
           }
         ]]></body>
       </method>
 
       <method name="_updateNewTabVisibility">
         <body><![CDATA[
-          let isCustomizing = this.tabContainer.parentNode.getAttribute("customizing") == "true";
-
-          // Confusingly, the <tabs> are never wrapped in <toolbarpaletteitem>s in customize mode,
-          // but the other items will be.
-          let sib = this.tabContainer.nextElementSibling;
-          if (isCustomizing) {
-            sib = sib && sib.firstElementChild;
-          }
-          while (sib && sib.hidden) {
-            if (isCustomizing) {
-              sib = sib.parentNode.nextElementSibling;
-              sib = sib && sib.firstElementChild;
-            } else {
-              sib = sib.nextElementSibling;
-            }
-          }
+          // Helper functions to help deal with customize mode wrapping some items
+          let wrap = n => n.parentNode.localName == "toolbarpaletteitem" ? n.parentNode : n;
+          let unwrap = n => n && n.localName == "toolbarpaletteitem" ? n.firstElementChild : n;
+
+          let sib = this.tabContainer;
+          do {
+            sib = unwrap(wrap(sib).nextElementSibling);
+          } while (sib && sib.hidden);
 
           const kAttr = "hasadjacentnewtabbutton";
           if (sib && sib.id == "new-tab-button") {
             this.tabContainer.setAttribute(kAttr, "true");
           } else {
             this.tabContainer.removeAttribute(kAttr);
           }
         ]]></body>
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -461,17 +461,18 @@ function openLinkIn(url, where, params) 
     // loading the New Tab page.
     focusUrlBar = w.document.activeElement == w.gURLBar.inputField &&
                   w.isBlankPageURL(url);
     break;
   case "tabshifted":
     loadInBackground = !loadInBackground;
     // fall through
   case "tab":
-    focusUrlBar = !loadInBackground && w.isBlankPageURL(url);
+    focusUrlBar = !loadInBackground && w.isBlankPageURL(url)
+      && !aboutNewTabService.willNotifyUser;
 
     let tabUsedForLoad = w.gBrowser.loadOneTab(url, {
       referrerURI: aReferrerURI,
       referrerPolicy: aReferrerPolicy,
       charset: aCharset,
       postData: aPostData,
       inBackground: loadInBackground,
       allowThirdPartyFixup: aAllowThirdPartyFixup,
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -678,8 +678,33 @@
        onmouseover="clearTimeout(gCustomizeMode._downloadPanelAutoHideTimeout);"
        onmouseout="gCustomizeMode._downloadPanelAutoHideTimeout = setTimeout(() => event.target.hidePopup(), 2000);"
        onpopuphidden="clearTimeout(gCustomizeMode._downloadPanelAutoHideTimeout);"
        >
   <checkbox id="downloads-button-autohide-checkbox"
             label="&customizeMode.autoHideDownloadsButton.label;" checked="true"
             oncommand="gCustomizeMode.onDownloadsAutoHideChange(event)"/>
 </panel>
+
+<panel id="extension-notification-panel"
+       role="group"
+       type="arrow"
+       hidden="true"
+       flip="slide"
+       position="bottomcenter topright"
+       tabspecific="true">
+  <popupnotification id="extension-new-tab-notification"
+                     popupid="extension-new-tab"
+                     label="&newTabControlled.header.message;"
+                     buttonlabel="&newTabControlled.keepButton.label;"
+                     buttonaccesskey="&newTabControlled.keepButton.accesskey;"
+                     secondarybuttonlabel="&newTabControlled.restoreButton.label;"
+                     secondarybuttonaccesskey="&newTabControlled.restoreButton.accesskey;"
+                     closebuttonhidden="true"
+                     dropmarkerhidden="true"
+                     checkboxhidden="true">
+    <popupnotificationcontent orient="vertical">
+      <description id="extension-new-tab-notification-description">
+        &newTabControlled.message;
+      </description>
+    </popupnotificationcontent>
+  </popupnotification>
+</panel>
--- a/browser/components/customizableui/test/browser_947914_button_history.js
+++ b/browser/components/customizableui/test/browser_947914_button_history.js
@@ -31,16 +31,16 @@ add_task(async function() {
   ok(historyPanel.getAttribute("current"), "History Panel is in view");
 
   let browserLoaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   let panelHiddenPromise = promiseOverflowHidden(window);
 
   let historyItems = document.getElementById("appMenu_historyMenu");
   let historyItemForURL = historyItems.querySelector("toolbarbutton.bookmark-item[label='Happy History Hero']");
   ok(historyItemForURL, "Should have a history item for the history we just made.");
-  historyItemForURL.click();
+  EventUtils.synthesizeMouseAtCenter(historyItemForURL, {});
   await browserLoaded;
   is(gBrowser.currentURI.spec, TEST_PATH + "dummy_history_item.html", "Should have expected page load");
 
   await panelHiddenPromise;
   await BrowserTestUtils.removeTab(tab);
   info("Menu panel was closed");
 });
--- a/browser/components/customizableui/test/browser_newtab_button_customizemode.js
+++ b/browser/components/customizableui/test/browser_newtab_button_customizemode.js
@@ -99,8 +99,27 @@ add_task(async function addremove_before
   CustomizableUI.removeWidgetFromArea("home-button");
   ok(gBrowser.tabContainer.hasAttribute("hasadjacentnewtabbutton"),
     "tabs should have the adjacent newtab attribute again");
   assertNewTabButton("inner");
 
   CustomizableUI.reset();
   ok(CustomizableUI.inDefaultState, "Should be in default state");
 });
+
+/**
+  * Reset to defaults in customize mode to see if that doesn't break things.
+  */
+add_task(async function reset_before_newtab_customizemode() {
+  await startCustomizing();
+  simulateItemDrag(document.getElementById("home-button"), kGlobalNewTabButton);
+  ok(!gBrowser.tabContainer.hasAttribute("hasadjacentnewtabbutton"),
+    "tabs should no longer have the adjacent newtab attribute");
+  await endCustomizing();
+  assertNewTabButton("global");
+  await startCustomizing();
+  await gCustomizeMode.reset();
+  ok(gBrowser.tabContainer.hasAttribute("hasadjacentnewtabbutton"),
+    "tabs should have the adjacent newtab attribute again");
+  await endCustomizing();
+  assertNewTabButton("inner");
+  ok(CustomizableUI.inDefaultState, "Should be in default state");
+});
--- a/browser/components/extensions/ext-url-overrides.js
+++ b/browser/components/extensions/ext-url-overrides.js
@@ -1,43 +1,142 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+/* import-globals-from ext-browser.js */
 
 "use strict";
 
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionSettingsStore",
                                   "resource://gre/modules/ExtensionSettingsStore.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
+                                  "resource://gre/modules/AddonManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
+                                  "resource:///modules/CustomizableUI.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
                                    "@mozilla.org/browser/aboutnewtab-service;1",
                                    "nsIAboutNewTabService");
 
 const STORE_TYPE = "url_overrides";
 const NEW_TAB_SETTING_NAME = "newTabURL";
+const NEW_TAB_CONFIRMED_TYPE = "newTabNotification";
+
+function userWasNotified(extensionId) {
+  let setting = ExtensionSettingsStore.getSetting(NEW_TAB_CONFIRMED_TYPE, extensionId);
+  return setting && setting.value;
+}
+
+async function handleNewTabOpened() {
+  // We don't need to open the doorhanger again until the controlling add-on changes.
+  // eslint-disable-next-line no-use-before-define
+  removeNewTabObserver();
+
+  let item = ExtensionSettingsStore.getSetting(STORE_TYPE, NEW_TAB_SETTING_NAME);
+
+  if (!item || !item.id || userWasNotified(item.id)) {
+    return;
+  }
+
+  // Find the elements we need.
+  let win = windowTracker.getCurrentWindow({});
+  let doc = win.document;
+  let panel = doc.getElementById("extension-notification-panel");
+
+  // Setup the command handler.
+  let handleCommand = async (event) => {
+    if (event.originalTarget.getAttribute("anonid") == "button") {
+      // Main action is to keep changes.
+      await ExtensionSettingsStore.addSetting(
+        item.id, NEW_TAB_CONFIRMED_TYPE, item.id, true, () => false);
+    } else {
+      // Secondary action is to restore settings.
+      ExtensionSettingsStore.removeSetting(NEW_TAB_CONFIRMED_TYPE, item.id);
+      let addon = await AddonManager.getAddonByID(item.id);
+      addon.userDisabled = true;
+    }
+    panel.hidePopup();
+    win.gURLBar.focus();
+  };
+  panel.addEventListener("command", handleCommand);
+  panel.addEventListener("popuphidden", () => {
+    panel.removeEventListener("command", handleCommand);
+  }, {once: true});
+
+  // Look for a browserAction on the toolbar.
+  let action = CustomizableUI.getWidget(
+    `${global.makeWidgetId(item.id)}-browser-action`);
+  if (action) {
+    action = action.areaType == "toolbar" && action.forWindow(win).node;
+  }
+
+  // Anchor to a toolbar browserAction if found, otherwise use the menu button.
+  let anchor = doc.getAnonymousElementByAttribute(
+    action || doc.getElementById("PanelUI-menu-button"),
+    "class", "toolbarbutton-icon");
+  panel.hidden = false;
+  panel.openPopup(anchor);
+}
+
+let newTabOpenedListener = {
+  observe(subject, topic, data) {
+    // Do this work in an idle callback to avoid interfering with new tab performance tracking.
+    windowTracker
+      .getCurrentWindow({})
+      .requestIdleCallback(handleNewTabOpened);
+  },
+};
+
+function removeNewTabObserver() {
+  if (aboutNewTabService.willNotifyUser) {
+    Services.obs.removeObserver(newTabOpenedListener, "browser-open-newtab-start");
+    aboutNewTabService.willNotifyUser = false;
+  }
+}
+
+function addNewTabObserver(extensionId) {
+  if (!aboutNewTabService.willNotifyUser && extensionId && !userWasNotified(extensionId)) {
+    Services.obs.addObserver(newTabOpenedListener, "browser-open-newtab-start");
+    aboutNewTabService.willNotifyUser = true;
+  }
+}
+
+function setNewTabURL(extensionId, url) {
+  aboutNewTabService.newTabURL = url;
+  if (aboutNewTabService.overridden) {
+    addNewTabObserver(extensionId);
+  } else {
+    removeNewTabObserver();
+  }
+}
 
 this.urlOverrides = class extends ExtensionAPI {
   processNewTabSetting(action) {
     let {extension} = this;
     let item = ExtensionSettingsStore[action](extension.id, STORE_TYPE, NEW_TAB_SETTING_NAME);
     if (item) {
-      aboutNewTabService.newTabURL = item.value || item.initialValue;
+      setNewTabURL(item.id, item.value || item.initialValue);
     }
   }
 
   async onManifestEntry(entryName) {
     let {extension} = this;
     let {manifest} = extension;
 
     await ExtensionSettingsStore.initialize();
 
     if (manifest.chrome_url_overrides.newtab) {
       // Set up the shutdown code for the setting.
       extension.callOnClose({
         close: () => {
+          if (extension.shutdownReason == "ADDON_DISABLE"
+              || extension.shutdownReason == "ADDON_UNINSTALL") {
+            ExtensionSettingsStore.removeSetting(
+              extension.id, NEW_TAB_CONFIRMED_TYPE, extension.id);
+          }
           switch (extension.shutdownReason) {
             case "ADDON_DISABLE":
               this.processNewTabSetting("disable");
               break;
 
             // We can remove the setting on upgrade or downgrade because it will be
             // added back in when the manifest is re-read. This will cover the case
             // where a new version of an add-on removes the manifest key.
@@ -60,13 +159,13 @@ this.urlOverrides = class extends Extens
       // This is required because addSetting above is used for both add and update.
       if (["ADDON_ENABLE", "ADDON_UPGRADE", "ADDON_DOWNGRADE"]
           .includes(extension.startupReason)) {
         item = ExtensionSettingsStore.enable(extension.id, STORE_TYPE, NEW_TAB_SETTING_NAME);
       }
 
       // Set the newTabURL to the current value of the setting.
       if (item) {
-        aboutNewTabService.newTabURL = item.value || item.initialValue;
+        setNewTabURL(extension.id, item.value || item.initialValue);
       }
     }
   }
 };
--- a/browser/components/extensions/test/browser/browser_ext_omnibox.js
+++ b/browser/components/extensions/test/browser/browser_ext_omnibox.js
@@ -90,16 +90,28 @@ add_task(async function() {
       () => gURLBar.popup.richlistbox.children.length > index &&
             gURLBar.popup.richlistbox.children[index].getAttribute("ac-text") == searchString,
       `Waiting for the autocomplete result for "${searchString}" at [${index}] to appear`);
     // Ensure the addition is complete, for proper mouse events on the entries.
     await new Promise(resolve => window.requestIdleCallback(resolve, {timeout: 1000}));
     return gURLBar.popup.richlistbox.children[index];
   }
 
+  async function promiseClickOnItem(item, details) {
+    // The Address Bar panel is animated and updated on a timer, thus it may not
+    // yet be listening to events when we try to click on it.  This uses a
+    // polling strategy to repeat the click, if it doesn't go through.
+    let clicked = false;
+    item.addEventListener("mousedown", () => { clicked = true; }, {once: true});
+    while (!clicked) {
+      EventUtils.synthesizeMouseAtCenter(item, details);
+      await new Promise(r => window.requestIdleCallback(r, {timeout: 1000}));
+    }
+  }
+
   let inputSessionSerial = 0;
   async function startInputSession(indexToWaitFor) {
     gURLBar.focus();
     gURLBar.value = keyword;
     EventUtils.synthesizeKey(" ", {});
     await expectEvent("on-input-started-fired");
     // Always use a different input at every invokation, so that
     // waitForAutocompleteResultAt can distinguish different cases.
@@ -193,45 +205,46 @@ add_task(async function() {
     let item = gURLBar.popup.richlistbox.children[0];
 
     is(item.getAttribute("title"), expectedText,
       `Expected heuristic result to have title: "${expectedText}".`);
 
     is(item.getAttribute("displayurl"), `${keyword} ${text}`,
       `Expected heuristic result to have displayurl: "${keyword} ${text}".`);
 
-    EventUtils.synthesizeMouseAtCenter(item, {});
-
-    await expectEvent("on-input-entered-fired", {
+    let promiseEvent = expectEvent("on-input-entered-fired", {
       text,
       disposition: "currentTab",
     });
+    await promiseClickOnItem(item, {});
+    await promiseEvent;
   }
 
   async function testDisposition(suggestionIndex, expectedDisposition, expectedText) {
     await startInputSession(suggestionIndex);
 
     // Select the suggestion.
     for (let i = 0; i < suggestionIndex; i++) {
       EventUtils.synthesizeKey("VK_DOWN", {});
     }
 
-    let item = gURLBar.popup.richlistbox.children[suggestionIndex];
-    if (expectedDisposition == "currentTab") {
-      EventUtils.synthesizeMouseAtCenter(item, {});
-    } else if (expectedDisposition == "newForegroundTab") {
-      EventUtils.synthesizeMouseAtCenter(item, {accelKey: true});
-    } else if (expectedDisposition == "newBackgroundTab") {
-      EventUtils.synthesizeMouseAtCenter(item, {shiftKey: true, accelKey: true});
-    }
-
-    await expectEvent("on-input-entered-fired", {
+    let promiseEvent = expectEvent("on-input-entered-fired", {
       text: expectedText,
       disposition: expectedDisposition,
     });
+
+    let item = gURLBar.popup.richlistbox.children[suggestionIndex];
+    if (expectedDisposition == "currentTab") {
+      await promiseClickOnItem(item, {});
+    } else if (expectedDisposition == "newForegroundTab") {
+      await promiseClickOnItem(item, {accelKey: true});
+    } else if (expectedDisposition == "newBackgroundTab") {
+      await promiseClickOnItem(item, {shiftKey: true, accelKey: true});
+    }
+    await promiseEvent;
   }
 
   async function testSuggestions(info) {
     extension.sendMessage("set-synchronous", {synchronous: false});
 
     function expectSuggestion({content, description}, index) {
       let item = gURLBar.popup.richlistbox.children[index + 1]; // Skip the heuristic result.
 
@@ -245,21 +258,22 @@ add_task(async function() {
 
     let text = await startInputSession(info.suggestions.length - 1);
 
     extension.sendMessage(info.test);
     await extension.awaitMessage("test-ready");
 
     info.suggestions.forEach(expectSuggestion);
 
-    EventUtils.synthesizeMouseAtCenter(gURLBar.popup.richlistbox.children[0], {});
-    await expectEvent("on-input-entered-fired", {
+    let promiseEvent = expectEvent("on-input-entered-fired", {
       text,
       disposition: "currentTab",
     });
+    await promiseClickOnItem(gURLBar.popup.richlistbox.children[0], {});
+    await promiseEvent;
   }
 
   await extension.startup();
 
   await SimpleTest.promiseFocus(window);
 
   await testInputEvents();
 
--- a/browser/components/extensions/test/browser/browser_ext_tabs_lastAccessed.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_lastAccessed.js
@@ -16,17 +16,17 @@ add_task(async function testLastAccessed
       browser.test.onMessage.addListener(async function(msg, past) {
         let [tab1] = await browser.tabs.query({url: "https://example.com/?1"});
         let [tab2] = await browser.tabs.query({url: "https://example.com/?2"});
 
         browser.test.assertTrue(tab1 && tab2, "Expected tabs were found");
 
         let now = Date.now();
 
-        browser.test.assertTrue(past < tab1.lastAccessed,
+        browser.test.assertTrue(past <= tab1.lastAccessed,
                                 "lastAccessed of tab 1 is later than the test start time.");
         browser.test.assertTrue(tab1.lastAccessed < tab2.lastAccessed,
                                 "lastAccessed of tab 2 is later than lastAccessed of tab 1.");
         browser.test.assertTrue(tab2.lastAccessed <= now,
                                 "lastAccessed of tab 2 is earlier than now.");
 
         await browser.tabs.remove([tab1.id, tab2.id]);
 
--- a/browser/components/extensions/test/browser/browser_ext_url_overrides_newtab.js
+++ b/browser/components/extensions/test/browser/browser_ext_url_overrides_newtab.js
@@ -1,16 +1,51 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 
 "use strict";
 
+XPCOMUtils.defineLazyModuleGetter(this, "ExtensionSettingsStore",
+                                  "resource://gre/modules/ExtensionSettingsStore.jsm");
+
 const NEWTAB_URI_1 = "webext-newtab-1.html";
 
-add_task(async function test_sending_message_from_newtab_page() {
+function getNotificationSetting(extensionId) {
+  return ExtensionSettingsStore.getSetting("newTabNotification", extensionId);
+}
+
+function getNewTabDoorhanger() {
+  return document.getElementById("extension-new-tab-notification");
+}
+
+function clickKeepChanges(notification) {
+  let button = document.getAnonymousElementByAttribute(
+    notification, "anonid", "button");
+  button.click();
+}
+
+function clickRestoreSettings(notification) {
+  let button = document.getAnonymousElementByAttribute(
+    notification, "anonid", "secondarybutton");
+  button.click();
+}
+
+function waitForNewTab() {
+  let eventName = "browser-open-newtab-start";
+  return new Promise(resolve => {
+    function observer() {
+      Services.obs.removeObserver(observer, eventName);
+      resolve();
+    }
+    Services.obs.addObserver(observer, eventName);
+  });
+}
+
+add_task(async function test_new_tab_opens() {
+  let panel = getNewTabDoorhanger().closest("panel");
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "chrome_url_overrides": {
         newtab: NEWTAB_URI_1,
       },
     },
     useAddonManager: "temporary",
     files: {
@@ -31,38 +66,276 @@ add_task(async function test_sending_mes
         };
       },
     },
   });
 
   await extension.startup();
 
   // Simulate opening the newtab open as a user would.
+  let popupShown = promisePopupShown(panel);
   BrowserOpenTab();
+  await popupShown;
 
   let url = await extension.awaitMessage("from-newtab-page");
   ok(url.endsWith(NEWTAB_URI_1),
-     "Newtab url is overriden by the extension.");
+     "Newtab url is overridden by the extension.");
+
+  // This will show a confirmation doorhanger, make sure we don't leave it open.
+  let popupHidden = promisePopupHidden(panel);
+  panel.hidePopup();
+  await popupHidden;
+
+  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+  await extension.unload();
+});
+
+add_task(async function test_new_tab_ignore_settings() {
+  await ExtensionSettingsStore.initialize();
+  let notification = getNewTabDoorhanger();
+  let panel = notification.closest("panel");
+  let extensionId = "newtabignore@mochi.test";
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      applications: {gecko: {id: extensionId}},
+      browser_action: {default_popup: "ignore.html"},
+      chrome_url_overrides: {newtab: "ignore.html"},
+    },
+    files: {"ignore.html": '<h1 id="extension-new-tab">New Tab!</h1>'},
+    useAddonManager: "temporary",
+  });
+
+  ok(panel.getAttribute("panelopen") != "true",
+     "The notification panel is initially closed");
+
+  await extension.startup();
+
+  // Simulate opening the New Tab as a user would.
+  let popupShown = promisePopupShown(panel);
+  BrowserOpenTab();
+  await popupShown;
+
+  // Ensure the doorhanger is shown and the setting isn't set yet.
+  is(panel.getAttribute("panelopen"), "true",
+     "The notification panel is open after opening New Tab");
+  is(gURLBar.focused, false, "The URL bar is not focused with a doorhanger");
+  is(getNotificationSetting(extensionId), null,
+     "The New Tab notification is not set for this extension");
+  is(panel.anchorNode.closest("toolbarbutton").id,
+     "newtabignore_mochi_test-browser-action",
+     "The doorhanger is anchored to the browser action");
+
+  // Manually close the panel, as if the user ignored it.
+  let popupHidden = promisePopupHidden(panel);
+  panel.hidePopup();
+  await popupHidden;
+
+  // Ensure panel is closed and the setting still isn't set.
+  ok(panel.getAttribute("panelopen") != "true",
+     "The notification panel is closed");
+  is(getNotificationSetting(extensionId), null,
+     "The New Tab notification is not set after ignoring the doorhanger");
+
+  // Close the first tab and open another new tab.
+  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+  let newTabOpened = waitForNewTab();
+  BrowserOpenTab();
+  await newTabOpened;
+
+  // Verify the doorhanger is not shown a second time.
+  ok(panel.getAttribute("panelopen") != "true",
+     "The notification panel doesn't open after ignoring the doorhanger");
+  is(gURLBar.focused, true, "The URL bar is focused with no doorhanger");
 
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
   await extension.unload();
 });
 
+add_task(async function test_new_tab_keep_settings() {
+  await ExtensionSettingsStore.initialize();
+  let notification = getNewTabDoorhanger();
+  let panel = notification.closest("panel");
+  let extensionId = "newtabkeep@mochi.test";
+  let manifest = {
+    version: "1.0",
+    applications: {gecko: {id: extensionId}},
+    chrome_url_overrides: {newtab: "keep.html"},
+  };
+  let files = {
+    "keep.html": '<script src="newtab.js"></script><h1 id="extension-new-tab">New Tab!</h1>',
+    "newtab.js": () => { window.onload = browser.test.sendMessage("newtab"); },
+  };
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest,
+    files,
+    useAddonManager: "permanent",
+  });
+
+  ok(panel.getAttribute("panelopen") != "true",
+     "The notification panel is initially closed");
+
+  await extension.startup();
+
+  // Simulate opening the New Tab as a user would.
+  let popupShown = promisePopupShown(panel);
+  BrowserOpenTab();
+  await extension.awaitMessage("newtab");
+  await popupShown;
+
+  // Ensure the panel is open and the setting isn't saved yet.
+  is(panel.getAttribute("panelopen"), "true",
+     "The notification panel is open after opening New Tab");
+  is(getNotificationSetting(extensionId), null,
+     "The New Tab notification is not set for this extension");
+  is(panel.anchorNode.closest("toolbarbutton").id, "PanelUI-menu-button",
+     "The doorhanger is anchored to the menu icon");
+
+  // Click the Keep Changes button.
+  let popupHidden = promisePopupHidden(panel);
+  clickKeepChanges(notification);
+  await popupHidden;
+
+  // Ensure panel is closed and setting is updated.
+  ok(panel.getAttribute("panelopen") != "true",
+     "The notification panel is closed after click");
+  is(getNotificationSetting(extensionId).value, true,
+     "The New Tab notification is set after keeping the changes");
+
+  // Close the first tab and open another new tab.
+  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+  BrowserOpenTab();
+  await extension.awaitMessage("newtab");
+
+  // Verify the doorhanger is not shown a second time.
+  ok(panel.getAttribute("panelopen") != "true",
+     "The notification panel is not opened after keeping the changes");
+
+  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+  let upgradedExtension = ExtensionTestUtils.loadExtension({
+    manifest: Object.assign({}, manifest, {version: "2.0"}),
+    files,
+    useAddonManager: "permanent",
+  });
+
+  await upgradedExtension.startup();
+
+  BrowserOpenTab();
+  await upgradedExtension.awaitMessage("newtab");
+
+  // Ensure panel is closed and setting is still set.
+  ok(panel.getAttribute("panelopen") != "true",
+     "The notification panel is closed after click");
+  is(getNotificationSetting(extensionId).value, true,
+     "The New Tab notification is set after keeping the changes");
+
+  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+  await extension.unload();
+  await upgradedExtension.unload();
+});
+
+add_task(async function test_new_tab_restore_settings() {
+  await ExtensionSettingsStore.initialize();
+  let notification = getNewTabDoorhanger();
+  let panel = notification.closest("panel");
+  let extensionId = "newtabrestore@mochi.test";
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      applications: {gecko: {id: extensionId}},
+      chrome_url_overrides: {newtab: "restore.html"},
+    },
+    files: {"restore.html": '<h1 id="extension-new-tab">New Tab!</h1>'},
+    useAddonManager: "temporary",
+  });
+
+  ok(panel.getAttribute("panelopen") != "true",
+     "The notification panel is initially closed");
+  is(getNotificationSetting(extensionId), null,
+     "The New Tab notification is not initially set for this extension");
+
+  await extension.startup();
+
+  // Simulate opening the newtab open as a user would.
+  let popupShown = promisePopupShown(panel);
+  BrowserOpenTab();
+  await popupShown;
+
+  // Verify that the panel is open and add-on is enabled.
+  let addon = await AddonManager.getAddonByID(extensionId);
+  is(addon.userDisabled, false, "The add-on is enabled at first");
+  is(panel.getAttribute("panelopen"), "true",
+     "The notification panel is open after opening New Tab");
+  is(getNotificationSetting(extensionId), null,
+     "The New Tab notification is not set for this extension");
+
+  // Click the Restore Changes button.
+  let addonDisabled = new Promise(resolve => {
+    let listener = {
+      onDisabled(disabledAddon) {
+        if (disabledAddon.id == addon.id) {
+          resolve();
+          AddonManager.removeAddonListener(listener);
+        }
+      },
+    };
+    AddonManager.addAddonListener(listener);
+  });
+  let popupHidden = promisePopupHidden(panel);
+  clickRestoreSettings(notification);
+  await popupHidden;
+  await addonDisabled;
+
+  // Ensure panel is closed, settings haven't changed and add-on is disabled.
+  ok(panel.getAttribute("panelopen") != "true",
+     "The notification panel is closed after click");
+  is(getNotificationSetting(extensionId), null,
+     "The New Tab notification is not set after resorting the settings");
+  is(addon.userDisabled, true, "The extension is now disabled");
+
+  // Reopen a browser tab and verify that there's no doorhanger.
+  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+  let newTabOpened = waitForNewTab();
+  BrowserOpenTab();
+  await newTabOpened;
+
+  ok(panel.getAttribute("panelopen") != "true",
+     "The notification panel is not opened after keeping the changes");
+
+  // FIXME: We need to enable the add-on so it gets cleared from the
+  // ExtensionSettingsStore for now. See bug 1408226.
+  let addonEnabled = new Promise(resolve => {
+    let listener = {
+      onEnabled(enabledAddon) {
+        if (enabledAddon.id == addon.id) {
+          AddonManager.removeAddonListener(listener);
+          resolve();
+        }
+      },
+    };
+    AddonManager.addAddonListener(listener);
+  });
+  addon.userDisabled = false;
+  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+  await addonEnabled;
+  await extension.unload();
+});
+
 /**
  * Ensure we don't show the extension URL in the URL bar temporarily in new tabs
  * while we're switching remoteness (when the URL we're loading and the
  * default content principal are different).
  */
 add_task(async function dontTemporarilyShowAboutExtensionPath() {
+  await ExtensionSettingsStore.initialize();
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       name: "Test Extension",
       applications: {
         gecko: {
-          id: "newtab@mochi.test",
+          id: "newtaburl@mochi.test",
         },
       },
       chrome_url_overrides: {
         newtab: "newtab.html",
       },
     },
     background() {
       browser.test.sendMessage("url", browser.runtime.getURL("newtab.html"));
--- a/browser/components/newtab/aboutNewTabService.js
+++ b/browser/components/newtab/aboutNewTabService.js
@@ -90,16 +90,17 @@ function AboutNewTabService() {
 AboutNewTabService.prototype = {
 
   _newTabURL: ABOUT_URL,
   _activityStreamEnabled: false,
   _activityStreamPrerender: false,
   _activityStreamPath: "",
   _activityStreamDebug: false,
   _overridden: false,
+  willNotifyUser: false,
 
   classID: Components.ID("{dfcd2adc-7867-4d3a-ba70-17501f208142}"),
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsIAboutNewTabService,
     Ci.nsIObserver
   ]),
   _xpcom_categories: [{
     service: true
--- a/browser/components/newtab/nsIAboutNewTabService.idl
+++ b/browser/components/newtab/nsIAboutNewTabService.idl
@@ -19,16 +19,21 @@ interface nsIAboutNewTabService : nsISup
   attribute ACString newTabURL;
 
   /**
    * Returns the default URL (local or activity stream depending on pref)
    */
   attribute ACString defaultURL;
 
   /**
+   * Returns true if opening the New Tab page will notify the user of a change.
+   */
+  attribute bool willNotifyUser;
+
+  /**
    * Returns true if the default resource got overridden.
    */
   readonly attribute bool overridden;
 
   /**
    * Returns true if the default resource is activity stream and isn't
    * overridden
    */
--- a/browser/components/places/content/browserPlacesViews.js
+++ b/browser/components/places/content/browserPlacesViews.js
@@ -2119,18 +2119,18 @@ this.PlacesPanelview = class extends Pla
 
   get panelMultiView() {
     return this._viewElt.panelMultiView;
   }
 
   handleEvent(event) {
     switch (event.type) {
       case "click":
-        // For left and middle clicks, fall through to the command handler.
-        if (event.button >= 2) {
+        // For middle clicks, fall through to the command handler.
+        if (event.button != 1) {
           break;
         }
       case "command":
         this._onCommand(event);
         break;
       case "dragend":
         this._onDragEnd(event);
         break;
@@ -2149,18 +2149,32 @@ this.PlacesPanelview = class extends Pla
     }
   }
 
   _onCommand(event) {
     let button = event.originalTarget;
     if (!button._placesNode)
       return;
 
+    let modifKey = AppConstants.platform === "macosx" ? event.metaKey
+                                                      : event.ctrlKey;
+    if (!PlacesUIUtils.openInTabClosesMenu && modifKey) {
+      // If 'Recent Bookmarks' in Bookmarks Panel.
+      if (button.parentNode.id == "panelMenu_bookmarksMenu") {
+        button.setAttribute("closemenu", "none");
+      }
+    } else {
+      button.removeAttribute("closemenu");
+    }
     PlacesUIUtils.openNodeWithEvent(button._placesNode, event);
-    this.panelMultiView.closest("panel").hidePopup();
+    // Unlike left-click, middle-click requires manual menu closing.
+    if (button.parentNode.id != "panelMenu_bookmarksMenu" ||
+        (event.type == "click" && event.button == 1 && PlacesUIUtils.openInTabClosesMenu)) {
+      this.panelMultiView.closest("panel").hidePopup();
+    }
   }
 
   _onDragEnd() {
     this._draggedElt = null;
   }
 
   _onDragStart(event) {
     let draggedElt = event.originalTarget;
--- a/browser/components/places/tests/browser/browser_stayopenmenu.js
+++ b/browser/components/places/tests/browser/browser_stayopenmenu.js
@@ -1,29 +1,23 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Menus should stay open (if pref is set) after ctrl-click, middle-click,
 // and contextmenu's "Open in a new tab" click.
 
 async function locateBookmarkAndTestCtrlClick(menupopup) {
-  let menuitem = null;
-  for (let node of menupopup.childNodes) {
-    if (node.label == "Test1") {
-      menuitem = node;
-      let promiseTabOpened = BrowserTestUtils.waitForNewTab(gBrowser, null);
-      EventUtils.synthesizeMouseAtCenter(menuitem,
-        AppConstants.platform === "macosx" ? {metaKey: true} : {ctrlKey: true});
-      let newTab = await promiseTabOpened;
-      ok(true, "Bookmark ctrl-click opened new tab.");
-      await BrowserTestUtils.removeTab(newTab);
-      break;
-    }
-  }
-  return menuitem;
+  let testMenuitem = [...menupopup.childNodes].find(node => node.label == "Test1");
+  ok(testMenuitem, "Found test bookmark.");
+  let promiseTabOpened = BrowserTestUtils.waitForNewTab(gBrowser, null);
+  EventUtils.synthesizeMouseAtCenter(testMenuitem, {accelKey: true});
+  let newTab = await promiseTabOpened;
+  ok(true, "Bookmark ctrl-click opened new tab.");
+  await BrowserTestUtils.removeTab(newTab);
+  return testMenuitem;
 }
 
 async function testContextmenu(menuitem) {
   let doc = menuitem.ownerDocument;
   let cm = doc.getElementById("placesContext");
   let promiseEvent = BrowserTestUtils.waitForEvent(cm, "popupshown");
   EventUtils.synthesizeMouseAtCenter(menuitem, {type: "contextmenu", button: 2});
   await promiseEvent;
@@ -117,16 +111,53 @@ add_task(async function testStayopenBook
   ok(true, "Bookmark contextmenu opened new tab.");
   ok(BMB.open, "Bookmarks Menu Button's Popup should still be open.");
   promiseEvent = BrowserTestUtils.waitForEvent(BMBpopup, "popuphidden");
   BMB.open = false;
   await promiseEvent;
   info("Closing menu");
   await BrowserTestUtils.removeTab(newTab);
 
+  // Test App Menu's Bookmarks Library stayopen clicks.
+  let appMenu = document.getElementById("PanelUI-menu-button");
+  let appMenuPopup = document.getElementById("appMenu-popup");
+  let PopupShownPromise = BrowserTestUtils.waitForEvent(appMenuPopup, "popupshown");
+  appMenu.click();
+  await PopupShownPromise;
+  let libView = document.getElementById("appMenu-libraryView");
+  let libraryBtn = document.getElementById("appMenu-library-button");
+  let ViewShownPromise = BrowserTestUtils.waitForEvent(libView, "ViewShown");
+  libraryBtn.click();
+  await ViewShownPromise;
+  info("Library panel shown.");
+  let bookmarks = document.getElementById("appMenu-library-bookmarks-button");
+  let BMview = document.getElementById("PanelUI-bookmarks");
+  ViewShownPromise = BrowserTestUtils.waitForEvent(BMview, "ViewShown");
+  bookmarks.click();
+  await ViewShownPromise;
+  info("Library's bookmarks panel shown.");
+
+  // Test App Menu's Bookmarks Library stayopen clicks: Ctrl-click.
+  let menu = document.getElementById("panelMenu_bookmarksMenu");
+  var testMenuitem = await locateBookmarkAndTestCtrlClick(menu);
+  ok(appMenu.open, "Menu should remain open.");
+
+  // Test App Menu's Bookmarks Library stayopen clicks: middle-click.
+  promiseTabOpened = BrowserTestUtils.waitForNewTab(gBrowser, null);
+  EventUtils.synthesizeMouseAtCenter(testMenuitem, {button: 1});
+  newTab = await promiseTabOpened;
+  ok(true, "Bookmark middle-click opened new tab.");
+  await BrowserTestUtils.removeTab(newTab);
+  is(PanelUI.multiView.current.id, "PanelUI-bookmarks", "Should still show the bookmarks subview");
+  ok(appMenu.open, "Menu should remain open.");
+
+  // Close the App Menu
+  appMenuPopup.hidePopup();
+  ok(!appMenu.open, "The menu should now be closed.");
+
   // Disable the rest of the tests on Mac due to Mac's handling of menus being
   // slightly different to the other platforms.
   if (AppConstants.platform === "macosx") {
     return;
   }
 
   // Test Bookmarks Menu (menubar) stayopen clicks: Ctrl-click.
   let BM = document.getElementById("bookmarksMenu");
--- a/browser/extensions/formautofill/FormAutofillHandler.jsm
+++ b/browser/extensions/formautofill/FormAutofillHandler.jsm
@@ -388,36 +388,36 @@ class FormAutofillSection {
         // For the focused input element, it will be filled with a valid value
         // anyway.
         // For the others, the fields should be only filled when their values
         // are empty.
         if (element == focusedInput ||
             (element != focusedInput && !element.value)) {
           element.setUserInput(value);
           this.changeFieldState(fieldDetail, FIELD_STATES.AUTO_FILLED);
-          continue;
         }
-      }
-
-      if (ChromeUtils.getClassName(element) === "HTMLSelectElement") {
+      } else if (ChromeUtils.getClassName(element) === "HTMLSelectElement") {
         let cache = this._cacheValue.matchingSelectOption.get(element) || {};
         let option = cache[value] && cache[value].get();
         if (!option) {
           continue;
         }
         // Do not change value or dispatch events if the option is already selected.
         // Use case for multiple select is not considered here.
         if (!option.selected) {
           option.selected = true;
           element.dispatchEvent(new element.ownerGlobal.UIEvent("input", {bubbles: true}));
           element.dispatchEvent(new element.ownerGlobal.Event("change", {bubbles: true}));
         }
         // Autofill highlight appears regardless if value is changed or not
         this.changeFieldState(fieldDetail, FIELD_STATES.AUTO_FILLED);
       }
+      if (fieldDetail.state == FIELD_STATES.AUTO_FILLED) {
+        element.addEventListener("input", this);
+      }
     }
   }
 
   /**
    * Populates result to the preview layers with given profile.
    *
    * @param {Object} profile
    *        A profile to be previewed with
@@ -549,28 +549,20 @@ class FormAutofillSection {
       } else {
         this.winUtils.removeManuallyManagedState(element, mmStateValue);
       }
     }
 
     fieldDetail.state = nextState;
   }
 
-  clearFieldState(focusedInput) {
-    let fieldDetail = this.getFieldDetailByElement(focusedInput);
-    this.changeFieldState(fieldDetail, FIELD_STATES.NORMAL);
-    let targetSet = this._getTargetSet(focusedInput);
-
-    if (!targetSet.fieldDetails.some(detail => detail.state == FIELD_STATES.AUTO_FILLED)) {
-      targetSet.filledRecordGUID = null;
-    }
-  }
-
   resetFieldStates() {
     for (let fieldDetail of this._validDetails) {
+      const element = fieldDetail.elementWeakRef.get();
+      element.removeEventListener("input", this);
       this.changeFieldState(fieldDetail, FIELD_STATES.NORMAL);
     }
     this.address.filledRecordGUID = null;
     this.creditCard.filledRecordGUID = null;
   }
 
   isFilled() {
     return !!(this.address.filledRecordGUID || this.creditCard.filledRecordGUID);
@@ -738,16 +730,36 @@ class FormAutofillSection {
       Services.cpmm.addMessageListener("FormAutofill:DecryptedString", function getResult(result) {
         Services.cpmm.removeMessageListener("FormAutofill:DecryptedString", getResult);
         resolve(result.data);
       });
 
       Services.cpmm.sendAsyncMessage("FormAutofill:GetDecryptedString", {cipherText, reauth});
     });
   }
+
+  handleEvent(event) {
+    switch (event.type) {
+      case "input": {
+        if (!event.isTrusted) {
+          return;
+        }
+        const target = event.target;
+        const fieldDetail = this.getFieldDetailByElement(target);
+        const targetSet = this._getTargetSet(target);
+        this.changeFieldState(fieldDetail, FIELD_STATES.NORMAL);
+
+        if (!targetSet.fieldDetails.some(detail => detail.state == FIELD_STATES.AUTO_FILLED)) {
+          targetSet.filledRecordGUID = null;
+        }
+        target.removeEventListener("input", this);
+        break;
+      }
+    }
+  }
 }
 
 /**
  * Handles profile autofill for a DOM Form element.
  */
 class FormAutofillHandler {
   /**
    * Initialize the form from `FormLike` object to handle the section or form
@@ -923,43 +935,38 @@ class FormAutofillHandler {
    *
    * @param {Object} profile
    *        A profile to be filled in.
    * @param {HTMLElement} focusedInput
    *        A focused input element needed to determine the address or credit
    *        card field.
    */
   async autofillFormFields(profile, focusedInput) {
-    let noFilledSections = !this.hasFilledSection();
+    let noFilledSectionsPreviously = !this.hasFilledSection();
     await this.getSectionByElement(focusedInput).autofillFields(profile, focusedInput);
 
-    // Handle the highlight style resetting caused by user's correction afterward.
-    log.debug("register change handler for filled form:", this.form);
     const onChangeHandler = e => {
       if (!e.isTrusted) {
         return;
       }
-
-      if (e.type == "input") {
-        let section = this.getSectionByElement(e.target);
-        section.clearFieldState(e.target);
-      } else if (e.type == "reset") {
+      if (e.type == "reset") {
         for (let section of this.sections) {
           section.resetFieldStates();
         }
       }
-
       // Unregister listeners once no field is in AUTO_FILLED state.
       if (!this.hasFilledSection()) {
         this.form.rootElement.removeEventListener("input", onChangeHandler);
         this.form.rootElement.removeEventListener("reset", onChangeHandler);
       }
     };
 
-    if (noFilledSections) {
+    if (noFilledSectionsPreviously) {
+      // Handle the highlight style resetting caused by user's correction afterward.
+      log.debug("register change handler for filled form:", this.form);
       this.form.rootElement.addEventListener("input", onChangeHandler);
       this.form.rootElement.addEventListener("reset", onChangeHandler);
     }
   }
 
   handleEvent(event) {
     switch (event.type) {
       case "input":
--- a/browser/extensions/onboarding/OnboardingTelemetry.jsm
+++ b/browser/extensions/onboarding/OnboardingTelemetry.jsm
@@ -11,29 +11,337 @@ const {utils: Cu} = Components;
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyModuleGetters(this, {
   PingCentre: "resource:///modules/PingCentre.jsm",
   Services: "resource://gre/modules/Services.jsm",
 });
 XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator",
   "@mozilla.org/uuid-generator;1", "nsIUUIDGenerator");
 
+// Flag to control if we want to send new/old telemetry
+// TODO: remove this flag and the legacy code in Bug 1419996
+const NEW_TABLE = false;
+
+// Validate the content has non-empty string
+function hasString(str) {
+  return typeof str == "string" && str.length > 0;
+}
+
+// Validate the content is an empty string
+function isEmptyString(str) {
+  return typeof str == "string" && str === "";
+}
+
+// Validate the content is an interger
+function isInteger(i) {
+  return Number.isInteger(i);
+}
+
+// Validate the content is a positive interger
+function isPositiveInteger(i) {
+  return Number.isInteger(i) && i > 0;
+}
+
+// Validate the number is -1
+function isMinusOne(num) {
+  return num === -1;
+}
+
+// Validate the category value is within the list
+function isValidCategory(category) {
+  return ["logo-interactions", "onboarding-interactions",
+    "overlay-interactions", "notification-interactions"]
+    .includes(category);
+}
+
+// Validate the page value is within the list
+function isValidPage(page) {
+  return ["about:newtab", "about:home"].includes(page);
+}
+
+// Validate the tour_type value is within the list
+function isValidTourType(type) {
+  return ["new", "update"].includes(type);
+}
+
+// Validate the bubble state value is within the list
+function isValidBubbleState(str) {
+  return ["bubble", "dot", "hide"].includes(str);
+}
+
+// Validate the logo state value is within the list
+function isValidLogoState(str) {
+  return ["logo", "watermark"].includes(str);
+}
+
+// Validate the notification state value is within the list
+function isValidNotificationState(str) {
+  return ["show", "hide", "finished"].includes(str);
+}
+
+// Validate the column must be defined per ping
+function definePerPing(column) {
+  return function() {
+    throw new Error(`Must define the '${column}' validator per ping because it is not the same for all pings`);
+  };
+}
+
+// Basic validators for session pings
+// client_id, locale are added by PingCentre, IP is added by server
+// so no need check these column here
+const BASIC_SESSION_SCHEMA = {
+  addon_version: hasString,
+  category: isValidCategory,
+  page: isValidPage,
+  parent_session_id: hasString,
+  root_session_id: hasString,
+  session_begin: isInteger,
+  session_end: isInteger,
+  session_id: hasString,
+  tour_type: isValidTourType,
+  type: hasString,
+};
+
+// Basic validators for event pings
+// client_id, locale are added by PingCentre, IP is added by server
+// so no need check these column here
+const BASIC_EVENT_SCHEMA = {
+  addon_version: hasString,
+  bubble_state: definePerPing("bubble_state"),
+  category: isValidCategory,
+  current_tour_id: definePerPing("current_tour_id"),
+  logo_state: definePerPing("logo_state"),
+  notification_impression: definePerPing("notification_impression"),
+  notification_state: definePerPing("notification_state"),
+  page: isValidPage,
+  parent_session_id: hasString,
+  root_session_id: hasString,
+  target_tour_id: definePerPing("target_tour_id"),
+  timestamp: isInteger,
+  tour_type: isValidTourType,
+  type: hasString,
+  width: isPositiveInteger,
+};
+
+/**
+ * We send 2 kinds (firefox-onboarding-event2, firefox-onboarding-session2) of pings to ping centre
+ * server (they call it `topic`). The `internal` state in `topic` field means this event is used internaly to
+ * track states and will not send out any message.
+ *
+ * To save server space and make query easier, we track session begin and end but only send pings
+ * when session end. Therefore the server will get single "onboarding/overlay/notification-session"
+ * event which includes both `session_begin` and `session_end` timestamp.
+ *
+ * We send `session_begin` and `session_end` timestamps instead of `session_duration` diff because
+ * of analytics engineer's request.
+ */
+const EVENT_WHITELIST = {
+  // track when a notification appears.
+  "notification-appear": {
+    topic: "firefox-onboarding-event2",
+    category: "notification-interactions",
+    validators: Object.assign({}, BASIC_EVENT_SCHEMA, {
+      bubble_state: isValidBubbleState,
+      current_tour_id: hasString,
+      logo_state: isValidLogoState,
+      notification_impression: isPositiveInteger,
+      notification_state: isValidNotificationState,
+      target_tour_id: isEmptyString,
+    }),
+  },
+  // track when a user clicks close notification button
+  "notification-close-button-click": {
+    topic: "firefox-onboarding-event2",
+    category: "notification-interactions",
+    validators: Object.assign({}, BASIC_EVENT_SCHEMA, {
+      bubble_state: isValidBubbleState,
+      current_tour_id: hasString,
+      logo_state: isValidLogoState,
+      notification_impression: isPositiveInteger,
+      notification_state: isValidNotificationState,
+      target_tour_id: hasString,
+    }),
+  },
+  // track when a user clicks notification's Call-To-Action button
+  "notification-cta-click": {
+    topic: "firefox-onboarding-event2",
+    category: "notification-interactions",
+    validators: Object.assign({}, BASIC_EVENT_SCHEMA, {
+      bubble_state: isValidBubbleState,
+      current_tour_id: hasString,
+      logo_state: isValidLogoState,
+      notification_impression: isPositiveInteger,
+      notification_state: isValidNotificationState,
+      target_tour_id: hasString,
+    }),
+  },
+  // track the start and end time of the notification session
+  "notification-session": {
+    topic: "firefox-onboarding-session2",
+    category: "notification-interactions",
+    validators: BASIC_SESSION_SCHEMA,
+  },
+  // track the start of a notification
+  "notification-session-begin": {topic: "internal"},
+  // track the end of a notification
+  "notification-session-end": {topic: "internal"},
+  // track when a user clicks the Firefox logo
+  "onboarding-logo-click": {
+    topic: "firefox-onboarding-event2",
+    category: "logo-interactions",
+    validators: Object.assign({}, BASIC_EVENT_SCHEMA, {
+      bubble_state: isValidBubbleState,
+      current_tour_id: isEmptyString,
+      logo_state: isValidLogoState,
+      notification_impression: isMinusOne,
+      notification_state: isValidNotificationState,
+      target_tour_id: isEmptyString,
+    }),
+  },
+  // track when the onboarding is not visisble due to small screen in the 1st load
+  "onboarding-noshow-smallscreen": {
+    topic: "firefox-onboarding-event2",
+    category: "onboarding-interactions",
+    validators: Object.assign({}, BASIC_EVENT_SCHEMA, {
+      bubble_state: isEmptyString,
+      current_tour_id: isEmptyString,
+      logo_state: isEmptyString,
+      notification_impression: isMinusOne,
+      notification_state: isEmptyString,
+      target_tour_id: isEmptyString,
+    }),
+  },
+  // init onboarding session with session_key, page url, and tour_type
+  "onboarding-register-session": {topic: "internal"},
+  // track the start and end time of the onboarding session
+  "onboarding-session": {
+    topic: "firefox-onboarding-session2",
+    category: "onboarding-interactions",
+    validators: BASIC_SESSION_SCHEMA,
+  },
+  // track onboarding start time (when user loads about:home or about:newtab)
+  "onboarding-session-begin": {topic: "internal"},
+  // track onboarding end time (when user unloads about:home or about:newtab)
+  "onboarding-session-end": {topic: "internal"},
+  // track when a user clicks the close overlay button
+  "overlay-close-button-click": {
+    topic: "firefox-onboarding-event2",
+    category: "overlay-interactions",
+    validators: Object.assign({}, BASIC_EVENT_SCHEMA, {
+      bubble_state: isEmptyString,
+      current_tour_id: hasString,
+      logo_state: isEmptyString,
+      notification_impression: isMinusOne,
+      notification_state: isEmptyString,
+      target_tour_id: hasString,
+    }),
+  },
+  // track when a user clicks outside the overlay area to end the tour
+  "overlay-close-outside-click": {
+    topic: "firefox-onboarding-event2",
+    category: "overlay-interactions",
+    validators: Object.assign({}, BASIC_EVENT_SCHEMA, {
+      bubble_state: isEmptyString,
+      current_tour_id: hasString,
+      logo_state: isEmptyString,
+      notification_impression: isMinusOne,
+      notification_state: isEmptyString,
+      target_tour_id: hasString,
+    }),
+  },
+  // track when a user clicks overlay's Call-To-Action button
+  "overlay-cta-click": {
+    topic: "firefox-onboarding-event2",
+    category: "overlay-interactions",
+    validators: Object.assign({}, BASIC_EVENT_SCHEMA, {
+      bubble_state: isEmptyString,
+      current_tour_id: hasString,
+      logo_state: isEmptyString,
+      notification_impression: isMinusOne,
+      notification_state: isEmptyString,
+      target_tour_id: hasString,
+    }),
+  },
+  // track when a tour is shown in the overlay
+  "overlay-current-tour": {
+    topic: "firefox-onboarding-event2",
+    category: "overlay-interactions",
+    validators: Object.assign({}, BASIC_EVENT_SCHEMA, {
+      bubble_state: isEmptyString,
+      current_tour_id: hasString,
+      logo_state: isEmptyString,
+      notification_impression: isMinusOne,
+      notification_state: isEmptyString,
+      target_tour_id: isEmptyString,
+    }),
+  },
+  // track when an overlay is opened and disappeared because the window is resized too small
+  "overlay-disapear-resize": {
+    topic: "firefox-onboarding-event2",
+    category: "overlay-interactions",
+    validators: Object.assign({}, BASIC_EVENT_SCHEMA, {
+      bubble_state: isEmptyString,
+      current_tour_id: isEmptyString,
+      logo_state: isEmptyString,
+      notification_impression: isMinusOne,
+      notification_state: isEmptyString,
+      target_tour_id: isEmptyString,
+    }),
+  },
+  // track when a user clicks a navigation button in the overlay
+  "overlay-nav-click": {
+    topic: "firefox-onboarding-event2",
+    category: "overlay-interactions",
+    validators: Object.assign({}, BASIC_EVENT_SCHEMA, {
+      bubble_state: isEmptyString,
+      current_tour_id: hasString,
+      logo_state: isEmptyString,
+      notification_impression: isMinusOne,
+      notification_state: isEmptyString,
+      target_tour_id: hasString,
+    }),
+  },
+  // track the start and end time of the overlay session
+  "overlay-session": {
+    topic: "firefox-onboarding-session2",
+    category: "overlay-interactions",
+    validators:  BASIC_SESSION_SCHEMA,
+  },
+  // track the start of an overlay session
+  "overlay-session-begin": {topic: "internal"},
+  // track the end of an overlay session
+  "overlay-session-end":  {topic: "internal"},
+  // track when a user clicks 'Skip Tour' button in the overlay
+  "overlay-skip-tour": {
+    topic: "firefox-onboarding-event2",
+    category: "overlay-interactions",
+    validators: Object.assign({}, BASIC_EVENT_SCHEMA, {
+      bubble_state: isEmptyString,
+      current_tour_id: hasString,
+      logo_state: isEmptyString,
+      notification_impression: isMinusOne,
+      notification_state: isEmptyString,
+      target_tour_id: isEmptyString,
+    }),
+  },
+};
+
 /**
  * We send 2 kinds (firefox-onboarding-event, firefox-onboarding-session) of pings to ping centre
  * server (they call it `topic`). The `internal` state in `topic` field means this event is used internaly to
  * track states and will not send out any message.
  *
  * To save server space and make query easier, we track session begin and end but only send pings
  * when session end. Therefore the server will get single "onboarding/overlay/notification-session"
  * event which includes both `session_begin` and `session_end` timestamp.
  *
  * We send `session_begin` and `session_end` timestamps instead of `session_duration` diff because
  * of analytics engineer's request.
  */
-const EVENT_WHITELIST = {
+const OLD_EVENT_WHITELIST = {
   // track when click the notification close button
   "notification-close-button-click": {topic: "firefox-onboarding-event", category: "notification-interactions"},
   // track when click the notification Call-To-Action button
   "notification-cta-click": {topic: "firefox-onboarding-event", category: "notification-interactions"},
   // track when notification is shown
   "notification-session-begin": {topic: "internal"},
   // track when the notification closed
   "notification-session-end": {topic: "firefox-onboarding-session", category: "notification-interactions"},
@@ -83,21 +391,28 @@ let OnboardingTelemetry = {
     this.state.sessions[session_key] = {
       page,
       session_id,
       tour_type,
     };
   },
 
   process(data) {
-    let { event, session_key } = data;
+    if (NEW_TABLE) {
+      throw new Error("Will implement in bug 1413830");
+    } else {
+      this.processOldPings(data);
+    }
+  },
 
-    let topic = EVENT_WHITELIST[event] && EVENT_WHITELIST[event].topic;
+  processOldPings(data) {
+    let { event, session_key } = data;
+    let topic = OLD_EVENT_WHITELIST[event] && OLD_EVENT_WHITELIST[event].topic;
     if (!topic) {
-      throw new Error(`ping-centre doesn't know ${event}, only knows ${Object.keys(EVENT_WHITELIST)}`);
+      throw new Error(`ping-centre doesn't know ${event}, only knows ${Object.keys(OLD_EVENT_WHITELIST)}`);
     }
 
     if (event === "onboarding-register-session") {
       this.registerNewTelemetrySession(data);
     }
 
     if (!this.state.sessions[session_key]) {
       throw new Error(`should pass valid session_key`);
@@ -111,39 +426,39 @@ let OnboardingTelemetry = {
         case "overlay-session-begin":
           this.state.sessions[session_key].overlay_session_begin = Date.now();
           break;
         case "notification-session-begin":
           this.state.sessions[session_key].notification_session_begin = Date.now();
           break;
       }
     } else {
-      this._send(topic, data);
+      this._sendOldPings(topic, data);
     }
   },
 
   // send out pings by topic
-  _send(topic, data) {
+  _sendOldPings(topic, data) {
     let {
       addon_version,
     } = this.state;
     let {
       event,
       tour_id = "",
       session_key,
     } = data;
     let {
       notification_session_begin,
       onboarding_session_begin,
       overlay_session_begin,
       page,
       session_id,
       tour_type,
     } = this.state.sessions[session_key];
-    let category = EVENT_WHITELIST[event].category;
+    let category = OLD_EVENT_WHITELIST[event].category;
     // the field is used to identify how user open the overlay (through default logo or watermark),
     // the number of open from notification can be retrieved via `notification-cta-click` event
     let tour_source = Services.prefs.getStringPref("browser.onboarding.state", "default");
     let session_begin;
     switch (topic) {
       case "firefox-onboarding-session":
         switch (event) {
           case "onboarding-session-end":
@@ -198,10 +513,41 @@ let OnboardingTelemetry = {
           session_id,
           timestamp,
           tour_id,
           tour_source,
           tour_type,
         }, {filter: ONBOARDING_ID});
         break;
     }
+  },
+
+  // validate data sanitation and make sure correct ping params are sent
+  _validatePayload(payload) {
+    let event = payload.type;
+    let { validators } = EVENT_WHITELIST[event];
+    if (!validators) {
+      throw new Error(`Event ${event} without validators should not be sent.`);
+    }
+    let validatorKeys = Object.keys(validators);
+    // Not send with undefined column
+    if (Object.keys(payload).length > validatorKeys.length) {
+      throw new Error(`Event ${event} want to send more columns than expect, should not be sent.`);
+    }
+    let results = {};
+    let failed = false;
+    // Per column validation
+    for (let key of validatorKeys) {
+      if (payload[key] !== undefined) {
+        results[key] = validators[key](payload[key]);
+        if (!results[key]) {
+          failed = true;
+        }
+      } else {
+        results[key] = false;
+        failed = true;
+      }
+    }
+    if (failed) {
+      throw new Error(`Event ${event} contains incorrect data: ${JSON.stringify(results)}, should not be sent.`);
+    }
   }
 };
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -962,16 +962,23 @@ you can use these alternative items. Oth
 <!ENTITY updateRestart.message2 "After a quick restart, &brandShorterName; will restore all your open tabs and windows that are not in Private Browsing mode.">
 <!ENTITY updateRestart.header.message2 "Restart to update &brandShorterName;.">
 <!ENTITY updateRestart.acceptButton.label "Restart and Restore">
 <!ENTITY updateRestart.acceptButton.accesskey "R">
 <!ENTITY updateRestart.cancelButton.label "Not Now">
 <!ENTITY updateRestart.cancelButton.accesskey "N">
 <!ENTITY updateRestart.panelUI.label2 "Restart to update &brandShorterName;">
 
+<!ENTITY newTabControlled.message "An extension has changed the page you see when you open a New Tab. You can restore your settings if you do not want this change.">
+<!ENTITY newTabControlled.header.message "Your New Tab has changed.">
+<!ENTITY newTabControlled.keepButton.label "Keep Changes">
+<!ENTITY newTabControlled.keepButton.accesskey "K">
+<!ENTITY newTabControlled.restoreButton.label "Restore Settings">
+<!ENTITY newTabControlled.restoreButton.accesskey "R">
+
 <!ENTITY pageActionButton.tooltip "Page actions">
 <!ENTITY pageAction.addToUrlbar.label "Add to Address Bar">
 <!ENTITY pageAction.removeFromUrlbar.label "Remove from Address Bar">
 <!ENTITY pageAction.allowInUrlbar.label "Show in Address Bar">
 <!ENTITY pageAction.disallowInUrlbar.label "Don’t Show in Address Bar">
 <!ENTITY pageAction.manageExtension.label "Manage Extension…">
 
 <!ENTITY pageAction.sendTabToDevice.label "Send Tab to Device">
--- a/browser/themes/shared/customizableui/panelUI.inc.css
+++ b/browser/themes/shared/customizableui/panelUI.inc.css
@@ -42,16 +42,17 @@
 #PanelUI-menu-button[badge-status] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
   display: -moz-box;
   height: 10px;
   width: 10px;
   background-size: contain;
   border: none;
 }
 
+#PanelUI-menu-button[badge-status="extension-new-tab"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
 #PanelUI-menu-button[badge-status="download-success"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
   display: none;
 }
 
 #PanelUI-menu-button[badge-status="update-available"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
 #PanelUI-menu-button[badge-status="update-manual"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
 #PanelUI-menu-button[badge-status="update-restart"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
   background: #74BF43 url(chrome://browser/skin/update-badge.svg) no-repeat center;
@@ -321,16 +322,40 @@ panelview:not([mainview]) .toolbarbutton
   text-align: start;
   display: -moz-box;
 }
 
 .cui-widget-panel > .panel-arrowcontainer > .panel-arrowcontent {
   padding: 4px 0;
 }
 
+/* START notification popups for extension controlled content */
+#extension-notification-panel > .panel-arrowcontainer > .panel-arrowcontent {
+  padding: 0;
+}
+
+#extension-new-tab-notification > .popup-notification-body-container > .popup-notification-body {
+  width: 30em;
+}
+
+#extension-new-tab-notification > .popup-notification-body-container > .popup-notification-body > hbox > vbox > .popup-notification-description {
+  font-size: 1.3em;
+  font-weight: lighter;
+}
+
+#extension-new-tab-notification-description {
+  margin-bottom: 0;
+}
+
+#extension-new-tab-notification > .popup-notification-body-container > .popup-notification-body > .popup-notification-warning,
+#extension-new-tab-notification > .popup-notification-body-container > .popup-notification-icon {
+  display: none;
+}
+/* END notification popups for extension controlled content */
+
 #appMenu-popup > .panel-arrowcontainer > .panel-arrowcontent,
 panel[photon] > .panel-arrowcontainer > .panel-arrowcontent {
   padding: 0;
 }
 
 #appMenu-popup panelview,
 #customizationui-widget-multiview panelview:not([extension]) {
   min-width: @menuPanelWidth@;
--- a/browser/themes/shared/privatebrowsing/aboutPrivateBrowsing.css
+++ b/browser/themes/shared/privatebrowsing/aboutPrivateBrowsing.css
@@ -35,27 +35,23 @@ p {
   line-height: 1.5em;
 }
 
 .list-row {
   overflow: auto;
 }
 
 .list-row > ul > li {
-  float: left;
+  float: inline-start;
   width: 16em;
   line-height: 2em;
   margin-inline-start: 1em;
   margin-bottom: 0;
 }
 
-.list-row > ul > li:dir(rtl) {
-  float: right;
-}
-
 .title {
   background-image: url("chrome://browser/skin/privatebrowsing/private-browsing.svg");
   background-position: left center;
   background-size: 2em;
   line-height: 2em;
   margin-inline-start: calc(-2em - 10px);
   padding-inline-start: calc(2em + 10px);
 }
@@ -148,21 +144,16 @@ a.button {
 }
 
 .toggle:checked + .toggle-btn {
   background: #16da00;
   border-color: #0CA700;
 }
 
 .toggle:checked + .toggle-btn::after {
-  left: 21px;
-}
-
-.toggle:checked + .toggle-btn:dir(rtl)::after {
-  left: auto;
-  right: 16px;
+  offset-inline-start: 21px;
 }
 
 .toggle:-moz-focusring + .toggle-btn {
   outline: 2px solid rgba(0, 149, 221, 0.5);
   outline-offset: 1px;
   -moz-outline-radius: 2px;
 }
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
@@ -207,16 +207,17 @@ skip-if = true #	Bug 1403188
 [browser_jsterm_autocomplete_inside_text.js]
 [browser_jsterm_autocomplete_nav_and_tab_key.js]
 [browser_jsterm_autocomplete_return_key_no_selection.js]
 [browser_jsterm_autocomplete_return_key.js]
 [browser_jsterm_autocomplete-properties-with-non-alphanumeric-names.js]
 [browser_jsterm_completion.js]
 [browser_jsterm_copy_command.js]
 [browser_jsterm_dollar.js]
+[browser_jsterm_history.js]
 [browser_jsterm_history_persist.js]
 [browser_jsterm_inspect.js]
 [browser_jsterm_no_autocompletion_on_defined_variables.js]
 [browser_jsterm_no_input_and_tab_key_pressed.js]
 [browser_jsterm_no_input_change_and_tab_key_pressed.js]
 [browser_jsterm_popup_close_on_tab_switch.js]
 [browser_jsterm_popup.js]
 [browser_jsterm_selfxss.js]
@@ -285,18 +286,16 @@ skip-if = true # Bug 1408937
 skip-if = true #	Bug 1404382
 [browser_webconsole_filter_scroll.js]
 skip-if = true #	Bug 1404392
 [browser_webconsole_filters.js]
 [browser_webconsole_filters_persist.js]
 [browser_webconsole_highlighter_console_helper.js]
 skip-if = true #	Bug 1404853
 # old console skip-if = true # Requires direct access to content nodes
-[browser_webconsole_history.js]
-skip-if = true # Bug 1408938
 [browser_webconsole_history_arrow_keys.js]
 skip-if = true # Bug 1408939
 [browser_webconsole_history_nav.js]
 skip-if = true # Bug 1408940
 [browser_webconsole_hpkp_invalid-headers.js]
 skip-if = true #	Bug 1405340
 # old console skip-if = (os == 'win' && bits == 64) # Bug 1390001
 [browser_webconsole_hsts_invalid-headers.js]
rename from devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_history.js
rename to devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_history.js
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_history.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_history.js
@@ -2,61 +2,55 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests the console history feature accessed via the up and down arrow keys.
 
 "use strict";
 
-const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
-                 "test/test-console.html";
-
-// Constants used for defining the direction of JSTerm input history navigation.
+const TEST_URI = "data:text/html;charset=UTF-8,test";
 const HISTORY_BACK = -1;
 const HISTORY_FORWARD = 1;
-
-add_task(function* () {
-  yield loadTab(TEST_URI);
-  let hud = yield openConsole();
-  hud.jsterm.clearOutput();
+const COMMANDS = ["document", "window", "window.location"];
 
-  let jsterm = hud.jsterm;
-  let input = jsterm.inputNode;
+add_task(async function () {
+  const { jsterm } = await openNewTabAndConsole(TEST_URI);
+  const { inputNode } = jsterm;
+  jsterm.clearOutput();
 
-  let executeList = ["document", "window", "window.location"];
-
-  for (let item of executeList) {
-    input.value = item;
-    yield jsterm.execute();
+  for (let command of COMMANDS) {
+    info(`Executing command ${command}`);
+    jsterm.setInputValue(command);
+    await jsterm.execute();
   }
 
-  for (let x = executeList.length - 1; x != -1; x--) {
+  for (let x = COMMANDS.length - 1; x != -1; x--) {
     jsterm.historyPeruse(HISTORY_BACK);
-    is(input.value, executeList[x], "check history previous idx:" + x);
+    is(inputNode.value, COMMANDS[x], "check history previous idx:" + x);
   }
 
   jsterm.historyPeruse(HISTORY_BACK);
-  is(input.value, executeList[0], "test that item is still index 0");
+  is(inputNode.value, COMMANDS[0], "test that item is still index 0");
 
   jsterm.historyPeruse(HISTORY_BACK);
-  is(input.value, executeList[0], "test that item is still still index 0");
+  is(inputNode.value, COMMANDS[0], "test that item is still still index 0");
 
-  for (let i = 1; i < executeList.length; i++) {
+  for (let i = 1; i < COMMANDS.length; i++) {
     jsterm.historyPeruse(HISTORY_FORWARD);
-    is(input.value, executeList[i], "check history next idx:" + i);
+    is(inputNode.value, COMMANDS[i], "check history next idx:" + i);
   }
 
   jsterm.historyPeruse(HISTORY_FORWARD);
-  is(input.value, "", "check input is empty again");
+  is(inputNode.value, "", "check input is empty again");
 
   // Simulate pressing Arrow_Down a few times and then if Arrow_Up shows
   // the previous item from history again.
   jsterm.historyPeruse(HISTORY_FORWARD);
   jsterm.historyPeruse(HISTORY_FORWARD);
   jsterm.historyPeruse(HISTORY_FORWARD);
 
-  is(input.value, "", "check input is still empty");
+  is(inputNode.value, "", "check input is still empty");
 
-  let idxLast = executeList.length - 1;
+  let idxLast = COMMANDS.length - 1;
   jsterm.historyPeruse(HISTORY_BACK);
-  is(input.value, executeList[idxLast], "check history next idx:" + idxLast);
+  is(inputNode.value, COMMANDS[idxLast], "check history next idx:" + idxLast);
 });
--- a/dom/animation/Animation.h
+++ b/dom/animation/Animation.h
@@ -107,22 +107,22 @@ public:
   void SetStartTime(const Nullable<TimeDuration>& aNewStartTime);
   Nullable<TimeDuration> GetCurrentTime() const;
   void SetCurrentTime(const TimeDuration& aNewCurrentTime);
   double PlaybackRate() const { return mPlaybackRate; }
   void SetPlaybackRate(double aPlaybackRate);
   AnimationPlayState PlayState() const;
   bool Pending() const { return mPendingState != PendingState::NotPending; }
   virtual Promise* GetReady(ErrorResult& aRv);
-  virtual Promise* GetFinished(ErrorResult& aRv);
+  Promise* GetFinished(ErrorResult& aRv);
   void Cancel();
-  virtual void Finish(ErrorResult& aRv);
+  void Finish(ErrorResult& aRv);
   virtual void Play(ErrorResult& aRv, LimitBehavior aLimitBehavior);
   virtual void Pause(ErrorResult& aRv);
-  virtual void Reverse(ErrorResult& aRv);
+  void Reverse(ErrorResult& aRv);
   bool IsRunningOnCompositor() const;
   IMPL_EVENT_HANDLER(finish);
   IMPL_EVENT_HANDLER(cancel);
 
   // Wrapper functions for Animation DOM methods when called
   // from script.
   //
   // We often use the same methods internally and from script but when called
--- a/dom/animation/test/mozilla/file_restyles.html
+++ b/dom/animation/test/mozilla/file_restyles.html
@@ -301,16 +301,24 @@ waitForAllPaints(() => {
   });
 
   add_task(
     async function restyling_transform_animations_in_scrolled_out_element() {
       if (!offscreenThrottlingEnabled) {
         return;
       }
 
+      // Skip this test on Android since this test have been failing
+      // intermittently.
+      // Bug 1413817: We should audit this test still fails once we have the
+      // conformant Promise micro task.
+      if (isAndroid) {
+        return;
+      }
+
       await SpecialPowers.pushPrefEnv({ set: [["ui.showHideScrollbars", 1]] });
 
       var parentElement = addDiv(null,
         { style: 'overflow-y: scroll; height: 20px;' });
       var div = addDiv(null,
         { style: 'animation: rotate 100s; position: relative; top: 100px;' });
       parentElement.appendChild(div);
       var animation = div.getAnimations()[0];
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -1974,17 +1974,22 @@ void HTMLMediaElement::DoLoad()
     LOG(LogLevel::Debug, ("%p Media not allowed", this));
     return;
   }
 
   if (mIsRunningLoadMethod) {
     return;
   }
 
+  // Detect if user has interacted with element so that play will not be
+  // blocked when initiated by a script. This enables sites to capture user
+  // intent to play by calling load() in the click handler of a "catalog
+  // view" of a gallery of videos.
   if (EventStateManager::IsHandlingUserInput()) {
+    mHasUserInteractedLoadOrSeek = true;
     // Mark the channel as urgent-start when autopaly so that it will play the
     // media from src after loading enough resource.
     if (HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay)) {
       mUseUrgentStartForChannel = true;
     }
   }
 
   SetPlayedOrSeeked(false);
@@ -2744,16 +2749,22 @@ HTMLMediaElement::Seek(double aTime,
   MOZ_ASSERT(!mozilla::IsNaN(aTime));
 
   RefPtr<Promise> promise = CreateDOMPromise(aRv);
 
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
+  // Detect if user has interacted with element by seeking so that
+  // play will not be blocked when initiated by a script.
+  if (EventStateManager::IsHandlingUserInput()) {
+    mHasUserInteractedLoadOrSeek = true;
+  }
+
   StopSuspendingAfterFirstFrame();
 
   if (mSrcStream) {
     // do nothing since media streams have an empty Seekable range.
     promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
     return promise.forget();
   }
 
@@ -4027,16 +4038,17 @@ HTMLMediaElement::HTMLMediaElement(alrea
     mShuttingDown(false),
     mSuspendedForPreloadNone(false),
     mSrcStreamIsPlaying(false),
     mMediaSecurityVerified(false),
     mCORSMode(CORS_NONE),
     mIsEncrypted(false),
     mWaitingForKey(NOT_WAITING_FOR_KEY),
     mDisableVideo(false),
+    mHasUserInteractedLoadOrSeek(false),
     mFirstFrameLoaded(false),
     mDefaultPlaybackStartPosition(0.0),
     mHasSuspendTaint(false),
     mMediaTracksConstructed(false),
     mVisibilityState(Visibility::UNTRACKED),
     mErrorSink(new ErrorSink(this)),
     mAudioChannelWrapper(new AudioChannelAgentCallback(this))
 {
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -736,16 +736,23 @@ public:
   void NotifyCueUpdated(TextTrackCue *aCue) {
     if (mTextTrackManager) {
       mTextTrackManager->NotifyCueUpdated(aCue);
     }
   }
 
   void NotifyCueDisplayStatesChanged();
 
+  bool GetAndClearHasUserInteractedLoadOrSeek()
+  {
+    bool result = mHasUserInteractedLoadOrSeek;
+    mHasUserInteractedLoadOrSeek = false;
+    return result;
+  }
+
   // A method to check whether we are currently playing.
   bool IsCurrentlyPlaying() const;
 
   // Returns true if the media element is being destroyed. Used in
   // dormancy checks to prevent dormant processing for an element
   // that will soon be gone.
   bool IsBeingDestroyed();
 
@@ -1773,16 +1780,20 @@ private:
   TimeDurationAccumulator mPlayTime;
 
   // Total time a video has spent playing while hidden.
   TimeDurationAccumulator mHiddenPlayTime;
 
   // Total time a video has (or would have) spent in video-decode-suspend mode.
   TimeDurationAccumulator mVideoDecodeSuspendTime;
 
+  // True if user has called load() or seek() via user input.
+  // It's *only* use for checking autoplay policy
+  bool mHasUserInteractedLoadOrSeek;
+
   // True if the first frame has been successfully loaded.
   bool mFirstFrameLoaded;
 
   // Media elements also have a default playback start position, which must
   // initially be set to zero seconds. This time is used to allow the element to
   // be seeked even before the media is loaded.
   double mDefaultPlaybackStartPosition;
 
--- a/dom/media/AutoplayPolicy.cpp
+++ b/dom/media/AutoplayPolicy.cpp
@@ -33,13 +33,16 @@ AutoplayPolicy::IsMediaElementAllowedToP
   }
 
   if (Preferences::GetBool("media.autoplay.enabled.user-gestures-needed")) {
     return AutoplayPolicy::IsDocumentAllowedToPlay(aElement->OwnerDoc());
   }
 
   // TODO : this old way would be removed when user-gestures-needed becomes
   // as a default option to block autoplay.
-  return EventStateManager::IsHandlingUserInput();
+  // If user triggers load() or seek() before play(), we would also allow the
+  // following play().
+  return aElement->GetAndClearHasUserInteractedLoadOrSeek() ||
+         EventStateManager::IsHandlingUserInput();
 }
 
 } // namespace dom
 } // namespace mozilla
\ No newline at end of file
--- a/dom/media/ChannelMediaResource.cpp
+++ b/dom/media/ChannelMediaResource.cpp
@@ -844,23 +844,23 @@ ChannelMediaResource::UpdatePrincipal()
   nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
   if (secMan) {
     secMan->GetChannelResultPrincipal(mChannel, getter_AddRefs(principal));
     mCacheStream.UpdatePrincipal(principal);
   }
 }
 
 void
-ChannelMediaResource::CacheClientNotifySuspendedStatusChanged()
+ChannelMediaResource::CacheClientNotifySuspendedStatusChanged(bool aSuspended)
 {
   mCallback->AbstractMainThread()->Dispatch(NewRunnableMethod<bool>(
     "MediaResourceCallback::NotifySuspendedStatusChanged",
     mCallback.get(),
     &MediaResourceCallback::NotifySuspendedStatusChanged,
-    IsSuspendedByCache()));
+    aSuspended));
 }
 
 nsresult
 ChannelMediaResource::Seek(int64_t aOffset, bool aResume)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mClosed) {
@@ -939,22 +939,16 @@ ChannelMediaResource::GetCachedDataEnd(i
 
 bool
 ChannelMediaResource::IsDataCachedToEndOfResource(int64_t aOffset)
 {
   return mCacheStream.IsDataCachedToEndOfStream(aOffset);
 }
 
 bool
-ChannelMediaResource::IsSuspendedByCache()
-{
-  return mCacheStream.AreAllStreamsForResourceSuspended();
-}
-
-bool
 ChannelMediaResource::IsSuspended()
 {
   return mSuspendAgent.IsSuspended();
 }
 
 void
 ChannelMediaResource::SetReadMode(MediaCacheStream::ReadMode aMode)
 {
--- a/dom/media/ChannelMediaResource.h
+++ b/dom/media/ChannelMediaResource.h
@@ -83,17 +83,17 @@ public:
   void CacheClientNotifyDataReceived();
   // Notify that we reached the end of the stream. This can happen even
   // if this stream didn't read any data, since another stream might have
   // received data for the same resource.
   void CacheClientNotifyDataEnded(nsresult aStatus);
   // Notify that the principal for the cached resource changed.
   void CacheClientNotifyPrincipalChanged();
   // Notify the decoder that the cache suspended status changed.
-  void CacheClientNotifySuspendedStatusChanged();
+  void CacheClientNotifySuspendedStatusChanged(bool aSuspended);
 
   // These are called on the main thread by MediaCache. These shouldn't block,
   // but they may grab locks --- the media cache is not holding its lock
   // when these are called.
   // Start a new load at the given aOffset. The old load is cancelled
   // and no more data from the old load will be notified via
   // MediaCacheStream::NotifyDataReceived/Ended.
   void CacheClientSeek(int64_t aOffset, bool aResume);
@@ -187,17 +187,16 @@ public:
   };
   friend class Listener;
 
   nsresult GetCachedRanges(MediaByteRangeSet& aRanges) override;
 
 protected:
   nsresult Seek(int64_t aOffset, bool aResume);
 
-  bool IsSuspendedByCache();
   // These are called on the main thread by Listener.
   nsresult OnStartRequest(nsIRequest* aRequest, int64_t aRequestOffset);
   nsresult OnStopRequest(nsIRequest* aRequest, nsresult aStatus);
   nsresult OnDataAvailable(uint32_t aLoadID,
                            nsIInputStream* aStream,
                            uint32_t aCount);
   nsresult OnChannelRedirect(nsIChannel* aOld,
                              nsIChannel* aNew,
--- a/dom/media/MediaCache.cpp
+++ b/dom/media/MediaCache.cpp
@@ -828,16 +828,17 @@ OffsetInBlock(int64_t aOffset)
   return int32_t(aOffset % MediaCache::BLOCK_SIZE);
 }
 
 int32_t
 MediaCache::FindBlockForIncomingData(TimeStamp aNow,
                                      MediaCacheStream* aStream,
                                      int32_t aStreamBlockIndex)
 {
+  MOZ_ASSERT(sThread->IsOnCurrentThread());
   mReentrantMonitor.AssertCurrentThreadIn();
 
   int32_t blockIndex =
     FindReusableBlock(aNow, aStream, aStreamBlockIndex, INT32_MAX);
 
   if (blockIndex < 0 || !IsBlockFree(blockIndex)) {
     // The block returned is already allocated.
     // Don't reuse it if a) there's room to expand the cache or
@@ -858,16 +859,18 @@ MediaCache::FindBlockForIncomingData(Tim
   }
 
   return blockIndex;
 }
 
 bool
 MediaCache::BlockIsReusable(int32_t aBlockIndex)
 {
+  mReentrantMonitor.AssertCurrentThreadIn();
+
   Block* block = &mIndex[aBlockIndex];
   for (uint32_t i = 0; i < block->mOwners.Length(); ++i) {
     MediaCacheStream* stream = block->mOwners[i].mStream;
     if (stream->mPinCount > 0 ||
         uint32_t(OffsetToBlockIndex(stream->mStreamOffset)) ==
           block->mOwners[i].mStreamBlock) {
       return false;
     }
@@ -899,16 +902,17 @@ MediaCache::AppendMostReusableBlock(Bloc
 }
 
 int32_t
 MediaCache::FindReusableBlock(TimeStamp aNow,
                                 MediaCacheStream* aForStream,
                                 int32_t aForStreamBlock,
                                 int32_t aMaxSearchBlockIndex)
 {
+  MOZ_ASSERT(sThread->IsOnCurrentThread());
   mReentrantMonitor.AssertCurrentThreadIn();
 
   uint32_t length = std::min(uint32_t(aMaxSearchBlockIndex), uint32_t(mIndex.Length()));
 
   if (aForStream && aForStreamBlock > 0 &&
       uint32_t(aForStreamBlock) <= aForStream->mBlocks.Length()) {
     int32_t prevCacheBlock = aForStream->mBlocks[aForStreamBlock - 1];
     if (prevCacheBlock >= 0) {
@@ -963,16 +967,18 @@ MediaCache::FindReusableBlock(TimeStamp 
   }
 
   return latestUseBlock;
 }
 
 MediaCache::BlockList*
 MediaCache::GetListForBlock(BlockOwner* aBlock)
 {
+  mReentrantMonitor.AssertCurrentThreadIn();
+
   switch (aBlock->mClass) {
   case METADATA_BLOCK:
     NS_ASSERTION(aBlock->mStream, "Metadata block has no stream?");
     return &aBlock->mStream->mMetadataBlocks;
   case PLAYED_BLOCK:
     NS_ASSERTION(aBlock->mStream, "Metadata block has no stream?");
     return &aBlock->mStream->mPlayedBlocks;
   case READAHEAD_BLOCK:
@@ -982,16 +988,18 @@ MediaCache::GetListForBlock(BlockOwner* 
     NS_ERROR("Invalid block class");
     return nullptr;
   }
 }
 
 MediaCache::BlockOwner*
 MediaCache::GetBlockOwner(int32_t aBlockIndex, MediaCacheStream* aStream)
 {
+  mReentrantMonitor.AssertCurrentThreadIn();
+
   Block* block = &mIndex[aBlockIndex];
   for (uint32_t i = 0; i < block->mOwners.Length(); ++i) {
     if (block->mOwners[i].mStream == aStream)
       return &block->mOwners[i];
   }
   return nullptr;
 }
 
@@ -1037,16 +1045,18 @@ MediaCache::SwapBlocks(int32_t aBlockInd
   }
 
   Verify();
 }
 
 void
 MediaCache::RemoveBlockOwner(int32_t aBlockIndex, MediaCacheStream* aStream)
 {
+  mReentrantMonitor.AssertCurrentThreadIn();
+
   Block* block = &mIndex[aBlockIndex];
   for (uint32_t i = 0; i < block->mOwners.Length(); ++i) {
     BlockOwner* bo = &block->mOwners[i];
     if (bo->mStream == aStream) {
       GetListForBlock(bo)->RemoveBlock(aBlockIndex);
       bo->mStream->mBlocks[bo->mStreamBlock] = -1;
       block->mOwners.RemoveElementAt(i);
       if (block->mOwners.IsEmpty()) {
@@ -1057,16 +1067,18 @@ MediaCache::RemoveBlockOwner(int32_t aBl
   }
 }
 
 void
 MediaCache::AddBlockOwnerAsReadahead(int32_t aBlockIndex,
                                        MediaCacheStream* aStream,
                                        int32_t aStreamBlockIndex)
 {
+  mReentrantMonitor.AssertCurrentThreadIn();
+
   Block* block = &mIndex[aBlockIndex];
   if (block->mOwners.IsEmpty()) {
     mFreeBlocks.RemoveBlock(aBlockIndex);
   }
   BlockOwner* bo = block->mOwners.AppendElement();
   mBlockOwnersWatermark =
     std::max(mBlockOwnersWatermark, uint32_t(block->mOwners.Length()));
   bo->mStream = aStream;
@@ -1097,16 +1109,17 @@ MediaCache::FreeBlock(int32_t aBlock)
   block->mOwners.Clear();
   mFreeBlocks.AddFirstBlock(aBlock);
   Verify();
 }
 
 TimeDuration
 MediaCache::PredictNextUse(TimeStamp aNow, int32_t aBlock)
 {
+  MOZ_ASSERT(sThread->IsOnCurrentThread());
   mReentrantMonitor.AssertCurrentThreadIn();
   NS_ASSERTION(!IsBlockFree(aBlock), "aBlock is free");
 
   Block* block = &mIndex[aBlock];
   // Blocks can be belong to multiple streams. The predicted next use
   // time is the earliest time predicted by any of the streams.
   TimeDuration result;
   for (uint32_t i = 0; i < block->mOwners.Length(); ++i) {
@@ -1152,16 +1165,17 @@ MediaCache::PredictNextUse(TimeStamp aNo
     }
   }
   return result;
 }
 
 TimeDuration
 MediaCache::PredictNextUseForIncomingData(MediaCacheStream* aStream)
 {
+  MOZ_ASSERT(sThread->IsOnCurrentThread());
   mReentrantMonitor.AssertCurrentThreadIn();
 
   int64_t bytesAhead = aStream->mChannelOffset - aStream->mStreamOffset;
   if (bytesAhead <= -BLOCK_SIZE) {
     // Hmm, no idea when data behind us will be used. Guess 24 hours.
     return TimeDuration::FromSeconds(24*60*60);
   }
   if (bytesAhead <= 0)
@@ -1532,17 +1546,18 @@ MediaCache::Update()
         break;
     }
   }
 
   // Notify streams about the suspended status changes.
   for (uint32_t i = 0; i < mSuspendedStatusToNotify.Length(); ++i) {
     MediaCache::ResourceStreamIterator iter(this, mSuspendedStatusToNotify[i]);
     while (MediaCacheStream* stream = iter.Next()) {
-      stream->mClient->CacheClientNotifySuspendedStatusChanged();
+      stream->mClient->CacheClientNotifySuspendedStatusChanged(
+        stream->AreAllStreamsForResourceSuspended());
     }
   }
   mSuspendedStatusToNotify.Clear();
 }
 
 class UpdateEvent : public Runnable
 {
 public:
@@ -1622,18 +1637,17 @@ MediaCache::Verify()
       lastStreamBlock = nextStreamBlock;
       block = stream->mReadaheadBlocks.GetNextBlock(block);
     }
   }
 }
 #endif
 
 void
-MediaCache::InsertReadaheadBlock(BlockOwner* aBlockOwner,
-                                   int32_t aBlockIndex)
+MediaCache::InsertReadaheadBlock(BlockOwner* aBlockOwner, int32_t aBlockIndex)
 {
   mReentrantMonitor.AssertCurrentThreadIn();
 
   // Find the last block whose stream block is before aBlockIndex's
   // stream block, and insert after it
   MediaCacheStream* stream = aBlockOwner->mStream;
   int32_t readaheadIndex = stream->mReadaheadBlocks.GetLastBlock();
   while (readaheadIndex >= 0) {
@@ -1654,16 +1668,17 @@ MediaCache::InsertReadaheadBlock(BlockOw
 
 void
 MediaCache::AllocateAndWriteBlock(MediaCacheStream* aStream,
                                   int32_t aStreamBlockIndex,
                                   MediaCacheStream::ReadMode aMode,
                                   Span<const uint8_t> aData1,
                                   Span<const uint8_t> aData2)
 {
+  MOZ_ASSERT(sThread->IsOnCurrentThread());
   mReentrantMonitor.AssertCurrentThreadIn();
 
   // Remove all cached copies of this block
   ResourceStreamIterator iter(this, aStream->mResourceID);
   while (MediaCacheStream* stream = iter.Next()) {
     while (aStreamBlockIndex >= int32_t(stream->mBlocks.Length())) {
       stream->mBlocks.AppendElement(-1);
     }
@@ -2100,16 +2115,18 @@ MediaCacheStream::NotifyDataReceived(uin
   // avoid waking up reader threads unnecessarily
   mon.NotifyAll();
 }
 
 void
 MediaCacheStream::FlushPartialBlockInternal(bool aNotifyAll,
                                             ReentrantMonitorAutoEnter& aReentrantMonitor)
 {
+  MOZ_ASSERT(OwnerThread()->IsOnCurrentThread());
+
   int32_t blockIndex = OffsetToBlockIndexUnchecked(mChannelOffset);
   int32_t blockOffset = OffsetInBlock(mChannelOffset);
   if (blockOffset > 0) {
     LOG("Stream %p writing partial block: [%d] bytes; "
         "mStreamOffset [%" PRId64 "] mChannelOffset[%"
         PRId64 "] mStreamLength [%" PRId64 "] notifying: [%s]",
         this, blockOffset, mStreamOffset, mChannelOffset, mStreamLength,
         aNotifyAll ? "yes" : "no");
@@ -2129,36 +2146,21 @@ MediaCacheStream::FlushPartialBlockInter
   // that will never come.
   if ((blockOffset > 0 || mChannelOffset == 0) && aNotifyAll) {
     // Wake up readers who may be waiting for this data
     aReentrantMonitor.NotifyAll();
   }
 }
 
 void
-MediaCacheStream::FlushPartialBlock()
-{
-  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
-
-  ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
-
-  // Write the current partial block to memory.
-  // Note: This writes a full block, so if data is not at the end of the
-  // stream, the decoder must subsequently choose correct start and end offsets
-  // for reading/seeking.
-  FlushPartialBlockInternal(false, mon);
-
-  mMediaCache->QueueUpdate();
-}
-
-void
 MediaCacheStream::NotifyDataEndedInternal(uint32_t aLoadID,
                                           nsresult aStatus,
                                           bool aReopenOnError)
 {
+  MOZ_ASSERT(OwnerThread()->IsOnCurrentThread());
   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
 
   if (mClosed || aLoadID != mLoadID) {
     // Nothing to do if the stream is closed or a new load has begun.
     return;
   }
 
   // Note that aStatus might have succeeded --- this might be a normal close
@@ -2284,17 +2286,19 @@ MediaCacheStream::~MediaCacheStream()
       lengthKb);
   Telemetry::Accumulate(Telemetry::HistogramID::MEDIACACHESTREAM_LENGTH_KB,
                         lengthKb);
 }
 
 bool
 MediaCacheStream::AreAllStreamsForResourceSuspended()
 {
+  MOZ_ASSERT(!NS_IsMainThread());
   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
+
   MediaCache::ResourceStreamIterator iter(mMediaCache, mResourceID);
   // Look for a stream that's able to read the data we need
   int64_t dataOffset = -1;
   while (MediaCacheStream* stream = iter.Next()) {
     if (stream->mCacheSuspended || stream->mChannelEnded || stream->mClosed) {
       continue;
     }
     if (dataOffset < 0) {
@@ -2335,65 +2339,72 @@ MediaCacheStream::Close()
   // it from CloseInternal since that gets called by Update() itself
   // sometimes, and we try to not to queue updates from Update().
   mMediaCache->QueueUpdate();
 }
 
 void
 MediaCacheStream::Pin()
 {
+  // TODO: Assert non-main thread.
   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
   ++mPinCount;
   // Queue an Update since we may no longer want to read more into the
   // cache, if this stream's block have become non-evictable
   mMediaCache->QueueUpdate();
 }
 
 void
 MediaCacheStream::Unpin()
 {
+  // TODO: Assert non-main thread.
   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
   NS_ASSERTION(mPinCount > 0, "Unbalanced Unpin");
   --mPinCount;
   // Queue an Update since we may be able to read more into the
   // cache, if this stream's block have become evictable
   mMediaCache->QueueUpdate();
 }
 
 int64_t
 MediaCacheStream::GetLength()
 {
+  // TODO: Assert non-main thread.
   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
   return mStreamLength;
 }
 
 int64_t
 MediaCacheStream::GetOffset() const
 {
+  // TODO: Assert non-main thread.
   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
   return mChannelOffset;
 }
 
 int64_t
 MediaCacheStream::GetNextCachedData(int64_t aOffset)
 {
+  MOZ_ASSERT(!NS_IsMainThread());
   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
   return GetNextCachedDataInternal(aOffset);
 }
 
 int64_t
 MediaCacheStream::GetCachedDataEnd(int64_t aOffset)
 {
+  // TODO: Assert non-main thread.
   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
   return GetCachedDataEndInternal(aOffset);
 }
 
 bool
 MediaCacheStream::IsDataCachedToEndOfStream(int64_t aOffset)
 {
+  MOZ_ASSERT(!NS_IsMainThread());
   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
   if (mStreamLength < 0)
     return false;
   return GetCachedDataEndInternal(aOffset) >= mStreamLength;
 }
 
 int64_t
 MediaCacheStream::GetCachedDataEndInternal(int64_t aOffset)
@@ -2468,16 +2479,17 @@ MediaCacheStream::GetNextCachedDataInter
 
   NS_NOTREACHED("Should return in loop");
   return -1;
 }
 
 void
 MediaCacheStream::SetReadMode(ReadMode aMode)
 {
+  // TODO: Assert non-main thread.
   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
   if (aMode == mCurrentMode)
     return;
   mCurrentMode = aMode;
   mMediaCache->QueueUpdate();
 }
 
 void
@@ -2607,18 +2619,17 @@ MediaCacheStream::ReadBlockFromCache(int
   }
 
   return bytesRead;
 }
 
 nsresult
 MediaCacheStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
 {
-  NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
-
+  MOZ_ASSERT(!NS_IsMainThread());
   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
 
   // Cache the offset in case it is changed again when we are waiting for the
   // monitor to be notified to avoid reading at the wrong position.
   auto streamOffset = mStreamOffset;
 
   // The buffer we are about to fill.
   auto buffer = MakeSpan<char>(aBuffer, aCount);
@@ -2705,27 +2716,27 @@ MediaCacheStream::Read(char* aBuffer, ui
   mStreamOffset = streamOffset;
   return NS_OK;
 }
 
 nsresult
 MediaCacheStream::ReadAt(int64_t aOffset, char* aBuffer,
                          uint32_t aCount, uint32_t* aBytes)
 {
-  NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
-
+  MOZ_ASSERT(!NS_IsMainThread());
   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
   nsresult rv = Seek(aOffset);
   if (NS_FAILED(rv)) return rv;
   return Read(aBuffer, aCount, aBytes);
 }
 
 nsresult
 MediaCacheStream::ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount)
 {
+  MOZ_ASSERT(!NS_IsMainThread());
   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
 
   // The buffer we are about to fill.
   auto buffer = MakeSpan<char>(aBuffer, aCount);
 
   // Read one block (or part of a block) at a time
   int64_t streamOffset = aOffset;
   while (!buffer.IsEmpty()) {
@@ -2853,16 +2864,17 @@ MediaCacheStream::InitAsClone(MediaCache
 nsIEventTarget*
 MediaCacheStream::OwnerThread() const
 {
   return mMediaCache->OwnerThread();
 }
 
 nsresult MediaCacheStream::GetCachedRanges(MediaByteRangeSet& aRanges)
 {
+  MOZ_ASSERT(!NS_IsMainThread());
   // Take the monitor, so that the cached data ranges can't grow while we're
   // trying to loop over them.
   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
 
   // We must be pinned while running this, otherwise the cached data ranges may
   // shrink while we're trying to loop over them.
   NS_ASSERTION(mPinCount > 0, "Must be pinned");
 
--- a/dom/media/MediaCache.h
+++ b/dom/media/MediaCache.h
@@ -264,19 +264,16 @@ public:
   // Notifies the cache that data has been received. The stream already
   // knows the offset because data is received in sequence and
   // the starting offset is known via NotifyDataStarted or because
   // the cache requested the offset in
   // ChannelMediaResource::CacheClientSeek, or because it defaulted to 0.
   void NotifyDataReceived(uint32_t aLoadID,
                           uint32_t aCount,
                           const uint8_t* aData);
-  // Notifies the cache that the current bytes should be written to disk.
-  // Called on the main thread.
-  void FlushPartialBlock();
 
   // Set the load ID so the following NotifyDataEnded() call can work properly.
   // Used in some rare cases where NotifyDataEnded() is called without the
   // preceding NotifyDataStarted().
   void NotifyLoadID(uint32_t aLoadID);
 
   // Notifies the cache that the channel has closed with the given status.
   void NotifyDataEnded(uint32_t aLoadID,
@@ -443,18 +440,17 @@ private:
   // This method assumes that the cache monitor is held and can be called on
   // any thread.
   int64_t GetCachedDataEndInternal(int64_t aOffset);
   // Returns the offset of the first byte of cached data at or after aOffset,
   // or -1 if there is no such cached data.
   // This method assumes that the cache monitor is held and can be called on
   // any thread.
   int64_t GetNextCachedDataInternal(int64_t aOffset);
-  // Writes |mPartialBlock| to disk.
-  // Used by |NotifyDataEnded| and |FlushPartialBlock|.
+  // Used by |NotifyDataEnded| to write |mPartialBlock| to disk.
   // If |aNotifyAll| is true, this function will wake up readers who may be
   // waiting on the media cache monitor. Called on the main thread only.
   void FlushPartialBlockInternal(bool aNotify, ReentrantMonitorAutoEnter& aReentrantMonitor);
 
   void NotifyDataStartedInternal(uint32_t aLoadID,
                                  int64_t aOffset,
                                  bool aSeekable,
                                  int64_t aLength);
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -524,16 +524,20 @@ MediaDecoder::OnPlaybackEvent(MediaEvent
 {
   switch (aEvent) {
     case MediaEventType::PlaybackEnded:
       PlaybackEnded();
       break;
     case MediaEventType::SeekStarted:
       SeekingStarted();
       break;
+    case MediaEventType::Loop:
+      GetOwner()->DispatchAsyncEvent(NS_LITERAL_STRING("seeking"));
+      GetOwner()->DispatchAsyncEvent(NS_LITERAL_STRING("seeked"));
+      break;
     case MediaEventType::Invalidate:
       Invalidate();
       break;
     case MediaEventType::EnterVideoSuspend:
       GetOwner()->DispatchAsyncEvent(NS_LITERAL_STRING("mozentervideosuspend"));
       break;
     case MediaEventType::ExitVideoSuspend:
       GetOwner()->DispatchAsyncEvent(NS_LITERAL_STRING("mozexitvideosuspend"));
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -603,37 +603,17 @@ public:
       TimeDuration decodeDuration = TimeStamp::Now() - mDecodeStartTime;
       SLOG("Exiting DECODING, decoded for %.3lfs", decodeDuration.ToSeconds());
     }
     mDormantTimer.Reset();
     mOnAudioPopped.DisconnectIfExists();
     mOnVideoPopped.DisconnectIfExists();
   }
 
-  void Step() override
-  {
-    if (mMaster->mPlayState != MediaDecoder::PLAY_STATE_PLAYING &&
-        mMaster->IsPlaying()) {
-      // We're playing, but the element/decoder is in paused state. Stop
-      // playing!
-      mMaster->StopPlayback();
-    }
-
-    // Start playback if necessary so that the clock can be properly queried.
-    if (!mIsPrerolling) {
-      mMaster->MaybeStartPlayback();
-    }
-
-    mMaster->UpdatePlaybackPositionPeriodically();
-
-    MOZ_ASSERT(!mMaster->IsPlaying() || mMaster->IsStateMachineScheduled(),
-               "Must have timer scheduled");
-
-    MaybeStartBuffering();
-  }
+  void Step() override;
 
   State GetState() const override
   {
     return DECODER_STATE_DECODING;
   }
 
   void HandleAudioDecoded(AudioData* aAudio) override
   {
@@ -1935,16 +1915,18 @@ public:
 
     // StopPlayback in order to reset the IsPlaying() state so audio
     // is restarted correctly.
     mMaster->StopPlayback();
 
     if (!mSentPlaybackEndedEvent) {
       auto clockTime =
         std::max(mMaster->AudioEndTime(), mMaster->VideoEndTime());
+      // Correct the time over the end once looping was turned on.
+      Reader()->AdjustByLooping(clockTime);
       if (mMaster->mDuration.Ref()->IsInfinite()) {
         // We have a finite duration when playback reaches the end.
         mMaster->mDuration = Some(clockTime);
       }
       mMaster->UpdatePlaybackPosition(clockTime);
 
       // Ensure readyState is updated before firing the 'ended' event.
       mMaster->mOnNextFrameStatus.Notify(
@@ -2193,16 +2175,23 @@ DecodeMetadataState::OnMetadataRead(Meta
 
   MOZ_ASSERT(mMaster->mDuration.Ref().isSome());
 
   mMaster->mMetadataLoadedEvent.Notify(
     Move(aMetadata.mInfo),
     Move(aMetadata.mTags),
     MediaDecoderEventVisibility::Observable);
 
+  // Check whether the media satisfies the requirement of seamless looing.
+  // (Before checking the media is audio only, we need to get metadata first.)
+  mMaster->mSeamlessLoopingAllowed = MediaPrefs::SeamlessLooping() &&
+                                     mMaster->HasAudio() &&
+                                     !mMaster->HasVideo();
+  mMaster->LoopingChanged();
+
   SetState<DecodingFirstFrameState>();
 }
 
 void
 MediaDecoderStateMachine::
 DormantState::HandlePlayStateChanged(MediaDecoder::PlayState aPlayState)
 {
   if (aPlayState == MediaDecoder::PLAY_STATE_PLAYING) {
@@ -2299,16 +2288,67 @@ DecodingState::Enter()
   // Will enter dormant when playback is paused for a while.
   if (mMaster->mPlayState == MediaDecoder::PLAY_STATE_PAUSED) {
     StartDormantTimer();
   }
 }
 
 void
 MediaDecoderStateMachine::
+DecodingState::Step()
+{
+  if (mMaster->mPlayState != MediaDecoder::PLAY_STATE_PLAYING &&
+      mMaster->IsPlaying()) {
+    // We're playing, but the element/decoder is in paused state. Stop
+    // playing!
+    mMaster->StopPlayback();
+  }
+
+  // Start playback if necessary so that the clock can be properly queried.
+  if (!mIsPrerolling) {
+    mMaster->MaybeStartPlayback();
+  }
+
+  TimeUnit before = mMaster->GetMediaTime();
+  mMaster->UpdatePlaybackPositionPeriodically();
+
+  // Fire the `seeking` and `seeked` events to meet the HTML spec
+  // when the media is looped back from the end to the beginning.
+  if (before > mMaster->GetMediaTime()) {
+    MOZ_ASSERT(mMaster->mLooping);
+    mMaster->mOnPlaybackEvent.Notify(MediaEventType::Loop);
+  // After looping is cancelled, the time won't be corrected, and therefore we
+  // can check it to see if the end of the media track is reached. Make sure
+  // the media is started before comparing the time, or it's meaningless.
+  // Without checking IsStarted(), the media will be terminated immediately
+  // after seeking forward. When the state is just transited from seeking state,
+  // GetClock() is smaller than GetMediaTime(), since GetMediaTime() is updated
+  // upon seek is completed while GetClock() will be updated after the media is
+  // started again.
+  } else if (mMaster->mMediaSink->IsStarted() && !mMaster->mLooping) {
+    TimeUnit adjusted = mMaster->GetClock();
+    Reader()->AdjustByLooping(adjusted);
+    if (adjusted < before) {
+      mMaster->StopPlayback();
+      mMaster->mAudioDataRequest.DisconnectIfExists();
+      AudioQueue().Finish();
+      mMaster->mAudioCompleted = true;
+      SetState<CompletedState>();
+      return;
+    }
+  }
+
+  MOZ_ASSERT(!mMaster->IsPlaying() || mMaster->IsStateMachineScheduled(),
+             "Must have timer scheduled");
+
+  MaybeStartBuffering();
+}
+
+void
+MediaDecoderStateMachine::
 DecodingState::HandleEndOfAudio()
 {
   AudioQueue().Finish();
   if (!mMaster->IsVideoDecoding()) {
     SetState<CompletedState>();
   } else {
     MaybeStopPrerolling();
   }
@@ -2616,16 +2656,17 @@ MediaDecoderStateMachine::MediaDecoderSt
   mAudioCaptured(false),
   mMinimizePreroll(aDecoder->GetMinimizePreroll()),
   mSentFirstFrameLoadedEvent(false),
   mVideoDecodeSuspended(false),
   mVideoDecodeSuspendTimer(mTaskQueue),
   mOutputStreamManager(new OutputStreamManager()),
   mVideoDecodeMode(VideoDecodeMode::Normal),
   mIsMSE(aDecoder->IsMSE()),
+  mSeamlessLoopingAllowed(false),
   INIT_MIRROR(mBuffered, TimeIntervals()),
   INIT_MIRROR(mPlayState, MediaDecoder::PLAY_STATE_LOADING),
   INIT_MIRROR(mVolume, 1.0),
   INIT_MIRROR(mPreservesPitch, true),
   INIT_MIRROR(mLooping, false),
   INIT_MIRROR(mSameOriginMedia, false),
   INIT_MIRROR(mMediaPrincipalHandle, PRINCIPAL_HANDLE_NONE),
   INIT_CANONICAL(mDuration, NullableTimeUnit()),
@@ -2672,16 +2713,17 @@ MediaDecoderStateMachine::Initialization
 
   // Initialize watchers.
   mWatchManager.Watch(mBuffered,
                       &MediaDecoderStateMachine::BufferedRangeUpdated);
   mWatchManager.Watch(mVolume, &MediaDecoderStateMachine::VolumeChanged);
   mWatchManager.Watch(mPreservesPitch,
                       &MediaDecoderStateMachine::PreservesPitchChanged);
   mWatchManager.Watch(mPlayState, &MediaDecoderStateMachine::PlayStateChanged);
+  mWatchManager.Watch(mLooping, &MediaDecoderStateMachine::LoopingChanged);
 
   MOZ_ASSERT(!mStateObj);
   auto* s = new DecodeMetadataState(this);
   mStateObj.reset(s);
   s->Enter();
 }
 
 void
@@ -3459,29 +3501,35 @@ MediaDecoderStateMachine::UpdatePlayback
     return;
   }
 
   // Cap the current time to the larger of the audio and video end time.
   // This ensures that if we're running off the system clock, we don't
   // advance the clock to after the media end time.
   if (VideoEndTime() > TimeUnit::Zero() || AudioEndTime() > TimeUnit::Zero()) {
 
-    const auto clockTime = GetClock();
+    auto clockTime = GetClock();
+
+    // Once looping was turned on, the time is probably larger than the duration
+    // of the media track, so the time over the end should be corrected.
+    mReader->AdjustByLooping(clockTime);
+    bool loopback = clockTime < GetMediaTime() && mLooping;
+
     // Skip frames up to the frame at the playback position, and figure out
     // the time remaining until it's time to display the next frame and drop
     // the current frame.
     NS_ASSERTION(clockTime >= TimeUnit::Zero(), "Should have positive clock time.");
 
     // These will be non -1 if we've displayed a video frame, or played an audio
     // frame.
     auto maxEndTime = std::max(VideoEndTime(), AudioEndTime());
     auto t = std::min(clockTime, maxEndTime);
     // FIXME: Bug 1091422 - chained ogg files hit this assertion.
     //MOZ_ASSERT(t >= GetMediaTime());
-    if (t > GetMediaTime()) {
+    if (loopback || t > GetMediaTime()) {
       UpdatePlaybackPosition(t);
     }
   }
   // Note we have to update playback position before releasing the monitor.
   // Otherwise, MediaDecoder::AddOutputStream could kick in when we are outside
   // the monitor and get a staled value from GetCurrentTimeUs() which hits the
   // assertion in GetClock().
 
@@ -3555,16 +3603,25 @@ MediaDecoderStateMachine::SetPlaybackRat
 }
 
 void MediaDecoderStateMachine::PreservesPitchChanged()
 {
   MOZ_ASSERT(OnTaskQueue());
   mMediaSink->SetPreservesPitch(mPreservesPitch);
 }
 
+void
+MediaDecoderStateMachine::LoopingChanged()
+{
+  MOZ_ASSERT(OnTaskQueue());
+  if (mSeamlessLoopingAllowed) {
+    mReader->SetSeamlessLoopingEnabled(mLooping);
+  }
+}
+
 TimeUnit
 MediaDecoderStateMachine::AudioEndTime() const
 {
   MOZ_ASSERT(OnTaskQueue());
   if (mMediaSink->IsStarted()) {
     return mMediaSink->GetEndTime(TrackInfo::kAudioTrack);
   }
   return GetMediaTime();
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -115,16 +115,17 @@ class TaskQueue;
 extern LazyLogModule gMediaDecoderLog;
 
 enum class MediaEventType : int8_t
 {
   PlaybackStarted,
   PlaybackStopped,
   PlaybackEnded,
   SeekStarted,
+  Loop,
   Invalidate,
   EnterVideoSuspend,
   ExitVideoSuspend,
   StartVideoSuspendTimer,
   CancelVideoSuspendTimer,
   VideoOnlySeekBegin,
   VideoOnlySeekCompleted,
 };
@@ -354,16 +355,17 @@ protected:
   void OnAudioPopped(const RefPtr<AudioData>& aSample);
   void OnVideoPopped(const RefPtr<VideoData>& aSample);
 
   void AudioAudibleChanged(bool aAudible);
 
   void VolumeChanged();
   void SetPlaybackRate(double aPlaybackRate);
   void PreservesPitchChanged();
+  void LoopingChanged();
 
   MediaQueue<AudioData>& AudioQueue() { return mAudioQueue; }
   MediaQueue<VideoData>& VideoQueue() { return mVideoQueue; }
 
   // True if we are low in decoded audio/video data.
   // May not be invoked when mReader->UseBufferingHeuristics() is false.
   bool HasLowDecodedData();
 
@@ -661,16 +663,18 @@ private:
   MediaEventProducer<MediaResult> mOnPlaybackErrorEvent;
 
   MediaEventProducer<DecoderDoctorEvent> mOnDecoderDoctorEvent;
 
   MediaEventProducer<NextFrameStatus> mOnNextFrameStatus;
 
   const bool mIsMSE;
 
+  bool mSeamlessLoopingAllowed;
+
 private:
   // The buffered range. Mirrored from the decoder thread.
   Mirror<media::TimeIntervals> mBuffered;
 
   // The current play state, mirrored from the main thread.
   Mirror<MediaDecoder::PlayState> mPlayState;
 
   // Volume of playback. 0.0 = muted. 1.0 = full volume.
--- a/dom/media/MediaPrefs.h
+++ b/dom/media/MediaPrefs.h
@@ -198,16 +198,18 @@ private:
 
   // resume background video decoding when the cursor is hovering over the tab.
   DECL_MEDIA_PREF("media.resume-bkgnd-video-on-tabhover",     ResumeVideoDecodingOnTabHover, bool, false);
 
   // Enable sandboxing support for cubeb
   DECL_MEDIA_PREF("media.cubeb.sandbox",                      CubebSandbox, bool, false);
   DECL_MEDIA_PREF("media.videocontrols.lock-video-orientation",  VideoOrientationLockEnabled, bool, false);
 
+  // Media Seamless Looping
+  DECL_MEDIA_PREF("media.seamless-looping",                   SeamlessLooping, bool, true);
 public:
   // Manage the singleton:
   static MediaPrefs& GetSingleton();
   static bool SingletonExists();
 
 private:
   template<class T> friend class StaticAutoPtr;
   static StaticAutoPtr<MediaPrefs> sInstance;
--- a/dom/media/ReaderProxy.cpp
+++ b/dom/media/ReaderProxy.cpp
@@ -14,30 +14,31 @@ namespace mozilla {
 ReaderProxy::ReaderProxy(AbstractThread* aOwnerThread,
                          MediaFormatReader* aReader)
   : mOwnerThread(aOwnerThread)
   , mReader(aReader)
   , mWatchManager(this, aReader->OwnerThread())
   , mDuration(aReader->OwnerThread(),
               media::NullableTimeUnit(),
               "ReaderProxy::mDuration (Mirror)")
+  , mSeamlessLoopingBlocked(false)
+  , mSeamlessLoopingEnabled(false)
 {
   // Must support either heuristic buffering or WaitForData().
   MOZ_ASSERT(mReader->UseBufferingHeuristics() ||
              mReader->IsWaitForDataSupported());
 }
 
 ReaderProxy::~ReaderProxy()
 {}
 
 media::TimeUnit
 ReaderProxy::StartTime() const
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
-  MOZ_ASSERT(!mShutdown);
   return mStartTime.ref();
 }
 
 RefPtr<ReaderProxy::MetadataPromise>
 ReaderProxy::ReadMetadata()
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
   MOZ_ASSERT(!mShutdown);
@@ -48,43 +49,97 @@ ReaderProxy::ReadMetadata()
     ->Then(mOwnerThread,
            __func__,
            this,
            &ReaderProxy::OnMetadataRead,
            &ReaderProxy::OnMetadataNotRead);
 }
 
 RefPtr<ReaderProxy::AudioDataPromise>
+ReaderProxy::OnAudioDataRequestCompleted(RefPtr<AudioData> aAudio)
+{
+  MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+
+  // Subtract the start time and add the looping-offset time.
+  int64_t offset =
+    StartTime().ToMicroseconds() - mLoopingOffset.ToMicroseconds();
+  aAudio->AdjustForStartTime(offset);
+  mLastAudioEndTime = aAudio->mTime;
+  return AudioDataPromise::CreateAndResolve(aAudio.forget(), __func__);
+}
+
+RefPtr<ReaderProxy::AudioDataPromise>
+ReaderProxy::OnAudioDataRequestFailed(const MediaResult& aError)
+{
+  MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+
+  if (mSeamlessLoopingBlocked || !mSeamlessLoopingEnabled ||
+      aError.Code() != NS_ERROR_DOM_MEDIA_END_OF_STREAM) {
+    return AudioDataPromise::CreateAndReject(aError, __func__);
+  }
+
+  // The data time in the audio queue is assumed to be increased linearly,
+  // so we need to add the last ending time as the offset to correct the
+  // audio data time in the next round when seamless looping is enabled.
+  mLoopingOffset = mLastAudioEndTime;
+
+  // Save the duration of the audio track if it hasn't been set.
+  if (!mAudioDuration.IsValid()) {
+    mAudioDuration = mLastAudioEndTime;
+  }
+
+  // For seamless looping, the demuxer is sought to the beginning and then
+  // keep requesting decoded data in advance, upon receiving EOS.
+  // The MDSM will not be aware of the EOS and keep receiving decoded data
+  // as usual while looping is on.
+  RefPtr<ReaderProxy> self = this;
+  RefPtr<MediaFormatReader> reader = mReader;
+  ResetDecode(TrackInfo::kAudioTrack);
+  return SeekInternal(SeekTarget(media::TimeUnit::Zero(), SeekTarget::Accurate))
+    ->Then(mReader->OwnerThread(),
+           __func__,
+           [reader]() { return reader->RequestAudioData(); },
+           [](const SeekRejectValue& aReject) {
+             return AudioDataPromise::CreateAndReject(aReject.mError, __func__);
+           })
+    ->Then(mOwnerThread,
+           __func__,
+           [self](RefPtr<AudioData> aAudio) {
+             return self->OnAudioDataRequestCompleted(aAudio.forget());
+           },
+           [](const MediaResult& aError) {
+             return AudioDataPromise::CreateAndReject(aError, __func__);
+           });
+}
+
+RefPtr<ReaderProxy::AudioDataPromise>
 ReaderProxy::RequestAudioData()
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
   MOZ_ASSERT(!mShutdown);
 
-  int64_t startTime = StartTime().ToMicroseconds();
+  mSeamlessLoopingBlocked = false;
   return InvokeAsync(mReader->OwnerThread(),
                      mReader.get(),
                      __func__,
                      &MediaFormatReader::RequestAudioData)
     ->Then(mOwnerThread,
            __func__,
-           [startTime](RefPtr<AudioData> aAudio) {
-             aAudio->AdjustForStartTime(startTime);
-             return AudioDataPromise::CreateAndResolve(aAudio.forget(), __func__);
-           },
-           [](const MediaResult& aError) {
-             return AudioDataPromise::CreateAndReject(aError, __func__);
-           });
+           this,
+           &ReaderProxy::OnAudioDataRequestCompleted,
+           &ReaderProxy::OnAudioDataRequestFailed);
 }
 
 RefPtr<ReaderProxy::VideoDataPromise>
 ReaderProxy::RequestVideoData(const media::TimeUnit& aTimeThreshold)
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
   MOZ_ASSERT(!mShutdown);
 
+  mSeamlessLoopingBlocked = false;
   const auto threshold = aTimeThreshold > media::TimeUnit::Zero()
                          ? aTimeThreshold + StartTime()
                          : aTimeThreshold;
 
   int64_t startTime = StartTime().ToMicroseconds();
   return InvokeAsync(mReader->OwnerThread(),
                      mReader.get(),
                      __func__,
@@ -101,16 +156,28 @@ ReaderProxy::RequestVideoData(const medi
              return VideoDataPromise::CreateAndReject(aError, __func__);
            });
 }
 
 RefPtr<ReaderProxy::SeekPromise>
 ReaderProxy::Seek(const SeekTarget& aTarget)
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+  mSeamlessLoopingBlocked = true;
+  // Reset the members for seamless looping if the seek is triggered outside.
+  mLoopingOffset = media::TimeUnit::Zero();
+  mLastAudioEndTime = media::TimeUnit::Zero();
+  mAudioDuration = media::TimeUnit::Invalid();
+  return SeekInternal(aTarget);
+}
+
+RefPtr<ReaderProxy::SeekPromise>
+ReaderProxy::SeekInternal(const SeekTarget& aTarget)
+{
+  MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
   SeekTarget adjustedTarget = aTarget;
   adjustedTarget.SetTime(adjustedTarget.GetTime() + StartTime());
   return InvokeAsync(mReader->OwnerThread(),
                      mReader.get(),
                      __func__,
                      &MediaFormatReader::Seek,
                      Move(adjustedTarget));
 }
@@ -217,9 +284,27 @@ ReaderProxy::SetCanonicalDuration(
     "ReaderProxy::SetCanonicalDuration", [this, self, canonical]() {
       mDuration.Connect(canonical);
       mWatchManager.Watch(mDuration, &ReaderProxy::UpdateDuration);
     });
   nsresult rv = mReader->OwnerThread()->Dispatch(r.forget());
   MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
 }
 
+void
+ReaderProxy::SetSeamlessLoopingEnabled(bool aEnabled)
+{
+  MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+  mSeamlessLoopingEnabled = aEnabled;
+}
+
+void
+ReaderProxy::AdjustByLooping(media::TimeUnit& aTime)
+{
+  MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+  MOZ_ASSERT(!mShutdown);
+  MOZ_ASSERT(!mSeamlessLoopingEnabled || !mSeamlessLoopingBlocked);
+  if (mAudioDuration.IsValid() && mAudioDuration.IsPositive()) {
+    aTime = aTime % mAudioDuration.ToMicroseconds();
+  }
+}
+
 } // namespace mozilla
--- a/dom/media/ReaderProxy.h
+++ b/dom/media/ReaderProxy.h
@@ -79,30 +79,52 @@ public:
 
   void SetCDMProxy(CDMProxy* aProxy) { mReader->SetCDMProxy(aProxy); }
 
   void SetVideoBlankDecode(bool aIsBlankDecode);
 
   void SetCanonicalDuration(
     AbstractCanonical<media::NullableTimeUnit>* aCanonical);
 
+  void SetSeamlessLoopingEnabled(bool aEnabled);
+
+  void AdjustByLooping(media::TimeUnit& aTime);
+
 private:
   ~ReaderProxy();
   RefPtr<MetadataPromise> OnMetadataRead(MetadataHolder&& aMetadata);
   RefPtr<MetadataPromise> OnMetadataNotRead(const MediaResult& aError);
   void UpdateDuration();
+  RefPtr<SeekPromise> SeekInternal(const SeekTarget& aTarget);
+
+  RefPtr<ReaderProxy::AudioDataPromise> OnAudioDataRequestCompleted(
+    RefPtr<AudioData> aAudio);
+  RefPtr<ReaderProxy::AudioDataPromise> OnAudioDataRequestFailed(
+    const MediaResult& aError);
 
   const RefPtr<AbstractThread> mOwnerThread;
   const RefPtr<MediaFormatReader> mReader;
 
   bool mShutdown = false;
   Maybe<media::TimeUnit> mStartTime;
 
   // State-watching manager.
   WatchManager<ReaderProxy> mWatchManager;
 
   // Duration, mirrored from the state machine task queue.
   Mirror<media::NullableTimeUnit> mDuration;
+
+  // The total duration of audio looping in previous rounds.
+  media::TimeUnit mLoopingOffset = media::TimeUnit::Zero();
+  // To keep tracking the latest time of decoded audio data.
+  media::TimeUnit mLastAudioEndTime = media::TimeUnit::Zero();
+  // The duration of the audio track.
+  media::TimeUnit mAudioDuration = media::TimeUnit::Invalid();
+
+  // To prevent seamless looping while seeking.
+  bool mSeamlessLoopingBlocked;
+  // Indicates whether we should loop the media.
+  bool mSeamlessLoopingEnabled;
 };
 
 } // namespace mozilla
 
 #endif // ReaderProxy_h_
--- a/dom/media/TextTrackRegion.cpp
+++ b/dom/media/TextTrackRegion.cpp
@@ -1,16 +1,15 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 et tw=78: */
 /* 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 "mozilla/dom/TextTrackRegion.h"
-#include "mozilla/dom/VTTRegionBinding.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TextTrackRegion, mParent)
 NS_IMPL_CYCLE_COLLECTING_ADDREF(TextTrackRegion)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(TextTrackRegion)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TextTrackRegion)
@@ -40,16 +39,17 @@ TextTrackRegion::Constructor(const Globa
 TextTrackRegion::TextTrackRegion(nsISupports* aGlobal)
   : mParent(aGlobal)
   , mWidth(100)
   , mLines(3)
   , mRegionAnchorX(0)
   , mRegionAnchorY(100)
   , mViewportAnchorX(0)
   , mViewportAnchorY(100)
+  , mScroll(ScrollSetting::_empty)
 {
 }
 
 void
 TextTrackRegion::CopyValues(TextTrackRegion& aRegion)
 {
   mId = aRegion.Id();
   mWidth = aRegion.Width();
--- a/dom/media/TextTrackRegion.h
+++ b/dom/media/TextTrackRegion.h
@@ -7,16 +7,17 @@
 #ifndef mozilla_dom_TextTrackRegion_h
 #define mozilla_dom_TextTrackRegion_h
 
 #include "nsCycleCollectionParticipant.h"
 #include "nsString.h"
 #include "nsWrapperCache.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/TextTrack.h"
+#include "mozilla/dom/VTTRegionBinding.h"
 #include "mozilla/Preferences.h"
 
 namespace mozilla {
 namespace dom {
 
 class GlobalObject;
 class TextTrack;
 
@@ -42,19 +43,23 @@ public:
   static already_AddRefed<TextTrackRegion>
   Constructor(const GlobalObject& aGlobal, ErrorResult& aRv);
 
   double Lines() const
   {
     return mLines;
   }
 
-  void SetLines(double aLines)
+  void SetLines(double aLines, ErrorResult& aRv)
   {
-    mLines = aLines;
+    if (aLines < 0) {
+      aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+    } else {
+      mLines = aLines;
+    }
   }
 
   double Width() const
   {
     return mWidth;
   }
 
   void SetWidth(double aWidth, ErrorResult& aRv)
@@ -107,29 +112,26 @@ public:
 
   void SetViewportAnchorY(double aVal, ErrorResult& aRv)
   {
     if (!InvalidValue(aVal, aRv)) {
       mViewportAnchorY = aVal;
     }
   }
 
-  void GetScroll(nsAString& aScroll) const
+  ScrollSetting Scroll() const
   {
-    aScroll = mScroll;
+    return mScroll;
   }
 
-  void SetScroll(const nsAString& aScroll, ErrorResult& aRv)
+  void SetScroll(const ScrollSetting& aScroll)
   {
-    if (!aScroll.EqualsLiteral("") && !aScroll.EqualsLiteral("up")) {
-      aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
-      return;
+    if (aScroll == ScrollSetting::_empty || aScroll == ScrollSetting::Up) {
+      mScroll = aScroll;
     }
-
-    mScroll = aScroll;
   }
 
   void GetId(nsAString& aId) const
   {
     aId = mId;
   }
 
   void SetId(const nsAString& aId)
@@ -140,37 +142,33 @@ public:
   /** end WebIDL Methods. */
 
 
   // Helper to aid copying of a given TextTrackRegion's width, lines,
   // anchor, viewport and scroll values.
   void CopyValues(TextTrackRegion& aRegion);
 
   // -----helpers-------
-  const nsAString& Scroll() const
-  {
-    return mScroll;
-  }
   const nsAString& Id() const
   {
     return mId;
   }
 
 private:
   ~TextTrackRegion() {}
 
   nsCOMPtr<nsISupports> mParent;
   nsString mId;
   double mWidth;
   long mLines;
   double mRegionAnchorX;
   double mRegionAnchorY;
   double mViewportAnchorX;
   double mViewportAnchorY;
-  nsString mScroll;
+  ScrollSetting mScroll;
 
   // Helper to ensure new value is in the range: 0.0% - 100.0%; throws
   // an IndexSizeError otherwise.
   inline bool InvalidValue(double aValue, ErrorResult& aRv)
   {
     if(aValue < 0.0  || aValue > 100.0) {
       aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
       return true;
--- a/dom/media/TimeUnits.h
+++ b/dom/media/TimeUnits.h
@@ -172,16 +172,20 @@ public:
   TimeUnit MultDouble(double aVal) const
   {
     return TimeUnit::FromSeconds(ToSeconds() * aVal);
   }
   friend TimeUnit operator/(const TimeUnit& aUnit, int aVal)
   {
     return TimeUnit(aUnit.mValue / aVal);
   }
+  friend TimeUnit operator%(const TimeUnit& aUnit, int aVal)
+  {
+    return TimeUnit(aUnit.mValue % aVal);
+  }
 
   bool IsValid() const { return mValue.isValid(); }
 
   constexpr TimeUnit()
     : mValue(CheckedInt64(0))
   {
   }
 
--- a/dom/media/fake-cdm/cdm-fake.cpp
+++ b/dom/media/fake-cdm/cdm-fake.cpp
@@ -47,21 +47,21 @@ void INITIALIZE_CDM_MODULE() {
 
 CDM_API
 void* CreateCdmInstance(int cdm_interface_version,
                         const char* key_system,
                         uint32_t key_system_size,
                         GetCdmHostFunc get_cdm_host_func,
                         void* user_data)
 {
-  if (cdm_interface_version != cdm::ContentDecryptionModule_8::kVersion) {
-    // Only support CDM version 8 currently.
+  if (cdm_interface_version != cdm::ContentDecryptionModule_9::kVersion) {
+    // Only support CDM version 9 currently.
     return nullptr;
   }
-  cdm::Host_8* host = static_cast<cdm::Host_8*>(
+  cdm::Host_9* host = static_cast<cdm::Host_9*>(
     get_cdm_host_func(cdm_interface_version, user_data));
   return new FakeDecryptor(host);
 }
 
 
 CDM_API
 bool
 VerifyCdmHost_0(const cdm::HostFile* aHostFiles, uint32_t aNumFiles)
--- a/dom/media/fake-cdm/cdm-test-decryptor.cpp
+++ b/dom/media/fake-cdm/cdm-test-decryptor.cpp
@@ -71,35 +71,33 @@ private:
   static void Finish() {
     FakeDecryptor::Message("test-storage complete");
   }
 
   std::mutex mMutex;
   set<string> mTestIDs;
 };
 
-FakeDecryptor::FakeDecryptor(cdm::Host_8* aHost)
+FakeDecryptor::FakeDecryptor(cdm::Host_9* aHost)
   : mHost(aHost)
 {
   MOZ_ASSERT(!sInstance);
   sInstance = this;
 }
 
 void
 FakeDecryptor::Message(const std::string& aMessage)
 {
   MOZ_ASSERT(sInstance);
   const static std::string sid("fake-session-id");
   sInstance->mHost->OnSessionMessage(sid.c_str(),
                                      sid.size(),
                                      cdm::MessageType::kLicenseRequest,
                                      aMessage.c_str(),
-                                     aMessage.size(),
-                                     nullptr,
-                                     0);
+                                     aMessage.size());
 }
 
 std::vector<std::string>
 Tokenize(const std::string& aString)
 {
   std::stringstream strstr(aString);
   std::istream_iterator<std::string> it(strstr), end;
   return std::vector<std::string>(it, end);
--- a/dom/media/fake-cdm/cdm-test-decryptor.h
+++ b/dom/media/fake-cdm/cdm-test-decryptor.h
@@ -5,25 +5,30 @@
 
 #ifndef FAKE_DECRYPTOR_H__
 #define FAKE_DECRYPTOR_H__
 
 #include "content_decryption_module.h"
 #include <string>
 #include "mozilla/Attributes.h"
 
-class FakeDecryptor : public cdm::ContentDecryptionModule_8 {
+class FakeDecryptor : public cdm::ContentDecryptionModule_9 {
 public:
-  explicit FakeDecryptor(cdm::Host_8* aHost);
+  explicit FakeDecryptor(cdm::Host_9* aHost);
 
   void Initialize(bool aAllowDistinctiveIdentifier,
                   bool aAllowPersistentState) override
   {
   }
 
+  void GetStatusForPolicy(uint32_t aPromiseId,
+                          const cdm::Policy& aPolicy) override
+  {
+  }
+
   void SetServerCertificate(uint32_t aPromiseId,
                             const uint8_t* aServerCertificateData,
                             uint32_t aServerCertificateDataSize)
                             override
   {
   }
 
   void CreateSessionAndGenerateRequest(uint32_t aPromiseId,
@@ -110,25 +115,31 @@ public:
   }
 
   void OnQueryOutputProtectionStatus(cdm::QueryResult aResult,
                                      uint32_t aLinkMask,
                                      uint32_t aOutputProtectionMask) override
   {
   }
 
+  void OnStorageId(uint32_t aVersion,
+                   const uint8_t* aStorageId,
+                   uint32_t aStorageIdSize) override
+  {
+  }
+
   void Destroy() override
   {
     delete this;
     sInstance = nullptr;
   }
 
   static void Message(const std::string& aMessage);
 
-  cdm::Host_8* mHost;
+  cdm::Host_9* mHost;
 
   static FakeDecryptor* sInstance;
 
 private:
 
   virtual ~FakeDecryptor() {}
 
   void TestStorage();
--- a/dom/media/fake-cdm/cdm-test-storage.cpp
+++ b/dom/media/fake-cdm/cdm-test-storage.cpp
@@ -41,17 +41,17 @@ public:
   {
   }
 
   void OnWriteComplete(Status aStatus) override
   {
     Done(aStatus);
   }
 
-  void Do(const string& aName, Host_8* aHost)
+  void Do(const string& aName, Host_9* aHost)
   {
     // Initialize the FileIO.
     mFileIO = aHost->CreateFileIO(this);
     mFileIO->Open(aName.c_str(), aName.size());
   }
 
 private:
   void Done(cdm::FileIOClient::Status aStatus)
@@ -77,33 +77,33 @@ private:
 
   FileIO* mFileIO = nullptr;
   function<void()> mOnSuccess;
   function<void()> mOnFailure;
   std::vector<uint8_t> mData;
 };
 
 void
-WriteRecord(Host_8* aHost,
+WriteRecord(Host_9* aHost,
             const std::string& aRecordName,
             const uint8_t* aData,
             uint32_t aNumBytes,
             function<void()>&& aOnSuccess,
             function<void()>&& aOnFailure)
 {
   // client will be delete in WriteRecordClient::Done
   WriteRecordClient* client = new WriteRecordClient(move(aOnSuccess),
                                                     move(aOnFailure),
                                                     aData,
                                                     aNumBytes);
   client->Do(aRecordName, aHost);
 }
 
 void
-WriteRecord(Host_8* aHost,
+WriteRecord(Host_9* aHost,
             const std::string& aRecordName,
             const std::string& aData,
             function<void()> &&aOnSuccess,
             function<void()>&& aOnFailure)
 {
   return WriteRecord(aHost,
                      aRecordName,
                      (const uint8_t*)aData.c_str(),
@@ -136,17 +136,17 @@ public:
   {
     Done(aStatus, aData, aDataSize);
   }
 
   void OnWriteComplete(Status aStatus) override
   {
   }
 
-  void Do(const string& aName, Host_8* aHost)
+  void Do(const string& aName, Host_9* aHost)
   {
     mFileIO = aHost->CreateFileIO(this);
     mFileIO->Open(aName.c_str(), aName.size());
   }
 
 private:
   void Done(cdm::FileIOClient::Status aStatus,
             const uint8_t* aData,
@@ -171,17 +171,17 @@ private:
     delete this;
   }
 
   FileIO* mFileIO = nullptr;
   function<void(bool, const uint8_t*, uint32_t)> mOnReadComplete;
 };
 
 void
-ReadRecord(Host_8* aHost,
+ReadRecord(Host_9* aHost,
            const std::string& aRecordName,
            function<void(bool, const uint8_t*, uint32_t)>&& aOnReadComplete)
 {
   // client will be delete in ReadRecordClient::Done
   ReadRecordClient* client = new ReadRecordClient(move(aOnReadComplete));
   client->Do(aRecordName, aHost);
 }
 
@@ -203,17 +203,17 @@ public:
                       uint32_t aDataSize) override
   {
   }
 
   void OnWriteComplete(Status aStatus) override
   {
   }
 
-  void Do(const string& aName, Host_8* aHost)
+  void Do(const string& aName, Host_9* aHost)
   {
     // Initialize the FileIO.
     mFileIO = aHost->CreateFileIO(this);
     mFileIO->Open(aName.c_str(), aName.size());
   }
 
 private:
   void Done(cdm::FileIOClient::Status aStatus)
@@ -237,16 +237,16 @@ private:
     delete this;
   }
 
   FileIO* mFileIO = nullptr;
   function<void(bool)> mOpenComplete;;
 };
 
 void
-OpenRecord(Host_8* aHost,
+OpenRecord(Host_9* aHost,
            const std::string& aRecordName,
            function<void(bool)>&& aOpenComplete)
 {
   // client will be delete in OpenRecordClient::Done
   OpenRecordClient* client = new OpenRecordClient(move(aOpenComplete));
   client->Do(aRecordName, aHost);
 }
--- a/dom/media/fake-cdm/cdm-test-storage.h
+++ b/dom/media/fake-cdm/cdm-test-storage.h
@@ -20,35 +20,35 @@
 class ReadContinuation {
 public:
   virtual ~ReadContinuation() {}
   virtual void operator()(bool aSuccess,
                           const uint8_t* aData,
                           uint32_t aDataSize) = 0;
 };
 
-void WriteRecord(cdm::Host_8* aHost,
+void WriteRecord(cdm::Host_9* aHost,
                  const std::string& aRecordName,
                  const std::string& aData,
                  std::function<void()>&& aOnSuccess,
                  std::function<void()>&& aOnFailure);
 
-void WriteRecord(cdm::Host_8* aHost,
+void WriteRecord(cdm::Host_9* aHost,
                  const std::string& aRecordName,
                  const uint8_t* aData,
                  uint32_t aNumBytes,
                  std::function<void()>&& aOnSuccess,
                  std::function<void()>&& aOnFailure);
 
-void ReadRecord(cdm::Host_8* aHost,
+void ReadRecord(cdm::Host_9* aHost,
                 const std::string& aRecordName,
                 std::function<void(bool, const uint8_t*, uint32_t)>&& aOnReadComplete);
 
 class OpenContinuation {
 public:
   virtual ~OpenContinuation() {}
   virtual void operator()(bool aSuccess) = 0;
 };
 
-void OpenRecord(cdm::Host_8* aHost,
+void OpenRecord(cdm::Host_9* aHost,
                 const std::string& aRecordName,
                 std::function<void(bool)>&& aOpenComplete);
 #endif // TEST_CDM_STORAGE_H__
--- a/dom/media/fake-cdm/manifest.json
+++ b/dom/media/fake-cdm/manifest.json
@@ -1,9 +1,9 @@
 {
     "name": "fake",
     "description": "Fake CDM Plugin",
     "version": "1",
     "x-cdm-module-versions": "4",
-    "x-cdm-interface-versions": "8",
-    "x-cdm-host-versions": "8",
+    "x-cdm-interface-versions": "9",
+    "x-cdm-host-versions": "9",
     "x-cdm-codecs": ""
 }
\ No newline at end of file
--- a/dom/media/gmp/GMPChild.cpp
+++ b/dom/media/gmp/GMPChild.cpp
@@ -6,16 +6,17 @@
 #include "GMPChild.h"
 #include "GMPContentChild.h"
 #include "GMPProcessChild.h"
 #include "GMPLoader.h"
 #include "GMPVideoDecoderChild.h"
 #include "GMPVideoEncoderChild.h"
 #include "GMPVideoHost.h"
 #include "nsDebugImpl.h"
+#include "nsExceptionHandler.h"
 #include "nsIFile.h"
 #include "nsXULAppAPI.h"
 #include "gmp-video-decode.h"
 #include "gmp-video-encode.h"
 #include "GMPPlatform.h"
 #include "mozilla/ipc/CrashReporterClient.h"
 #include "mozilla/ipc/ProcessChild.h"
 #include "GMPUtils.h"
@@ -540,57 +541,84 @@ ToCString(const nsTArray<Pair<nsCString,
 
 mozilla::ipc::IPCResult
 GMPChild::AnswerStartPlugin(const nsString& aAdapter)
 {
   LOGD("%s", __FUNCTION__);
 
   nsCString libPath;
   if (!GetUTF8LibPath(libPath)) {
-    return IPC_FAIL_NO_REASON(this);
+    CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("GMPLibraryPath"),
+                                       NS_ConvertUTF16toUTF8(mPluginPath));
+
+#ifdef XP_WIN
+    return IPC_FAIL(
+      this,
+      nsPrintfCString("Failed to get lib path with error(%d).", GetLastError())
+        .get());
+#else
+    return IPC_FAIL(
+      this,
+      "Failed to get lib path.");
+#endif
   }
 
   auto platformAPI = new GMPPlatformAPI();
   InitPlatformAPI(*platformAPI, this);
 
   mGMPLoader = MakeUnique<GMPLoader>();
 #if defined(MOZ_GMP_SANDBOX)
   if (!mGMPLoader->CanSandbox()) {
     LOGD("%s Can't sandbox GMP, failing", __FUNCTION__);
     delete platformAPI;
-    return IPC_FAIL_NO_REASON(this);
+    return IPC_FAIL(this, "Can't sandbox GMP.");
   }
 #endif
   bool isChromium = aAdapter.EqualsLiteral("chromium");
 #if defined(MOZ_GMP_SANDBOX) && defined(XP_MACOSX)
   MacSandboxPluginType pluginType = MacSandboxPluginType_GMPlugin_Default;
   if (isChromium) {
     pluginType = MacSandboxPluginType_GMPlugin_EME_Widevine;
   }
   if (!SetMacSandboxInfo(pluginType)) {
     NS_WARNING("Failed to set Mac GMP sandbox info");
     delete platformAPI;
-    return IPC_FAIL_NO_REASON(this);
+    return IPC_FAIL(
+      this,
+      nsPrintfCString("Failed to set Mac GMP sandbox info with plugin type %d.",
+                      pluginType).get());
   }
 #endif
 
   GMPAdapter* adapter = nullptr;
   if (isChromium) {
     auto&& paths = MakeCDMHostVerificationPaths();
     GMP_LOG("%s CDM host paths=%s", __func__, ToCString(paths).get());
     adapter = new ChromiumCDMAdapter(Move(paths));
   }
 
   if (!mGMPLoader->Load(libPath.get(),
                         libPath.Length(),
                         platformAPI,
                         adapter)) {
     NS_WARNING("Failed to load GMP");
     delete platformAPI;
-    return IPC_FAIL_NO_REASON(this);
+    CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("GMPLibraryPath"),
+                                       NS_ConvertUTF16toUTF8(mPluginPath));
+
+#ifdef XP_WIN
+    return IPC_FAIL(
+      this,
+      nsPrintfCString("Failed to load GMP with error(%d).", GetLastError())
+        .get());
+#else
+    return IPC_FAIL(
+      this,
+      "Failed to load GMP.");
+#endif
   }
 
   return IPC_OK();
 }
 
 MessageLoop*
 GMPChild::GMPMessageLoop()
 {
@@ -618,31 +646,35 @@ GMPChild::ActorDestroy(ActorDestroyReaso
   CrashReporterClient::DestroySingleton();
 
   XRE_ShutdownChildProcess();
 }
 
 void
 GMPChild::ProcessingError(Result aCode, const char* aReason)
 {
+  if (!aReason) {
+    aReason = "";
+  }
+
   switch (aCode) {
     case MsgDropped:
       _exit(0); // Don't trigger a crash report.
     case MsgNotKnown:
-      MOZ_CRASH("aborting because of MsgNotKnown");
+      MOZ_CRASH_UNSAFE_PRINTF("aborting because of MsgNotKnown, reason(%s)", aReason);
     case MsgNotAllowed:
-      MOZ_CRASH("aborting because of MsgNotAllowed");
+      MOZ_CRASH_UNSAFE_PRINTF("aborting because of MsgNotAllowed, reason(%s)", aReason);
     case MsgPayloadError:
-      MOZ_CRASH("aborting because of MsgPayloadError");
+      MOZ_CRASH_UNSAFE_PRINTF("aborting because of MsgPayloadError, reason(%s)", aReason);
     case MsgProcessingError:
-      MOZ_CRASH("aborting because of MsgProcessingError");
+      MOZ_CRASH_UNSAFE_PRINTF("aborting because of MsgProcessingError, reason(%s)", aReason);
     case MsgRouteError:
-      MOZ_CRASH("aborting because of MsgRouteError");
+      MOZ_CRASH_UNSAFE_PRINTF("aborting because of MsgRouteError, reason(%s)", aReason);
     case MsgValueError:
-      MOZ_CRASH("aborting because of MsgValueError");
+      MOZ_CRASH_UNSAFE_PRINTF("aborting because of MsgValueError, reason(%s)", aReason);
     default:
       MOZ_CRASH("not reached");
   }
 }
 
 PGMPTimerChild*
 GMPChild::AllocPGMPTimerChild()
 {
--- a/dom/media/systemservices/CamerasParent.cpp
+++ b/dom/media/systemservices/CamerasParent.cpp
@@ -47,17 +47,17 @@ ResolutionFeasibilityDistance(int32_t ca
 {
   // The purpose of this function is to find a smallest resolution
   // which is larger than all requested capabilities.
   // Then we can use down-scaling to fulfill each request.
   uint32_t distance;
   if (candidate >= requested) {
     distance = (candidate - requested) * 1000 / std::max(candidate, requested);
   } else {
-    distance = (UINT32_MAX / 2) + (requested - candidate) *
+    distance = 10000 + (requested - candidate) *
       1000 / std::max(candidate, requested);
   }
   return distance;
 }
 
 uint32_t
 FeasibilityDistance(int32_t candidate, int32_t requested)
 {
@@ -857,24 +857,24 @@ CamerasParent::RecvStartCapture(const Ca
           capability.width = ipcCaps.width();
           capability.height = ipcCaps.height();
           capability.maxFPS = ipcCaps.maxFPS();
           capability.expectedCaptureDelay = ipcCaps.expectedCaptureDelay();
           capability.rawType = static_cast<webrtc::RawVideoType>(ipcCaps.rawType());
           capability.codecType = static_cast<webrtc::VideoCodecType>(ipcCaps.codecType());
           capability.interlaced = ipcCaps.interlaced();
 
+#ifdef DEBUG
+          auto deviceUniqueID = sDeviceUniqueIDs.find(capnum);
+          MOZ_ASSERT(deviceUniqueID == sDeviceUniqueIDs.end());
+#endif
+          sDeviceUniqueIDs.emplace(capnum, cap.VideoCapture()->CurrentDeviceName());
+          sAllRequestedCapabilities.emplace(capnum, capability);
+
           if (aCapEngine == CameraEngine) {
-#ifdef DEBUG
-            auto deviceUniqueID = sDeviceUniqueIDs.find(capnum);
-            MOZ_ASSERT(deviceUniqueID == sDeviceUniqueIDs.end());
-#endif
-            sDeviceUniqueIDs.emplace(capnum, cap.VideoCapture()->CurrentDeviceName());
-            sAllRequestedCapabilities.emplace(capnum, capability);
-
             for (const auto &it : sDeviceUniqueIDs) {
               if (strcmp(it.second, cap.VideoCapture()->CurrentDeviceName()) == 0) {
                 capability.width = std::max(
                   capability.width, sAllRequestedCapabilities[it.first].width);
                 capability.height = std::max(
                   capability.height, sAllRequestedCapabilities[it.first].height);
                 capability.maxFPS = std::max(
                   capability.maxFPS, sAllRequestedCapabilities[it.first].maxFPS);
@@ -903,16 +903,26 @@ CamerasParent::RecvStartCapture(const Ca
                   candidateCapability.second.maxFPS, capability.maxFPS));
               if (distance < minDistance) {
                 minIdx = candidateCapability.first;;
                 minDistance = distance;
               }
             }
             MOZ_ASSERT(minIdx != -1);
             capability = candidateCapabilities->second[minIdx];
+          } else if (aCapEngine == ScreenEngine ||
+                     aCapEngine == BrowserEngine ||
+                     aCapEngine == WinEngine ||
+                     aCapEngine == AppEngine) {
+            for (const auto &it : sDeviceUniqueIDs) {
+              if (strcmp(it.second, cap.VideoCapture()->CurrentDeviceName()) == 0) {
+                capability.maxFPS = std::max(
+                  capability.maxFPS, sAllRequestedCapabilities[it.first].maxFPS);
+              }
+            }
           }
 
           error = cap.VideoCapture()->StartCapture(capability);
 
           if (!error) {
             cap.VideoCapture()->RegisterCaptureDataCallback(
               static_cast<rtc::VideoSinkInterface<webrtc::VideoFrame>*>(*cbh));
           }
@@ -944,26 +954,24 @@ CamerasParent::StopCapture(const Capture
 {
   if (auto engine = EnsureInitialized(aCapEngine)) {
     // we're removing elements, iterate backwards
     for (size_t i = mCallbacks.Length(); i > 0; i--) {
       if (mCallbacks[i - 1]->mCapEngine == aCapEngine &&
           mCallbacks[i - 1]->mStreamId == (uint32_t)capnum) {
 
         CallbackHelper* cbh = mCallbacks[i-1];
-        engine->WithEntry(capnum,[cbh, &capnum, &aCapEngine](VideoEngine::CaptureEntry& cap){
+        engine->WithEntry(capnum,[cbh, &capnum](VideoEngine::CaptureEntry& cap){
           if (cap.VideoCapture()) {
             cap.VideoCapture()->DeRegisterCaptureDataCallback(
               static_cast<rtc::VideoSinkInterface<webrtc::VideoFrame>*>(cbh));
             cap.VideoCapture()->StopCaptureIfAllClientsClose();
 
-            if (aCapEngine == CameraEngine) {
-              sDeviceUniqueIDs.erase(capnum);
-              sAllRequestedCapabilities.erase(capnum);
-            }
+            sDeviceUniqueIDs.erase(capnum);
+            sAllRequestedCapabilities.erase(capnum);
           }
         });
 
         delete mCallbacks[i - 1];
         mCallbacks.RemoveElementAt(i - 1);
         break;
       }
     }
--- a/dom/media/webrtc/MediaEngine.h
+++ b/dom/media/webrtc/MediaEngine.h
@@ -212,31 +212,34 @@ public:
   virtual bool GetScary() const { return false; };
 
   class AllocationHandle
   {
   public:
     NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AllocationHandle);
   protected:
     ~AllocationHandle() {}
+    static uint64_t sId;
   public:
     AllocationHandle(const dom::MediaTrackConstraints& aConstraints,
                      const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
                      const MediaEnginePrefs& aPrefs,
                      const nsString& aDeviceId)
 
     : mConstraints(aConstraints),
       mPrincipalInfo(aPrincipalInfo),
       mPrefs(aPrefs),
-      mDeviceId(aDeviceId) {}
+      mDeviceId(aDeviceId),
+      mId(sId++) {}
   public:
     NormalizedConstraints mConstraints;
     mozilla::ipc::PrincipalInfo mPrincipalInfo;
     MediaEnginePrefs mPrefs;
     nsString mDeviceId;
+    uint64_t mId;
   };
 
   /* Release the device back to the system. */
   virtual nsresult Deallocate(AllocationHandle* aHandle)
   {
     MOZ_ASSERT(aHandle);
     RefPtr<AllocationHandle> handle = aHandle;
 
@@ -361,16 +364,17 @@ protected:
    * aPrefs            - As passed in (in case of changes in about:config).
    * aDeviceId         - As passed in (origin dependent).
    * aOutBadConstraint - Result: nonzero if failed to apply. Name of culprit.
    */
 
   virtual nsresult
   UpdateSingleSource(const AllocationHandle* aHandle,
                      const NormalizedConstraints& aNetConstraints,
+                     const NormalizedConstraints& aNewConstraint,
                      const MediaEnginePrefs& aPrefs,
                      const nsString& aDeviceId,
                      const char** aOutBadConstraint) {
     return NS_ERROR_NOT_IMPLEMENTED;
   };
 
   /* ReevaluateAllocation - Call to change constraints for an allocation of
    * a single device. Manages allocation handles, calculates net constraints
@@ -389,36 +393,42 @@ protected:
                        NormalizedConstraints* aConstraintsUpdate,
                        const MediaEnginePrefs& aPrefs,
                        const nsString& aDeviceId,
                        const char** aOutBadConstraint)
   {
     // aHandle and/or aConstraintsUpdate may be nullptr (see below)
 
     AutoTArray<const NormalizedConstraints*, 10> allConstraints;
+    AutoTArray<const NormalizedConstraints*, 1> updatedConstraint;
     for (auto& registered : mRegisteredHandles) {
       if (aConstraintsUpdate && registered.get() == aHandle) {
         continue; // Don't count old constraints
       }
       allConstraints.AppendElement(&registered->mConstraints);
     }
     if (aConstraintsUpdate) {
       allConstraints.AppendElement(aConstraintsUpdate);
+      updatedConstraint.AppendElement(aConstraintsUpdate);
     } else if (aHandle) {
       // In the case of AddShareOfSingleSource, the handle isn't registered yet.
       allConstraints.AppendElement(&aHandle->mConstraints);
+      updatedConstraint.AppendElement(&aHandle->mConstraints);
+    } else {
+      updatedConstraint.AppendElements(allConstraints);
     }
 
     NormalizedConstraints netConstraints(allConstraints);
     if (netConstraints.mBadConstraint) {
       *aOutBadConstraint = netConstraints.mBadConstraint;
       return NS_ERROR_FAILURE;
     }
 
-    nsresult rv = UpdateSingleSource(aHandle, netConstraints, aPrefs, aDeviceId,
+    NormalizedConstraints newConstraint(updatedConstraint);
+    nsresult rv = UpdateSingleSource(aHandle, netConstraints, newConstraint, aPrefs, aDeviceId,
                                      aOutBadConstraint);
     if (NS_FAILED(rv)) {
       return rv;
     }
     if (aHandle && aConstraintsUpdate) {
       aHandle->mConstraints = *aConstraintsUpdate;
     }
     return NS_OK;
--- a/dom/media/webrtc/MediaEngineCameraVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineCameraVideoSource.cpp
@@ -20,20 +20,30 @@ extern LogModule* GetMediaManagerLog();
 // guts for appending data to the MSG track
 bool MediaEngineCameraVideoSource::AppendToTrack(SourceMediaStream* aSource,
                                                  layers::Image* aImage,
                                                  TrackID aID,
                                                  StreamTime delta,
                                                  const PrincipalHandle& aPrincipalHandle)
 {
   MOZ_ASSERT(aSource);
+  MOZ_ASSERT(aImage);
+
+  if (!aImage) {
+    return 0;
+  }
 
   VideoSegment segment;
   RefPtr<layers::Image> image = aImage;
-  IntSize size(image ? mWidth : 0, image ? mHeight : 0);
+  IntSize size = image->GetSize();
+
+  if (!size.width || !size.height) {
+    return 0;
+  }
+
   segment.AppendFrame(image.forget(), delta, size, aPrincipalHandle);
 
   // This is safe from any thread, and is safe if the track is Finished
   // or Destroyed.
   // This can fail if either a) we haven't added the track yet, or b)
   // we've removed or finished the track.
   return aSource->AppendToTrack(aID, &(segment));
 }
@@ -50,16 +60,29 @@ void
 MediaEngineCameraVideoSource::GetCapability(size_t aIndex,
                                             webrtc::CaptureCapability& aOut) const
 {
   MOZ_ASSERT(aIndex < mHardcodedCapabilities.Length());
   aOut = mHardcodedCapabilities.SafeElementAt(aIndex, webrtc::CaptureCapability());
 }
 
 uint32_t
+MediaEngineCameraVideoSource::GetDistance(
+    const webrtc::CaptureCapability& aCandidate,
+    const NormalizedConstraintSet &aConstraints,
+    const nsString& aDeviceId,
+    const DistanceCalculation aCalculate) const
+{
+  if (aCalculate == kFeasibility) {
+    return GetFeasibilityDistance(aCandidate, aConstraints, aDeviceId);
+  }
+  return GetFitnessDistance(aCandidate, aConstraints, aDeviceId);
+}
+
+uint32_t
 MediaEngineCameraVideoSource::GetFitnessDistance(
     const webrtc::CaptureCapability& aCandidate,
     const NormalizedConstraintSet &aConstraints,
     const nsString& aDeviceId) const
 {
   // Treat width|height|frameRate == 0 on capability as "can do any".
   // This allows for orthogonal capabilities that are not in discrete steps.
 
@@ -70,16 +93,37 @@ MediaEngineCameraVideoSource::GetFitness
                                                aConstraints.mWidth) : 0) +
     uint64_t(aCandidate.height? FitnessDistance(int32_t(aCandidate.height),
                                                 aConstraints.mHeight) : 0) +
     uint64_t(aCandidate.maxFPS? FitnessDistance(double(aCandidate.maxFPS),
                                                 aConstraints.mFrameRate) : 0);
   return uint32_t(std::min(distance, uint64_t(UINT32_MAX)));
 }
 
+uint32_t
+MediaEngineCameraVideoSource::GetFeasibilityDistance(
+    const webrtc::CaptureCapability& aCandidate,
+    const NormalizedConstraintSet &aConstraints,
+    const nsString& aDeviceId) const
+{
+  // Treat width|height|frameRate == 0 on capability as "can do any".
+  // This allows for orthogonal capabilities that are not in discrete steps.
+
+  uint64_t distance =
+    uint64_t(FitnessDistance(aDeviceId, aConstraints.mDeviceId)) +
+    uint64_t(FitnessDistance(mFacingMode, aConstraints.mFacingMode)) +
+    uint64_t(aCandidate.width? FeasibilityDistance(int32_t(aCandidate.width),
+                                               aConstraints.mWidth) : 0) +
+    uint64_t(aCandidate.height? FeasibilityDistance(int32_t(aCandidate.height),
+                                                aConstraints.mHeight) : 0) +
+    uint64_t(aCandidate.maxFPS? FeasibilityDistance(double(aCandidate.maxFPS),
+                                                aConstraints.mFrameRate) : 0);
+  return uint32_t(std::min(distance, uint64_t(UINT32_MAX)));
+}
+
 // Find best capability by removing inferiors. May leave >1 of equal distance
 
 /* static */ void
 MediaEngineCameraVideoSource::TrimLessFitCandidates(CapabilitySet& set) {
   uint32_t best = UINT32_MAX;
   for (auto& candidate : set) {
     if (best > candidate.mDistance) {
       best = candidate.mDistance;
@@ -213,17 +257,19 @@ MediaEngineCameraVideoSource::LogCapabil
                       uint32_t(sizeof(codec) / sizeof(*codec) - 1))],
        aDistance));
 }
 
 bool
 MediaEngineCameraVideoSource::ChooseCapability(
     const NormalizedConstraints &aConstraints,
     const MediaEnginePrefs &aPrefs,
-    const nsString& aDeviceId)
+    const nsString& aDeviceId,
+    webrtc::CaptureCapability& aCapability,
+    const DistanceCalculation aCalculate)
 {
   if (MOZ_LOG_TEST(GetMediaManagerLog(), LogLevel::Debug)) {
     LOG(("ChooseCapability: prefs: %dx%d @%dfps",
          aPrefs.GetWidth(), aPrefs.GetHeight(),
          aPrefs.mFPS));
     LogConstraints(aConstraints);
     if (!aConstraints.mAdvanced.empty()) {
       LOG(("Advanced array[%zu]:", aConstraints.mAdvanced.size()));
@@ -241,17 +287,17 @@ MediaEngineCameraVideoSource::ChooseCapa
   }
 
   // First, filter capabilities by required constraints (min, max, exact).
 
   for (size_t i = 0; i < candidateSet.Length();) {
     auto& candidate = candidateSet[i];
     webrtc::CaptureCapability cap;
     GetCapability(candidate.mIndex, cap);
-    candidate.mDistance = GetFitnessDistance(cap, aConstraints, aDeviceId);
+    candidate.mDistance = GetDistance(cap, aConstraints, aDeviceId, aCalculate);
     LogCapability("Capability", cap, candidate.mDistance);
     if (candidate.mDistance == UINT32_MAX) {
       candidateSet.RemoveElementAt(i);
     } else {
       ++i;
     }
   }
 
@@ -263,17 +309,17 @@ MediaEngineCameraVideoSource::ChooseCapa
   // Filter further with all advanced constraints (that don't overconstrain).
 
   for (const auto &cs : aConstraints.mAdvanced) {
     CapabilitySet rejects;
     for (size_t i = 0; i < candidateSet.Length();) {
       auto& candidate = candidateSet[i];
       webrtc::CaptureCapability cap;
       GetCapability(candidate.mIndex, cap);
-      if (GetFitnessDistance(cap, cs, aDeviceId) == UINT32_MAX) {
+      if (GetDistance(cap, cs, aDeviceId, aCalculate) == UINT32_MAX) {
         rejects.AppendElement(candidate);
         candidateSet.RemoveElementAt(i);
       } else {
         ++i;
       }
     }
     if (!candidateSet.Length()) {
       candidateSet.AppendElements(Move(rejects));
@@ -294,39 +340,39 @@ MediaEngineCameraVideoSource::ChooseCapa
     prefs.mWidth.SetAsLong() = aPrefs.GetWidth();
     prefs.mHeight.SetAsLong() = aPrefs.GetHeight();
     prefs.mFrameRate.SetAsDouble() = aPrefs.mFPS;
     NormalizedConstraintSet normPrefs(prefs, false);
 
     for (auto& candidate : candidateSet) {
       webrtc::CaptureCapability cap;
       GetCapability(candidate.mIndex, cap);
-      candidate.mDistance = GetFitnessDistance(cap, normPrefs, aDeviceId);
+      candidate.mDistance = GetDistance(cap, normPrefs, aDeviceId, aCalculate);
     }
     TrimLessFitCandidates(candidateSet);
   }
 
   // Any remaining multiples all have the same distance, but may vary on
   // format. Some formats are more desirable for certain use like WebRTC.
   // E.g. I420 over RGB24 can remove a needless format conversion.
 
   bool found = false;
   for (auto& candidate : candidateSet) {
     webrtc::CaptureCapability cap;
     GetCapability(candidate.mIndex, cap);
     if (cap.rawType == webrtc::RawVideoType::kVideoI420 ||
         cap.rawType == webrtc::RawVideoType::kVideoYUY2 ||
         cap.rawType == webrtc::RawVideoType::kVideoYV12) {
-      mCapability = cap;
+      aCapability = cap;
       found = true;
       break;
     }
   }
   if (!found) {
-    GetCapability(candidateSet[0].mIndex, mCapability);
+    GetCapability(candidateSet[0].mIndex, aCapability);
   }
 
   LogCapability("Chosen capability", mCapability, sameDistance);
   return true;
 }
 
 void
 MediaEngineCameraVideoSource::SetName(nsString aName)
--- a/dom/media/webrtc/MediaEngineCameraVideoSource.h
+++ b/dom/media/webrtc/MediaEngineCameraVideoSource.h
@@ -19,16 +19,29 @@
 #include "webrtc/modules/video_capture/video_capture_defines.h"
 
 namespace webrtc {
   using CaptureCapability = VideoCaptureCapability;
 }
 
 namespace mozilla {
 
+// Fitness distance is defined in
+// https://www.w3.org/TR/2017/CR-mediacapture-streams-20171003/#dfn-selectsettings
+// The main difference of feasibility and fitness distance is that if the
+// constraint is required ('max', or 'exact'), and the settings dictionary's value
+// for the constraint does not satisfy the constraint, the fitness distance is
+// positive infinity. Given a continuous space of settings dictionaries comprising
+// all discrete combinations of dimension and frame-rate related properties,
+// the feasibility distance is still in keeping with the constraints algorithm.
+enum DistanceCalculation {
+  kFitness,
+  kFeasibility
+};
+
 class MediaEngineCameraVideoSource : public MediaEngineVideoSource
 {
 public:
   // Some subclasses use an index to track multiple instances.
   explicit MediaEngineCameraVideoSource(int aIndex,
                                         const char* aMonitorName = "Camera.Monitor")
     : MediaEngineVideoSource(kReleased)
     , mMonitor(aMonitorName)
@@ -81,29 +94,40 @@ protected:
   ~MediaEngineCameraVideoSource() {}
 
   // guts for appending data to the MSG track
   virtual bool AppendToTrack(SourceMediaStream* aSource,
                              layers::Image* aImage,
                              TrackID aID,
                              StreamTime delta,
                              const PrincipalHandle& aPrincipalHandle);
+  uint32_t GetDistance(const webrtc::CaptureCapability& aCandidate,
+                       const NormalizedConstraintSet &aConstraints,
+                       const nsString& aDeviceId,
+                       const DistanceCalculation aCalculate) const;
   uint32_t GetFitnessDistance(const webrtc::CaptureCapability& aCandidate,
                               const NormalizedConstraintSet &aConstraints,
                               const nsString& aDeviceId) const;
+  uint32_t GetFeasibilityDistance(const webrtc::CaptureCapability& aCandidate,
+                              const NormalizedConstraintSet &aConstraints,
+                              const nsString& aDeviceId) const;
   static void TrimLessFitCandidates(CapabilitySet& set);
   static void LogConstraints(const NormalizedConstraintSet& aConstraints);
   static void LogCapability(const char* aHeader,
                             const webrtc::CaptureCapability &aCapability,
                             uint32_t aDistance);
   virtual size_t NumCapabilities() const;
   virtual void GetCapability(size_t aIndex, webrtc::CaptureCapability& aOut) const;
-  virtual bool ChooseCapability(const NormalizedConstraints &aConstraints,
-                                const MediaEnginePrefs &aPrefs,
-                                const nsString& aDeviceId);
+  virtual bool ChooseCapability(
+    const NormalizedConstraints &aConstraints,
+    const MediaEnginePrefs &aPrefs,
+    const nsString& aDeviceId,
+    webrtc::CaptureCapability& aCapability,
+    const DistanceCalculation aCalculate
+  );
   void SetName(nsString aName);
   void SetUUID(const char* aUUID);
   const nsCString& GetUUID() const; // protected access
 
   // Engine variables.
 
   // mMonitor protects mImage access/changes, and transitions of mState
   // from kStarted to kStopped (which are combined with EndTrack() and
@@ -111,25 +135,30 @@ protected:
   // mMonitor also protects mSources[] and mPrincipalHandles[] access/changes.
   // mSources[] and mPrincipalHandles[] are accessed from webrtc threads.
 
   // All the mMonitor accesses are from the child classes.
   Monitor mMonitor; // Monitor for processing Camera frames.
   nsTArray<RefPtr<SourceMediaStream>> mSources; // When this goes empty, we shut down HW
   nsTArray<PrincipalHandle> mPrincipalHandles; // Directly mapped to mSources.
   RefPtr<layers::Image> mImage;
+  nsTArray<RefPtr<layers::Image>> mImages;
+  nsTArray<webrtc::CaptureCapability> mTargetCapabilities;
+  nsTArray<uint64_t> mHandleIds;
   RefPtr<layers::ImageContainer> mImageContainer;
   // end of data protected by mMonitor
 
   int mWidth, mHeight;
   bool mInitDone;
   int mCaptureIndex;
   TrackID mTrackID;
 
   webrtc::CaptureCapability mCapability;
+  webrtc::CaptureCapability mTargetCapability;
+  uint64_t mHandleId;
 
   mutable nsTArray<webrtc::CaptureCapability> mHardcodedCapabilities;
 private:
   nsString mDeviceName;
   nsCString mUniqueId;
   nsString mFacingMode;
 };
 
--- a/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp
@@ -5,23 +5,28 @@
 
 #include "MediaEngineRemoteVideoSource.h"
 
 #include "mozilla/RefPtr.h"
 #include "VideoUtils.h"
 #include "nsIPrefService.h"
 #include "MediaTrackConstraints.h"
 #include "CamerasChild.h"
+#include "VideoFrameUtils.h"
+#include "webrtc/api/video/i420_buffer.h"
+#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
 
 extern mozilla::LogModule* GetMediaManagerLog();
 #define LOG(msg) MOZ_LOG(GetMediaManagerLog(), mozilla::LogLevel::Debug, msg)
 #define LOGFRAME(msg) MOZ_LOG(GetMediaManagerLog(), mozilla::LogLevel::Verbose, msg)
 
 namespace mozilla {
 
+uint64_t MediaEngineCameraVideoSource::AllocationHandle::sId = 0;
+
 // These need a definition somewhere because template
 // code is allowed to take their address, and they aren't
 // guaranteed to have one without this.
 const unsigned int MediaEngineSource::kMaxDeviceNameLength;
 const unsigned int MediaEngineSource::kMaxUniqueIdLength;;
 
 using dom::ConstrainLongRange;
 
@@ -75,16 +80,19 @@ MediaEngineRemoteVideoSource::Shutdown()
     bool empty;
 
     while (1) {
       {
         MonitorAutoLock lock(mMonitor);
         empty = mSources.IsEmpty();
         if (empty) {
           MOZ_ASSERT(mPrincipalHandles.IsEmpty());
+          MOZ_ASSERT(mTargetCapabilities.IsEmpty());
+          MOZ_ASSERT(mHandleIds.IsEmpty());
+          MOZ_ASSERT(mImages.IsEmpty());
           break;
         }
         source = mSources[0];
       }
       Stop(source, kVideoTrack); // XXX change to support multiple tracks
     }
     MOZ_ASSERT(mState == kStopped);
   }
@@ -121,16 +129,19 @@ MediaEngineRemoteVideoSource::Allocate(
   if (NS_FAILED(rv)) {
     return rv;
   }
   if (mState == kStarted &&
       MOZ_LOG_TEST(GetMediaManagerLog(), mozilla::LogLevel::Debug)) {
     MonitorAutoLock lock(mMonitor);
     if (mSources.IsEmpty()) {
       MOZ_ASSERT(mPrincipalHandles.IsEmpty());
+      MOZ_ASSERT(mTargetCapabilities.IsEmpty());
+      MOZ_ASSERT(mHandleIds.IsEmpty());
+      MOZ_ASSERT(mImages.IsEmpty());
       LOG(("Video device %d reallocated", mCaptureIndex));
     } else {
       LOG(("Video device %d allocated shared", mCaptureIndex));
     }
   }
   return NS_OK;
 }
 
@@ -163,30 +174,38 @@ MediaEngineRemoteVideoSource::Start(Sour
 {
   LOG((__PRETTY_FUNCTION__));
   AssertIsOnOwningThread();
   if (!mInitDone || !aStream) {
     LOG(("No stream or init not done"));
     return NS_ERROR_FAILURE;
   }
 
+  mImageContainer =
+    layers::LayerManager::CreateImageContainer(layers::ImageContainer::ASYNCHRONOUS);
+
   {
     MonitorAutoLock lock(mMonitor);
     mSources.AppendElement(aStream);
     mPrincipalHandles.AppendElement(aPrincipalHandle);
+    mTargetCapabilities.AppendElement(mTargetCapability);
+    mHandleIds.AppendElement(mHandleId);
+    mImages.AppendElement(mImageContainer->CreatePlanarYCbCrImage());
+
     MOZ_ASSERT(mSources.Length() == mPrincipalHandles.Length());
+    MOZ_ASSERT(mSources.Length() == mTargetCapabilities.Length());
+    MOZ_ASSERT(mSources.Length() == mHandleIds.Length());
+    MOZ_ASSERT(mSources.Length() == mImages.Length());
   }
 
   aStream->AddTrack(aID, 0, new VideoSegment(), SourceMediaStream::ADDTRACK_QUEUED);
 
   if (mState == kStarted) {
     return NS_OK;
   }
-  mImageContainer =
-    layers::LayerManager::CreateImageContainer(layers::ImageContainer::ASYNCHRONOUS);
 
   mState = kStarted;
   mTrackID = aID;
 
   if (mozilla::camera::GetChildAndCall(
     &mozilla::camera::CamerasChild::StartCapture,
     mCapEngine, mCaptureIndex, mCapability, this)) {
     LOG(("StartCapture failed"));
@@ -213,18 +232,24 @@ MediaEngineRemoteVideoSource::Stop(mozil
 
     size_t i = mSources.IndexOf(aSource);
     if (i == mSources.NoIndex) {
       // Already stopped - this is allowed
       return NS_OK;
     }
 
     MOZ_ASSERT(mSources.Length() == mPrincipalHandles.Length());
+    MOZ_ASSERT(mSources.Length() == mTargetCapabilities.Length());
+    MOZ_ASSERT(mSources.Length() == mHandleIds.Length());
+    MOZ_ASSERT(mSources.Length() == mImages.Length());
     mSources.RemoveElementAt(i);
     mPrincipalHandles.RemoveElementAt(i);
+    mTargetCapabilities.RemoveElementAt(i);
+    mHandleIds.RemoveElementAt(i);
+    mImages.RemoveElementAt(i);
 
     aSource->EndTrack(aID);
 
     if (!mSources.IsEmpty()) {
       return NS_OK;
     }
     if (mState != kStarted) {
       return NS_ERROR_FAILURE;
@@ -257,52 +282,84 @@ MediaEngineRemoteVideoSource::Restart(Al
   return ReevaluateAllocation(aHandle, &constraints, aPrefs, aDeviceId,
                               aOutBadConstraint);
 }
 
 nsresult
 MediaEngineRemoteVideoSource::UpdateSingleSource(
     const AllocationHandle* aHandle,
     const NormalizedConstraints& aNetConstraints,
+    const NormalizedConstraints& aNewConstraint,
     const MediaEnginePrefs& aPrefs,
     const nsString& aDeviceId,
     const char** aOutBadConstraint)
 {
-  if (!ChooseCapability(aNetConstraints, aPrefs, aDeviceId)) {
-    *aOutBadConstraint = FindBadConstraint(aNetConstraints, *this, aDeviceId);
-    return NS_ERROR_FAILURE;
-  }
-
   switch (mState) {
     case kReleased:
       MOZ_ASSERT(aHandle);
+      mHandleId = aHandle->mId;
+      if (!ChooseCapability(aNetConstraints, aPrefs, aDeviceId, mCapability, kFitness)) {
+        *aOutBadConstraint = FindBadConstraint(aNetConstraints, *this, aDeviceId);
+        return NS_ERROR_FAILURE;
+      }
+      mTargetCapability = mCapability;
+
       if (camera::GetChildAndCall(&camera::CamerasChild::AllocateCaptureDevice,
                                   mCapEngine, GetUUID().get(),
                                   kMaxUniqueIdLength, mCaptureIndex,
                                   aHandle->mPrincipalInfo)) {
         return NS_ERROR_FAILURE;
       }
       mState = kAllocated;
       SetLastCapability(mCapability);
       LOG(("Video device %d allocated", mCaptureIndex));
       break;
 
     case kStarted:
-      if (mCapability != mLastCapability) {
-        camera::GetChildAndCall(&camera::CamerasChild::StopCapture,
-                                mCapEngine, mCaptureIndex);
-        if (camera::GetChildAndCall(&camera::CamerasChild::StartCapture,
-                                    mCapEngine, mCaptureIndex, mCapability,
-                                    this)) {
-          LOG(("StartCapture failed"));
+      {
+        size_t index = mHandleIds.NoIndex;
+        if (aHandle) {
+          mHandleId = aHandle->mId;
+          index = mHandleIds.IndexOf(mHandleId);
+        }
+
+        if (!ChooseCapability(aNewConstraint, aPrefs, aDeviceId, mTargetCapability,
+                              kFitness)) {
+          *aOutBadConstraint = FindBadConstraint(aNewConstraint, *this, aDeviceId);
           return NS_ERROR_FAILURE;
         }
-        SetLastCapability(mCapability);
+
+        if (index != mHandleIds.NoIndex) {
+          MonitorAutoLock lock(mMonitor);
+          mTargetCapabilities[index] = mTargetCapability;
+          MOZ_ASSERT(mSources.Length() == mPrincipalHandles.Length());
+          MOZ_ASSERT(mSources.Length() == mTargetCapabilities.Length());
+          MOZ_ASSERT(mSources.Length() == mHandleIds.Length());
+          MOZ_ASSERT(mSources.Length() == mImages.Length());
+        }
+
+        if (!ChooseCapability(aNetConstraints, aPrefs, aDeviceId, mCapability,
+                              kFeasibility)) {
+          *aOutBadConstraint = FindBadConstraint(aNetConstraints, *this, aDeviceId);
+          return NS_ERROR_FAILURE;
+        }
+
+        if (mCapability != mLastCapability) {
+          camera::GetChildAndCall(&camera::CamerasChild::StopCapture,
+                                  mCapEngine, mCaptureIndex);
+          if (camera::GetChildAndCall(&camera::CamerasChild::StartCapture,
+                                      mCapEngine, mCaptureIndex, mCapability,
+                                      this)) {
+            LOG(("StartCapture failed"));
+            return NS_ERROR_FAILURE;
+          }
+          SetLastCapability(mCapability);
+        }
+        break;
       }
-      break;
 
     default:
       LOG(("Video device %d in ignored state %d", mCaptureIndex, mState));
       break;
   }
   return NS_OK;
 }
 
@@ -338,28 +395,32 @@ MediaEngineRemoteVideoSource::SetLastCap
 }
 
 void
 MediaEngineRemoteVideoSource::NotifyPull(MediaStreamGraph* aGraph,
                                          SourceMediaStream* aSource,
                                          TrackID aID, StreamTime aDesiredTime,
                                          const PrincipalHandle& aPrincipalHandle)
 {
-  VideoSegment segment;
-
+  StreamTime delta = 0;
+  size_t i;
   MonitorAutoLock lock(mMonitor);
   if (mState != kStarted) {
     return;
   }
 
-  StreamTime delta = aDesiredTime - aSource->GetEndOfAppendedData(aID);
+  i = mSources.IndexOf(aSource);
+  if (i == mSources.NoIndex) {
+    return;
+  }
+
+  delta = aDesiredTime - aSource->GetEndOfAppendedData(aID);
 
   if (delta > 0) {
-    // nullptr images are allowed
-    AppendToTrack(aSource, mImage, aID, delta, aPrincipalHandle);
+    AppendToTrack(aSource, mImages[i], aID, delta, aPrincipalHandle);
   }
 }
 
 void
 MediaEngineRemoteVideoSource::FrameSizeChange(unsigned int w, unsigned int h)
 {
   if ((mWidth < 0) || (mHeight < 0) ||
       (w !=  (unsigned int) mWidth) || (h != (unsigned int) mHeight)) {
@@ -372,73 +433,137 @@ MediaEngineRemoteVideoSource::FrameSizeC
       settings->mWidth.Value() = w;
       settings->mHeight.Value() = h;
       return NS_OK;
     }));
   }
 }
 
 int
-MediaEngineRemoteVideoSource::DeliverFrame(uint8_t* aBuffer ,
+MediaEngineRemoteVideoSource::DeliverFrame(uint8_t* aBuffer,
                                     const camera::VideoFrameProperties& aProps)
 {
+  MonitorAutoLock lock(mMonitor);
   // Check for proper state.
-  if (mState != kStarted) {
+  if (mState != kStarted || !mImageContainer) {
     LOG(("DeliverFrame: video not started"));
     return 0;
   }
 
   // Update the dimensions
   FrameSizeChange(aProps.width(), aProps.height());
 
-  layers::PlanarYCbCrData data;
-  RefPtr<layers::PlanarYCbCrImage> image;
-  {
-    // We grab the lock twice, but don't hold it across the (long) CopyData
-    MonitorAutoLock lock(mMonitor);
-    if (!mImageContainer) {
-      LOG(("DeliverFrame() called after Stop()!"));
-      return 0;
+  MOZ_ASSERT(mSources.Length() == mPrincipalHandles.Length());
+  MOZ_ASSERT(mSources.Length() == mTargetCapabilities.Length());
+  MOZ_ASSERT(mSources.Length() == mHandleIds.Length());
+  MOZ_ASSERT(mSources.Length() == mImages.Length());
+
+  for (uint32_t i = 0; i < mTargetCapabilities.Length(); i++ ) {
+    int32_t req_max_width = mTargetCapabilities[i].width & 0xffff;
+    int32_t req_max_height = mTargetCapabilities[i].height & 0xffff;
+    int32_t req_ideal_width = (mTargetCapabilities[i].width >> 16) & 0xffff;
+    int32_t req_ideal_height = (mTargetCapabilities[i].height >> 16) & 0xffff;
+
+    int32_t dest_max_width = std::min(req_max_width, mWidth);
+    int32_t dest_max_height = std::min(req_max_height, mHeight);
+    // This logic works for both camera and screen sharing case.
+    // for camera case, req_ideal_width and req_ideal_height is 0.
+    // The following snippet will set dst_width to dest_max_width and dst_height to dest_max_height
+    int32_t dst_width = std::min(req_ideal_width > 0 ? req_ideal_width : mWidth, dest_max_width);
+    int32_t dst_height = std::min(req_ideal_height > 0 ? req_ideal_height : mHeight, dest_max_height);
+
+    int dst_stride_y = dst_width;
+    int dst_stride_uv = (dst_width + 1) / 2;
+
+    camera::VideoFrameProperties properties;
+    uint8_t* frame;
+    bool needReScale = !((dst_width == mWidth && dst_height == mHeight) ||
+                         (dst_width > mWidth || dst_height > mHeight));
+
+    if (!needReScale) {
+      dst_width = mWidth;
+      dst_height = mHeight;
+      frame = aBuffer;
+    } else {
+      rtc::scoped_refptr<webrtc::I420Buffer> i420Buffer;
+      i420Buffer = webrtc::I420Buffer::Create(mWidth, mHeight, mWidth,
+                                              (mWidth + 1) / 2, (mWidth + 1) / 2);
+
+      const int conversionResult = webrtc::ConvertToI420(webrtc::kI420,
+                                                         aBuffer,
+                                                         0, 0,  // No cropping
+                                                         mWidth, mHeight,
+                                                         mWidth * mHeight * 3 / 2,
+                                                         webrtc::kVideoRotation_0,
+                                                         i420Buffer.get());
+
+      webrtc::VideoFrame captureFrame(i420Buffer, 0, 0, webrtc::kVideoRotation_0);
+      if (conversionResult < 0) {
+        return 0;
+      }
+
+      rtc::scoped_refptr<webrtc::I420Buffer> scaledBuffer;
+      scaledBuffer = webrtc::I420Buffer::Create(dst_width, dst_height, dst_stride_y,
+                                                dst_stride_uv, dst_stride_uv);
+
+      scaledBuffer->CropAndScaleFrom(*captureFrame.video_frame_buffer().get());
+      webrtc::VideoFrame scaledFrame(scaledBuffer, 0, 0, webrtc::kVideoRotation_0);
+
+      VideoFrameUtils::InitFrameBufferProperties(scaledFrame, properties);
+      frame = new unsigned char[properties.bufferSize()];
+
+      if (!frame) {
+        return 0;
+      }
+
+      VideoFrameUtils::CopyVideoFrameBuffers(frame,
+                                             properties.bufferSize(), scaledFrame);
     }
+
     // Create a video frame and append it to the track.
-    image = mImageContainer->CreatePlanarYCbCrImage();
+    RefPtr<layers::PlanarYCbCrImage> image = mImageContainer->CreatePlanarYCbCrImage();
 
-    uint8_t* frame = static_cast<uint8_t*> (aBuffer);
     const uint8_t lumaBpp = 8;
     const uint8_t chromaBpp = 4;
 
+    layers::PlanarYCbCrData data;
+
     // Take lots of care to round up!
     data.mYChannel = frame;
-    data.mYSize = IntSize(mWidth, mHeight);
-    data.mYStride = (mWidth * lumaBpp + 7)/ 8;
-    data.mCbCrStride = (mWidth * chromaBpp + 7) / 8;
-    data.mCbChannel = frame + mHeight * data.mYStride;
-    data.mCrChannel = data.mCbChannel + ((mHeight+1)/2) * data.mCbCrStride;
-    data.mCbCrSize = IntSize((mWidth+1)/ 2, (mHeight+1)/ 2);
+    data.mYSize = IntSize(dst_width, dst_height);
+    data.mYStride = (dst_width * lumaBpp + 7) / 8;
+    data.mCbCrStride = (dst_width * chromaBpp + 7) / 8;
+    data.mCbChannel = frame + dst_height * data.mYStride;
+    data.mCrChannel = data.mCbChannel + ((dst_height + 1) / 2) * data.mCbCrStride;
+    data.mCbCrSize = IntSize((dst_width + 1) / 2, (dst_height + 1) / 2);
     data.mPicX = 0;
     data.mPicY = 0;
-    data.mPicSize = IntSize(mWidth, mHeight);
+    data.mPicSize = IntSize(dst_width, dst_height);
     data.mStereoMode = StereoMode::MONO;
-  }
+
+    if (!image->CopyData(data)) {
+      MOZ_ASSERT(false);
+      return 0;
+    }
 
-  if (!image->CopyData(data)) {
-    MOZ_ASSERT(false);
-    return 0;
-  }
+    if (needReScale && frame) {
+      delete frame;
+      frame = nullptr;
+    }
 
-  MonitorAutoLock lock(mMonitor);
 #ifdef DEBUG
-  static uint32_t frame_num = 0;
-  LOGFRAME(("frame %d (%dx%d); timeStamp %u, ntpTimeMs %" PRIu64 ", renderTimeMs %" PRIu64,
-            frame_num++, mWidth, mHeight,
-            aProps.timeStamp(), aProps.ntpTimeMs(), aProps.renderTimeMs()));
+    static uint32_t frame_num = 0;
+    LOGFRAME(("frame %d (%dx%d); timeStamp %u, ntpTimeMs %" PRIu64 ", renderTimeMs %" PRIu64,
+              frame_num++, mWidth, mHeight,
+              aProps.timeStamp(), aProps.ntpTimeMs(), aProps.renderTimeMs()));
 #endif
 
-  // implicitly releases last image
-  mImage = image.forget();
+    // implicitly releases last image
+    mImages[i] = image.forget();
+  }
 
   // We'll push the frame into the MSG on the next NotifyPull. This will avoid
   // swamping the MSG with frames should it be taking longer than normal to run
   // an iteration.
 
   return 0;
 }
 
@@ -459,38 +584,41 @@ MediaEngineRemoteVideoSource::NumCapabil
   }
   return num;
 }
 
 bool
 MediaEngineRemoteVideoSource::ChooseCapability(
     const NormalizedConstraints &aConstraints,
     const MediaEnginePrefs &aPrefs,
-    const nsString& aDeviceId)
+    const nsString& aDeviceId,
+    webrtc::CaptureCapability& aCapability,
+    const DistanceCalculation aCalculate)
 {
   AssertIsOnOwningThread();
 
   switch(mMediaSource) {
     case dom::MediaSourceEnum::Screen:
     case dom::MediaSourceEnum::Window:
     case dom::MediaSourceEnum::Application: {
       FlattenedConstraints c(aConstraints);
       // The actual resolution to constrain around is not easy to find ahead of
       // time (and may in fact change over time), so as a hack, we push ideal
       // and max constraints down to desktop_capture_impl.cc and finish the
       // algorithm there.
-      mCapability.width = (c.mWidth.mIdeal.valueOr(0) & 0xffff) << 16 |
-                          (c.mWidth.mMax & 0xffff);
-      mCapability.height = (c.mHeight.mIdeal.valueOr(0) & 0xffff) << 16 |
-                           (c.mHeight.mMax & 0xffff);
-      mCapability.maxFPS = c.mFrameRate.Clamp(c.mFrameRate.mIdeal.valueOr(aPrefs.mFPS));
+      aCapability.width =
+        (c.mWidth.mIdeal.valueOr(0) & 0xffff) << 16 | (c.mWidth.mMax & 0xffff);
+      aCapability.height =
+        (c.mHeight.mIdeal.valueOr(0) & 0xffff) << 16 | (c.mHeight.mMax & 0xffff);
+      aCapability.maxFPS =
+        c.mFrameRate.Clamp(c.mFrameRate.mIdeal.valueOr(aPrefs.mFPS));
       return true;
     }
     default:
-      return MediaEngineCameraVideoSource::ChooseCapability(aConstraints, aPrefs, aDeviceId);
+      return MediaEngineCameraVideoSource::ChooseCapability(aConstraints, aPrefs, aDeviceId, aCapability, aCalculate);
   }
 
 }
 
 void
 MediaEngineRemoteVideoSource::GetCapability(size_t aIndex,
                                             webrtc::CaptureCapability& aOut) const
 {
--- a/dom/media/webrtc/MediaEngineRemoteVideoSource.h
+++ b/dom/media/webrtc/MediaEngineRemoteVideoSource.h
@@ -79,19 +79,22 @@ public:
                   SourceMediaStream* aSource,
                   TrackID aId,
                   StreamTime aDesiredTime,
                   const PrincipalHandle& aPrincipalHandle) override;
   dom::MediaSourceEnum GetMediaSource() const override {
     return mMediaSource;
   }
 
-  bool ChooseCapability(const NormalizedConstraints &aConstraints,
-                        const MediaEnginePrefs &aPrefs,
-                        const nsString& aDeviceId) override;
+  bool ChooseCapability(
+    const NormalizedConstraints &aConstraints,
+    const MediaEnginePrefs &aPrefs,
+    const nsString& aDeviceId,
+    webrtc::CaptureCapability& aCapability,
+    const DistanceCalculation aCalculate) override;
 
   void Refresh(int aIndex);
 
   void Shutdown() override;
 
   bool GetScary() const override { return mScary; }
 
 protected:
@@ -102,16 +105,17 @@ private:
   void Init();
   size_t NumCapabilities() const override;
   void GetCapability(size_t aIndex, webrtc::CaptureCapability& aOut) const override;
   void SetLastCapability(const webrtc::CaptureCapability& aCapability);
 
   nsresult
   UpdateSingleSource(const AllocationHandle* aHandle,
                      const NormalizedConstraints& aNetConstraints,
+                     const NormalizedConstraints& aNewConstraint,
                      const MediaEnginePrefs& aPrefs,
                      const nsString& aDeviceId,
                      const char** aOutBadConstraint) override;
 
   dom::MediaSourceEnum mMediaSource; // source of media (camera | application | screen)
   mozilla::camera::CaptureEngine mCapEngine;
 
   // To only restart camera when needed, we keep track previous settings.
--- a/dom/media/webrtc/MediaEngineWebRTC.h
+++ b/dom/media/webrtc/MediaEngineWebRTC.h
@@ -561,16 +561,17 @@ public:
 
 protected:
   ~MediaEngineWebRTCMicrophoneSource() {}
 
 private:
   nsresult
   UpdateSingleSource(const AllocationHandle* aHandle,
                      const NormalizedConstraints& aNetConstraints,
+                     const NormalizedConstraints& aNewConstraint,
                      const MediaEnginePrefs& aPrefs,
                      const nsString& aDeviceId,
                      const char** aOutBadConstraint) override;
 
   void SetLastPrefs(const MediaEnginePrefs& aPrefs);
 
   // These allocate/configure and release the channel
   bool AllocChannel();
--- a/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
@@ -274,16 +274,17 @@ bool operator == (const MediaEnginePrefs
 {
   return !memcmp(&a, &b, sizeof(MediaEnginePrefs));
 };
 
 nsresult
 MediaEngineWebRTCMicrophoneSource::UpdateSingleSource(
     const AllocationHandle* aHandle,
     const NormalizedConstraints& aNetConstraints,
+    const NormalizedConstraints& aNewConstraint, /* Ignored */
     const MediaEnginePrefs& aPrefs,
     const nsString& aDeviceId,
     const char** aOutBadConstraint)
 {
   FlattenedConstraints c(aNetConstraints);
 
   MediaEnginePrefs prefs = aPrefs;
   prefs.mAecOn = c.mEchoCancellation.Get(prefs.mAecOn);
--- a/dom/media/webrtc/MediaTrackConstraints.cpp
+++ b/dom/media/webrtc/MediaTrackConstraints.cpp
@@ -412,16 +412,38 @@ MediaConstraintsHelper::FitnessDistance(
   }
   if (aN == aRange.mIdeal.valueOr(aN)) {
     return 0;
   }
   return uint32_t(ValueType((std::abs(aN - aRange.mIdeal.value()) * 1000) /
                             std::max(std::abs(aN), std::abs(aRange.mIdeal.value()))));
 }
 
+template<class ValueType, class NormalizedRange>
+/* static */ uint32_t
+MediaConstraintsHelper::FeasibilityDistance(ValueType aN,
+                                            const NormalizedRange& aRange)
+{
+  if (aRange.mMin > aN) {
+    return UINT32_MAX;
+  }
+  // We prefer larger resolution because now we support downscaling
+  if (aN == aRange.mIdeal.valueOr(aN)) {
+    return 0;
+  }
+
+  if (aN > aRange.mIdeal.value()) {
+    return uint32_t(ValueType((std::abs(aN - aRange.mIdeal.value()) * 1000) /
+      std::max(std::abs(aN), std::abs(aRange.mIdeal.value()))));
+  }
+
+  return 10000 + uint32_t(ValueType((std::abs(aN - aRange.mIdeal.value()) * 1000) /
+    std::max(std::abs(aN), std::abs(aRange.mIdeal.value()))));
+}
+
 // Fitness distance returned as integer math * 1000. Infinity = UINT32_MAX
 
 /* static */ uint32_t
 MediaConstraintsHelper::FitnessDistance(
     nsString aN,
     const NormalizedConstraintSet::StringRange& aParams)
 {
   if (!aParams.mExact.empty() && aParams.mExact.find(aN) == aParams.mExact.end()) {
--- a/dom/media/webrtc/MediaTrackConstraints.h
+++ b/dom/media/webrtc/MediaTrackConstraints.h
@@ -80,22 +80,29 @@ public:
     ValueType Clamp(ValueType n) const { return std::max(mMin, std::min(n, mMax)); }
     ValueType Get(ValueType defaultValue) const {
       return Clamp(mIdeal.valueOr(defaultValue));
     }
     bool Intersects(const Range& aOther) const {
       return mMax >= aOther.mMin && mMin <= aOther.mMax;
     }
     void Intersect(const Range& aOther) {
-      MOZ_ASSERT(Intersects(aOther));
       mMin = std::max(mMin, aOther.mMin);
-      mMax = std::min(mMax, aOther.mMax);
+      if (Intersects(aOther)) {
+        mMax = std::min(mMax, aOther.mMax);
+      } else {
+        // If there is no intersection, we will down-scale or drop frame
+        mMax = std::max(mMax, aOther.mMax);
+      }
     }
     bool Merge(const Range& aOther) {
-      if (!Intersects(aOther)) {
+      if (strcmp(mName, "width") != 0 &&
+          strcmp(mName, "height") != 0 &&
+          strcmp(mName, "frameRate") != 0 &&
+          !Intersects(aOther)) {
         return false;
       }
       Intersect(aOther);
 
       if (aOther.mIdeal.isSome()) {
         // Ideal values, as stored, may be outside their min max range, so use
         // clamped values in averaging, to avoid extreme outliers skewing results.
         if (mIdeal.isNothing()) {
@@ -292,16 +299,18 @@ struct FlattenedConstraints : public Nor
 
 // A helper class for MediaEngines
 
 class MediaConstraintsHelper
 {
 protected:
   template<class ValueType, class NormalizedRange>
   static uint32_t FitnessDistance(ValueType aN, const NormalizedRange& aRange);
+  template<class ValueType, class NormalizedRange>
+  static uint32_t FeasibilityDistance(ValueType aN, const NormalizedRange& aRange);
   static uint32_t FitnessDistance(nsString aN,
       const NormalizedConstraintSet::StringRange& aConstraint);
 
   static uint32_t
   GetMinimumFitnessDistance(const NormalizedConstraintSet &aConstraints,
                             const nsString& aDeviceId);
 
   template<class DeviceType>
--- a/dom/tests/mochitest/general/test_interfaces.js
+++ b/dom/tests/mochitest/general/test_interfaces.js
@@ -1184,17 +1184,17 @@ var interfaceNamesInGlobalScope =
     {name: "VRFrameData", releaseNonWindowsAndMac: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "VRPose", releaseNonWindowsAndMac: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "VRStageParameters", releaseNonWindowsAndMac: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "VTTCue",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "VTTRegion", disabled: true},
+    "VTTRegion",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "WaveShaperNode",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "WebAuthnAssertion", disabled: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "WebAuthnAttestation", disabled: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "WebAuthentication", disabled: true},
--- a/dom/webidl/VTTRegion.webidl
+++ b/dom/webidl/VTTRegion.webidl
@@ -1,28 +1,32 @@
 /* -*- Mode: IDL; 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/.
  *
  * The origin of this IDL file is
- *  http://dev.w3.org/html5/webvtt/#extension-of-the-texttrack-interface-for-region-support
+ * https://w3c.github.io/webvtt/#the-vttregion-interface
  */
 
+enum ScrollSetting {
+  "",
+  "up"
+};
+
 [Constructor, Pref="media.webvtt.regions.enabled"]
 interface VTTRegion {
            attribute DOMString id;
            [SetterThrows]
            attribute double width;
-
+           [SetterThrows]
            attribute long lines;
-
            [SetterThrows]
            attribute double regionAnchorX;
            [SetterThrows]
            attribute double regionAnchorY;
            [SetterThrows]
            attribute double viewportAnchorX;
            [SetterThrows]
            attribute double viewportAnchorY;
-           [SetterThrows]
-           attribute DOMString scroll;
+
+           attribute ScrollSetting scroll;
 };
--- a/editor/libeditor/TextEditor.cpp
+++ b/editor/libeditor/TextEditor.cpp
@@ -461,16 +461,19 @@ TextEditor::CreateBRImpl(nsCOMPtr<nsIDOM
       nsCOMPtr<nsIContent> newLeftNode = SplitNode(atStartOfNewLine, error);
       if (NS_WARN_IF(error.Failed())) {
         return error.StealNSResult();
       }
       // The right node offset in the parent is now changed.  Recompute it.
       pointToInsertBrNode.Set(node);
       Unused << newLeftNode;
     }
+    // Lock the offset of pointToInsertBrNode because it'll be referred after
+    // inserting a new <br> node before it.
+    Unused << pointToInsertBrNode.Offset();
     // create br
     brNode = CreateNode(nsGkAtoms::br, pointToInsertBrNode);
     if (NS_WARN_IF(!brNode)) {
       return NS_ERROR_FAILURE;
     }
     *aInOutParent = GetAsDOMNode(pointToInsertBrNode.Container());
     *aInOutOffset = pointToInsertBrNode.Offset() + 1;
   } else {
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -645,16 +645,17 @@ private:
   DECL_GFX_PREF(Live, "layout.css.scroll-behavior.spring-constant", ScrollBehaviorSpringConstant, float, 250.0f);
   DECL_GFX_PREF(Live, "layout.css.scroll-snap.prediction-max-velocity", ScrollSnapPredictionMaxVelocity, int32_t, 2000);
   DECL_GFX_PREF(Live, "layout.css.scroll-snap.prediction-sensitivity", ScrollSnapPredictionSensitivity, float, 0.750f);
   DECL_GFX_PREF(Live, "layout.css.scroll-snap.proximity-threshold", ScrollSnapProximityThreshold, int32_t, 200);
   DECL_GFX_PREF(Live, "layout.css.touch_action.enabled",       TouchActionEnabled, bool, false);
 
   DECL_GFX_PREF(Live, "layout.display-list.build-twice",       LayoutDisplayListBuildTwice, bool, false);
   DECL_GFX_PREF(Live, "layout.display-list.retain",            LayoutRetainDisplayList, bool, true);
+  DECL_GFX_PREF(Live, "layout.display-list.retain.verify",     LayoutVerifyRetainDisplayList, bool, false);
   DECL_GFX_PREF(Live, "layout.display-list.rebuild-frame-limit", LayoutRebuildFrameLimit, uint32_t, 500);
   DECL_GFX_PREF(Live, "layout.display-list.dump",              LayoutDumpDisplayList, bool, false);
   DECL_GFX_PREF(Live, "layout.display-list.dump-content",      LayoutDumpDisplayListContent, bool, false);
   DECL_GFX_PREF(Live, "layout.display-list.dump-parent",       LayoutDumpDisplayListParent, bool, false);
   DECL_GFX_PREF(Live, "layout.display-list.show-rebuild-area", LayoutDisplayListShowArea, bool, false);
 
   DECL_GFX_PREF(Live, "layout.event-regions.enabled",          LayoutEventRegionsEnabledDoNotUseDirectly, bool, false);
   DECL_GFX_PREF(Once, "layout.frame_rate",                     LayoutFrameRate, int32_t, -1);
--- a/layout/base/nsCSSFrameConstructor.h
+++ b/layout/base/nsCSSFrameConstructor.h
@@ -11,17 +11,16 @@
 
 #ifndef nsCSSFrameConstructor_h___
 #define nsCSSFrameConstructor_h___
 
 #include "mozilla/ArenaAllocator.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/RestyleManager.h"
-#include "mozilla/RestyleManager.h"
 
 #include "nsCOMPtr.h"
 #include "nsILayoutHistoryState.h"
 #include "nsQuoteList.h"
 #include "nsCounterManager.h"
 #include "nsIAnonymousContentCreator.h"
 #include "nsFrameManager.h"
 #include "ScrollbarStyles.h"
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -123,16 +123,17 @@
 #include "mozilla/StyleSetHandleInlines.h"
 #include "RegionBuilder.h"
 #include "SVGViewportElement.h"
 #include "DisplayItemClip.h"
 #include "mozilla/layers/StackingContextHelper.h"
 #include "mozilla/layers/WebRenderLayerManager.h"
 #include "prenv.h"
 #include "RetainedDisplayListBuilder.h"
+#include "DisplayListChecker.h"
 #include "TextDrawTarget.h"
 #include "nsDeckFrame.h"
 #include "nsIEffectiveTLDService.h" // for IsInStyloBlocklist
 #include "mozilla/StylePrefs.h"
 
 #ifdef MOZ_XUL
 #include "nsXULPopupManager.h"
 #endif
@@ -3842,26 +3843,36 @@ nsLayoutUtils::PaintFrame(gfxContext* aR
       nsDisplayListBuilder::AutoCurrentScrollParentIdSetter idSetter(&builder, id);
 
       builder.SetVisibleRect(visibleRect);
       builder.SetIsBuilding(true);
       builder.SetAncestorHasApzAwareEventHandler(
           builder.IsBuildingLayerEventRegions() &&
           nsLayoutUtils::HasDocumentLevelListenersForApzAwareEvents(presShell));
 
+      DisplayListChecker beforeMergeChecker;
+      DisplayListChecker afterMergeChecker;
+
       // Attempt to do a partial build and merge into the existing list.
       // This calls BuildDisplayListForStacking context on a subset of the
       // viewport.
       bool merged = false;
 
       if (useRetainedBuilder) {
+        if (gfxPrefs::LayoutVerifyRetainDisplayList()) {
+          beforeMergeChecker.Set(&list, "BM");
+        }
         merged = retainedBuilder->AttemptPartialUpdate(aBackstop);
+        if (merged && beforeMergeChecker) {
+          afterMergeChecker.Set(&list, "AM");
+        }
       }
 
-      if (merged && gfxPrefs::LayoutDisplayListBuildTwice()) {
+      if (merged &&
+          (gfxPrefs::LayoutDisplayListBuildTwice() || afterMergeChecker)) {
         merged = false;
         if (gfxPrefs::LayersDrawFPS()) {
           if (RefPtr<LayerManager> lm = builder.GetWidgetLayerManager()) {
             if (PaintTiming* pt = ClientLayerManager::MaybeGetPaintTiming(lm)) {
               pt->dl2Ms() = (TimeStamp::Now() - dlStart).ToMilliseconds();
             }
           }
         }
@@ -3872,16 +3883,32 @@ nsLayoutUtils::PaintFrame(gfxContext* aR
         list.DeleteAll(&builder);
         builder.EnterPresShell(aFrame);
         builder.SetDirtyRect(visibleRect);
         builder.ClearWindowDraggingRegion();
         aFrame->BuildDisplayListForStackingContext(&builder, &list);
         AddExtraBackgroundItems(builder, list, aFrame, canvasArea, visibleRegion, aBackstop);
 
         builder.LeavePresShell(aFrame, &list);
+
+        if (afterMergeChecker) {
+          DisplayListChecker nonRetainedChecker(&list, "NR");
+          std::stringstream ss;
+          ss << "**** Differences between retained-after-merged (AM) and "
+             << "non-retained (NR) display lists:";
+          if (!nonRetainedChecker.CompareList(afterMergeChecker, ss)) {
+            ss << "\n\n*** non-retained display items:";
+            nonRetainedChecker.Dump(ss);
+            ss << "\n\n*** before-merge retained display items:";
+            beforeMergeChecker.Dump(ss);
+            ss << "\n\n*** after-merge retained display items:";
+            afterMergeChecker.Dump(ss);
+            fprintf(stderr, "%s\n\n", ss.str().c_str());
+          }
+        }
       }
     }
 
     builder.SetIsBuilding(false);
     builder.IncrementPresShellPaintCount(presShell);
 
     if (gfxPrefs::LayersDrawFPS()) {
       if (RefPtr<LayerManager> lm = builder.GetWidgetLayerManager()) {
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -3626,29 +3626,33 @@ ScrollFrameHelper::BuildDisplayList(nsDi
 
   if (mWillBuildScrollableLayer && aBuilder->IsPaintingToWindow()) {
     aBuilder->ForceLayerForScrollParent();
   }
 
   if (couldBuildLayer) {
     // Make sure that APZ will dispatch events back to content so we can create
     // a displayport for this frame. We'll add the item later on.
-    nsDisplayLayerEventRegions* inactiveRegionItem = nullptr;
-    if (aBuilder->IsPaintingToWindow() &&
-        !mWillBuildScrollableLayer &&
-        aBuilder->IsBuildingLayerEventRegions())
-    {
-      inactiveRegionItem = new (aBuilder) nsDisplayLayerEventRegions(aBuilder, mScrolledFrame, 1);
-      inactiveRegionItem->AddInactiveScrollPort(mScrolledFrame, mScrollPort + aBuilder->ToReferenceFrame(mOuter));
-    }
-
-    if (inactiveRegionItem) {
+    if (!mWillBuildScrollableLayer) {
       int32_t zIndex =
         MaxZIndexInListOfItemsContainedInFrame(scrolledContent.PositionedDescendants(), mOuter);
-      AppendInternalItemToTop(scrolledContent, inactiveRegionItem, zIndex);
+      if (aBuilder->BuildCompositorHitTestInfo()) {
+        CompositorHitTestInfo info = CompositorHitTestInfo::eVisibleToHitTest
+                                   | CompositorHitTestInfo::eDispatchToContent;
+        nsDisplayCompositorHitTestInfo* hitInfo =
+            new (aBuilder) nsDisplayCompositorHitTestInfo(aBuilder, mScrolledFrame, info, 1);
+        hitInfo->SetArea(mScrollPort + aBuilder->ToReferenceFrame(mOuter));
+        AppendInternalItemToTop(scrolledContent, hitInfo, zIndex);
+      }
+      if (aBuilder->IsBuildingLayerEventRegions()) {
+        nsDisplayLayerEventRegions* inactiveRegionItem =
+            new (aBuilder) nsDisplayLayerEventRegions(aBuilder, mScrolledFrame, 1);
+        inactiveRegionItem->AddInactiveScrollPort(mScrolledFrame, mScrollPort + aBuilder->ToReferenceFrame(mOuter));
+        AppendInternalItemToTop(scrolledContent, inactiveRegionItem, zIndex);
+      }
     }
 
     if (aBuilder->ShouldBuildScrollInfoItemsForHoisting()) {
       aBuilder->AppendNewScrollInfoItemForHoisting(
         new (aBuilder) nsDisplayScrollInfoLayer(aBuilder, mScrolledFrame,
                                                 mOuter));
     }
   }
new file mode 100644
--- /dev/null
+++ b/layout/painting/DisplayListChecker.cpp
@@ -0,0 +1,371 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DisplayListChecker.h"
+
+#include "nsDisplayList.h"
+
+namespace mozilla {
+
+class DisplayItemBlueprint;
+
+// Stack node used during tree visits, to store the path to a display item.
+struct DisplayItemBlueprintStack
+{
+  const DisplayItemBlueprintStack* mPrevious;
+  const DisplayItemBlueprint* mItem;
+  // Output stack to aSs, with format "name#index > ... > name#index".
+  // Returns true if anything was output, false if empty.
+  bool Output(std::stringstream& aSs) const;
+};
+
+// Object representing a list of display items (either the top of the tree, or
+// an item's children), with just enough information to compare with another
+// tree and output useful debugging information.
+class DisplayListBlueprint
+{
+public:
+  DisplayListBlueprint(nsDisplayList* aList, const char* aName)
+    : DisplayListBlueprint(aList, 0, aName)
+  {
+  }
+
+  DisplayListBlueprint(nsDisplayList* aList,
+                       const char* aName,
+                       unsigned& aIndex)
+  {
+    processChildren(aList, aName, aIndex);
+  }
+
+  // Find a display item with the given frame and per-frame key.
+  // Returns empty string if not found.
+  std::string Find(const nsIFrame* aFrame, uint32_t aPerFrameKey) const
+  {
+    const DisplayItemBlueprintStack stack{ nullptr, nullptr };
+    return Find(aFrame, aPerFrameKey, stack);
+  }
+
+  std::string Find(const nsIFrame* aFrame,
+                   uint32_t aPerFrameKey,
+                   const DisplayItemBlueprintStack& aStack) const;
+
+  // Compare this list with another one, output differences between the two
+  // into aDiff.
+  // Differences include: Display items from one tree for which a corresponding
+  // item (same frame and per-frame key) cannot be found under corresponding
+  // parent items.
+  // Returns true if trees are similar, false if different.
+  bool CompareList(const DisplayListBlueprint& aOther,
+                   std::stringstream& aDiff) const
+  {
+    const DisplayItemBlueprintStack stack{ nullptr, nullptr };
+    const bool ab = CompareList(*this, aOther, aOther, aDiff, stack, stack);
+    const bool ba =
+      aOther.CompareList(aOther, *this, *this, aDiff, stack, stack);
+    return ab && ba;
+  }
+
+  bool CompareList(const DisplayListBlueprint& aRoot,
+                   const DisplayListBlueprint& aOther,
+                   const DisplayListBlueprint& aOtherRoot,
+                   std::stringstream& aDiff,
+                   const DisplayItemBlueprintStack& aStack,
+                   const DisplayItemBlueprintStack& aStackOther) const;
+
+  // Output this tree to aSs.
+  void Dump(std::stringstream& aSs) const { Dump(aSs, 0); }
+
+  void Dump(std::stringstream& aSs, unsigned aDepth) const;
+
+private:
+  // Only used by first constructor, to call the 2nd constructor with an index
+  // variable on the stack.
+  DisplayListBlueprint(nsDisplayList* aList, unsigned aIndex, const char* aName)
+    : DisplayListBlueprint(aList, aName, aIndex)
+  {
+  }
+
+  void processChildren(nsDisplayList* aList,
+                       const char* aName,
+                       unsigned& aIndex);
+
+  std::vector<DisplayItemBlueprint> mItems;
+};
+
+// Object representing one display item, with just enough information to
+// compare with another item and output useful debugging information.
+class DisplayItemBlueprint
+{
+public:
+  DisplayItemBlueprint(nsDisplayItem& aItem,
+                       const char* aName,
+                       unsigned& aIndex)
+    : mListName(aName)
+    , mIndex(++aIndex)
+    , mIndexString(WriteIndex(aName, aIndex))
+    , mIndexStringFW(WriteIndexFW(aName, aIndex))
+    , mDisplayItemPointer(WriteDisplayItemPointer(aItem))
+    , mDescription(WriteDescription(aName, aIndex, aItem))
+    , mFrame(aItem.HasDeletedFrame() ? nullptr : aItem.Frame())
+    , mPerFrameKey(aItem.GetPerFrameKey())
+    , mChildren(aItem.GetChildren(), aName, aIndex)
+  {
+  }
+
+  // Compare this item with another one, based on frame and per-frame key.
+  // Not recursive! I.e., children are not examined.
+  bool CompareItem(const DisplayItemBlueprint& aOther,
+                   std::stringstream& aDiff) const
+  {
+    return mFrame == aOther.mFrame && mPerFrameKey == aOther.mPerFrameKey;
+  }
+
+  void Dump(std::stringstream& aSs, unsigned aDepth) const;
+
+  const char* mListName;
+  const unsigned mIndex;
+  const std::string mIndexString;
+  const std::string mIndexStringFW;
+  const std::string mDisplayItemPointer;
+  const std::string mDescription;
+
+  // For pointer comparison only, do not dereference!
+  const nsIFrame* const mFrame;
+  const uint32_t mPerFrameKey;
+
+  const DisplayListBlueprint mChildren;
+
+private:
+  static std::string WriteIndex(const char* aName, unsigned aIndex)
+  {
+    return nsPrintfCString("%s#%u", aName, aIndex).get();
+  }
+
+  static std::string WriteIndexFW(const char* aName, unsigned aIndex)
+  {
+    return nsPrintfCString("%s#%4u", aName, aIndex).get();
+  }
+
+  static std::string WriteDisplayItemPointer(nsDisplayItem& aItem)
+  {
+    return nsPrintfCString("0x%p", &aItem).get();
+  }
+
+  static std::string WriteDescription(const char* aName,
+                                      unsigned aIndex,
+                                      nsDisplayItem& aItem)
+  {
+    if (aItem.HasDeletedFrame()) {
+      return nsPrintfCString(
+               "%s %s#%u 0x%p f=0x0", aItem.Name(), aName, aIndex, &aItem)
+        .get();
+    }
+
+    const nsIFrame* f = aItem.Frame();
+    nsAutoString contentData;
+#ifdef DEBUG_FRAME_DUMP
+    f->GetFrameName(contentData);
+#endif
+    nsIContent* content = f->GetContent();
+    if (content) {
+      nsString tmp;
+      if (content->GetID()) {
+        content->GetID()->ToString(tmp);
+        contentData.AppendLiteral(" id:");
+        contentData.Append(tmp);
+      }
+      const nsAttrValue* classes =
+        content->IsElement() ? content->AsElement()->GetClasses() : nullptr;
+      if (classes) {
+        classes->ToString(tmp);
+        contentData.AppendLiteral(" class:");
+        contentData.Append(tmp);
+      }
+    }
+    return nsPrintfCString("%s %s#%u p=0x%p f=0x%p(%s) key=%" PRIu32,
+                           aItem.Name(),
+                           aName,
+                           aIndex,
+                           &aItem,
+                           f,
+                           NS_ConvertUTF16toUTF8(contentData).get(),
+                           aItem.GetPerFrameKey())
+      .get();
+  }
+};
+
+void
+DisplayListBlueprint::processChildren(nsDisplayList* aList,
+                                      const char* aName,
+                                      unsigned& aIndex)
+{
+  if (!aList) {
+    return;
+  }
+  const uint32_t n = aList->Count();
+  if (n == 0) {
+    return;
+  }
+  mItems.reserve(n);
+  for (nsDisplayItem* item = aList->GetBottom(); item;
+       item = item->GetAbove()) {
+    mItems.emplace_back(*item, aName, aIndex);
+  }
+  MOZ_ASSERT(mItems.size() == n);
+}
+
+bool
+DisplayItemBlueprintStack::Output(std::stringstream& aSs) const
+{
+  const bool output = mPrevious ? mPrevious->Output(aSs) : false;
+  if (mItem) {
+    if (output) {
+      aSs << " > ";
+    }
+    aSs << mItem->mIndexString;
+    return true;
+  }
+  return output;
+}
+
+std::string
+DisplayListBlueprint::Find(const nsIFrame* aFrame,
+                           uint32_t aPerFrameKey,
+                           const DisplayItemBlueprintStack& aStack) const
+{
+  for (const DisplayItemBlueprint& item : mItems) {
+    if (item.mFrame == aFrame && item.mPerFrameKey == aPerFrameKey) {
+      std::stringstream ss;
+      if (aStack.Output(ss)) {
+        ss << " > ";
+      }
+      ss << item.mDescription;
+      return ss.str();
+    }
+    const DisplayItemBlueprintStack stack = { &aStack, &item };
+    std::string s = item.mChildren.Find(aFrame, aPerFrameKey, stack);
+    if (!s.empty()) {
+      return s;
+    }
+  }
+  return "";
+}
+
+bool
+DisplayListBlueprint::CompareList(
+  const DisplayListBlueprint& aRoot,
+  const DisplayListBlueprint& aOther,
+  const DisplayListBlueprint& aOtherRoot,
+  std::stringstream& aDiff,
+  const DisplayItemBlueprintStack& aStack,
+  const DisplayItemBlueprintStack& aStackOther) const
+{
+  bool same = true;
+  for (const DisplayItemBlueprint& itemBefore : mItems) {
+    bool found = false;
+    for (const DisplayItemBlueprint& itemAfter : aOther.mItems) {
+      if (itemBefore.CompareItem(itemAfter, aDiff)) {
+        found = true;
+
+        const DisplayItemBlueprintStack stack = { &aStack, &itemBefore };
+        const DisplayItemBlueprintStack stackOther = { &aStackOther,
+                                                       &itemAfter };
+        if (!itemBefore.mChildren.CompareList(aRoot,
+                                              itemAfter.mChildren,
+                                              aOtherRoot,
+                                              aDiff,
+                                              stack,
+                                              stackOther)) {
+          same = false;
+        }
+        break;
+      }
+    }
+    if (!found) {
+      same = false;
+      aDiff << "\n";
+      if (aStack.Output(aDiff)) {
+        aDiff << " > ";
+      }
+      aDiff << itemBefore.mDescription;
+      aDiff << "\n * Cannot find corresponding item under ";
+      if (!aStackOther.Output(aDiff)) {
+        if (!aOtherRoot.mItems.empty()) {
+          aDiff << aOtherRoot.mItems[0].mListName;
+        } else {
+          aDiff << "other root";
+        }
+      }
+      std::string elsewhere =
+        aOtherRoot.Find(itemBefore.mFrame, itemBefore.mPerFrameKey);
+      if (!elsewhere.empty()) {
+        aDiff << "\n * But found: " << elsewhere;
+      }
+    }
+  }
+  return same;
+}
+
+void
+DisplayListBlueprint::Dump(std::stringstream& aSs, unsigned aDepth) const
+{
+  for (const DisplayItemBlueprint& item : mItems) {
+    item.Dump(aSs, aDepth);
+  }
+}
+
+void
+DisplayItemBlueprint::Dump(std::stringstream& aSs, unsigned aDepth) const
+{
+  aSs << "\n" << mIndexStringFW << " ";
+  for (unsigned i = 0; i < aDepth; ++i) {
+    aSs << "  ";
+  }
+  aSs << mDescription;
+  mChildren.Dump(aSs, aDepth + 1);
+}
+
+DisplayListChecker::DisplayListChecker()
+  : mBlueprint(nullptr)
+{
+}
+
+DisplayListChecker::DisplayListChecker(nsDisplayList* aList, const char* aName)
+  : mBlueprint(MakeUnique<DisplayListBlueprint>(aList, aName))
+{
+}
+
+DisplayListChecker::~DisplayListChecker() = default;
+
+void
+DisplayListChecker::Set(nsDisplayList* aList, const char* aName)
+{
+  mBlueprint = MakeUnique<DisplayListBlueprint>(aList, aName);
+}
+
+// Compare this list with another one, output differences between the two
+// into aDiff.
+// Differences include: Display items from one tree for which a corresponding
+// item (same frame and per-frame key) cannot be found under corresponding
+// parent items.
+// Returns true if trees are similar, false if different.
+bool
+DisplayListChecker::CompareList(const DisplayListChecker& aOther,
+                                std::stringstream& aDiff) const
+{
+  MOZ_ASSERT(mBlueprint);
+  MOZ_ASSERT(aOther.mBlueprint);
+  return mBlueprint->CompareList(*aOther.mBlueprint, aDiff);
+}
+
+void
+DisplayListChecker::Dump(std::stringstream& aSs) const
+{
+  MOZ_ASSERT(mBlueprint);
+  mBlueprint->Dump(aSs);
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/layout/painting/DisplayListChecker.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DisplayListChecker_h__
+#define DisplayListChecker_h__
+
+#include <sstream>
+#include <mozilla/UniquePtr.h>
+
+class nsDisplayList;
+
+namespace mozilla {
+
+class DisplayListBlueprint;
+
+class DisplayListChecker
+{
+public:
+  DisplayListChecker();
+  DisplayListChecker(nsDisplayList* aList, const char* aName);
+
+  ~DisplayListChecker();
+
+  void Set(nsDisplayList* aList, const char* aName);
+
+  explicit operator bool() const { return mBlueprint.get(); }
+
+  // Compare this list with another one, output differences between the two
+  // into aDiff.
+  // Differences include: Display items from one tree for which a corresponding
+  // item (same frame and per-frame key) cannot be found under corresponding
+  // parent items.
+  // Returns true if trees are similar, false if different.
+  bool CompareList(const DisplayListChecker& aOther,
+                   std::stringstream& aDiff) const;
+
+  // Output this tree to aSs.
+  void Dump(std::stringstream& aSs) const;
+
+private:
+  UniquePtr<DisplayListBlueprint> mBlueprint;
+};
+
+} // namespace mozilla
+
+#endif // DisplayListChecker_h__
--- a/layout/painting/moz.build
+++ b/layout/painting/moz.build
@@ -30,16 +30,17 @@ EXPORTS.mozilla += [
 ]
 
 UNIFIED_SOURCES += [
     'ActiveLayerTracker.cpp',
     'DashedCornerFinder.cpp',
     'DisplayItemClip.cpp',
     'DisplayItemClipChain.cpp',
     'DisplayItemScrollClip.cpp',
+    'DisplayListChecker.cpp',
     'DisplayListClipState.cpp',
     'DottedCornerFinder.cpp',
     'FrameLayerBuilder.cpp',
     'MaskLayerImageCache.cpp',
     'nsCSSRendering.cpp',
     'nsCSSRenderingBorders.cpp',
     'nsCSSRenderingGradients.cpp',
     'nsDisplayList.cpp',
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -4865,62 +4865,75 @@ nsDisplayEventReceiver::CreateWebRenderC
   // This display item should never be getting created when building a display
   // list for WebRender consumption, so this function should never get called.
   MOZ_ASSERT(false);
   return true;
 }
 
 nsDisplayCompositorHitTestInfo::nsDisplayCompositorHitTestInfo(nsDisplayListBuilder* aBuilder,
                                                                nsIFrame* aFrame,
-                                                               mozilla::gfx::CompositorHitTestInfo aHitTestInfo)
+                                                               mozilla::gfx::CompositorHitTestInfo aHitTestInfo,
+                                                               uint32_t aIndex)
   : nsDisplayEventReceiver(aBuilder, aFrame)
   , mHitTestInfo(aHitTestInfo)
+  , mIndex(aIndex)
 {
   MOZ_COUNT_CTOR(nsDisplayCompositorHitTestInfo);
   // We should never even create this display item if we're not building
   // compositor hit-test info or if the computed hit info indicated the
   // frame is invisible to hit-testing
   MOZ_ASSERT(aBuilder->BuildCompositorHitTestInfo());
   MOZ_ASSERT(mHitTestInfo != mozilla::gfx::CompositorHitTestInfo::eInvisibleToHitTest);
 
   if (aBuilder->GetCurrentScrollbarFlags() != nsDisplayOwnLayerFlags::eNone) {
     // In the case of scrollbar frames, we use the scrollbar's target scrollframe
     // instead of the scrollframe with which the scrollbar actually moves.
     MOZ_ASSERT(mHitTestInfo & CompositorHitTestInfo::eScrollbar);
     mScrollTarget = Some(aBuilder->GetCurrentScrollbarTarget());
   }
 }
 
+void
+nsDisplayCompositorHitTestInfo::SetArea(const nsRect& aArea)
+{
+  mArea = Some(aArea);
+}
+
 bool
 nsDisplayCompositorHitTestInfo::CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
                                                         mozilla::wr::IpcResourceUpdateQueue& aResources,
                                                         const StackingContextHelper& aSc,
                                                         mozilla::layers::WebRenderLayerManager* aManager,
                                                         nsDisplayListBuilder* aDisplayListBuilder)
 {
-  nsRect borderBox;
-  nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetScrollableFrameFor(mFrame);
-  if (scrollFrame) {
-    // If the frame is content of a scrollframe, then we need to pick up the
-    // area corresponding to the overflow rect as well. Otherwise the parts of
-    // the overflow that are not occupied by descendants get skipped and the
-    // APZ code sends touch events to the content underneath instead.
-    // See https://bugzilla.mozilla.org/show_bug.cgi?id=1127773#c15.
-    borderBox = mFrame->GetScrollableOverflowRect();
-  } else {
-    borderBox = nsRect(nsPoint(0, 0), mFrame->GetSize());
-  }
-
-  if (borderBox.IsEmpty()) {
-    return true;
-  }
-
+  if (mArea.isNothing()) {
+    nsRect borderBox;
+    nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetScrollableFrameFor(mFrame);
+    if (scrollFrame) {
+      // If the frame is content of a scrollframe, then we need to pick up the
+      // area corresponding to the overflow rect as well. Otherwise the parts of
+      // the overflow that are not occupied by descendants get skipped and the
+      // APZ code sends touch events to the content underneath instead.
+      // See https://bugzilla.mozilla.org/show_bug.cgi?id=1127773#c15.
+      borderBox = mFrame->GetScrollableOverflowRect();
+    } else {
+      borderBox = nsRect(nsPoint(0, 0), mFrame->GetSize());
+    }
+
+    if (borderBox.IsEmpty()) {
+      return true;
+    }
+
+    mArea = Some(borderBox + aDisplayListBuilder->ToReferenceFrame(mFrame));
+  }
+
+  MOZ_ASSERT(mArea.isSome());
   wr::LayoutRect rect = aSc.ToRelativeLayoutRect(
       LayoutDeviceRect::FromAppUnits(
-          borderBox + aDisplayListBuilder->ToReferenceFrame(mFrame),
+          *mArea,
           mFrame->PresContext()->AppUnitsPerDevPixel()));
 
   // XXX: eventually this scrollId computation and the SetHitTestInfo
   // call will get moved out into the WR display item iteration code so that
   // we don't need to do it as often, and so that we can do it for other
   // display item types as well (reducing the need for as many instances of
   // this display item).
   FrameMetrics::ViewID scrollId = mScrollTarget.valueOrFrom(
@@ -4940,16 +4953,34 @@ nsDisplayCompositorHitTestInfo::CreateWe
 }
 
 void
 nsDisplayCompositorHitTestInfo::WriteDebugInfo(std::stringstream& aStream)
 {
   aStream << nsPrintfCString(" (hitTestInfo 0x%x)", (int)mHitTestInfo).get();
 }
 
+uint32_t
+nsDisplayCompositorHitTestInfo::GetPerFrameKey() const
+{
+  return (mIndex << TYPE_BITS) | nsDisplayItem::GetPerFrameKey();
+}
+
+int32_t
+nsDisplayCompositorHitTestInfo::ZIndex() const
+{
+  return mOverrideZIndex ? *mOverrideZIndex : nsDisplayItem::ZIndex();
+}
+
+void
+nsDisplayCompositorHitTestInfo::SetOverrideZIndex(int32_t aZIndex)
+{
+  mOverrideZIndex = Some(aZIndex);
+}
+
 void
 nsDisplayLayerEventRegions::AddFrame(nsDisplayListBuilder* aBuilder,
                                      nsIFrame* aFrame)
 {
   NS_ASSERTION(aBuilder->FindReferenceFrameFor(aFrame) == aBuilder->FindReferenceFrameFor(mFrame),
                "Reference frame mismatch");
   CompositorHitTestInfo hitInfo =
       aFrame->GetCompositorHitTestInfo(aBuilder);
--- a/layout/painting/nsDisplayList.h
+++ b/layout/painting/nsDisplayList.h
@@ -4338,39 +4338,47 @@ public:
  * Similar to nsDisplayEventReceiver in that it is used for hit-testing. However
  * this gets built when we're doing widget painting and we need to send the
  * compositor some hit-test info for a frame. This is effectively a dummy item
  * whose sole purpose is to carry the hit-test info to the compositor.
  */
 class nsDisplayCompositorHitTestInfo : public nsDisplayEventReceiver {
 public:
   nsDisplayCompositorHitTestInfo(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
-                                 mozilla::gfx::CompositorHitTestInfo aHitTestInfo);
+                                 mozilla::gfx::CompositorHitTestInfo aHitTestInfo,
+                                 uint32_t aIndex = 0);
 
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayCompositorHitTestInfo()
   {
     MOZ_COUNT_DTOR(nsDisplayCompositorHitTestInfo);
   }
 #endif
 
   mozilla::gfx::CompositorHitTestInfo HitTestInfo() const { return mHitTestInfo; }
+  void SetArea(const nsRect& aArea);
 
   bool CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
                                mozilla::wr::IpcResourceUpdateQueue& aResources,
                                const StackingContextHelper& aSc,
                                mozilla::layers::WebRenderLayerManager* aManager,
                                nsDisplayListBuilder* aDisplayListBuilder) override;
   void WriteDebugInfo(std::stringstream& aStream) override;
+  uint32_t GetPerFrameKey() const override;
+  int32_t ZIndex() const override;
+  void SetOverrideZIndex(int32_t aZIndex);
 
   NS_DISPLAY_DECL_NAME("CompositorHitTestInfo", TYPE_COMPOSITOR_HITTEST_INFO)
 
 private:
   mozilla::gfx::CompositorHitTestInfo mHitTestInfo;
   mozilla::Maybe<mozilla::layers::FrameMetrics::ViewID> mScrollTarget;
+  mozilla::Maybe<nsRect> mArea;
+  uint32_t mIndex;
+  mozilla::Maybe<int32_t> mOverrideZIndex;
 };
 
 /**
  * A display item that tracks event-sensitive regions which will be set
  * on the ContainerLayer that eventually contains this item.
  *
  * One of these is created for each stacking context and pseudo-stacking-context.
  * It accumulates regions for event targets contributed by the border-boxes of
--- a/layout/style/test/animation_utils.js
+++ b/layout/style/test/animation_utils.js
@@ -257,19 +257,17 @@ function runOMTATest(aTestFunction, aOnS
 
     return waitForDocumentLoad()
       .then(loadPaintListener)
       .then(function() {
         // Put refresh driver under test control and trigger animation
         utils.advanceTimeAndRefresh(0);
         div.style.animation = animationName + " 10s";
 
-        // Trigger style flush
-        div.clientTop;
-        return waitForPaints();
+        return waitForPaintsFlushed();
       }).then(function() {
         var opacity = utils.getOMTAStyle(div, "opacity");
         cleanUp();
         return Promise.resolve(opacity == 0.5);
       }).catch(function(err) {
         cleanUp();
         return Promise.reject(err);
       });
@@ -280,22 +278,16 @@ function runOMTATest(aTestFunction, aOnS
       if (document.readyState === "complete") {
         resolve();
       } else {
         window.addEventListener("load", resolve);
       }
     });
   }
 
-  function waitForPaints() {
-    return new Promise(function(resolve, reject) {
-      waitForAllPaintsFlushed(resolve);
-    });
-  }
-
   function loadPaintListener() {
     return new Promise(function(resolve, reject) {
       if (typeof(window.waitForAllPaints) !== "function") {
         var script = document.createElement("script");
         script.onload = resolve;
         script.onerror = function() {
           reject(new Error("Failed to load paint listener"));
         };
--- a/media/gmp-clearkey/0.1/ClearKeyCDM.cpp
+++ b/media/gmp-clearkey/0.1/ClearKeyCDM.cpp
@@ -1,29 +1,38 @@
 #include "ClearKeyCDM.h"
 
 #include "ClearKeyUtils.h"
 
 using namespace cdm;
 
-ClearKeyCDM::ClearKeyCDM(Host_8* aHost)
+ClearKeyCDM::ClearKeyCDM(Host_9* aHost)
 {
   mHost = aHost;
   mSessionManager = new ClearKeySessionManager(mHost);
 }
 
 void
 ClearKeyCDM::Initialize(bool aAllowDistinctiveIdentifier,
                         bool aAllowPersistentState)
 {
   mSessionManager->Init(aAllowDistinctiveIdentifier,
                         aAllowPersistentState);
 }
 
 void
+ClearKeyCDM::GetStatusForPolicy(uint32_t aPromiseId,
+                                const Policy& aPolicy)
+{
+  // MediaKeys::GetStatusForPolicy checks the keysystem and
+  // reject the promise with NS_ERROR_DOM_NOT_SUPPORTED_ERR without calling CDM.
+  // This function should never be called and is not supported.
+  assert(false);
+}
+void
 ClearKeyCDM::SetServerCertificate(uint32_t aPromiseId,
                                   const uint8_t* aServerCertificateData,
                                   uint32_t aServerCertificateDataSize)
 {
   mSessionManager->SetServerCertificate(aPromiseId,
                                         aServerCertificateData,
                                         aServerCertificateDataSize);
 }
@@ -179,19 +188,28 @@ ClearKeyCDM::OnQueryOutputProtectionStat
                                            uint32_t aLinkMask,
                                            uint32_t aOutputProtectionMask)
 {
   // This function should never be called and is not supported.
   assert(false);
 }
 
 void
+ClearKeyCDM::OnStorageId(uint32_t aVersion,
+                         const uint8_t* aStorageId,
+                         uint32_t aStorageIdSize)
+{
+  // This function should never be called and is not supported.
+  assert(false);
+}
+
+void
 ClearKeyCDM::Destroy()
 {
   mSessionManager->DecryptingComplete();
 #ifdef ENABLE_WMF
   // If we have called 'DeinitializeDecoder' mVideoDecoder will be null.
   if (mVideoDecoder) {
     mVideoDecoder->DecodingComplete();
   }
 #endif
   delete this;
-}
\ No newline at end of file
+}
--- a/media/gmp-clearkey/0.1/ClearKeyCDM.h
+++ b/media/gmp-clearkey/0.1/ClearKeyCDM.h
@@ -8,33 +8,36 @@
 #include "stddef.h"
 #include "content_decryption_module.h"
 
 #ifdef ENABLE_WMF
 #include "WMFUtils.h"
 #include "VideoDecoder.h"
 #endif
 
-class ClearKeyCDM : public cdm::ContentDecryptionModule_8
+class ClearKeyCDM : public cdm::ContentDecryptionModule_9
 {
 private:
   RefPtr<ClearKeySessionManager> mSessionManager;
 #ifdef ENABLE_WMF
   RefPtr<VideoDecoder> mVideoDecoder;
 #endif
 
 protected:
-  cdm::Host_8* mHost;
+  cdm::Host_9* mHost;
 
 public:
-  explicit ClearKeyCDM(cdm::Host_8* mHost);
+  explicit ClearKeyCDM(cdm::Host_9* mHost);
 
   void Initialize(bool aAllowDistinctiveIdentifier,
                   bool aAllowPersistentState) override;
 
+  void GetStatusForPolicy(uint32_t aPromiseId,
+                          const cdm::Policy& aPolicy) override;
+
   void SetServerCertificate(uint32_t aPromiseId,
                             const uint8_t* aServerCertificateData,
                             uint32_t aServerCertificateDataSize)
                             override;
 
   void CreateSessionAndGenerateRequest(uint32_t aPromiseId,
                                        cdm::SessionType aSessionType,
                                        cdm::InitDataType aInitDataType,
@@ -87,12 +90,16 @@ public:
   void OnPlatformChallengeResponse(
     const cdm::PlatformChallengeResponse& aResponse) override;
 
   void
     OnQueryOutputProtectionStatus(cdm::QueryResult aResult,
                                   uint32_t aLinkMask,
                                   uint32_t aOutputProtectionMask) override;
 
+  void OnStorageId(uint32_t aVersion,
+                   const uint8_t* aStorageId,
+                   uint32_t aStorageIdSize) override;
+
   void Destroy() override;
 };
 
-#endif
\ No newline at end of file
+#endif
--- a/media/gmp-clearkey/0.1/ClearKeyPersistence.cpp
+++ b/media/gmp-clearkey/0.1/ClearKeyPersistence.cpp
@@ -96,17 +96,17 @@ ClearKeyPersistence::WriteIndex() {
   WriteData(mHost,
             filename,
             data,
             move(onIndexSuccess),
             move(onIndexFail));
 }
 
 
-ClearKeyPersistence::ClearKeyPersistence(Host_8* aHost)
+ClearKeyPersistence::ClearKeyPersistence(Host_9* aHost)
 {
   this->mHost = aHost;
 }
 
 void
 ClearKeyPersistence::EnsureInitialized(bool aPersistentStateAllowed,
                                        function<void()>&& aOnInitialized)
 {
--- a/media/gmp-clearkey/0.1/ClearKeyPersistence.h
+++ b/media/gmp-clearkey/0.1/ClearKeyPersistence.h
@@ -36,30 +36,30 @@ enum PersistentKeyState {
   UNINITIALIZED,
   LOADING,
   LOADED
 };
 
 class ClearKeyPersistence : public RefCounted
 {
 public:
-  explicit ClearKeyPersistence(cdm::Host_8* aHost);
+  explicit ClearKeyPersistence(cdm::Host_9* aHost);
 
   void EnsureInitialized(bool aPersistentStateAllowed,
                          std::function<void()>&& aOnInitialized);
 
   bool IsLoaded() const;
 
   std::string GetNewSessionId(cdm::SessionType aSessionType);
 
   bool IsPersistentSessionId(const std::string& aSid);
 
   void PersistentSessionRemoved(std::string& aSid);
 private:
-  cdm::Host_8* mHost = nullptr;
+  cdm::Host_9* mHost = nullptr;
 
   PersistentKeyState mPersistentKeyState = PersistentKeyState::UNINITIALIZED;
 
   std::set<uint32_t> mPersistentSessionIds;
 
   void ReadAllRecordsFromIndex(std::function<void()>&& aOnComplete);
   void WriteIndex();
 };
--- a/media/gmp-clearkey/0.1/ClearKeySessionManager.cpp
+++ b/media/gmp-clearkey/0.1/ClearKeySessionManager.cpp
@@ -28,17 +28,17 @@
 #include <assert.h>
 #include <stdint.h>
 #include <stdio.h>
 #include <string.h>
 
 using namespace std;
 using namespace cdm;
 
-ClearKeySessionManager::ClearKeySessionManager(Host_8* aHost)
+ClearKeySessionManager::ClearKeySessionManager(Host_9* aHost)
   : mDecryptionManager(ClearKeyDecryptionManager::Get())
 {
   CK_LOGD("ClearKeySessionManager ctor %p", this);
   AddRef();
 
   mHost = aHost;
   mPersistence = new ClearKeyPersistence(mHost);
 }
@@ -112,17 +112,17 @@ ClearKeySessionManager::CreateSession(ui
 
   // initDataType must be "cenc", "keyids", or "webm".
   if (aInitDataType != InitDataType::kCenc &&
       aInitDataType != InitDataType::kKeyIds &&
       aInitDataType != InitDataType::kWebM) {
 
     string message = "initDataType is not supported by ClearKey";
     mHost->OnRejectPromise(aPromiseId,
-                           Error::kNotSupportedError,
+                           Exception::kExceptionNotSupportedError,
                            0,
                            message.c_str(),
                            message.size());
 
     return;
   }
 
   string sessionId = mPersistence->GetNewSessionId(aSessionType);
@@ -132,17 +132,17 @@ ClearKeySessionManager::CreateSession(ui
                                                  aSessionType);
 
   if (!session->Init(aInitDataType, aInitData, aInitDataSize)) {
 
     CK_LOGD("Failed to initialize session: %s", sessionId.c_str());
 
     const static char* message = "Failed to initialize session";
     mHost->OnRejectPromise(aPromiseId,
-                           Error::kUnknownError,
+                           Exception::kExceptionInvalidStateError,
                            0,
                            message,
                            strlen(message));
     delete session;
 
     return;
   }
 
@@ -173,19 +173,17 @@ ClearKeySessionManager::CreateSession(ui
   mHost->OnResolveNewSessionPromise(aPromiseId,
                                     sessionId.c_str(),
                                     sessionId.size());
 
   mHost->OnSessionMessage(sessionId.c_str(),
                           sessionId.size(),
                           MessageType::kLicenseRequest,
                           request.c_str(),
-                          request.size(),
-                          nullptr,
-                          0);
+                          request.size());
 }
 
 void
 ClearKeySessionManager::LoadSession(uint32_t aPromiseId,
                                     const char* aSessionId,
                                     uint32_t aSessionIdLength)
 {
   CK_LOGD("ClearKeySessionManager::LoadSession");
@@ -351,49 +349,49 @@ ClearKeySessionManager::UpdateSession(ui
 
   CK_LOGD("Updating session: %s", sessionId.c_str());
 
   auto itr = mSessions.find(sessionId);
   if (itr == mSessions.end() || !(itr->second)) {
     CK_LOGW("ClearKey CDM couldn't resolve session ID in UpdateSession.");
     CK_LOGD("Unable to find session: %s", sessionId.c_str());
     mHost->OnRejectPromise(aPromiseId,
-                           Error::kInvalidAccessError,
+                           Exception::kExceptionTypeError,
                            0,
                            nullptr,
                            0);
 
     return;
   }
   ClearKeySession* session = itr->second;
 
   // Verify the size of session response.
   if (aResponseSize >= kMaxSessionResponseLength) {
     CK_LOGW("Session response size is not within a reasonable size.");
     CK_LOGD("Failed to parse response for session %s", sessionId.c_str());
 
     mHost->OnRejectPromise(aPromiseId,
-                           Error::kInvalidAccessError,
+                           Exception::kExceptionTypeError,
                            0,
                            nullptr,
                            0);
 
     return;
   }
 
   // Parse the response for any (key ID, key) pairs.
   vector<KeyIdPair> keyPairs;
   if (!ClearKeyUtils::ParseJWK(aResponse,
                                aResponseSize,
                                keyPairs,
                                session->Type())) {
     CK_LOGW("ClearKey CDM failed to parse JSON Web Key.");
 
     mHost->OnRejectPromise(aPromiseId,
-                           Error::kInvalidAccessError,
+                           Exception::kExceptionTypeError,
                            0,
                            nullptr,
                            0);
 
     return;
   }
 
   vector<KeyInformation> keyInfos;
@@ -437,17 +435,17 @@ ClearKeySessionManager::UpdateSession(ui
   function<void()> reject = [self, aPromiseId] ()
   {
     if (!self->mHost) {
       return;
     }
 
     static const char* message = "Couldn't store cenc key init data";
     self->mHost->OnRejectPromise(aPromiseId,
-                                 Error::kInvalidStateError,
+                                 Exception::kExceptionInvalidStateError,
                                  0,
                                  message,
                                  strlen(message));
   };
 
   WriteData(mHost, sessionId, keydata, move(resolve), move(reject));
 }
 
@@ -498,17 +496,17 @@ ClearKeySessionManager::CloseSession(uin
   if (!mHost) {
     return;
   }
 
   auto itr = mSessions.find(sessionId);
   if (itr == mSessions.end()) {
     CK_LOGW("ClearKey CDM couldn't close non-existent session.");
     mHost->OnRejectPromise(aPromiseId,
-                           Error::kInvalidAccessError,
+                           Exception::kExceptionTypeError,
                            0,
                            nullptr,
                            0);
 
     return;
   }
 
   ClearKeySession* session = itr->second;
@@ -558,17 +556,17 @@ ClearKeySessionManager::RemoveSession(ui
     return;
   }
 
   auto itr = mSessions.find(sessionId);
   if (itr == mSessions.end()) {
     CK_LOGW("ClearKey CDM couldn't remove non-existent session.");
 
     mHost->OnRejectPromise(aPromiseId,
-                           Error::kInvalidAccessError,
+                           Exception::kExceptionTypeError,
                            0,
                            nullptr,
                            0);
 
     return;
   }
 
   ClearKeySession* session = itr->second;
@@ -596,34 +594,34 @@ ClearKeySessionManager::RemoveSession(ui
 
   function<void()> reject = [self, aPromiseId] ()
   {
     if (!self->mHost) {
       return;
     }
     static const char* message = "Could not remove session";
     self->mHost->OnRejectPromise(aPromiseId,
-                                 Error::kInvalidAccessError,
+                                 Exception::kExceptionTypeError,
                                  0,
                                  message,
                                  strlen(message));
   };
 
   WriteData(mHost, sessionId, emptyKeydata, move(resolve), move(reject));
 }
 
 void
 ClearKeySessionManager::SetServerCertificate(uint32_t aPromiseId,
                                              const uint8_t* aServerCert,
                                              uint32_t aServerCertSize)
 {
   // ClearKey CDM doesn't support this method by spec.
   CK_LOGD("ClearKeySessionManager::SetServerCertificate");
   mHost->OnRejectPromise(aPromiseId,
-                         Error::kNotSupportedError,
+                         Exception::kExceptionNotSupportedError,
                          0,
                          nullptr /* message */,
                          0 /* messageLen */);
 }
 
 Status
 ClearKeySessionManager::Decrypt(const InputBuffer& aBuffer,
                                 DecryptedBlock* aDecryptedBlock)
--- a/media/gmp-clearkey/0.1/ClearKeySessionManager.h
+++ b/media/gmp-clearkey/0.1/ClearKeySessionManager.h
@@ -31,17 +31,17 @@
 #include <map>
 #include <queue>
 #include <set>
 #include <string>
 
 class ClearKeySessionManager final : public RefCounted
 {
 public:
-  explicit ClearKeySessionManager(cdm::Host_8* aHost);
+  explicit ClearKeySessionManager(cdm::Host_9* aHost);
 
   void Init(bool aDistinctiveIdentifierAllowed,
             bool aPersistentStateAllowed);
 
   void CreateSession(uint32_t aPromiseId,
                      cdm::InitDataType aInitDataType,
                      const uint8_t* aInitData,
                      uint32_t aInitDataSize,
@@ -86,17 +86,17 @@ private:
   void ClearInMemorySessionData(ClearKeySession* aSession);
   bool MaybeDeferTillInitialized(std::function<void()>&& aMaybeDefer);
   void Serialize(const ClearKeySession* aSession,
                  std::vector<uint8_t>& aOutKeyData);
 
   RefPtr<ClearKeyDecryptionManager> mDecryptionManager;
   RefPtr<ClearKeyPersistence> mPersistence;
 
-  cdm::Host_8* mHost = nullptr;
+  cdm::Host_9* mHost = nullptr;
 
   std::set<KeyId> mKeyIds;
   std::map<std::string, ClearKeySession*> mSessions;
 
   std::queue<std::function<void()>> mDeferredInitialize;
 };
 
 #endif // __ClearKeyDecryptor_h__
--- a/media/gmp-clearkey/0.1/ClearKeyStorage.cpp
+++ b/media/gmp-clearkey/0.1/ClearKeyStorage.cpp
@@ -32,17 +32,17 @@ using namespace std;
 
 class WriteRecordClient : public FileIOClient
 {
 public:
   /*
    * This function will take the memory ownership of the parameters and
    * delete them when done.
    */
-  static void Write(Host_8* aHost,
+  static void Write(Host_9* aHost,
                     string& aRecordName,
                     const vector<uint8_t>& aData,
                     function<void()>&& aOnSuccess,
                     function<void()>&& aOnFailure)
 {
     WriteRecordClient* client = new WriteRecordClient(aData,
                                                       move(aOnSuccess),
                                                       move(aOnFailure));
@@ -77,17 +77,17 @@ private:
   explicit WriteRecordClient(const vector<uint8_t>& aData,
                              function<void()>&& aOnSuccess,
                              function<void()>&& aOnFailure)
     : mFileIO(nullptr)
     , mOnSuccess(move(aOnSuccess))
     , mOnFailure(move(aOnFailure))
     , mData(aData) {}
 
-  void Do(const string& aName, Host_8* aHost)
+  void Do(const string& aName, Host_9* aHost)
   {
     // Initialize the FileIO.
     mFileIO = aHost->CreateFileIO(this);
     mFileIO->Open(aName.c_str(), aName.size());
   }
 
   void Done(cdm::FileIOClient::Status aStatus)
   {
@@ -113,17 +113,17 @@ private:
 
   function<void()> mOnSuccess;
   function<void()> mOnFailure;
 
   const vector<uint8_t> mData;
 };
 
 void
-WriteData(Host_8* aHost,
+WriteData(Host_9* aHost,
           string& aRecordName,
           const vector<uint8_t>& aData,
           function<void()>&& aOnSuccess,
           function<void()>&& aOnFailure)
 {
   WriteRecordClient::Write(aHost,
                            aRecordName,
                            aData,
@@ -133,17 +133,17 @@ WriteData(Host_8* aHost,
 
 class ReadRecordClient : public FileIOClient
 {
 public:
   /*
    * This function will take the memory ownership of the parameters and
    * delete them when done.
    */
-  static void Read(Host_8* aHost,
+  static void Read(Host_9* aHost,
                    string& aRecordName,
                    function<void(const uint8_t*, uint32_t)>&& aOnSuccess,
                    function<void()>&& aOnFailure)
   {
 
     (new ReadRecordClient(move(aOnSuccess), move(aOnFailure)))->
       Do(aRecordName, aHost);
   }
@@ -174,17 +174,17 @@ public:
 private:
   explicit ReadRecordClient(function<void(const uint8_t*, uint32_t)>&& aOnSuccess,
                             function<void()>&& aOnFailure)
     : mFileIO(nullptr)
     , mOnSuccess(move(aOnSuccess))
     , mOnFailure(move(aOnFailure))
   {}
 
-  void Do(const string& aName, Host_8* aHost)
+  void Do(const string& aName, Host_9* aHost)
   {
     mFileIO = aHost->CreateFileIO(this);
     mFileIO->Open(aName.c_str(), aName.size());
   }
 
   void Done(cdm::FileIOClient::Status aStatus,
             const uint8_t* aData,
             uint32_t aDataSize)
@@ -209,17 +209,17 @@ private:
 
   FileIO* mFileIO = nullptr;
 
   function<void(const uint8_t*, uint32_t)> mOnSuccess;
   function<void()> mOnFailure;
 };
 
 void
-ReadData(Host_8* mHost,
+ReadData(Host_9* mHost,
          string& aRecordName,
          function<void(const uint8_t*, uint32_t)>&& aOnSuccess,
          function<void()>&& aOnFailure)
 {
   ReadRecordClient::Read(mHost,
                          aRecordName,
                          move(aOnSuccess),
                          move(aOnFailure));
--- a/media/gmp-clearkey/0.1/ClearKeyStorage.h
+++ b/media/gmp-clearkey/0.1/ClearKeyStorage.h
@@ -23,21 +23,21 @@
 #include <vector>
 
 #include "ClearKeySessionManager.h"
 
 #define IO_SUCCEEDED(x) ((x) == cdm::FileIOClient::Status::kSuccess)
 #define IO_FAILED(x) ((x) != cdm::FileIOClient::Status::kSuccess)
 
 // Writes data to a file and fires the appropriate callback when complete.
-void WriteData(cdm::Host_8* aHost,
+void WriteData(cdm::Host_9* aHost,
                std::string& aRecordName,
                const std::vector<uint8_t>& aData,
                std::function<void()>&& aOnSuccess,
                std::function<void()>&& aOnFailure);
 
 // Reads data from a file and fires the appropriate callback when complete.
-void ReadData(cdm::Host_8* aHost,
+void ReadData(cdm::Host_9* aHost,
               std::string& aRecordName,
               std::function<void(const uint8_t*, uint32_t)>&& aOnSuccess,
               std::function<void()>&& aOnFailure);
 
 #endif // __ClearKeyStorage_h__
--- a/media/gmp-clearkey/0.1/VideoDecoder.cpp
+++ b/media/gmp-clearkey/0.1/VideoDecoder.cpp
@@ -22,17 +22,17 @@
 #include "ClearKeyDecryptionManager.h"
 #include "ClearKeyUtils.h"
 #include "VideoDecoder.h"
 #include "mozilla/CheckedInt.h"
 
 using namespace wmf;
 using namespace cdm;
 
-VideoDecoder::VideoDecoder(Host_8 *aHost)
+VideoDecoder::VideoDecoder(Host_9 *aHost)
   : mHost(aHost)
   , mHasShutdown(false)
 {
   CK_LOGD("VideoDecoder created");
 
   // We drop the ref in DecodingComplete().
   AddRef();
 
--- a/media/gmp-clearkey/0.1/VideoDecoder.h
+++ b/media/gmp-clearkey/0.1/VideoDecoder.h
@@ -25,17 +25,17 @@
 // on Unix systems.
 #include "stddef.h"
 #include "content_decryption_module.h"
 #include "WMFH264Decoder.h"
 
 class VideoDecoder : public RefCounted
 {
 public:
-  explicit VideoDecoder(cdm::Host_8 *aHost);
+  explicit VideoDecoder(cdm::Host_9 *aHost);
 
   cdm::Status InitDecode(const cdm::VideoDecoderConfig& aConfig);
 
   cdm::Status Decode(const cdm::InputBuffer& aEncryptedBuffer,
                      cdm::VideoFrame* aVideoFrame);
 
   void Reset();
 
@@ -59,17 +59,17 @@ private:
 
   HRESULT SampleToVideoFrame(IMFSample* aSample,
                              int32_t aPictureWidth,
                              int32_t aPictureHeight,
                              int32_t aStride,
                              int32_t aFrameHeight,
                              cdm::VideoFrame* aVideoFrame);
 
-  cdm::Host_8* mHost;
+  cdm::Host_9* mHost;
   wmf::AutoPtr<wmf::WMFH264Decoder> mDecoder;
 
   std::queue<wmf::CComPtr<IMFSample>> mOutputQueue;
 
   bool mHasShutdown;
 };
 
 #endif // __VideoDecoder_h__
--- a/media/gmp-clearkey/0.1/gmp-clearkey.cpp
+++ b/media/gmp-clearkey/0.1/gmp-clearkey.cpp
@@ -51,17 +51,17 @@ void* CreateCdmInstance(int cdm_interfac
                         const char* key_system,
                         uint32_t key_system_size,
                         GetCdmHostFunc get_cdm_host_func,
                         void* user_data)
 {
 
   CK_LOGE("ClearKey CreateCDMInstance");
 
-  if (cdm_interface_version != cdm::ContentDecryptionModule_8::kVersion) {
+  if (cdm_interface_version != cdm::ContentDecryptionModule_9::kVersion) {
     CK_LOGE("ClearKey CreateCDMInstance failed due to requesting unsupported version %d.",
             cdm_interface_version);
     return nullptr;
   }
 #ifdef ENABLE_WMF
   if (!wmf::EnsureLibs()) {
     CK_LOGE("Required libraries were not found");
     return nullptr;
@@ -70,17 +70,17 @@ void* CreateCdmInstance(int cdm_interfac
 
 #ifdef MOZILLA_OFFICIAL
   // Test that we're able to read the host files.
   if (!sCanReadHostVerificationFiles) {
     return nullptr;
   }
 #endif
 
-  cdm::Host_8* host = static_cast<cdm::Host_8*>(
+  cdm::Host_9* host = static_cast<cdm::Host_9*>(
     get_cdm_host_func(cdm_interface_version, user_data));
   ClearKeyCDM* clearKey = new ClearKeyCDM(host);
 
   CK_LOGE("Created ClearKeyCDM instance!");
 
   return clearKey;
 }
 
--- a/media/gmp-clearkey/0.1/manifest.json.in
+++ b/media/gmp-clearkey/0.1/manifest.json.in
@@ -1,13 +1,13 @@
 {
     "name": "clearkey",
     "description": "ClearKey Gecko Media Plugin",
     "version": "1",
     "x-cdm-module-versions": "4",
-    "x-cdm-interface-versions": "8",
-    "x-cdm-host-versions": "8",
+    "x-cdm-interface-versions": "9",
+    "x-cdm-host-versions": "9",
 #ifdef ENABLE_WMF
     "x-cdm-codecs": "avc1"
 #else
     "x-cdm-codecs": ""
 #endif
 }
\ No newline at end of file
--- a/media/libcubeb/README_MOZILLA
+++ b/media/libcubeb/README_MOZILLA
@@ -1,8 +1,8 @@
 The source from this directory was copied from the cubeb 
 git repository using the update.sh script.  The only changes
 made were those applied by update.sh and the addition of
 Makefile.in build files for the Mozilla build system.
 
 The cubeb git repository is: git://github.com/kinetiknz/cubeb.git
 
-The git commit ID used was cf5ddc5316dd1ab3ee7f54b2dcbcc9980e556d13 (2017-10-26 09:48:04 +1300)
+The git commit ID used was 8a0a30091cd7a7c71042f9dd25ba851ac3964466 (2017-11-15 14:03:13 +1300)
--- a/media/libcubeb/src/cubeb_audiounit.cpp
+++ b/media/libcubeb/src/cubeb_audiounit.cpp
@@ -107,26 +107,26 @@ to_string(io_side side)
   case OUTPUT:
     return "output";
   }
 }
 
 typedef uint32_t device_flags_value;
 
 enum device_flags {
-  DEV_UKNOWN            = 0x00, /* Unkown */
+  DEV_UNKNOWN           = 0x00, /* Unknown */
   DEV_INPUT             = 0x01, /* Record device like mic */
   DEV_OUTPUT            = 0x02, /* Playback device like speakers */
   DEV_SYSTEM_DEFAULT    = 0x04, /* System default device */
   DEV_SELECTED_DEFAULT  = 0x08, /* User selected to use the system default device */
 };
 
 struct device_info {
   AudioDeviceID id = kAudioObjectUnknown;
-  device_flags_value flags = DEV_UKNOWN;
+  device_flags_value flags = DEV_UNKNOWN;
 };
 
 struct cubeb_stream {
   explicit cubeb_stream(cubeb * context);
 
   cubeb * context;
   cubeb_data_callback data_callback = nullptr;
   cubeb_state_callback state_callback = nullptr;
@@ -711,63 +711,59 @@ audiounit_property_listener_callback(Aud
                                      void * user)
 {
   cubeb_stream * stm = (cubeb_stream*) user;
   if (stm->switching_device) {
     LOG("Switching is already taking place. Skip Event %s for id=%d", event_addr_to_string(addresses[0].mSelector), id);
     return noErr;
   }
   stm->switching_device = true;
-  device_flags_value switch_side = DEV_UKNOWN;
 
   LOG("(%p) Audio device changed, %u events.", stm, (unsigned int) address_count);
   for (UInt32 i = 0; i < address_count; i++) {
     switch(addresses[i].mSelector) {
       case kAudioHardwarePropertyDefaultOutputDevice: {
           LOG("Event[%u] - mSelector == kAudioHardwarePropertyDefaultOutputDevice for id=%d", (unsigned int) i, id);
-          // Allow restart to choose the new default
-          switch_side |= DEV_OUTPUT;
         }
         break;
       case kAudioHardwarePropertyDefaultInputDevice: {
           LOG("Event[%u] - mSelector == kAudioHardwarePropertyDefaultInputDevice for id=%d", (unsigned int) i, id);
-          // Allow restart to choose the new default
-          switch_side |= DEV_INPUT;
         }
       break;
       case kAudioDevicePropertyDeviceIsAlive: {
           LOG("Event[%u] - mSelector == kAudioDevicePropertyDeviceIsAlive for id=%d", (unsigned int) i, id);
           // If this is the default input device ignore the event,
           // kAudioHardwarePropertyDefaultInputDevice will take care of the switch
           if (stm->input_device.flags & DEV_SYSTEM_DEFAULT) {
             LOG("It's the default input device, ignore the event");
             stm->switching_device = false;
             return noErr;
           }
-          // Allow restart to choose the new default. Event register only for input.
-          switch_side |= DEV_INPUT;
         }
         break;
       case kAudioDevicePropertyDataSource: {
           LOG("Event[%u] - mSelector == kAudioHardwarePropertyDataSource for id=%d", (unsigned int) i, id);
-          if (stm->input_unit) {
-            switch_side |= DEV_INPUT;
-          }
-          if (stm->output_unit) {
-            switch_side |= DEV_OUTPUT;
-          }
         }
         break;
       default:
         LOG("Event[%u] - mSelector == Unexpected Event id %d, return", (unsigned int) i, addresses[i].mSelector);
         stm->switching_device = false;
         return noErr;
     }
   }
 
+  // Allow restart to choose the new default
+  device_flags_value switch_side = DEV_UNKNOWN;
+  if (has_input(stm)) {
+    switch_side |= DEV_INPUT;
+  }
+  if (has_input(stm)) {
+    switch_side |= DEV_OUTPUT;
+  }
+
   for (UInt32 i = 0; i < address_count; i++) {
     switch(addresses[i].mSelector) {
     case kAudioHardwarePropertyDefaultOutputDevice:
     case kAudioHardwarePropertyDefaultInputDevice:
     case kAudioDevicePropertyDeviceIsAlive:
       /* fall through */
     case kAudioDevicePropertyDataSource: {
         auto_lock dev_cb_lock(stm->device_changed_callback_lock);
@@ -779,19 +775,19 @@ audiounit_property_listener_callback(Aud
     }
   }
 
   // Use a new thread, through the queue, to avoid deadlock when calling
   // Get/SetProperties method from inside notify callback
   dispatch_async(stm->context->serial_queue, ^() {
     if (audiounit_reinit_stream(stm, switch_side) != CUBEB_OK) {
       if (audiounit_uninstall_system_changed_callback(stm) != CUBEB_OK) {
-        LOG("(%p) Could not uninstall the device changed callback", stm);
+        LOG("(%p) Could not uninstall system changed callback", stm);
       }
-      stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
+      stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
       LOG("(%p) Could not reopen the stream after switching.", stm);
     }
     stm->switching_device = false;
   });
 
   return noErr;
 }
 
@@ -1678,16 +1674,18 @@ audiounit_activate_clock_drift_compensat
       LOG("AudioObjectSetPropertyData/kAudioSubDevicePropertyDriftCompensation, rv=%d", rv);
       return CUBEB_OK;
     }
   }
   return CUBEB_OK;
 }
 
 static int audiounit_destroy_aggregate_device(AudioObjectID plugin_id, AudioDeviceID * aggregate_device_id);
+static void audiounit_get_available_samplerate(AudioObjectID devid, AudioObjectPropertyScope scope,
+                                   uint32_t * min, uint32_t * max, uint32_t * def);
 
 /*
  * Aggregate Device is a virtual audio interface which utilizes inputs and outputs
  * of one or more physical audio interfaces. It is possible to use the clock of
  * one of the devices as a master clock for all the combined devices and enable
  * drift compensation for the devices that are not designated clock master.
  *
  * Creating a new aggregate device programmatically requires [0][1]:
@@ -1701,16 +1699,36 @@ static int audiounit_destroy_aggregate_d
  *
  * [0] https://lists.apple.com/archives/coreaudio-api/2006/Apr/msg00092.html
  * [1] https://lists.apple.com/archives/coreaudio-api/2005/Jul/msg00150.html
  * [2] CoreAudio.framework/Headers/AudioHardware.h
  * */
 static int
 audiounit_create_aggregate_device(cubeb_stream * stm)
 {
+  uint32_t input_min_rate = 0;
+  uint32_t input_max_rate = 0;
+  uint32_t input_nominal_rate = 0;
+  audiounit_get_available_samplerate(stm->input_device.id, kAudioObjectPropertyScopeGlobal,
+                                     &input_min_rate, &input_max_rate, &input_nominal_rate);
+  LOG("(%p) Input device %u min: %u, max: %u, nominal: %u rate", stm, stm->input_device.id
+      , input_min_rate, input_max_rate, input_nominal_rate);
+  uint32_t output_min_rate = 0;
+  uint32_t output_max_rate = 0;
+  uint32_t output_nominal_rate = 0;
+  audiounit_get_available_samplerate(stm->output_device.id, kAudioObjectPropertyScopeGlobal,
+                                     &output_min_rate, &output_max_rate, &output_nominal_rate);
+  LOG("(%p) Output device %u min: %u, max: %u, nominal: %u rate", stm, stm->output_device.id
+      , output_min_rate, output_max_rate, output_nominal_rate);
+
+  if ((output_nominal_rate < input_min_rate || output_nominal_rate > output_max_rate)
+      || (input_nominal_rate < output_min_rate || input_nominal_rate > output_max_rate)){
+    return CUBEB_ERROR;
+  }
+
   int r = audiounit_create_blank_aggregate_device(&stm->plugin_id, &stm->aggregate_device_id);
   if (r != CUBEB_OK) {
     LOG("(%p) Failed to create blank aggregate device", stm);
     audiounit_destroy_aggregate_device(stm->plugin_id, &stm->aggregate_device_id);
     return CUBEB_ERROR;
   }
 
   r = audiounit_set_aggregate_sub_device_list(stm->aggregate_device_id, stm->input_device.id, stm->output_device.id);
@@ -1729,16 +1747,34 @@ audiounit_create_aggregate_device(cubeb_
 
   r = audiounit_activate_clock_drift_compensation(stm->aggregate_device_id);
   if (r != CUBEB_OK) {
     LOG("(%p) Failed to activate clock drift compensation for aggregate device", stm);
     audiounit_destroy_aggregate_device(stm->plugin_id, &stm->aggregate_device_id);
     return  CUBEB_ERROR;
   }
 
+  if (input_nominal_rate != output_nominal_rate) {
+    Float64 rate = std::min(input_nominal_rate, output_nominal_rate);
+    AudioObjectPropertyAddress addr = {kAudioDevicePropertyNominalSampleRate,
+                                       kAudioObjectPropertyScopeGlobal,
+                                       kAudioObjectPropertyElementMaster};
+
+    OSStatus rv = AudioObjectSetPropertyData(stm->aggregate_device_id,
+                                             &addr,
+                                             0,
+                                             nullptr,
+                                             sizeof(Float64),
+                                             &rate);
+    if (rv != noErr) {
+      LOG("AudioObjectSetPropertyData/kAudioDevicePropertyNominalSampleRate, rv=%d", rv);
+      return CUBEB_ERROR;
+    }
+  }
+
   return CUBEB_OK;
 }
 
 static int
 audiounit_destroy_aggregate_device(AudioObjectID plugin_id, AudioDeviceID * aggregate_device_id)
 {
   assert(aggregate_device_id &&
          *aggregate_device_id != kAudioDeviceUnknown &&
--- a/media/webrtc/signaling/gtest/videoconduit_unittests.cpp
+++ b/media/webrtc/signaling/gtest/videoconduit_unittests.cpp
@@ -9,16 +9,17 @@
 #include "nspr.h"
 #include "nss.h"
 #include "ssl.h"
 
 #include "VideoConduit.h"
 #include "WebrtcGmpVideoCodec.h"
 
 #include "webrtc/media/base/videoadapter.h"
+#include "webrtc/media/base/videosinkinterface.h"
 
 #include "MockCall.h"
 
 using namespace mozilla;
 
 namespace test {
 
 class MockVideoAdapter : public cricket::VideoAdapter {
@@ -30,39 +31,59 @@ public:
                             int* cropped_width,
                             int* cropped_height,
                             int* out_width,
                             int* out_height) override
   {
     mInWidth = in_width;
     mInHeight = in_height;
     mInTimestampNs = in_timestamp_ns;
-    return true;
+    return cricket::VideoAdapter::AdaptFrameResolution(in_width, in_height,
+                                                       in_timestamp_ns,
+                                                       cropped_width,
+                                                       cropped_height,
+                                                       out_width,
+                                                       out_height);
   }
 
   void OnResolutionRequest(rtc::Optional<int> max_pixel_count,
                            rtc::Optional<int> max_pixel_count_step_up) override
   {
     mMaxPixelCount = max_pixel_count.value_or(-1);
     mMaxPixelCountStepUp = max_pixel_count_step_up.value_or(-1);
+    cricket::VideoAdapter::OnResolutionRequest(max_pixel_count, max_pixel_count_step_up);
   }
 
   void OnScaleResolutionBy(rtc::Optional<float> scale_resolution_by) override
   {
     mScaleResolutionBy = scale_resolution_by.value_or(-1.0);
+    cricket::VideoAdapter::OnScaleResolutionBy(scale_resolution_by);
   }
 
   int mInWidth;
   int mInHeight;
   int64_t mInTimestampNs;
   int mMaxPixelCount;
   int mMaxPixelCountStepUp;
   int mScaleResolutionBy;
 };
 
+class MockVideoSink : public rtc::VideoSinkInterface<webrtc::VideoFrame>
+{
+public:
+  ~MockVideoSink() override = default;
+
+  void OnFrame(const webrtc::VideoFrame& frame) override
+  {
+    mVideoFrame = frame;
+  }
+
+  webrtc::VideoFrame mVideoFrame;
+};
+
 class VideoConduitTest : public ::testing::Test {
 public:
 
   VideoConduitTest()
     : mCall(new MockCall())
     , mAdapter(new MockVideoAdapter)
   {
     NSS_NoDB_Init(nullptr);
@@ -71,26 +92,28 @@ public:
                                            UniquePtr<cricket::VideoAdapter>(mAdapter));
     std::vector<unsigned int> ssrcs = {42};
     mVideoConduit->SetLocalSSRCs(ssrcs);
   }
 
   ~VideoConduitTest() override = default;
 
   MediaConduitErrorCode SendVideoFrame(unsigned short width,
-                                       unsigned short height)
+                                       unsigned short height,
+                                       uint64_t capture_time_ms)
   {
     unsigned int yplane_length = width*height;
     unsigned int cbcrplane_length = (width*height + 1)/2;
     unsigned int video_length = yplane_length + cbcrplane_length;
     uint8_t* buffer = new uint8_t[video_length];
     memset(buffer, 0x10, yplane_length);
     memset(buffer + yplane_length, 0x80, cbcrplane_length);
     return mVideoConduit->SendVideoFrame(buffer, video_length, width, height,
-                                         VideoType::kVideoI420, 1);
+                                         VideoType::kVideoI420,
+                                         capture_time_ms);
   }
 
   MockCall* mCall;
   MockVideoAdapter* mAdapter;
   RefPtr<mozilla::WebrtcVideoConduit> mVideoConduit;
 };
 
 TEST_F(VideoConduitTest, TestConfigureReceiveMediaCodecs)
@@ -416,30 +439,30 @@ TEST_F(VideoConduitTest, TestConfigureSe
   VideoCodecConfig::SimulcastEncoding encoding;
 
   constraints.maxMbps = 0;
   VideoCodecConfig codecConfig(120, "VP8", constraints);
   codecConfig.mSimulcastEncodings.push_back(encoding);
   ec = mVideoConduit->ConfigureSendMediaCodec(&codecConfig);
   ASSERT_EQ(ec, kMediaConduitNoError);
   mVideoConduit->StartTransmitting();
-  SendVideoFrame(640, 480);
+  SendVideoFrame(640, 480, 1);
   std::vector<webrtc::VideoStream> videoStreams;
   videoStreams = mCall->mEncoderConfig.video_stream_factory->CreateEncoderStreams(640, 480, mCall->mEncoderConfig);
   ASSERT_EQ(videoStreams.size(), 1U);
   ASSERT_EQ(videoStreams[0].max_framerate, 30); // DEFAULT_VIDEO_MAX_FRAMERATE
   mVideoConduit->StopTransmitting();
 
   constraints.maxMbps = 10000;
   VideoCodecConfig codecConfig2(120, "VP8", constraints);
   codecConfig.mSimulcastEncodings.push_back(encoding);
   ec = mVideoConduit->ConfigureSendMediaCodec(&codecConfig2);
   ASSERT_EQ(ec, kMediaConduitNoError);
   mVideoConduit->StartTransmitting();
-  SendVideoFrame(640, 480);
+  SendVideoFrame(640, 480, 1);
   videoStreams = mCall->mEncoderConfig.video_stream_factory->CreateEncoderStreams(640, 480, mCall->mEncoderConfig);
   ASSERT_EQ(videoStreams.size(), 1U);
   ASSERT_EQ(videoStreams[0].max_framerate, 8);
   mVideoConduit->StopTransmitting();
 }
 
 TEST_F(VideoConduitTest, TestConfigureSendMediaCodecDefaults)
 {
@@ -455,17 +478,17 @@ TEST_F(VideoConduitTest, TestConfigureSe
   mVideoConduit->StartTransmitting();
   videoStreams = mCall->mEncoderConfig.video_stream_factory->CreateEncoderStreams(640, 480, mCall->mEncoderConfig);
   ASSERT_EQ(videoStreams.size(), 1U);
   ASSERT_EQ(videoStreams[0].min_bitrate_bps, static_cast<int32_t>(WebrtcVideoConduit::kDefaultMinBitrate_bps));
   ASSERT_EQ(videoStreams[0].target_bitrate_bps, static_cast<int32_t>(WebrtcVideoConduit::kDefaultStartBitrate_bps));
   ASSERT_EQ(videoStreams[0].max_bitrate_bps, static_cast<int32_t>(WebrtcVideoConduit::kDefaultMaxBitrate_bps));
 
   // SelectBitrates not called until we send a frame
-  SendVideoFrame(1280, 720);
+  SendVideoFrame(1280, 720, 1);
   videoStreams = mCall->mEncoderConfig.video_stream_factory->CreateEncoderStreams(1280, 720, mCall->mEncoderConfig);
   ASSERT_EQ(videoStreams.size(), 1U);
   // These values come from a table and are determined by resolution
   ASSERT_EQ(videoStreams[0].min_bitrate_bps, 600000);
   ASSERT_EQ(videoStreams[0].target_bitrate_bps, 800000);
   ASSERT_EQ(videoStreams[0].max_bitrate_bps, 2500000);
   mVideoConduit->StopTransmitting();
 }
@@ -479,32 +502,32 @@ TEST_F(VideoConduitTest, TestConfigureSe
 
   // TIAS
   VideoCodecConfig codecConfigTias(120, "VP8", constraints);
   codecConfigTias.mSimulcastEncodings.push_back(encoding);
   codecConfigTias.mTias = 1000000;
   ec = mVideoConduit->ConfigureSendMediaCodec(&codecConfigTias);
   ASSERT_EQ(ec, kMediaConduitNoError);
   mVideoConduit->StartTransmitting();
-  SendVideoFrame(1280, 720);
+  SendVideoFrame(1280, 720, 1);
   videoStreams = mCall->mEncoderConfig.video_stream_factory->CreateEncoderStreams(1280, 720, mCall->mEncoderConfig);
   ASSERT_EQ(videoStreams.size(), 1U);
   ASSERT_EQ(videoStreams[0].min_bitrate_bps, 600000);
   ASSERT_EQ(videoStreams[0].target_bitrate_bps, 800000);
   ASSERT_EQ(videoStreams[0].max_bitrate_bps, 1000000);
   mVideoConduit->StopTransmitting();
 
   // TIAS (too low)
   VideoCodecConfig codecConfigTiasLow(120, "VP8", constraints);
   codecConfigTiasLow.mSimulcastEncodings.push_back(encoding);
   codecConfigTiasLow.mTias = 1000;
   ec = mVideoConduit->ConfigureSendMediaCodec(&codecConfigTiasLow);
   ASSERT_EQ(ec, kMediaConduitNoError);
   mVideoConduit->StartTransmitting();
-  SendVideoFrame(1280, 720);
+  SendVideoFrame(1280, 720, 1);
   videoStreams = mCall->mEncoderConfig.video_stream_factory->CreateEncoderStreams(1280, 720, mCall->mEncoderConfig);
   ASSERT_EQ(videoStreams.size(), 1U);
   ASSERT_EQ(videoStreams[0].min_bitrate_bps, 30000);
   ASSERT_EQ(videoStreams[0].target_bitrate_bps, 30000);
   ASSERT_EQ(videoStreams[0].max_bitrate_bps, 30000);
   mVideoConduit->StopTransmitting();
 }
 
@@ -516,17 +539,17 @@ TEST_F(VideoConduitTest, TestConfigureSe
   std::vector<webrtc::VideoStream> videoStreams;
 
   VideoCodecConfig codecConfig(120, "VP8", constraints);
   encoding.constraints.maxBr = 50000;
   codecConfig.mSimulcastEncodings.push_back(encoding);
   ec = mVideoConduit->ConfigureSendMediaCodec(&codecConfig);
   ASSERT_EQ(ec, kMediaConduitNoError);
   mVideoConduit->StartTransmitting();
-  SendVideoFrame(1280, 720);
+  SendVideoFrame(1280, 720, 1);
   videoStreams = mCall->mEncoderConfig.video_stream_factory->CreateEncoderStreams(1280, 720, mCall->mEncoderConfig);
   ASSERT_EQ(videoStreams.size(), 1U);
   ASSERT_LE(videoStreams[0].min_bitrate_bps, 50000);
   ASSERT_LE(videoStreams[0].target_bitrate_bps, 50000);
   ASSERT_EQ(videoStreams[0].max_bitrate_bps, 50000);
   mVideoConduit->StopTransmitting();
 }
 
@@ -544,17 +567,17 @@ TEST_F(VideoConduitTest, TestConfigureSe
   encoding.constraints.scaleDownBy = 2;
   codecConfig.mSimulcastEncodings.push_back(encoding);
   encoding.constraints.scaleDownBy = 4;
   codecConfig.mSimulcastEncodings.push_back(encoding);
   ec = mVideoConduit->ConfigureSendMediaCodec(&codecConfig);
   ASSERT_EQ(ec, kMediaConduitNoError);
   mVideoConduit->StartTransmitting();
   ASSERT_EQ(mAdapter->mScaleResolutionBy, 2);
-  SendVideoFrame(640, 360);
+  SendVideoFrame(640, 360, 1);
   videoStreams = mCall->mEncoderConfig.video_stream_factory->CreateEncoderStreams(640, 360, mCall->mEncoderConfig);
   ASSERT_EQ(videoStreams.size(), 2U);
   ASSERT_EQ(videoStreams[0].width, 320U);
   ASSERT_EQ(videoStreams[0].height, 180U);
   ASSERT_EQ(videoStreams[1].width, 640U);
   ASSERT_EQ(videoStreams[1].height, 360U);
   mVideoConduit->StopTransmitting();
 }
@@ -709,9 +732,141 @@ TEST_F(VideoConduitTest, TestOnSinkWants
 
   wants.max_pixel_count = rtc::Optional<int>(64000);
   codecConfig.mEncodingConstraints.maxFs = 500;
   mVideoConduit->ConfigureSendMediaCodec(&codecConfig);
   mVideoConduit->OnSinkWantsChanged(wants);
   ASSERT_EQ(mAdapter->mMaxPixelCount, 64000);
 }
 
+TEST_F(VideoConduitTest, TestVideoEncode)
+{
+  MediaConduitErrorCode ec;
+  EncodingConstraints constraints;
+  VideoCodecConfig::SimulcastEncoding encoding;
+  std::vector<webrtc::VideoStream> videoStreams;
+
+  VideoCodecConfig codecConfig(120, "VP8", constraints);
+  codecConfig.mSimulcastEncodings.push_back(encoding);
+  ec = mVideoConduit->ConfigureSendMediaCodec(&codecConfig);
+  ASSERT_EQ(ec, kMediaConduitNoError);
+
+  UniquePtr<MockVideoSink> sink(new MockVideoSink());
+  rtc::VideoSinkWants wants;
+  mVideoConduit->AddOrUpdateSink(sink.get(), wants);
+
+  mVideoConduit->StartTransmitting();
+  SendVideoFrame(1280, 720, 1);
+  ASSERT_EQ(sink->mVideoFrame.width(), 1280);
+  ASSERT_EQ(sink->mVideoFrame.height(), 720);
+  ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 1000U);
+
+  SendVideoFrame(640, 360, 2);
+  ASSERT_EQ(sink->mVideoFrame.width(), 640);
+  ASSERT_EQ(sink->mVideoFrame.height(), 360);
+  ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 2000U);
+
+  SendVideoFrame(1920, 1280, 3);
+  ASSERT_EQ(sink->mVideoFrame.width(), 1920);
+  ASSERT_EQ(sink->mVideoFrame.height(), 1280);
+  ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 3000U);
+
+  mVideoConduit->StopTransmitting();
+  mVideoConduit->RemoveSink(sink.get());
+}
+
+TEST_F(VideoConduitTest, TestVideoEncodeMaxFs)
+{
+  MediaConduitErrorCode ec;
+  EncodingConstraints constraints;
+  VideoCodecConfig::SimulcastEncoding encoding;
+  std::vector<webrtc::VideoStream> videoStreams;
+
+  VideoCodecConfig codecConfig(120, "VP8", constraints);
+  codecConfig.mEncodingConstraints.maxFs = 3600;
+  codecConfig.mSimulcastEncodings.push_back(encoding);
+  ec = mVideoConduit->ConfigureSendMediaCodec(&codecConfig);
+  ASSERT_EQ(ec, kMediaConduitNoError);
+
+  UniquePtr<MockVideoSink> sink(new MockVideoSink());
+  rtc::VideoSinkWants wants;
+  mVideoConduit->AddOrUpdateSink(sink.get(), wants);
+
+  mVideoConduit->StartTransmitting();
+  SendVideoFrame(1280, 720, 1);
+  ASSERT_EQ(sink->mVideoFrame.width(), 1280);
+  ASSERT_EQ(sink->mVideoFrame.height(), 720);
+  ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 1000U);
+
+  SendVideoFrame(640, 360, 2);
+  ASSERT_EQ(sink->mVideoFrame.width(), 640);
+  ASSERT_EQ(sink->mVideoFrame.height(), 360);
+  ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 2000U);
+
+  SendVideoFrame(1920, 1280, 3);
+  ASSERT_EQ(sink->mVideoFrame.width(), 960);
+  ASSERT_EQ(sink->mVideoFrame.height(), 640);
+  ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 3000U);
+
+  // maxFs should not force pixel count above what a sink has requested.
+  // We set 3600 macroblocks (16x16 pixels), so we request 3500 here.
+  wants.max_pixel_count = rtc::Optional<int>(3500*16*16);
+  mVideoConduit->AddOrUpdateSink(sink.get(), wants);
+
+  SendVideoFrame(1280, 720, 4);
+  ASSERT_EQ(sink->mVideoFrame.width(), 960);
+  ASSERT_EQ(sink->mVideoFrame.height(), 540);
+  ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 4000U);
+
+  SendVideoFrame(640, 360, 5);
+  ASSERT_EQ(sink->mVideoFrame.width(), 640);
+  ASSERT_EQ(sink->mVideoFrame.height(), 360);
+  ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 5000U);
+
+  SendVideoFrame(1920, 1280, 6);
+  ASSERT_EQ(sink->mVideoFrame.width(), 960);
+  ASSERT_EQ(sink->mVideoFrame.height(), 640);
+  ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 6000U);
+
+  mVideoConduit->StopTransmitting();
+  mVideoConduit->RemoveSink(sink.get());
+}
+
+// Disabled: See Bug 1420493
+TEST_F(VideoConduitTest, DISABLED_TestVideoEncodeMaxWidthAndHeight)
+{
+  MediaConduitErrorCode ec;
+  EncodingConstraints constraints;
+  VideoCodecConfig::SimulcastEncoding encoding;
+  std::vector<webrtc::VideoStream> videoStreams;
+
+  VideoCodecConfig codecConfig(120, "VP8", constraints);
+  codecConfig.mEncodingConstraints.maxWidth = 1280;
+  codecConfig.mEncodingConstraints.maxHeight = 720;
+  codecConfig.mSimulcastEncodings.push_back(encoding);
+  ec = mVideoConduit->ConfigureSendMediaCodec(&codecConfig);
+  ASSERT_EQ(ec, kMediaConduitNoError);
+
+  UniquePtr<MockVideoSink> sink(new MockVideoSink());
+  rtc::VideoSinkWants wants;
+  mVideoConduit->AddOrUpdateSink(sink.get(), wants);
+
+  mVideoConduit->StartTransmitting();
+  SendVideoFrame(1280, 720, 1);
+  ASSERT_EQ(sink->mVideoFrame.width(), 1280);
+  ASSERT_EQ(sink->mVideoFrame.height(), 720);
+  ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 1000U);
+
+  SendVideoFrame(640, 360, 2);
+  ASSERT_EQ(sink->mVideoFrame.width(), 640);
+  ASSERT_EQ(sink->mVideoFrame.height(), 360);
+  ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 2000U);
+
+  SendVideoFrame(1920, 1280, 3);
+  ASSERT_EQ(sink->mVideoFrame.width(), 1080);
+  ASSERT_EQ(sink->mVideoFrame.height(), 720);
+  ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 3000U);
+
+  mVideoConduit->StopTransmitting();
+  mVideoConduit->RemoveSink(sink.get());
+}
+
 } // End namespace test.
--- a/media/webrtc/trunk/webrtc/video_engine/desktop_capture_impl.cc
+++ b/media/webrtc/trunk/webrtc/video_engine/desktop_capture_impl.cc
@@ -577,55 +577,17 @@ int32_t DesktopCaptureImpl::IncomingFram
     webrtc::VideoFrame captureFrame(buffer, 0, 0, kVideoRotation_0);
     if (conversionResult < 0) {
       WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
                    "Failed to convert capture frame from type %d to I420",
                    frameInfo.rawType);
       return -1;
     }
 
-    int32_t req_max_width = _requestedCapability.width & 0xffff;
-    int32_t req_max_height = _requestedCapability.height & 0xffff;
-    int32_t req_ideal_width = (_requestedCapability.width >> 16) & 0xffff;
-    int32_t req_ideal_height = (_requestedCapability.height >> 16) & 0xffff;
-
-    int32_t dest_max_width = std::min(req_max_width, target_width);
-    int32_t dest_max_height = std::min(req_max_height, target_height);
-    int32_t dst_width = std::min(req_ideal_width > 0 ? req_ideal_width : target_width, dest_max_width);
-    int32_t dst_height = std::min(req_ideal_height > 0 ? req_ideal_height : target_height, dest_max_height);
-
-    // scale to average of portrait and landscape
-    float scale_width = (float)dst_width / (float)target_width;
-    float scale_height = (float)dst_height / (float)target_height;
-    float scale = (scale_width + scale_height) / 2;
-    dst_width = (int)(scale * target_width);
-    dst_height = (int)(scale * target_height);
-
-    // if scaled rectangle exceeds max rectangle, scale to minimum of portrait and landscape
-    if (dst_width > dest_max_width || dst_height > dest_max_height) {
-      scale_width = (float)dest_max_width / (float)dst_width;
-      scale_height = (float)dest_max_height / (float)dst_height;
-      scale = std::min(scale_width, scale_height);
-      dst_width = (int)(scale * dst_width);
-      dst_height = (int)(scale * dst_height);
-    }
-
-    int dst_stride_y = dst_width;
-    int dst_stride_uv = (dst_width + 1) / 2;
-    if (dst_width == target_width && dst_height == target_height) {
-      DeliverCapturedFrame(captureFrame, captureTime);
-    } else {
-      rtc::scoped_refptr<webrtc::I420Buffer> buffer;
-      buffer = I420Buffer::Create(dst_width, dst_height, dst_stride_y,
-                                  dst_stride_uv, dst_stride_uv);
-
-      buffer->ScaleFrom(*captureFrame.video_frame_buffer().get());
-      webrtc::VideoFrame scaledFrame(buffer, 0, 0, kVideoRotation_0);
-      DeliverCapturedFrame(scaledFrame, captureTime);
-    }
+    DeliverCapturedFrame(captureFrame, captureTime);
   } else {
     assert(false);
     return -1;
   }
 
   const int64_t processTime =
     (rtc::TimeNanos() - startProcessTime)/rtc::kNumNanosecsPerMillisec;
 
--- a/mobile/android/base/java/org/mozilla/gecko/Tabs.java
+++ b/mobile/android/base/java/org/mozilla/gecko/Tabs.java
@@ -147,16 +147,17 @@ public class Tabs implements BundleEvent
             null);
 
         EventDispatcher.getInstance().registerUiThreadListener(this,
             "Content:LocationChange",
             "Content:SubframeNavigation",
             "Content:SecurityChange",
             "Content:StateChange",
             "Content:LoadError",
+            "Content:DOMContentLoaded",
             "Content:PageShow",
             "Content:DOMTitleChanged",
             "DesktopMode:Changed",
             "Link:Favicon",
             "Link:Feed",
             "Link:Manifest",
             "Link:OpenSearch",
             "Link:Touchicon",
@@ -632,16 +633,20 @@ public class Tabs implements BundleEvent
                 tab.handleDocumentStop(message.getBoolean("success"));
                 notifyListeners(tab, Tabs.TabEvents.STOP);
             }
 
         } else if ("Content:LoadError".equals(event)) {
             tab.handleContentLoaded();
             notifyListeners(tab, Tabs.TabEvents.LOAD_ERROR);
 
+        } else if ("Content:DOMContentLoaded".equals(event)) {
+            tab.handleContentLoaded();
+            notifyListeners(tab, TabEvents.LOADED);
+
         } else if ("Content:PageShow".equals(event)) {
             tab.setLoadedFromCache(message.getBoolean("fromCache"));
             tab.updateUserRequested(message.getString("userRequested"));
             notifyListeners(tab, TabEvents.PAGE_SHOW);
 
         } else if ("Content:DOMTitleChanged".equals(event)) {
             tab.updateTitle(message.getString("title"));
 
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/AboutHomeTest.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/AboutHomeTest.java
@@ -80,17 +80,17 @@ abstract class AboutHomeTest extends Pix
         mAsserter.ok(isCorrect, "Checking that " + url + " displayed as a bookmark", url + " displayed");
     }
 
     // Loads a bookmark by tapping on the bookmark view in the Bookmarks tab
     protected void loadBookmark(String url) {
         View bookmark = getDisplayedBookmark(url);
         if (bookmark != null) {
             Actions.EventExpecter contentEventExpecter =
-                    mActions.expectGlobalEvent(Actions.EventType.GECKO, "Content:DOMContentLoaded");
+                    mActions.expectGlobalEvent(Actions.EventType.UI, "Content:DOMContentLoaded");
             mSolo.clickOnView(bookmark);
             contentEventExpecter.blockForEvent();
             contentEventExpecter.unregisterListener();
         } else {
             mAsserter.ok(false, url + " is not one of the displayed bookmarks", "Please make sure the url provided is bookmarked");
         }
     }
 
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/OldBaseTest.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/OldBaseTest.java
@@ -153,17 +153,17 @@ abstract class OldBaseTest extends BaseR
 
     protected final Fragment getBrowserSearch() {
         final FragmentManager fm = ((FragmentActivity) getActivity()).getSupportFragmentManager();
         return fm.findFragmentByTag("browser_search");
     }
 
     protected final void hitEnterAndWait() {
         Actions.EventExpecter contentEventExpecter =
-                mActions.expectGlobalEvent(Actions.EventType.GECKO, "Content:DOMContentLoaded");
+                mActions.expectGlobalEvent(Actions.EventType.UI, "Content:DOMContentLoaded");
         mActions.sendSpecialKey(Actions.SpecialKey.ENTER);
         // wait for screen to load
         contentEventExpecter.blockForEvent();
         contentEventExpecter.unregisterListener();
     }
 
     /**
      * Load <code>url</code> by sending key strokes to the URL bar UI.
@@ -197,17 +197,17 @@ abstract class OldBaseTest extends BaseR
     }
 
     /**
      * Load <code>url</code> using the internal
      * <code>org.mozilla.gecko.Tabs</code> API and wait for DOMContentLoaded.
      */
     protected final void loadUrlAndWait(final String url) {
         Actions.EventExpecter contentEventExpecter =
-                mActions.expectGlobalEvent(Actions.EventType.GECKO, "Content:DOMContentLoaded");
+                mActions.expectGlobalEvent(Actions.EventType.UI, "Content:DOMContentLoaded");
         loadUrl(url);
         contentEventExpecter.blockForEvent();
         contentEventExpecter.unregisterListener();
     }
 
     protected final void closeTab(int tabId) {
         Tabs tabs = Tabs.getInstance();
         Tab tab = tabs.getTab(tabId);
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/helpers/WaitHelper.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/helpers/WaitHelper.java
@@ -115,17 +115,17 @@ public final class WaitHelper {
         // state.
         final ChangeVerifier[] pageLoadVerifiers = PAGE_LOAD_VERIFIERS;
         for (final ChangeVerifier verifier : pageLoadVerifiers) {
             verifier.storeState();
         }
 
         // Wait for the page load and title changed event.
         final EventExpecter[] eventExpecters = new EventExpecter[] {
-            sActions.expectGlobalEvent(Actions.EventType.GECKO, "Content:DOMContentLoaded"),
+            sActions.expectGlobalEvent(Actions.EventType.UI, "Content:DOMContentLoaded"),
             sActions.expectGlobalEvent(Actions.EventType.UI, "Content:DOMTitleChanged")
         };
 
         initiatingAction.run();
 
         // PAGE_LOAD_WAIT_MS is the total time we wait for all events to finish.
         final long expecterStartMillis = SystemClock.uptimeMillis();
         for (final EventExpecter expecter : eventExpecters) {
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testAboutPage.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testAboutPage.java
@@ -25,17 +25,17 @@ public class testAboutPage extends Pixel
         url = getAbsoluteUrl(mStringHelper.ROBOCOP_BLANK_PAGE_01_URL);
         loadUrlAndWait(url);
 
         // At this point the page title should have been set.
         verifyUrlInContentDescription(url);
 
         // Set up listeners to catch the page load we're about to do.
         Actions.EventExpecter tabEventExpecter = mActions.expectGlobalEvent(Actions.EventType.UI, "Tab:Added");
-        Actions.EventExpecter contentEventExpecter = mActions.expectGlobalEvent(Actions.EventType.GECKO, "Content:DOMContentLoaded");
+        Actions.EventExpecter contentEventExpecter = mActions.expectGlobalEvent(Actions.EventType.UI, "Content:DOMContentLoaded");
 
         selectSettingsItem(mStringHelper.MOZILLA_SECTION_LABEL, mStringHelper.ABOUT_LABEL);
 
         // Wait for the new tab and page to load
         tabEventExpecter.blockForEvent();
         contentEventExpecter.blockForEvent();
 
         tabEventExpecter.unregisterListener();
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testAddonManager.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testAddonManager.java
@@ -25,17 +25,17 @@ public class testAddonManager extends Pi
 
         blockForGeckoReady();
 
         // Use the menu to open the Addon Manger
         selectMenuItem(mStringHelper.ADDONS_LABEL);
 
         // Set up listeners to catch the page load we're about to do
         tabEventExpecter = mActions.expectGlobalEvent(Actions.EventType.UI, "Tab:Added");
-        contentEventExpecter = mActions.expectGlobalEvent(Actions.EventType.GECKO, "Content:DOMContentLoaded");
+        contentEventExpecter = mActions.expectGlobalEvent(Actions.EventType.UI, "Content:DOMContentLoaded");
 
         // Wait for the new tab and page to load
         tabEventExpecter.blockForEvent();
         contentEventExpecter.blockForEvent();
 
         tabEventExpecter.unregisterListener();
         contentEventExpecter.unregisterListener();
 
@@ -46,17 +46,17 @@ public class testAddonManager extends Pi
         mSolo.goBack();
 
         // Load the about:addons page and verify it was loaded
         loadAndPaint(aboutAddonsURL);
         verifyUrlBarTitle(aboutAddonsURL);
 
         // Setup wait for tab to spawn and load
         tabEventExpecter = mActions.expectGlobalEvent(Actions.EventType.UI, "Tab:Added");
-        contentEventExpecter = mActions.expectGlobalEvent(Actions.EventType.GECKO, "Content:DOMContentLoaded");
+        contentEventExpecter = mActions.expectGlobalEvent(Actions.EventType.UI, "Content:DOMContentLoaded");
 
         // Open a new tab
         final String blankURL = getAbsoluteUrl(mStringHelper.ROBOCOP_BLANK_PAGE_01_URL);
         addTab(blankURL);
 
         // Wait for the new tab and page to load
         tabEventExpecter.blockForEvent();
         contentEventExpecter.blockForEvent();
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -431,16 +431,19 @@ pref("media.decoder-doctor.new-issue-end
 pref("media.suspend-bkgnd-video.enabled", true);
 // Delay, in ms, from time window goes to background to suspending
 // video decoders. Defaults to 10 seconds.
 pref("media.suspend-bkgnd-video.delay-ms", 10000);
 // Resume video decoding when the cursor is hovering on a background tab to
 // reduce the resume latency and improve the user experience.
 pref("media.resume-bkgnd-video-on-tabhover", true);;
 
+// Whether to enable media seamless looping.
+pref("media.seamless-looping", true);
+
 #ifdef MOZ_WEBRTC
 pref("media.navigator.enabled", true);
 pref("media.navigator.video.enabled", true);
 pref("media.navigator.video.default_fps",30);
 pref("media.navigator.video.use_remb", true);
 pref("media.navigator.video.use_tmmbr", false);
 pref("media.navigator.audio.use_fec", true);
 pref("media.navigator.video.red_ulpfec_enabled", false);
@@ -540,17 +543,17 @@ pref("dom.webaudio.enabled", true);
 
 #if !defined(ANDROID)
 pref("media.getusermedia.screensharing.enabled", true);
 #endif
 
 pref("media.getusermedia.audiocapture.enabled", false);
 
 // TextTrack WebVTT Region extension support.
-pref("media.webvtt.regions.enabled", false);
+pref("media.webvtt.regions.enabled", true);
 
 // WebVTT pseudo element and class support.
 pref("media.webvtt.pseudo.enabled", true);
 
 // AudioTrack and VideoTrack support
 pref("media.track.enabled", false);
 
 // Whether to enable MediaSource support.
--- a/services/.eslintrc.js
+++ b/services/.eslintrc.js
@@ -1,10 +1,12 @@
 "use strict";
 
 module.exports = {
   plugins: [
     "mozilla"
   ],
   "rules": {
     "no-throw-literal": 2,
+
+    "mozilla/use-services": "error",
   },
 }
--- a/services/common/blocklist-clients.js
+++ b/services/common/blocklist-clients.js
@@ -376,30 +376,28 @@ async function updateCertBlocklist(recor
  * collection.
  *
  * @param {Object} records   current records in the local db.
  */
 async function updatePinningList(records) {
   if (!Services.prefs.getBoolPref(PREF_BLOCKLIST_PINNING_ENABLED)) {
     return;
   }
-  const appInfo = Cc["@mozilla.org/xre/app-info;1"]
-      .getService(Ci.nsIXULAppInfo);
 
   const siteSecurityService = Cc["@mozilla.org/ssservice;1"]
       .getService(Ci.nsISiteSecurityService);
 
   // clear the current preload list
   siteSecurityService.clearPreloads();
 
   // write each KeyPin entry to the preload list
   for (let item of records) {
     try {
       const {pinType, pins = [], versions} = item;
-      if (versions.indexOf(appInfo.version) != -1) {
+      if (versions.indexOf(Services.appinfo.version) != -1) {
         if (pinType == "KeyPin" && pins.length) {
           siteSecurityService.setKeyPins(item.hostName,
               item.includeSubdomains,
               item.expires,
               pins.length,
               pins, true);
         }
         if (pinType == "STSPin") {
--- a/services/common/observers.js
+++ b/services/common/observers.js
@@ -5,16 +5,17 @@
 this.EXPORTED_SYMBOLS = ["Observers"];
 
 var Cc = Components.classes;
 var Ci = Components.interfaces;
 var Cr = Components.results;
 var Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
 
 /**
  * A service for adding, removing and notifying observers of notifications.
  * Wraps the nsIObserverService interface.
  *
  * @version 0.2
  */
 this.Observers = {
@@ -31,17 +32,17 @@ this.Observers = {
    * @param   thisObject  {Object}  [optional]
    *          the object to use as |this| when calling a Function callback
    *
    * @returns the observer
    */
   add(topic, callback, thisObject) {
     let observer = new Observer(topic, callback, thisObject);
     this._cache.push(observer);
-    this._service.addObserver(observer, topic, true);
+    Services.obs.addObserver(observer, topic, true);
 
     return observer;
   },
 
   /**
    * Unregister the given callback as an observer of the given topic.
    *
    * @param topic       {String}
@@ -57,17 +58,17 @@ this.Observers = {
     // This seems fairly inefficient, but I'm not sure how much better
     // we can make it.  We could index by topic, but we can't index by callback
     // or thisObject, as far as I know, since the keys to JavaScript hashes
     // (a.k.a. objects) can apparently only be primitive values.
     let [observer] = this._cache.filter(v => v.topic == topic &&
                                              v.callback == callback &&
                                              v.thisObject == thisObject);
     if (observer) {
-      this._service.removeObserver(observer, topic);
+      Services.obs.removeObserver(observer, topic);
       this._cache.splice(this._cache.indexOf(observer), 1);
     } else {
       throw new Error("Attempt to remove non-existing observer");
     }
   },
 
   /**
    * Notify observers about something.
@@ -83,22 +84,19 @@ this.Observers = {
    *        is sufficient to pass all needed information to the JS observers
    *        that this module targets; if you have multiple values to pass to
    *        the observer, wrap them in an object and pass them via the subject
    *        parameter (i.e.: { foo: 1, bar: "some string", baz: myObject })
    */
   notify(topic, subject, data) {
     subject = (typeof subject == "undefined") ? null : new Subject(subject);
        data = (typeof data == "undefined") ? null : data;
-    this._service.notifyObservers(subject, topic, data);
+    Services.obs.notifyObservers(subject, topic, data);
   },
 
-  _service: Cc["@mozilla.org/observer-service;1"].
-            getService(Ci.nsIObserverService),
-
   /**
    * A cache of observers that have been added.
    *
    * We use this to remove observers when a caller calls |remove|.
    *
    * XXX This might result in reference cycles, causing memory leaks,
    * if we hold a reference to an observer that holds a reference to us.
    * Could we fix that by making this an independent top-level object
--- a/services/common/tests/unit/test_blocklist_pinning.js
+++ b/services/common/tests/unit/test_blocklist_pinning.js
@@ -2,17 +2,17 @@
 
 const { Constructor: CC } = Components;
 
 Cu.import("resource://testing-common/httpd.js");
 
 const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
   "nsIBinaryInputStream", "setInputStream");
 
-// First, we need to setup appInfo or we can't do version checks
+// First, we need to setup Services.appinfo or we can't do version checks
 var id = "xpcshell@tests.mozilla.org";
 var appName = "XPCShell";
 var version = "1";
 var platformVersion = "1.9.2";
 Cu.import("resource://testing-common/AppInfo.jsm", this);
 
 updateAppInfo({
   name: appName,
@@ -161,19 +161,16 @@ function run_test() {
 
   do_register_cleanup(function() {
     server.stop(() => { });
   });
 }
 
 // get a response for a given request from sample data
 function getSampleResponse(req, port) {
-  const appInfo = Cc["@mozilla.org/xre/app-info;1"]
-                     .getService(Ci.nsIXULAppInfo);
-
   const responses = {
     "OPTIONS": {
       "sampleHeaders": [
         "Access-Control-Allow-Headers: Content-Length,Expires,Backoff,Retry-After,Last-Modified,Total-Records,ETag,Pragma,Cache-Control,authorization,content-type,if-none-match,Alert,Next-Page",
         "Access-Control-Allow-Methods: GET,HEAD,OPTIONS,POST,DELETE,OPTIONS",
         "Access-Control-Allow-Origin: *",
         "Content-Type: application/json; charset=UTF-8",
         "Server: waitress"
@@ -211,17 +208,17 @@ function getSampleResponse(req, port) {
       "status": {status: 200, statusText: "OK"},
       "responseBody": JSON.stringify({"data": [{
         "pinType": "KeyPin",
         "hostName": "one.example.com",
         "includeSubdomains": false,
         "expires": new Date().getTime() + 1000000,
         "pins": ["cUPcTAZWKaASuYWhhneDttWpY3oBAkE3h2+soZS7sWs=",
                   "M8HztCzM3elUxkcjR2S5P4hhyBNf6lHkmjAHKhpGPWE="],
-        "versions": [appInfo.version],
+        "versions": [Services.appinfo.version],
         "id": "78cf8900-fdea-4ce5-f8fb-b78710617718",
         "last_modified": 3000
       }]})
     },
     "GET:/v1/buckets/pinning/collections/pins/records?_sort=-last_modified&_since=3000": {
       "sampleHeaders": [
         "Access-Control-Allow-Origin: *",
         "Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
@@ -232,27 +229,27 @@ function getSampleResponse(req, port) {
       "status": {status: 200, statusText: "OK"},
       "responseBody": JSON.stringify({"data": [{
         "pinType": "KeyPin",
         "hostName": "two.example.com",
         "includeSubdomains": false,
         "expires": new Date().getTime() + 1000000,
         "pins": ["cUPcTAZWKaASuYWhhneDttWpY3oBAkE3h2+soZS7sWs=",
                   "M8HztCzM3elUxkcjR2S5P4hhyBNf6lHkmjAHKhpGPWE="],
-        "versions": [appInfo.version],
+        "versions": [Services.appinfo.version],
         "id": "dabafde9-df4a-ddba-2548-748da04cc02c",
         "last_modified": 4000
       }, {
         "pinType": "KeyPin",
         "hostName": "three.example.com",
         "includeSubdomains": false,
         "expires": new Date().getTime() + 1000000,
         "pins": ["cUPcTAZWKaASuYWhhneDttWpY3oBAkE3h2+soZS7sWs=",
                   "M8HztCzM3elUxkcjR2S5P4hhyBNf6lHkmjAHKhpGPWE="],
-        "versions": [appInfo.version, "some other version that won't match"],
+        "versions": [Services.appinfo.version, "some other version that won't match"],
         "id": "dabafde9-df4a-ddba-2548-748da04cc02d",
         "last_modified": 4000
       }, {
         "pinType": "KeyPin",
         "hostName": "four.example.com",
         "includeSubdomains": false,
         "expires": new Date().getTime() + 1000000,
         "pins": ["cUPcTAZWKaASuYWhhneDttWpY3oBAkE3h2+soZS7sWs=",
@@ -260,17 +257,17 @@ function getSampleResponse(req, port) {
         "versions": ["some version that won't match"],
         "id": "dabafde9-df4a-ddba-2548-748da04cc02e",
         "last_modified": 4000
       }, {
         "pinType": "STSPin",
         "hostName": "five.example.com",
         "includeSubdomains": false,
         "expires": new Date().getTime() + 1000000,
-        "versions": [appInfo.version, "some version that won't match"],
+        "versions": [Services.appinfo.version, "some version that won't match"],
         "id": "dabafde9-df4a-ddba-2548-748da04cc032",
         "last_modified": 4000
       }]})
     },
     "GET:/v1/buckets/pinning/collections/pins/records?_sort=-last_modified&_since=4000": {
       "sampleHeaders": [
         "Access-Control-Allow-Origin: *",
         "Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
@@ -294,25 +291,25 @@ function getSampleResponse(req, port) {
         "id": "dabafde9-df4a-ddba-2548-748da04cc030",
         "last_modified": 5000
       }, {
         "irrelevant": "this entry is missing the actual pins",
         "pinType": "KeyPin",
         "hostName": "missingpins.example.com",
         "includeSubdomains": false,
         "expires": new Date().getTime() + 1000000,
-        "versions": [appInfo.version],
+        "versions": [Services.appinfo.version],
         "id": "dabafde9-df4a-ddba-2548-748da04cc031",
         "last_modified": 5000
       }, {
         "pinType": "STSPin",
         "hostName": "five.example.com",
         "includeSubdomains": true,
         "expires": new Date().getTime() + 1000000,
-        "versions": [appInfo.version, "some version that won't match"],
+        "versions": [Services.appinfo.version, "some version that won't match"],
         "id": "dabafde9-df4a-ddba-2548-748da04cc032",
         "last_modified": 5000
       }]})
     }
   };
   return responses[`${req.method}:${req.path}?${req.queryString}`] ||
          responses[req.method];
 
--- a/services/crypto/tests/unit/head_helpers.js
+++ b/services/crypto/tests/unit/head_helpers.js
@@ -4,16 +4,17 @@ var Cr = Components.results;
 var Cu = Components.utils;
 
 /* import-globals-from ../../../common/tests/unit/head_helpers.js */
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 try {
   // In the context of xpcshell tests, there won't be a default AppInfo
+  // eslint-disable-next-line mozilla/use-services
   Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo);
 } catch (ex) {
 
 // Make sure to provide the right OS so crypto loads the right binaries
 var OS = "XPCShell";
 if (mozinfo.os == "win")
   OS = "WINNT";
 else if (mozinfo.os == "mac")
--- a/services/fxaccounts/FxAccountsConfig.jsm
+++ b/services/fxaccounts/FxAccountsConfig.jsm
@@ -63,32 +63,16 @@ this.FxAccountsConfig = {
     }
     // They have the autoconfig uri pref set, so we clear all the prefs that we
     // will have initialized, which will leave them pointing at production.
     for (let pref of CONFIG_PREFS) {
       Services.prefs.clearUserPref(pref);
     }
     // Reset the webchannel.
     EnsureFxAccountsWebChannel();
-    if (!Services.prefs.prefHasUserValue("webchannel.allowObject.urlWhitelist")) {
-      return;
-    }
-    let whitelistValue = Services.prefs.getCharPref("webchannel.allowObject.urlWhitelist");
-    if (whitelistValue.startsWith(autoconfigURL + " ")) {
-      whitelistValue = whitelistValue.slice(autoconfigURL.length + 1);
-      // Check and see if the value will be the default, and just clear the pref if it would
-      // to avoid it showing up as changed in about:config.
-      let defaultWhitelist = Services.prefs.getDefaultBranch("webchannel.allowObject.").getCharPref("urlWhitelist", "");
-
-      if (defaultWhitelist === whitelistValue) {
-        Services.prefs.clearUserPref("webchannel.allowObject.urlWhitelist");
-      } else {
-        Services.prefs.setCharPref("webchannel.allowObject.urlWhitelist", whitelistValue);
-      }
-    }
   },
 
   getAutoConfigURL() {
     let pref = Services.prefs.getCharPref("identity.fxaccounts.autoconfig.uri", "");
     if (!pref) {
       // no pref / empty pref means we don't bother here.
       return "";
     }
@@ -158,21 +142,16 @@ this.FxAccountsConfig = {
       Services.prefs.setCharPref("identity.fxaccounts.remote.webchannel.uri", rootURL);
       Services.prefs.setCharPref("identity.fxaccounts.settings.uri", rootURL + "/settings?service=sync&context=" + contextParam);
       Services.prefs.setCharPref("identity.fxaccounts.settings.devices.uri", rootURL + "/settings/clients?service=sync&context=" + contextParam);
       Services.prefs.setCharPref("identity.fxaccounts.remote.signup.uri", rootURL + "/signup?service=sync&context=" + contextParam);
       Services.prefs.setCharPref("identity.fxaccounts.remote.signin.uri", rootURL + "/signin?service=sync&context=" + contextParam);
       Services.prefs.setCharPref("identity.fxaccounts.remote.email.uri", rootURL + "/?service=sync&context=" + contextParam + "&action=email");
       Services.prefs.setCharPref("identity.fxaccounts.remote.force_auth.uri", rootURL + "/force_auth?service=sync&context=" + contextParam);
 
-      let whitelistValue = Services.prefs.getCharPref("webchannel.allowObject.urlWhitelist");
-      if (!whitelistValue.includes(rootURL)) {
-        whitelistValue = `${rootURL} ${whitelistValue}`;
-        Services.prefs.setCharPref("webchannel.allowObject.urlWhitelist", whitelistValue);
-      }
       // Ensure the webchannel is pointed at the correct uri
       EnsureFxAccountsWebChannel();
     } catch (e) {
       log.error("Failed to initialize configuration preferences from autoconfig object", e);
       throw e;
     }
   },
 
--- a/services/sync/Weave.js
+++ b/services/sync/Weave.js
@@ -150,19 +150,18 @@ AboutWeaveLog.prototype = {
     let channel = Services.io.newChannelFromURIWithLoadInfo(uri, aLoadInfo);
 
     channel.originalURI = aURI;
 
     // Ensure that the about page has the same privileges as a regular directory
     // view. That way links to files can be opened. make sure we use the correct
     // origin attributes when creating the principal for accessing the
     // about:sync-log data.
-    let ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
-                .getService(Ci.nsIScriptSecurityManager);
-    let principal = ssm.createCodebasePrincipal(uri, aLoadInfo.originAttributes);
+    let principal = Services.scriptSecurityManager.createCodebasePrincipal(uri,
+      aLoadInfo.originAttributes);
 
     channel.owner = principal;
     return channel;
   }
 };
 
 const components = [WeaveService, AboutWeaveLog];
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
--- a/services/sync/modules/engines/bookmarks.js
+++ b/services/sync/modules/engines/bookmarks.js
@@ -16,19 +16,17 @@ Cu.import("resource://services-common/as
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/record.js");
 Cu.import("resource://services-sync/util.js");
 
 XPCOMUtils.defineLazyModuleGetter(this, "BookmarkValidator",
                                   "resource://services-sync/bookmark_validator.js");
 XPCOMUtils.defineLazyGetter(this, "PlacesBundle", () => {
-  let bundleService = Cc["@mozilla.org/intl/stringbundle;1"]
-                        .getService(Ci.nsIStringBundleService);
-  return bundleService.createBundle("chrome://places/locale/places.properties");
+  return Services.strings.createBundle("chrome://places/locale/places.properties");
 });
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesSyncUtils",
                                   "resource://gre/modules/PlacesSyncUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesBackups",
                                   "resource://gre/modules/PlacesBackups.jsm");
 
--- a/services/sync/modules/engines/prefs.js
+++ b/services/sync/modules/engines/prefs.js
@@ -86,20 +86,18 @@ PrefStore.prototype = {
   get _prefs() {
     if (!this.__prefs) {
       this.__prefs = new Preferences();
     }
     return this.__prefs;
   },
 
   _getSyncPrefs() {
-    let syncPrefs = Cc["@mozilla.org/preferences-service;1"]
-                      .getService(Ci.nsIPrefService)
-                      .getBranch(PREF_SYNC_PREFS_PREFIX)
-                      .getChildList("", {});
+    let syncPrefs = Services.prefs.getBranch(PREF_SYNC_PREFS_PREFIX)
+                                  .getChildList("", {});
     // Also sync preferences that determine which prefs get synced.
     let controlPrefs = syncPrefs.map(pref => PREF_SYNC_PREFS_PREFIX + pref);
     return controlPrefs.concat(syncPrefs);
   },
 
   _isSynced(pref) {
     return pref.startsWith(PREF_SYNC_PREFS_PREFIX) ||
            this._prefs.get(PREF_SYNC_PREFS_PREFIX + pref, false);
--- a/services/sync/modules/util.js
+++ b/services/sync/modules/util.js
@@ -611,17 +611,17 @@ this.Utils = {
     try {
       // hostname of the system, usually assigned by the user or admin
       hostname = Cc["@mozilla.org/network/dns-service;1"].getService(Ci.nsIDNSService).myHostName;
     } catch (ex) {
       Cu.reportError(ex);
     }
     let system =
       // 'device' is defined on unix systems
-      Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2).get("device") ||
+      Services.sysinfo.get("device") ||
       hostname ||
       // fall back on ua info string
       Cc["@mozilla.org/network/protocol;1?name=http"].getService(Ci.nsIHttpProtocolHandler).oscpu;
 
     let syncStrings = Services.strings.createBundle("chrome://weave/locale/sync.properties");
     return syncStrings.formatStringFromName("client.name2", [user, brandName, system], 3);
   },
 
--- a/services/sync/tests/unit/test_addon_utils.js
+++ b/services/sync/tests/unit/test_addon_utils.js
@@ -64,35 +64,32 @@ add_test(function test_handle_empty_sour
   do_check_true(result.skipped.includes(ID));
 
   server.stop(run_next_test);
 });
 
 add_test(function test_ignore_untrusted_source_uris() {
   _("Ensures that source URIs from insecure schemes are rejected.");
 
-  let ioService = Cc["@mozilla.org/network/io-service;1"]
-                  .getService(Ci.nsIIOService);
-
   const bad = ["http://example.com/foo.xpi",
                "ftp://example.com/foo.xpi",
                "silly://example.com/foo.xpi"];
 
   const good = ["https://example.com/foo.xpi"];
 
   for (let s of bad) {
-    let sourceURI = ioService.newURI(s);
+    let sourceURI = Services.io.newURI(s);
     let addon = {sourceURI, name: "bad", id: "bad"};
 
     let canInstall = AddonUtils.canInstallAddon(addon);
     do_check_false(canInstall, "Correctly rejected a bad URL");
   }
 
   for (let s of good) {
-    let sourceURI = ioService.newURI(s);
+    let sourceURI = Services.io.newURI(s);
     let addon = {sourceURI, name: "good", id: "good"};
 
     let canInstall = AddonUtils.canInstallAddon(addon);
     do_check_true(canInstall, "Correctly accepted a good URL");
   }
   run_next_test();
 });
 
--- a/services/sync/tests/unit/test_addons_store.js
+++ b/services/sync/tests/unit/test_addons_store.js
@@ -290,48 +290,42 @@ add_test(function test_addon_syncability
   dummy.foreignInstall = true;
   do_check_false(store.isAddonSyncable(dummy));
   dummy.foreignInstall = false;
 
   uninstallAddon(addon);
 
   do_check_false(store.isSourceURITrusted(null));
 
-  function createURI(s) {
-    let service = Components.classes["@mozilla.org/network/io-service;1"]
-                  .getService(Components.interfaces.nsIIOService);
-    return service.newURI(s);
-  }
-
   let trusted = [
     "https://addons.mozilla.org/foo",
     "https://other.example.com/foo"
   ];
 
   let untrusted = [
     "http://addons.mozilla.org/foo",     // non-https
     "ftps://addons.mozilla.org/foo",     // non-https
     "https://untrusted.example.com/foo", // non-trusted hostname`
   ];
 
   for (let uri of trusted) {
-    do_check_true(store.isSourceURITrusted(createURI(uri)));
+    do_check_true(store.isSourceURITrusted(Services.io.newURI(uri)));
   }
 
   for (let uri of untrusted) {
-    do_check_false(store.isSourceURITrusted(createURI(uri)));
+    do_check_false(store.isSourceURITrusted(Services.io.newURI(uri)));
   }
 
   Svc.Prefs.set("addons.trustedSourceHostnames", "");
   for (let uri of trusted) {
-    do_check_false(store.isSourceURITrusted(createURI(uri)));
+    do_check_false(store.isSourceURITrusted(Services.io.newURI(uri)));
   }
 
   Svc.Prefs.set("addons.trustedSourceHostnames", "addons.mozilla.org");
-  do_check_true(store.isSourceURITrusted(createURI("https://addons.mozilla.org/foo")));
+  do_check_true(store.isSourceURITrusted(Services.io.newURI("https://addons.mozilla.org/foo")));
 
   Svc.Prefs.reset("addons.trustedSourceHostnames");
 
   run_next_test();
 });
 
 add_test(function test_ignore_hotfixes() {
   _("Ensure that hotfix extensions are ignored.");
@@ -351,19 +345,17 @@ add_test(function test_ignore_hotfixes()
 
   // Basic sanity check.
   do_check_true(store.isAddonSyncable(dummy));
 
   extensionPrefs.set("hotfix.id", dummy.id);
   do_check_false(store.isAddonSyncable(dummy));
 
   // Verify that int values don't throw off checking.
-  let prefSvc = Cc["@mozilla.org/preferences-service;1"]
-                .getService(Ci.nsIPrefService)
-                .getBranch("extensions.");
+  let prefSvc = Services.prefs.getBranch("extensions.");
   // Need to delete pref before changing type.
   prefSvc.deleteBranch("hotfix.id");
   prefSvc.setIntPref("hotfix.id", 0xdeadbeef);
 
   do_check_true(store.isAddonSyncable(dummy));
 
   uninstallAddon(addon);
 
--- a/services/sync/tests/unit/test_bookmark_engine.js
+++ b/services/sync/tests/unit/test_bookmark_engine.js
@@ -248,20 +248,17 @@ async function test_restoreOrImport(aRep
     _("Create a single record.");
     let bmk1 = await PlacesUtils.bookmarks.insert({
       parentGuid: folder1.guid,
       url: "http://getfirefox.com/",
       title: "Get Firefox!",
     });
     _(`Get Firefox!: ${bmk1.guid}`);
 
-    let dirSvc = Cc["@mozilla.org/file/directory_service;1"]
-      .getService(Ci.nsIProperties);
-
-    let backupFile = dirSvc.get("TmpD", Ci.nsIFile);
+    let backupFile = Services.dirsvc.get("TmpD", Ci.nsIFile);
 
     _("Make a backup.");
     backupFile.append("t_b_e_" + Date.now() + ".json");
 
     _(`Backing up to file ${backupFile.path}`);
     await bookmarkUtils.exportToFile(backupFile.path);
 
     _("Create a different record and sync.");
--- a/services/sync/tests/unit/test_places_guid_downgrade.js
+++ b/services/sync/tests/unit/test_places_guid_downgrade.js
@@ -4,18 +4,16 @@
 Cu.import("resource://services-common/utils.js");
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/engines/history.js");
 Cu.import("resource://services-sync/engines/bookmarks.js");
 Cu.import("resource://services-sync/service.js");
 
 const kDBName = "places.sqlite";
-const storageSvc = Cc["@mozilla.org/storage/service;1"]
-                     .getService(Ci.mozIStorageService);
 
 function setPlacesDatabase(aFileName) {
   removePlacesDatabase();
   _("Copying over places.sqlite.");
   let file = do_get_file(aFileName);
   file.copyTo(gSyncProfile, kDBName);
 }
 
@@ -38,17 +36,17 @@ Svc.Obs.add("places-shutdown", function(
 // Verify initial database state. Function borrowed from places tests.
 add_test(function test_initial_state() {
   _("Verify initial setup: v11 database is available");
 
   // Mostly sanity checks our starting DB to make sure it's setup as we expect
   // it to be.
   let dbFile = gSyncProfile.clone();
   dbFile.append(kDBName);
-  let db = storageSvc.openUnsharedDatabase(dbFile);
+  let db = Services.storage.openUnsharedDatabase(dbFile);
 
   let stmt = db.createStatement("PRAGMA journal_mode");
   do_check_true(stmt.executeStep());
   // WAL journal mode should have been unset this database when it was migrated
   // down to v10.
   do_check_neq(stmt.getString(0).toLowerCase(), "wal");
   stmt.finalize();
 
--- a/services/sync/tests/unit/test_resource_header.js
+++ b/services/sync/tests/unit/test_resource_header.js
@@ -35,18 +35,17 @@ function contentHandler(metadata, respon
 
 // Set a proxy function to cause an internal redirect.
 function triggerRedirect() {
   const PROXY_FUNCTION = "function FindProxyForURL(url, host) {" +
                          "  return 'PROXY a_non_existent_domain_x7x6c572v:80; " +
                                    "PROXY localhost:" + HTTP_PORT + "';" +
                          "}";
 
-  let prefsService = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService);
-  let prefs = prefsService.getBranch("network.proxy.");
+  let prefs = Services.prefs.getBranch("network.proxy.");
   prefs.setIntPref("type", 2);
   prefs.setCharPref("autoconfig_url", "data:text/plain," + PROXY_FUNCTION);
 }
 
 add_task(async function test_headers_copied() {
   triggerRedirect();
 
   _("Issuing request.");
--- a/services/sync/tests/unit/test_utils_misc.js
+++ b/services/sync/tests/unit/test_utils_misc.js
@@ -7,17 +7,17 @@ add_test(function test_default_device_na
   // We are just hoping to avoid a repeat of bug 1369285.
   let def = Utils._orig_getDefaultDeviceName(); // make sure it doesn't throw.
   _("default value is " + def);
   ok(def.length > 0);
 
   // This is obviously tied to the implementation, but we want early warning
   // if any of these things fail.
   // We really want one of these 2 to provide a value.
-  let hostname = Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2).get("device") ||
+  let hostname = Services.sysinfo.get("device") ||
                  Cc["@mozilla.org/network/dns-service;1"].getService(Ci.nsIDNSService).myHostName;
   _("hostname is " + hostname);
   ok(hostname.length > 0);
   // the hostname should be in the default.
   ok(def.includes(hostname));
   // We expect the following to work as a fallback to the above.
   let fallback = Cc["@mozilla.org/network/protocol;1?name=http"].getService(Ci.nsIHttpProtocolHandler).oscpu;
   _("UA fallback is " + fallback);
--- a/services/sync/tps/extensions/tps/components/tps-cmdline.js
+++ b/services/sync/tps/extensions/tps/components/tps-cmdline.js
@@ -58,20 +58,18 @@ TPSCmdLineHandler.prototype = {
 
     options.ignoreUnusedEngines = cmdLine.handleFlag("ignore-unused-engines",
                                                      false);
     let uri = cmdLine.resolveURI(OS.Path.normalize(uristr)).spec;
 
     const onStartupFinished = () => {
       Services.obs.removeObserver(onStartupFinished, "browser-delayed-startup-finished");
       /* Ignore the platform's online/offline status while running tests. */
-      var ios = Components.classes["@mozilla.org/network/io-service;1"]
-                          .getService(Components.interfaces.nsIIOService);
-      ios.manageOfflineStatus = false;
-      ios.offline = false;
+      Services.io.manageOfflineStatus = false;
+      Services.io.offline = false;
       Components.utils.import("resource://tps/tps.jsm");
       Components.utils.import("resource://tps/quit.js", TPS);
       TPS.RunTestPhase(uri, phase, logfile, options).catch(err => TPS.DumpError("TestPhase failed", err));
     };
     Services.obs.addObserver(onStartupFinished, "browser-delayed-startup-finished");
   },
 
   helpInfo: "  --tps <file>              Run TPS tests with the given test file.\n" +
--- a/services/sync/tps/extensions/tps/resource/logger.jsm
+++ b/services/sync/tps/extensions/tps/resource/logger.jsm
@@ -6,33 +6,33 @@
     Components.utils.import() and acts as a singleton.
     Only the following listed symbols will exposed on import, and only when
     and where imported. */
 
 var EXPORTED_SYMBOLS = ["Logger"];
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
+Cu.import("resource://gre/modules/Services.jsm");
+
 var Logger = {
   _foStream: null,
   _converter: null,
   _potentialError: null,
 
   init(path) {
     if (this._converter != null) {
       // we're already open!
       return;
     }
 
-    let prefs = Cc["@mozilla.org/preferences-service;1"]
-                .getService(Ci.nsIPrefBranch);
     if (path) {
-      prefs.setCharPref("tps.logfile", path);
+      Services.prefs.setCharPref("tps.logfile", path);
     } else {
-      path = prefs.getCharPref("tps.logfile");
+      path = Services.prefs.getCharPref("tps.logfile");
     }
 
     this._file = Cc["@mozilla.org/file/local;1"]
                  .createInstance(Ci.nsIFile);
     this._file.initWithPath(path);
     var exists = this._file.exists();
 
     // Make a file output stream and converter to handle it.
@@ -138,9 +138,8 @@ var Logger = {
     else
       this.log("CROSSWEAVE INFO: " + msg);
   },
 
   logPass(msg) {
     this.log("CROSSWEAVE TEST PASS: " + msg);
   },
 };
-
--- a/services/sync/tps/extensions/tps/resource/modules/prefs.jsm
+++ b/services/sync/tps/extensions/tps/resource/modules/prefs.jsm
@@ -8,19 +8,17 @@
    and where imported. */
 
 var EXPORTED_SYMBOLS = ["Preference"];
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 const WEAVE_PREF_PREFIX = "services.sync.prefs.sync.";
 
-var prefs = Cc["@mozilla.org/preferences-service;1"]
-            .getService(Ci.nsIPrefBranch);
-
+Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://tps/logger.jsm");
 
 /**
  * Preference class constructor
  *
  * Initializes instance properties.
  */
 function Preference(props) {
@@ -42,41 +40,41 @@ Preference.prototype = {
    * Throws on error.
    *
    * @return nothing
    */
   Modify() {
     // Determine if this pref is actually something Weave even looks at.
     let weavepref = WEAVE_PREF_PREFIX + this.name;
     try {
-      let syncPref = prefs.getBoolPref(weavepref);
+      let syncPref = Services.prefs.getBoolPref(weavepref);
       if (!syncPref)
-        prefs.setBoolPref(weavepref, true);
+        Services.prefs.setBoolPref(weavepref, true);
     } catch (e) {
       Logger.AssertTrue(false, "Weave doesn't sync pref " + this.name);
     }
 
     // Modify the pref; throw an exception if the pref type is different
     // than the value type specified in the test.
-    let prefType = prefs.getPrefType(this.name);
+    let prefType = Services.prefs.getPrefType(this.name);
     switch (prefType) {
       case Ci.nsIPrefBranch.PREF_INT:
         Logger.AssertEqual(typeof(this.value), "number",
           "Wrong type used for preference value");
-        prefs.setIntPref(this.name, this.value);
+        Services.prefs.setIntPref(this.name, this.value);
         break;
       case Ci.nsIPrefBranch.PREF_STRING:
         Logger.AssertEqual(typeof(this.value), "string",
           "Wrong type used for preference value");
-        prefs.setCharPref(this.name, this.value);
+        Services.prefs.setCharPref(this.name, this.value);
         break;
       case Ci.nsIPrefBranch.PREF_BOOL:
         Logger.AssertEqual(typeof(this.value), "boolean",
           "Wrong type used for preference value");
-        prefs.setBoolPref(this.name, this.value);
+        Services.prefs.setBoolPref(this.name, this.value);
         break;
     }
   },
 
   /**
    * Find
    *
    * Verifies that the preference this.name has the value
@@ -84,32 +82,31 @@ Preference.prototype = {
    * doesn't match.
    *
    * @return nothing
    */
   Find() {
     // Read the pref value.
     let value;
     try {
-      let prefType = prefs.getPrefType(this.name);
+      let prefType = Services.prefs.getPrefType(this.name);
       switch (prefType) {
         case Ci.nsIPrefBranch.PREF_INT:
-          value = prefs.getIntPref(this.name);
+          value = Services.prefs.getIntPref(this.name);
           break;
         case Ci.nsIPrefBranch.PREF_STRING:
-          value = prefs.getCharPref(this.name);
+          value = Services.prefs.getCharPref(this.name);
           break;
         case Ci.nsIPrefBranch.PREF_BOOL:
-          value = prefs.getBoolPref(this.name);
+          value = Services.prefs.getBoolPref(this.name);
           break;
       }
     } catch (e) {
       Logger.AssertTrue(false, "Error accessing pref " + this.name);
     }
 
     // Throw an exception if the current and expected values aren't of
     // the same type, or don't have the same values.
     Logger.AssertEqual(typeof(value), typeof(this.value),
       "Value types don't match");
     Logger.AssertEqual(value, this.value, "Preference values don't match");
   },
 };
-
--- a/services/sync/tps/extensions/tps/resource/modules/tabs.jsm
+++ b/services/sync/tps/extensions/tps/resource/modules/tabs.jsm
@@ -6,16 +6,17 @@
    Components.utils.import() and acts as a singleton.
    Only the following listed symbols will exposed on import, and only when
    and where imported. */
 
 const EXPORTED_SYMBOLS = ["BrowserTabs"];
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
+Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://services-sync/main.js");
 
 // Unfortunately, due to where TPS is run, we can't directly reuse the logic from
 // BrowserTestUtils.jsm. Moreover, we can't resolve the URI it loads the content
 // frame script from ("chrome://mochikit/content/tests/BrowserTestUtils/content-utils.js"),
 // hence the hackiness here and in BrowserTabs.Add.
 Cc["@mozilla.org/globalmessagemanager;1"]
 .getService(Ci.nsIMessageListenerManager)
@@ -35,19 +36,17 @@ var BrowserTabs = {
    *
    * @param uri The uri to load in the new tab
    * @return nothing
    */
   Add(uri, fn) {
 
     // Open the uri in a new tab in the current browser window, and calls
     // the callback fn from the tab's onload handler.
-    let wm = Cc["@mozilla.org/appshell/window-mediator;1"]
-               .getService(Ci.nsIWindowMediator);
-    let mainWindow = wm.getMostRecentWindow("navigator:browser");
+    let mainWindow = Services.wm.getMostRecentWindow("navigator:browser");
     let browser = mainWindow.getBrowser();
     let mm = browser.ownerGlobal.messageManager;
     mm.addMessageListener("tps:loadEvent", function onLoad(msg) {
       mm.removeMessageListener("tps:loadEvent", onLoad);
       fn();
     });
     let newtab = browser.addTab(uri);
     browser.selectedTab = newtab;
@@ -77,9 +76,8 @@ var BrowserTabs = {
         if (uri == weaveTabUrl && profile == client.clientName)
           if (title == undefined || title == tab.title)
             return true;
       }
     }
     return false;
   },
 };
-
--- a/services/sync/tps/extensions/tps/resource/modules/windows.jsm
+++ b/services/sync/tps/extensions/tps/resource/modules/windows.jsm
@@ -7,29 +7,28 @@
    Components.utils.import() and acts as a singleton.
    Only the following listed symbols will exposed on import, and only when
    and where imported. */
 
 const EXPORTED_SYMBOLS = ["BrowserWindows"];
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
+Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://services-sync/main.js");
 
 var BrowserWindows = {
   /**
    * Add
    *
    * Opens a new window. Throws on error.
    *
    * @param aPrivate The private option.
    * @return nothing
    */
   Add(aPrivate, fn) {
-    let wm = Cc["@mozilla.org/appshell/window-mediator;1"]
-               .getService(Ci.nsIWindowMediator);
-    let mainWindow = wm.getMostRecentWindow("navigator:browser");
+    let mainWindow = Services.wm.getMostRecentWindow("navigator:browser");
     let win = mainWindow.OpenBrowserWindow({private: aPrivate});
     win.addEventListener("load", function() {
       fn.call(win);
     }, {once: true});
   }
 };
--- a/services/sync/tps/extensions/tps/resource/quit.js
+++ b/services/sync/tps/extensions/tps/resource/quit.js
@@ -32,28 +32,25 @@ function goQuitApplication() {
   }
 
   const kAppStartup = "@mozilla.org/toolkit/app-startup;1";
   const kAppShell   = "@mozilla.org/appshell/appShellService;1";
   var appService;
   var forceQuit;
 
   if (kAppStartup in Components.classes) {
-    appService = Components.classes[kAppStartup]
-                 .getService(Components.interfaces.nsIAppStartup);
+    appService = Services.startup;
     forceQuit  = Components.interfaces.nsIAppStartup.eForceQuit;
   } else if (kAppShell in Components.classes) {
-    appService = Components.classes[kAppShell].
-      getService(Components.interfaces.nsIAppShellService);
+    appService = Services.appShell;
     forceQuit = Components.interfaces.nsIAppShellService.eForceQuit;
   } else {
     throw new Error("goQuitApplication: no AppStartup/appShell");
   }
 
   try {
     appService.quit(forceQuit);
   } catch (ex) {
     throw new Error(`goQuitApplication: ${ex.message}`);
   }
 
   return true;
 }
-
--- a/services/sync/tps/extensions/tps/resource/tps.jsm
+++ b/services/sync/tps/extensions/tps/resource/tps.jsm
@@ -43,18 +43,16 @@ Cu.import("resource://tps/modules/forms.
 Cu.import("resource://tps/modules/history.jsm");
 Cu.import("resource://tps/modules/passwords.jsm");
 Cu.import("resource://tps/modules/prefs.jsm");
 Cu.import("resource://tps/modules/tabs.jsm");
 Cu.import("resource://tps/modules/windows.jsm");
 
 var hh = Cc["@mozilla.org/network/protocol;1?name=http"]
          .getService(Ci.nsIHttpProtocolHandler);
-var prefs = Cc["@mozilla.org/preferences-service;1"]
-            .getService(Ci.nsIPrefBranch);
 
 XPCOMUtils.defineLazyGetter(this, "fileProtocolHandler", () => {
   let fileHandler = Services.io.getProtocolHandler("file");
   return fileHandler.QueryInterface(Ci.nsIFileProtocolHandler);
 });
 
 XPCOMUtils.defineLazyGetter(this, "gTextDecoder", () => {
   return new TextDecoder();
@@ -739,17 +737,17 @@ var TPS = {
         SyncTelemetry.shutdown();
         // we're all done
         Logger.logInfo("test phase " + this._currentPhase + ": " +
                        (this._errors ? "FAIL" : "PASS"));
         this._phaseFinished = true;
         this.quit();
         return;
       }
-      this.seconds_since_epoch = prefs.getIntPref("tps.seconds_since_epoch");
+      this.seconds_since_epoch = Services.prefs.getIntPref("tps.seconds_since_epoch");
       if (this.seconds_since_epoch)
         this._usSinceEpoch = this.seconds_since_epoch * 1000 * 1000;
       else {
         this.DumpError("seconds-since-epoch not set");
         return;
       }
 
       let phase = this._phaselist[this._currentPhase];
@@ -882,17 +880,17 @@ var TPS = {
 
   /**
    * Executes a single test phase.
    *
    * This is called by RunTestPhase() after the environment is validated.
    */
   async _executeTestPhase(file, phase, settings) {
     try {
-      this.config = JSON.parse(prefs.getCharPref("tps.config"));
+      this.config = JSON.parse(Services.prefs.getCharPref("tps.config"));
       // parse the test file
       Services.scriptloader.loadSubScript(file, this);
       this._currentPhase = phase;
       // cleanup phases are in the format `cleanup-${profileName}`.
       if (this._currentPhase.startsWith("cleanup-")) {
         let profileToClean = this._currentPhase.slice("cleanup-".length);
         this.phases[this._currentPhase] = profileToClean;
         this.Phase(this._currentPhase, [[this.Cleanup]]);
--- a/servo/README.md
+++ b/servo/README.md
@@ -70,34 +70,34 @@ If `virtualenv` does not exist, try `pyt
 
 #### On Fedora
 
 ``` sh
 sudo dnf install curl freeglut-devel libtool gcc-c++ libXi-devel \
     freetype-devel mesa-libGL-devel mesa-libEGL-devel glib2-devel libX11-devel libXrandr-devel gperf \
     fontconfig-devel cabextract ttmkfdir python python-virtualenv python-pip expat-devel \
     rpm-build openssl-devel cmake bzip2-devel libXcursor-devel libXmu-devel mesa-libOSMesa-devel \
-    dbus-devel ncurses-devel pulseaudio-libs-devel
+    dbus-devel ncurses-devel pulseaudio-libs-devel clang clang-libs
 ```
 #### On CentOS
 
 ``` sh
 sudo yum install curl freeglut-devel libtool gcc-c++ libXi-devel \
     freetype-devel mesa-libGL-devel mesa-libEGL-devel glib2-devel libX11-devel libXrandr-devel gperf \
     fontconfig-devel cabextract ttmkfdir python python-virtualenv python-pip expat-devel \
     rpm-build openssl-devel cmake bzip2-devel libXcursor-devel libXmu-devel mesa-libOSMesa-devel \
-    dbus-devel ncurses-devel python34 pulseaudio-libs-devel
+    dbus-devel ncurses-devel python34 pulseaudio-libs-devel clang clang-libs
 ```
 
 #### On openSUSE Linux
 ``` sh
 sudo zypper install libX11-devel libexpat-devel libbz2-devel Mesa-libEGL-devel Mesa-libGL-devel cabextract cmake \
     dbus-1-devel fontconfig-devel freetype-devel gcc-c++ git glib2-devel gperf \
     harfbuzz-devel libOSMesa-devel libXcursor-devel libXi-devel libXmu-devel libXrandr-devel libopenssl-devel \
-    python-pip python-virtualenv rpm-build glu-devel
+    python-pip python-virtualenv rpm-build glu-devel llvm-clang libclang
 ```
 #### On Arch Linux
 
 ``` sh
 sudo pacman -S --needed base-devel git python2 python2-virtualenv python2-pip mesa cmake bzip2 libxmu glu pkg-config
 ```
 #### On Gentoo Linux
 
--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -2154,34 +2154,38 @@ GeckoDriver.prototype.findElements = asy
 
     case Context.Content:
       resp.body = await this.listener.findElementsContent(using, value, opts);
       break;
   }
 };
 
 /**
- * Return the active element on the page.
+ * Return the active element in the document.
  *
  * @return {WebElement}
- *     Active element of the current browsing context's document element.
+ *     Active element of the current browsing context's document
+ *     element, if the document element is non-null.
  *
  * @throws {UnsupportedOperationError}
  *     Not available in current context.
  * @throws {NoSuchWindowError}
  *     Top-level browsing context has been discarded.
  * @throws {UnexpectedAlertOpenError}
  *     A modal dialog is open, blocking this operation.
+ * @throws {NoSuchElementError}
+ *     If the document does not have an active element, i.e. if
+ *     its document element has been deleted.
  */
-GeckoDriver.prototype.getActiveElement = async function(cmd, resp) {
+GeckoDriver.prototype.getActiveElement = async function() {
   assert.content(this.context);
   assert.window(this.getCurrentWindow());
   this._assertAndDismissModal();
 
-  resp.body.value = await this.listener.getActiveElement();
+  return this.listener.getActiveElement();
 };
 
 /**
  * Send click event to element.
  *
  * @param {string} id
  *     Reference ID to the element that will be clicked.
  *
--- a/testing/marionette/listener.js
+++ b/testing/marionette/listener.js
@@ -1269,19 +1269,32 @@ async function findElementsContent(strat
   }
 
   opts.all = true;
   let els = await element.find(curContainer, strategy, selector, opts);
   let webEls = seenEls.addAll(els);
   return webEls;
 }
 
-/** Find and return the active element on the page. */
+/**
+ * Return the active element in the document.
+ *
+ * @return {WebElement}
+ *     Active element of the current browsing context's document
+ *     element, if the document element is non-null.
+ *
+ * @throws {NoSuchElementError}
+ *     If the document does not have an active element, i.e. if
+ *     its document element has been deleted.
+ */
 function getActiveElement() {
   let el = curContainer.frame.document.activeElement;
+  if (!el) {
+    throw new NoSuchElementError();
+  }
   return evaluate.toJSON(el, seenEls);
 }
 
 /**
  * Send click event to element.
  *
  * @param {number} commandID
  *     ID of the currently handled message between the driver and
--- a/testing/marionette/server.js
+++ b/testing/marionette/server.js
@@ -13,27 +13,27 @@ const ServerSocket = CC(
 
 Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 Cu.import("chrome://marionette/content/assert.js");
 const {GeckoDriver} = Cu.import("chrome://marionette/content/driver.js", {});
+const {WebElement} = Cu.import("chrome://marionette/content/element.js", {});
 const {
   error,
   UnknownCommandError,
 } = Cu.import("chrome://marionette/content/error.js", {});
 const {
   Command,
   Message,
   Response,
 } = Cu.import("chrome://marionette/content/message.js", {});
-const {DebuggerTransport} =
-    Cu.import("chrome://marionette/content/transport.js", {});
+const {DebuggerTransport} = Cu.import("chrome://marionette/content/transport.js", {});
 
 XPCOMUtils.defineLazyServiceGetter(
     this, "env", "@mozilla.org/process/environment;1", "nsIEnvironment");
 
 const logger = Log.repository.getLogger("Marionette");
 
 const {KeepWhenOffline, LoopbackOnly} = Ci.nsIServerSocket;
 
@@ -555,17 +555,17 @@ server.TCPConnection = class {
 
     if (!["newSession", "WebDriver:NewSession"].includes(cmd.name)) {
       assert.session(this.driver);
     }
 
     let rv = await fn.bind(this.driver)(cmd, resp);
 
     if (typeof rv != "undefined") {
-      if (typeof rv != "object") {
+      if (rv instanceof WebElement || typeof rv != "object") {
         resp.body = {value: rv};
       } else {
         resp.body = rv;
       }
     }
   }
 
   /**
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -575988,17 +575988,17 @@
    "5ba51b660c7203bba3ada597c2f56fe094358e1f",
    "wdspec"
   ],
   "webdriver/tests/element_click/stale.py": [
    "37af63203540dfe11d36fe05d74694f05c6505f2",
    "wdspec"
   ],
   "webdriver/tests/element_retrieval/get_active_element.py": [
-   "74bb0beec41ab857f6814d47191f29065a536802",
+   "918c6e48047f31a088ec44e9b0d070b0ae3d6077",
    "wdspec"
   ],
   "webdriver/tests/fullscreen_window.py": [
    "817011a8cdff7cfd7e445fb8ecb84e5d91f03993",
    "wdspec"
   ],
   "webdriver/tests/get_window_rect.py": [
    "5d907b2a16b9ff7dba8e39bba19ea7f85f29f71e",
--- a/testing/web-platform/meta/webdriver/tests/element_retrieval/get_active_element.py.ini
+++ b/testing/web-platform/meta/webdriver/tests/element_retrieval/get_active_element.py.ini
@@ -1,11 +1,8 @@
 [get_active_element.py]
   type: wdspec
+
   [get_active_element.py::test_handle_prompt_dismiss]
     expected: FAIL
 
   [get_active_element.py::test_handle_prompt_accept]
     expected: FAIL
-
-  [get_active_element.py::test_sucess_without_body]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/webvtt/api/VTTRegion/constructor.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[constructor.html]
-  type: testharness
-  [VTTRegion() initial values]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/webvtt/api/VTTRegion/id.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[id.html]
-  type: testharness
-  [VTTRegion.id script-created region]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/webvtt/api/VTTRegion/lines.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[lines.html]
-  type: testharness
-  [VTTRegion.lines script-created region]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/webvtt/api/VTTRegion/regionAnchorX.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[regionAnchorX.html]
-  type: testharness
-  [VTTRegion.regionAnchorX script-created region]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/webvtt/api/VTTRegion/regionAnchorY.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[regionAnchorY.html]
-  type: testharness
-  [VTTRegion.regionAnchorY script-created region]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/webvtt/api/VTTRegion/scroll.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[scroll.html]
-  type: testharness
-  [VTTRegion.scroll script-created region]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/webvtt/api/VTTRegion/viewportAnchorX.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[viewportAnchorX.html]
-  type: testharness
-  [VTTRegion.viewportAnchorX script-created region]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/webvtt/api/VTTRegion/viewportAnchorY.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[viewportAnchorY.html]
-  type: testharness
-  [VTTRegion.viewportAnchorY script-created region]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/webvtt/api/VTTRegion/width.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[width.html]
-  type: testharness
-  [VTTRegion.width script-created region]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/webvtt/api/historical.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[historical.html]
-  type: testharness
-  [VTTRegion track member must be nuked]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/webvtt/api/interfaces.html.ini
+++ /dev/null
@@ -1,101 +0,0 @@
-[interfaces.html]
-  type: testharness
-  [VTTCue interface: attribute region]
-    expected: FAIL
-
-  [VTTCue interface: new VTTCue(0, 0, "") must inherit property "region" with the proper type (0)]
-    expected: FAIL
-
-  [VTTRegion interface: existence and properties of interface object]
-    expected: FAIL
-
-  [VTTRegion interface object length]
-    expected: FAIL
-
-  [VTTRegion interface object name]
-    expected: FAIL
-
-  [VTTRegion interface: existence and properties of interface prototype object]
-    expected: FAIL
-
-  [VTTRegion interface: existence and properties of interface prototype object's "constructor" property]
-    expected: FAIL
-
-  [VTTRegion interface: attribute width]
-    expected: FAIL
-
-  [VTTRegion interface: attribute lines]
-    expected: FAIL
-
-  [VTTRegion interface: attribute regionAnchorX]
-    expected: FAIL
-
-  [VTTRegion interface: attribute regionAnchorY]
-    expected: FAIL
-
-  [VTTRegion interface: attribute viewportAnchorX]
-    expected: FAIL
-
-  [VTTRegion interface: attribute viewportAnchorY]
-    expected: FAIL
-
-  [VTTRegion interface: attribute scroll]
-    expected: FAIL
-
-  [VTTRegion must be primary interface of new VTTRegion()]
-    expected: FAIL
-
-  [Stringification of new VTTRegion()]
-    expected: FAIL
-
-  [VTTRegion interface: new VTTRegion() must inherit property "width" with the proper type (0)]
-    expected: FAIL
-
-  [VTTRegion interface: new VTTRegion() must inherit property "lines" with the proper type (1)]
-    expected: FAIL
-
-  [VTTRegion interface: new VTTRegion() must inherit property "regionAnchorX" with the proper type (2)]
-    expected: FAIL
-
-  [VTTRegion interface: new VTTRegion() must inherit property "regionAnchorY" with the proper type (3)]
-    expected: FAIL
-
-  [VTTRegion interface: new VTTRegion() must inherit property "viewportAnchorX" with the proper type (4)]
-    expected: FAIL
-
-  [VTTRegion interface: new VTTRegion() must inherit property "viewportAnchorY" with the proper type (5)]
-    expected: FAIL
-
-  [VTTRegion interface: new VTTRegion() must inherit property "scroll" with the proper type (6)]
-    expected: FAIL
-
-  [VTTCue interface: new VTTCue(0, 0, "") must inherit property "region" with the proper type]
-    expected: FAIL
-
-  [VTTRegion interface: attribute id]
-    expected: FAIL
-
-  [VTTRegion interface: new VTTRegion() must inherit property "id" with the proper type]
-    expected: FAIL
-
-  [VTTRegion interface: new VTTRegion() must inherit property "width" with the proper type]
-    expected: FAIL
-
-  [VTTRegion interface: new VTTRegion() must inherit property "lines" with the proper type]
-    expected: FAIL
-
-  [VTTRegion interface: new VTTRegion() must inherit property "regionAnchorX" with the proper type]
-    expected: FAIL
-
-  [VTTRegion interface: new VTTRegion() must inherit property "regionAnchorY" with the proper type]
-    expected: FAIL
-
-  [VTTRegion interface: new VTTRegion() must inherit property "viewportAnchorX" with the proper type]
-    expected: FAIL
-
-  [VTTRegion interface: new VTTRegion() must inherit property "viewportAnchorY" with the proper type]
-    expected: FAIL
-
-  [VTTRegion interface: new VTTRegion() must inherit property "scroll" with the proper type]
-    expected: FAIL
-
--- a/testing/web-platform/meta/webvtt/parsing/file-parsing/tests/regions-old.html.ini
+++ b/testing/web-platform/meta/webvtt/parsing/file-parsing/tests/regions-old.html.ini
@@ -1,5 +1,5 @@
 [regions-old.html]
   type: testharness
   [regions, old]
-    expected: FAIL
+    expected: PASS
 
--- a/testing/web-platform/tests/webdriver/tests/element_retrieval/get_active_element.py
+++ b/testing/web-platform/tests/webdriver/tests/element_retrieval/get_active_element.py
@@ -1,15 +1,17 @@
 from tests.support.asserts import assert_error, assert_dialog_handled, assert_same_element
 from tests.support.fixtures import create_dialog
 from tests.support.inline import inline
 
+
 def read_global(session, name):
     return session.execute_script("return %s;" % name)
 
+
 def get_active_element(session):
     return session.transport.send("GET", "session/%s/element/active" % session.session_id)
 
 
 def assert_is_active_element(session, response):
     """Ensure that the provided object is a successful WebDriver
     response describing an element reference and that the referenced
     element matches the element returned by the `activeElement`
@@ -239,19 +241,19 @@ def test_success_iframe_content(session)
         iframe.contentDocument.body.appendChild(input);
         input.focus();
         """)
 
     response = get_active_element(session)
     assert_is_active_element(session, response)
 
 
-def test_sucess_without_body(session):
+def test_missing_document_element(session):
     session.url = inline("<body></body>")
     session.execute_script("""
         if (document.body.remove) {
           document.body.remove();
         } else {
           document.body.removeNode(true);
         }""")
 
     response = get_active_element(session)
-    assert_is_active_element(session, response)
+    assert_error(response, "no such element")
--- a/toolkit/.eslintrc.js
+++ b/toolkit/.eslintrc.js
@@ -2,10 +2,21 @@
 
 module.exports = {
   rules: {
     // XXX Bug 1326071 - This should be reduced down - probably to 20 or to
     // be removed & synced with the mozilla/recommended value.
     "complexity": ["error", 41],
 
     "mozilla/no-task": "error",
-  }
+
+    "mozilla/use-services": "error",
+  },
+
+  "overrides": [{
+    // Turn off use-services for xml files. XBL bindings are going away, and
+    // working out the valid globals for those is difficult.
+    "files": "**/*.xml",
+    "rules": {
+      "mozilla/use-services": "off",
+    }
+  }]
 };
deleted file mode 100644
--- a/toolkit/components/.eslintrc.js
+++ /dev/null
@@ -1,7 +0,0 @@
-"use strict";
-
-module.exports = {
-  rules: {
-    "mozilla/use-services": "error",
-  }
-};
--- a/toolkit/modules/LoadContextInfo.jsm
+++ b/toolkit/modules/LoadContextInfo.jsm
@@ -6,10 +6,12 @@
  * This jsm is here only for compatibility.  Extension developers may use it
  * to build nsILoadContextInfo to pass down to HTTP cache APIs.  Originally
  * it was possible to implement nsILoadContextInfo in JS.  But now it turned
  * out to be a built-in class only, so we need a component (service) as
  * a factory to build nsILoadContextInfo in a JS code.
  */
 
 this.EXPORTED_SYMBOLS = ["LoadContextInfo"];
+// XXX Bug 1417937 will remove this file.
+// eslint-disable-next-line mozilla/use-services
 this.LoadContextInfo = Components.classes["@mozilla.org/load-context-info-factory;1"]
                                  .getService(Components.interfaces.nsILoadContextInfoFactory);
--- a/toolkit/modules/Promise.jsm
+++ b/toolkit/modules/Promise.jsm
@@ -91,11 +91,12 @@ this.EXPORTED_SYMBOLS = [
 
 // These constants must be defined on the "this" object for them to be visible
 // by subscripts in B2G, since "this" does not match the global scope.
 this.Cc = Components.classes;
 this.Ci = Components.interfaces;
 this.Cu = Components.utils;
 this.Cr = Components.results;
 
+// eslint-disable-next-line mozilla/use-services
 this.Cc["@mozilla.org/moz/jssubscript-loader;1"]
     .getService(this.Ci.mozIJSSubScriptLoader)
     .loadSubScript("resource://gre/modules/Promise-backend.js", this);
--- a/toolkit/mozapps/downloads/DownloadLastDir.jsm
+++ b/toolkit/mozapps/downloads/DownloadLastDir.jsm
@@ -63,20 +63,18 @@ var observer = {
 
         cps2.removeByName(LAST_DIR_PREF, nonPrivateLoadContext);
         cps2.removeByName(LAST_DIR_PREF, privateLoadContext);
         break;
     }
   }
 };
 
-var os = Components.classes["@mozilla.org/observer-service;1"]
-                   .getService(Components.interfaces.nsIObserverService);
-os.addObserver(observer, "last-pb-context-exited", true);
-os.addObserver(observer, "browser:purge-session-history", true);
+Services.obs.addObserver(observer, "last-pb-context-exited", true);
+Services.obs.addObserver(observer, "browser:purge-session-history", true);
 
 function readLastDirPref() {
   try {
     return Services.prefs.getComplexValue(LAST_DIR_PREF, nsIFile);
   } catch (e) {
     return null;
   }
 }
--- a/toolkit/mozapps/downloads/DownloadUtils.jsm
+++ b/toolkit/mozapps/downloads/DownloadUtils.jsm
@@ -89,19 +89,17 @@ var gStr = {
 };
 
 // This lazily initializes the string bundle upon first use.
 Object.defineProperty(this, "gBundle", {
   configurable: true,
   enumerable: true,
   get() {
     delete this.gBundle;
-    return this.gBundle = Cc["@mozilla.org/intl/stringbundle;1"].
-                          getService(Ci.nsIStringBundleService).
-                          createBundle(kDownloadProperties);
+    return this.gBundle = Services.strings.createBundle(kDownloadProperties);
   },
 });
 
 // Keep track of at most this many second/lastSec pairs so that multiple calls
 // to getTimeLeft produce the same time left
 const kCachedLastMaxSize = 10;
 var gCachedLast = [];
 
@@ -380,27 +378,23 @@ this.DownloadUtils = {
    * Get the appropriate display host string for a URI string depending on if
    * the URI has an eTLD + 1, is an IP address, a local file, or other protocol
    *
    * @param aURIString
    *        The URI string to try getting an eTLD + 1, etc.
    * @return A pair: [display host for the URI string, full host name]
    */
   getURIHost: function DU_getURIHost(aURIString) {
-    let ioService = Cc["@mozilla.org/network/io-service;1"].
-                    getService(Ci.nsIIOService);
-    let eTLDService = Cc["@mozilla.org/network/effective-tld-service;1"].
-                      getService(Ci.nsIEffectiveTLDService);
     let idnService = Cc["@mozilla.org/network/idn-service;1"].
                      getService(Ci.nsIIDNService);
 
     // Get a URI that knows about its components
     let uri;
     try {
-      uri = ioService.newURI(aURIString);
+      uri = Services.io.newURI(aURIString);
     } catch (ex) {
       return ["", ""];
     }
 
     // Get the inner-most uri for schemes like jar:
     if (uri instanceof Ci.nsINestedURI)
       uri = uri.innermostURI;
 
@@ -410,17 +404,17 @@ this.DownloadUtils = {
       fullHost = uri.host;
     } catch (e) {
       fullHost = "";
     }
 
     let displayHost;
     try {
       // This might fail if it's an IP address or doesn't have more than 1 part
-      let baseDomain = eTLDService.getBaseDomain(uri);
+      let baseDomain = Services.eTLD.getBaseDomain(uri);
 
       // Convert base domain for display; ignore the isAscii out param
       displayHost = idnService.convertToDisplayIDN(baseDomain, {});
     } catch (e) {
       // Default to the host name
       displayHost = fullHost;
     }
 
@@ -550,12 +544,11 @@ function convertTimeUnitsUnits(aTime, aI
 /**
  * Private helper function to log errors to the error console and command line
  *
  * @param aMsg
  *        Error message to log or an array of strings to concat
  */
 function log(aMsg) {
   let msg = "DownloadUtils.jsm: " + (aMsg.join ? aMsg.join("") : aMsg);
-  Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).
-    logStringMessage(msg);
+  Services.console.logStringMessage(msg);
   dump(msg + "\n");
 }
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -9,16 +9,17 @@ const Ci = Components.interfaces;
 const Cr = Components.results;
 const Cu = Components.utils;
 
 // Cannot use Services.appinfo here, or else xpcshell-tests will blow up, as
 // most tests later register different nsIAppInfo implementations, which
 // wouldn't be reflected in Services.appinfo anymore, as the lazy getter
 // underlying it would have been initialized if we used it here.
 if ("@mozilla.org/xre/app-info;1" in Cc) {
+  // eslint-disable-next-line mozilla/use-services
   let runtime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
   if (runtime.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
     // Refuse to run in child processes.
     throw new Error("You cannot use the AddonManager in child processes!");
   }
 }
 
 Cu.import("resource://gre/modules/AppConstants.jsm");
@@ -2855,20 +2856,18 @@ var AddonManagerInternal = {
         }
 
         let args = {};
         args.url = url;
         args.installs = [proxy];
         args.wrappedJSObject = args;
 
         try {
-          Cc["@mozilla.org/base/telemetry;1"].
-                       getService(Ci.nsITelemetry).
-                       getHistogramById("SECURITY_UI").
-                       add(Ci.nsISecurityUITelemetry.WARNING_CONFIRM_ADDON_INSTALL);
+          Services.telemetry.getHistogramById("SECURITY_UI")
+                  .add(Ci.nsISecurityUITelemetry.WARNING_CONFIRM_ADDON_INSTALL);
           let parentWindow = null;
           if (browser) {
             // Find the outer browser
             let docShell = browser.ownerGlobal
                                   .QueryInterface(Ci.nsIInterfaceRequestor)
                                   .getInterface(Ci.nsIDocShell)
                                   .QueryInterface(Ci.nsIDocShellTreeItem);
             if (docShell.itemType == Ci.nsIDocShellTreeItem.typeContent)
--- a/toolkit/mozapps/extensions/amContentHandler.js
+++ b/toolkit/mozapps/extensions/amContentHandler.js
@@ -86,15 +86,14 @@ amContentHandler.prototype = {
     messageManager.sendAsyncMessage(MSG_INSTALL_ADDON, install);
   },
 
   classID: Components.ID("{7beb3ba8-6ec3-41b4-b67c-da89b8518922}"),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentHandler]),
 
   log(aMsg) {
     let msg = "amContentHandler.js: " + (aMsg.join ? aMsg.join("") : aMsg);
-    Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).
-      logStringMessage(msg);
+    Services.console.logStringMessage(msg);
     dump(msg + "\n");
   }
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([amContentHandler]);
--- a/toolkit/mozapps/extensions/content/extensions.js
+++ b/toolkit/mozapps/extensions/content/extensions.js
@@ -951,19 +951,17 @@ var gViewController = {
       doCommand() {
         let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].
                          createInstance(Ci.nsISupportsPRBool);
         Services.obs.notifyObservers(cancelQuit, "quit-application-requested",
                                      "restart");
         if (cancelQuit.data)
           return; // somebody canceled our quit request
 
-        let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].
-                         getService(Ci.nsIAppStartup);
-        appStartup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
+        Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
       }
     },
 
     cmd_enableCheckCompatibility: {
       isEnabled() {
         return true;
       },
       doCommand() {
@@ -3602,21 +3600,23 @@ var gDetailView = {
     var rows = document.getElementById("detail-downloads").parentNode;
 
     if (this._addon.optionsType == AddonManager.OPTIONS_TYPE_INLINE_BROWSER) {
       whenViewLoaded(async () => {
         await this._addon.startupPromise;
 
         const browserContainer = await this.createOptionsBrowser(rows);
 
-        // Make sure the browser is unloaded as soon as we change views,
-        // rather than waiting for the next detail view to load.
-        document.addEventListener("ViewChanged", function() {
-          browserContainer.remove();
-        }, {once: true});
+        if (browserContainer) {
+          // Make sure the browser is unloaded as soon as we change views,
+          // rather than waiting for the next detail view to load.
+          document.addEventListener("ViewChanged", function() {
+            browserContainer.remove();
+          }, {once: true});
+        }
 
         finish(browserContainer);
       });
     }
 
     if (aCallback)
       aCallback();
   },
@@ -3632,18 +3632,27 @@ var gDetailView = {
       let detailViewBoxObject = gDetailView.node.boxObject;
       top -= detailViewBoxObject.y;
 
       detailViewBoxObject.scrollTo(0, top);
     }
   },
 
   async createOptionsBrowser(parentNode) {
-    let stack = document.createElement("stack");
-    stack.setAttribute("id", "addon-options-prompts-stack");
+    const containerId = "addon-options-prompts-stack";
+
+    let stack = document.getElementById(containerId);
+
+    if (stack) {
+      // Remove the existent options container (if any).
+      stack.remove();
+    }
+
+    stack = document.createElement("stack");
+    stack.setAttribute("id", containerId);
 
     let browser = document.createElement("browser");
     browser.setAttribute("type", "content");
     browser.setAttribute("disableglobalhistory", "true");
     browser.setAttribute("id", "addon-options");
     browser.setAttribute("class", "inline-options-browser");
     browser.setAttribute("transparent", "true");
     browser.setAttribute("forcemessagemanager", "true");
@@ -3651,17 +3660,17 @@ var gDetailView = {
 
     // The outer about:addons document listens for key presses to focus
     // the search box when / is pressed.  But if we're focused inside an
     // options page, don't let those keypresses steal focus.
     browser.addEventListener("keypress", event => {
       event.stopPropagation();
     });
 
-    let {optionsURL} = this._addon;
+    let {optionsURL, optionsBrowserStyle} = this._addon;
     let remote = !E10SUtils.canLoadURIInProcess(optionsURL, Services.appinfo.PROCESS_TYPE_DEFAULT);
 
     let readyPromise;
     if (remote) {
       browser.setAttribute("remote", "true");
       browser.setAttribute("remoteType", E10SUtils.EXTENSION_REMOTE_TYPE);
       readyPromise = promiseEvent("XULFrameLoaderCreated", browser);
     } else {
@@ -3670,40 +3679,59 @@ var gDetailView = {
 
     stack.appendChild(browser);
     parentNode.appendChild(stack);
 
     // Force bindings to apply synchronously.
     browser.clientTop;
 
     await readyPromise;
+
+    if (!browser.messageManager) {
+      // If the browser.messageManager is undefined, the browser element has been
+      // removed from the document in the meantime (e.g. due to a rapid sequence
+      // of addon reload), ensure that the stack is also removed and return null.
+      stack.remove();
+      return null;
+    }
+
     ExtensionParent.apiManager.emit("extension-browser-inserted", browser);
 
     return new Promise(resolve => {
       let messageListener = {
         receiveMessage({name, data}) {
           if (name === "Extension:BrowserResized")
             browser.style.height = `${data.height}px`;
           else if (name === "Extension:BrowserContentLoaded")
             resolve(stack);
         },
       };
 
       let mm = browser.messageManager;
+
+      if (!mm) {
+        // If the browser.messageManager is undefined, the browser element has been
+        // removed from the document in the meantime (e.g. due to a rapid sequence
+        // of addon reload), ensure that the stack is also removed and return null.
+        stack.remove();
+        resolve(null);
+        return;
+      }
+
       mm.loadFrameScript("chrome://extensions/content/ext-browser-content.js",
                          false);
       mm.addMessageListener("Extension:BrowserContentLoaded", messageListener);
       mm.addMessageListener("Extension:BrowserResized", messageListener);
 
       let browserOptions = {
         fixedWidth: true,
         isInline: true,
       };
 
-      if (this._addon.optionsBrowserStyle) {
+      if (optionsBrowserStyle) {
         browserOptions.stylesheets = extensionStylesheets;
       }
 
       mm.sendAsyncMessage("Extension:InitBrowser", browserOptions);
 
       browser.loadURI(optionsURL);
     });
   },
--- a/toolkit/mozapps/extensions/content/newaddon.js
+++ b/toolkit/mozapps/extensions/content/newaddon.js
@@ -119,19 +119,17 @@ function restartClicked() {
                    createInstance(Ci.nsISupportsPRBool);
   Services.obs.notifyObservers(cancelQuit, "quit-application-requested",
                                "restart");
   if (cancelQuit.data)
     return; // somebody canceled our quit request
 
   window.close();
 
-  let appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"].
-                   getService(Components.interfaces.nsIAppStartup);
-  appStartup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
+  Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
 }
 
 function cancelClicked() {
   gAddon.userDisabled = true;
   AddonManager.addAddonListener(EnableListener);
 
   document.getElementById("allow").disabled = false;
   document.getElementById("buttonDeck").selectedPanel = document.getElementById("continuePanel");
--- a/toolkit/mozapps/extensions/content/xpinstallConfirm.js
+++ b/toolkit/mozapps/extensions/content/xpinstallConfirm.js
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 var XPInstallConfirm = {};
 
 XPInstallConfirm.init = function() {
   Components.utils.import("resource://gre/modules/AddonManager.jsm");
+  Components.utils.import("resource://gre/modules/Services.jsm");
 
   var _installCountdown;
   var _installCountdownInterval;
   var _focused;
   var _timeout;
 
   // Default to cancelling the install when the window unloads
   XPInstallConfirm._installOK = false;
@@ -25,19 +26,17 @@ XPInstallConfirm.init = function() {
   // the window
   if (args.installs.every(i => i.state != AddonManager.STATE_DOWNLOADED)) {
     window.close();
     return;
   }
 
   var _installCountdownLength = 5;
   try {
-    var prefs = Components.classes["@mozilla.org/preferences-service;1"]
-                          .getService(Components.interfaces.nsIPrefBranch);
-    var delay_in_milliseconds = prefs.getIntPref("security.dialog_enable_delay");
+    var delay_in_milliseconds = Services.prefs.getIntPref("security.dialog_enable_delay");
     _installCountdownLength = Math.round(delay_in_milliseconds / 500);
   } catch (ex) { }
 
   var itemList = document.getElementById("itemList");
 
   let installMap = new WeakMap();
   let installListener = {
     onDownloadCancelled(install) {
@@ -162,20 +161,18 @@ XPInstallConfirm.init = function() {
 
     okButton.disabled = true;
     setWidgetsAfterFocus();
   } else
     okButton.label = bundle.getString("installButtonLabel");
 };
 
 XPInstallConfirm.onOK = function() {
-  Components.classes["@mozilla.org/base/telemetry;1"].
-    getService(Components.interfaces.nsITelemetry).
-    getHistogramById("SECURITY_UI").
-    add(Components.interfaces.nsISecurityUITelemetry.WARNING_CONFIRM_ADDON_INSTALL_CLICK_THROUGH);
+  Services.telemetry.getHistogramById("SECURITY_UI")
+    .add(Components.interfaces.nsISecurityUITelemetry.WARNING_CONFIRM_ADDON_INSTALL_CLICK_THROUGH);
   // Perform the install or cancel after the window has unloaded
   XPInstallConfirm._installOK = true;
   return true;
 };
 
 XPInstallConfirm.onCancel = function() {
   // Perform the install or cancel after the window has unloaded
   XPInstallConfirm._installOK = false;
--- a/toolkit/mozapps/extensions/internal/AddonRepository.jsm
+++ b/toolkit/mozapps/extensions/internal/AddonRepository.jsm
@@ -767,19 +767,17 @@ this.AddonRepository = {
 
     let pref = PREF_GETADDONS_BYIDS;
 
     if (aSendPerformance) {
       let type = Services.prefs.getPrefType(PREF_GETADDONS_BYIDS_PERFORMANCE);
       if (type == Services.prefs.PREF_STRING) {
         pref = PREF_GETADDONS_BYIDS_PERFORMANCE;
 
-        let startupInfo = Cc["@mozilla.org/toolkit/app-startup;1"].
-                          getService(Ci.nsIAppStartup).
-                          getStartupInfo();
+        let startupInfo = Services.startup.getStartupInfo();
 
         params.TIME_MAIN = "";
         params.TIME_FIRST_PAINT = "";
         params.TIME_SESSION_RESTORED = "";
         if (startupInfo.process) {
           if (startupInfo.main) {
             params.TIME_MAIN = startupInfo.main - startupInfo.process;
           }
--- a/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm
+++ b/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm
@@ -847,24 +847,21 @@ this.AddonUpdateChecker = {
   getNewestCompatibleUpdate(aUpdates, aAppVersion, aPlatformVersion,
                                       aIgnoreMaxVersion, aIgnoreStrictCompat,
                                       aCompatOverrides) {
     if (!aAppVersion)
       aAppVersion = Services.appinfo.version;
     if (!aPlatformVersion)
       aPlatformVersion = Services.appinfo.platformVersion;
 
-    let blocklist = Cc["@mozilla.org/extensions/blocklist;1"].
-                    getService(Ci.nsIBlocklistService);
-
     let newest = null;
     for (let update of aUpdates) {
       if (!update.updateURL)
         continue;
-      let state = blocklist.getAddonBlocklistState(update, aAppVersion, aPlatformVersion);
+      let state = Services.blocklist.getAddonBlocklistState(update, aAppVersion, aPlatformVersion);
       if (state != Ci.nsIBlocklistService.STATE_NOT_BLOCKED)
         continue;
       if ((newest == null || (Services.vc.compare(newest.version, update.version) < 0)) &&
           matchesVersions(update, aAppVersion, aPlatformVersion,
                           aIgnoreMaxVersion, aIgnoreStrictCompat,
                           aCompatOverrides)) {
         newest = update;
       }
--- a/toolkit/mozapps/extensions/internal/PluginProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/PluginProvider.jsm
@@ -376,26 +376,22 @@ PluginWrapper.prototype = {
       AddonManagerPrivate.callAddonListeners("onPropertyChanged", this, ["userDisabled"]);
     }
 
     return val;
   },
 
   get blocklistState() {
     let { tags: [tag] } = pluginFor(this);
-    let bs = Cc["@mozilla.org/extensions/blocklist;1"].
-             getService(Ci.nsIBlocklistService);
-    return bs.getPluginBlocklistState(tag);
+    return Services.blocklist.getPluginBlocklistState(tag);
   },
 
   get blocklistURL() {
     let { tags: [tag] } = pluginFor(this);
-    let bs = Cc["@mozilla.org/extensions/blocklist;1"].
-             getService(Ci.nsIBlocklistService);
-    return bs.getPluginBlocklistURL(tag);
+    return Services.blocklist.getPluginBlocklistURL(tag);
   },
 
   get size() {
     function getDirectorySize(aFile) {
       let size = 0;
       let entries = aFile.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
       let entry;
       while ((entry = entries.nextFile)) {
--- a/toolkit/mozapps/extensions/nsBlocklistService.js
+++ b/toolkit/mozapps/extensions/nsBlocklistService.js
@@ -75,26 +75,22 @@ var gBlocklistLevel = DEFAULT_LEVEL;
 XPCOMUtils.defineLazyServiceGetter(this, "gConsole",
                                    "@mozilla.org/consoleservice;1",
                                    "nsIConsoleService");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gVersionChecker",
                                    "@mozilla.org/xpcom/version-comparator;1",
                                    "nsIVersionComparator");
 
-XPCOMUtils.defineLazyGetter(this, "gPref", function() {
-  return Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService).
-         QueryInterface(Ci.nsIPrefBranch);
-});
-
 // From appinfo in Services.jsm. It is not possible to use the one in
 // Services.jsm since it will not successfully QueryInterface nsIXULAppInfo in
 // xpcshell tests due to other code calling Services.appinfo before the
 // nsIXULAppInfo is created by the tests.
 XPCOMUtils.defineLazyGetter(this, "gApp", function() {
+  // eslint-disable-next-line mozilla/use-services
   let appinfo = Cc["@mozilla.org/xre/app-info;1"]
                   .getService(Ci.nsIXULRuntime);
   try {
     appinfo.QueryInterface(Ci.nsIXULAppInfo);
   } catch (ex) {
     // Not all applications implement nsIXULAppInfo (e.g. xpcshell doesn't).
     if (!(ex instanceof Components.Exception) ||
         ex.result != Cr.NS_NOINTERFACE)
@@ -120,27 +116,25 @@ XPCOMUtils.defineLazyGetter(this, "gABI"
     if (macutils.isUniversalBinary)
       abi += "-u-" + macutils.architecturesInBinary;
   }
   return abi;
 });
 
 XPCOMUtils.defineLazyGetter(this, "gOSVersion", function() {
   let osVersion;
-  let sysInfo = Cc["@mozilla.org/system-info;1"].
-                getService(Ci.nsIPropertyBag2);
   try {
-    osVersion = sysInfo.getProperty("name") + " " + sysInfo.getProperty("version");
+    osVersion = Services.sysinfo.getProperty("name") + " " + Services.sysinfo.getProperty("version");
   } catch (e) {
     LOG("BlockList Global gOSVersion: OS Version unknown.");
   }
 
   if (osVersion) {
     try {
-      osVersion += " (" + sysInfo.getProperty("secondaryLibrary") + ")";
+      osVersion += " (" + Services.sysinfo.getProperty("secondaryLibrary") + ")";
     } catch (e) {
       // Not all platforms have a secondary widget library, so an error is nothing to worry about.
     }
     osVersion = encodeURIComponent(osVersion);
   }
   return osVersion;
 });
 
@@ -172,50 +166,34 @@ function LOG(string) {
  * @param   defaultValue
  *          The default value to return in the event the preference has
  *          no setting
  * @returns The value of the preference, or undefined if there was no
  *          user or default value.
  */
 function getPref(func, preference, defaultValue) {
   try {
-    return gPref[func](preference);
+    return Services.prefs[func](preference);
   } catch (e) {
   }
   return defaultValue;
 }
 
-/**
- * Constructs a URI to a spec.
- * @param   spec
- *          The spec to construct a URI to
- * @returns The nsIURI constructed.
- */
-function newURI(spec) {
-  var ioServ = Cc["@mozilla.org/network/io-service;1"].
-               getService(Ci.nsIIOService);
-  return ioServ.newURI(spec);
-}
-
 // Restarts the application checking in with observers first
 function restartApp() {
   // Notify all windows that an application quit has been requested.
-  var os = Cc["@mozilla.org/observer-service;1"].
-           getService(Ci.nsIObserverService);
   var cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].
                    createInstance(Ci.nsISupportsPRBool);
-  os.notifyObservers(cancelQuit, "quit-application-requested");
+  Services.obs.notifyObservers(cancelQuit, "quit-application-requested");
 
   // Something aborted the quit process.
   if (cancelQuit.data)
     return;
 
-  var as = Cc["@mozilla.org/toolkit/app-startup;1"].
-           getService(Ci.nsIAppStartup);
-  as.quit(Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit);
+  Services.startup.quit(Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit);
 }
 
 /**
  * Checks whether this blocklist element is valid for the current OS and ABI.
  * If the element has an "os" attribute then the current OS must appear in
  * its comma separated list for the element to be valid. Similarly for the
  * xpcomabi attribute.
  */
@@ -241,17 +219,17 @@ function matchesOSABI(blocklistElement) 
  * exists in nsHttpHandler.cpp when building the UA string.
  */
 function getLocale() {
   return Services.locale.getRequestedLocale();
 }
 
 /* Get the distribution pref values, from defaults only */
 function getDistributionPrefValue(aPrefName) {
-  return gPref.getDefaultBranch(null).getCharPref(aPrefName, "default");
+  return Services.prefs.getDefaultBranch(null).getCharPref(aPrefName, "default");
 }
 
 /**
  * Parse a string representation of a regular expression. Needed because we
  * use the /pattern/flags form (because it's detectable), which is only
  * supported as a literal in JS.
  *
  * @param  aStr
@@ -274,18 +252,18 @@ function parseRegExp(aStr) {
 
 function Blocklist() {
   Services.obs.addObserver(this, "xpcom-shutdown");
   Services.obs.addObserver(this, "sessionstore-windows-restored");
   gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false);
   gBlocklistEnabled = getPref("getBoolPref", PREF_BLOCKLIST_ENABLED, true);
   gBlocklistLevel = Math.min(getPref("getIntPref", PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL),
                                      MAX_BLOCK_LEVEL);
-  gPref.addObserver("extensions.blocklist.", this);
-  gPref.addObserver(PREF_EM_LOGGING_ENABLED, this);
+  Services.prefs.addObserver("extensions.blocklist.", this);
+  Services.prefs.addObserver(PREF_EM_LOGGING_ENABLED, this);
   this.wrappedJSObject = this;
   // requests from child processes come in here, see receiveMessage.
   Services.ppmm.addMessageListener("Blocklist:getPluginBlocklistState", this);
   Services.ppmm.addMessageListener("Blocklist:content-blocklist-updated", this);
 }
 
 Blocklist.prototype = {
   /**
@@ -306,18 +284,18 @@ Blocklist.prototype = {
   _addonEntries: null,
   _gfxEntries: null,
   _pluginEntries: null,
 
   shutdown() {
     Services.obs.removeObserver(this, "xpcom-shutdown");
     Services.ppmm.removeMessageListener("Blocklist:getPluginBlocklistState", this);
     Services.ppmm.removeMessageListener("Blocklist:content-blocklist-updated", this);
-    gPref.removeObserver("extensions.blocklist.", this);
-    gPref.removeObserver(PREF_EM_LOGGING_ENABLED, this);
+    Services.prefs.removeObserver("extensions.blocklist.", this);
+    Services.prefs.removeObserver(PREF_EM_LOGGING_ENABLED, this);
   },
 
   observe(aSubject, aTopic, aData) {
     switch (aTopic) {
     case "xpcom-shutdown":
       this.shutdown();
       break;
     case "nsPref:changed":
@@ -522,17 +500,17 @@ Blocklist.prototype = {
     return url;
   },
 
   notify(aTimer) {
     if (!gBlocklistEnabled)
       return;
 
     try {
-      var dsURI = gPref.getCharPref(PREF_BLOCKLIST_URL);
+      var dsURI = Services.prefs.getCharPref(PREF_BLOCKLIST_URL);
     } catch (e) {
       LOG("Blocklist::notify: The " + PREF_BLOCKLIST_URL + " preference" +
           " is missing!");
       return;
     }
 
     var pingCountVersion = getPref("getIntPref", PREF_BLOCKLIST_PINGCOUNTVERSION, 0);
     var pingCountTotal = getPref("getIntPref", PREF_BLOCKLIST_PINGCOUNTTOTAL, 1);
@@ -589,32 +567,32 @@ Blocklist.prototype = {
     // so this code doesn't bother trying to do the "right thing" here.
     if (pingCountVersion != "invalid") {
       pingCountVersion++;
       if (pingCountVersion > 2147483647) {
         // Rollover to -1 if the value is greater than what is support by an
         // integer preference. The -1 indicates that the counter has been reset.
         pingCountVersion = -1;
       }
-      gPref.setIntPref(PREF_BLOCKLIST_PINGCOUNTVERSION, pingCountVersion);
+      Services.prefs.setIntPref(PREF_BLOCKLIST_PINGCOUNTVERSION, pingCountVersion);
     }
 
     if (pingCountTotal != "invalid") {
       pingCountTotal++;
       if (pingCountTotal > 2147483647) {
         // Rollover to 1 if the value is greater than what is support by an
         // integer preference.
         pingCountTotal = -1;
       }
-      gPref.setIntPref(PREF_BLOCKLIST_PINGCOUNTTOTAL, pingCountTotal);
+      Services.prefs.setIntPref(PREF_BLOCKLIST_PINGCOUNTTOTAL, pingCountTotal);
     }
 
     // Verify that the URI is valid
     try {
-      var uri = newURI(dsURI);
+      var uri = Services.io.newURI(dsURI);
     } catch (e) {
       LOG("Blocklist::notify: There was an error creating the blocklist URI\r\n" +
           "for: " + dsURI + ", error: " + e);
       return;
     }
 
     LOG("Blocklist::notify: Requesting " + uri.spec);
     let request = new ServiceRequest();
@@ -629,17 +607,17 @@ Blocklist.prototype = {
 
     // When the blocklist loads we need to compare it to the current copy so
     // make sure we have loaded it.
     if (!this._isBlocklistLoaded())
       this._loadBlocklist();
 
     // If blocklist update via Kinto is enabled, poll for changes and sync.
     // Currently certificates blocklist relies on it by default.
-    if (gPref.getBoolPref(PREF_BLOCKLIST_UPDATE_ENABLED)) {
+    if (Services.prefs.getBoolPref(PREF_BLOCKLIST_UPDATE_ENABLED)) {
       BlocklistUpdater.checkVersions().catch(() => {
         // Bug 1254099 - Telemetry (success or errors) will be collected during this process.
       });
     }
   },
 
   async onXMLLoad(aEvent) {
     let request = aEvent.target;
@@ -1276,17 +1254,17 @@ Blocklist.prototype = {
   },
 
   _blocklistUpdated(oldAddonEntries, oldPluginEntries) {
     var addonList = [];
 
     // A helper function that reverts the prefs passed to default values.
     function resetPrefs(prefs) {
       for (let pref of prefs)
-        gPref.clearUserPref(pref);
+        Services.prefs.clearUserPref(pref);
     }
     const types = ["extension", "theme", "locale", "dictionary", "service"];
     AddonManager.getAddonsByTypes(types, addons => {
       for (let addon of addons) {
         let oldState = addon.blocklistState;
         if (addon.updateBlocklistState) {
           addon.updateBlocklistState(false);
         } else if (oldAddonEntries) {
--- a/toolkit/mozapps/extensions/test/browser/browser-common.ini
+++ b/toolkit/mozapps/extensions/test/browser/browser-common.ini
@@ -52,8 +52,10 @@ skip-if = buildapp == 'mulet'
 [browser_pluginprefs.js]
 [browser_pluginprefs_is_not_disabled.js]
 skip-if = buildapp == 'mulet'
 [browser_CTP_plugins.js]
 tags = blocklist
 skip-if = buildapp == 'mulet'
 [browser_webext_options.js]
 tags = webextensions
+[browser_webext_options_addon_reload.js]
+tags = webextensions
\ No newline at end of file
--- a/toolkit/mozapps/extensions/test/browser/browser_addonrepository_performance.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_addonrepository_performance.js
@@ -3,17 +3,16 @@
  */
 
 // Tests that the metadata request includes startup time measurements
 
 var tmp = {};
 Components.utils.import("resource://gre/modules/addons/AddonRepository.jsm", tmp);
 var AddonRepository = tmp.AddonRepository;
 
-var gTelemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry);
 var gProvider;
 
 function parseParams(aQuery) {
   let params = {};
 
   for (let param of aQuery.split("&")) {
     let [key, value] = param.split("=");
     params[key] = value;
@@ -38,18 +37,18 @@ function test() {
     let url = aSubject.URI.QueryInterface(Ci.nsIURL);
     if (url.filePath != "/extensions-dummy/metadata") {
       return;
     }
     info(url.query);
 
     // Check if we encountered telemetry errors and turn the tests for which
     // we don't have valid data into known failures.
-    let snapshot = gTelemetry.getHistogramById("STARTUP_MEASUREMENT_ERRORS")
-                             .snapshot();
+    let snapshot = Services.telemetry.getHistogramById("STARTUP_MEASUREMENT_ERRORS")
+                           .snapshot();
 
     let tProcessValid = (snapshot.counts[0] == 0);
     let tMainValid = tProcessValid && (snapshot.counts[2] == 0);
     let tFirstPaintValid = tProcessValid && (snapshot.counts[5] == 0);
     let tSessionRestoredValid = tProcessValid && (snapshot.counts[6] == 0);
 
     let params = parseParams(url.query);
 
@@ -90,9 +89,8 @@ function test() {
 
   AddonRepository._beginGetAddons(["test1@tests.mozilla.org"], {
     searchFailed() {
       ok(gSeenRequest, "Should have seen metadata request");
       finish();
     }
   }, true);
 }
-
--- a/toolkit/mozapps/extensions/test/browser/browser_bug567127.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_bug567127.js
@@ -59,30 +59,26 @@ function checkInstallConfirmation(...url
         windows.delete(window);
 
         if (windows.size > 0) {
           return;
         }
 
         is(urls.length, 0, "Saw install dialogs for all expected urls");
 
-        let wm = Cc["@mozilla.org/appshell/window-mediator;1"]
-                             .getService(Ci.nsIWindowMediator);
-        wm.removeListener(listener);
+        Services.wm.removeListener(listener);
 
         is(notificationCount, nurls, `Saw ${nurls} addon-install-started notifications`);
         Services.obs.removeObserver(observer, "addon-install-started");
 
         resolve();
       }
     };
 
-    let wm = Cc["@mozilla.org/appshell/window-mediator;1"]
-                         .getService(Ci.nsIWindowMediator);
-    wm.addListener(listener);
+    Services.wm.addListener(listener);
   });
 }
 
 add_task(async function test_install_from_file() {
   gManagerWindow = await open_manager("addons://list/extension");
 
   var filePaths = [
                    get_addon_file_url("browser_bug567127_1.xpi"),
--- a/toolkit/mozapps/extensions/test/browser/browser_dragdrop.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_dragdrop.js
@@ -7,19 +7,17 @@
 // in its own window.
 // Tests are only simulations of the drag and drop events, we cannot really do
 // this automatically.
 
 // Instead of loading EventUtils.js into the test scope in browser-test.js for all tests,
 // we only need EventUtils.js for a few files which is why we are using loadSubScript.
 var gManagerWindow;
 var EventUtils = {};
-this._scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
-                     getService(Ci.mozIJSSubScriptLoader);
-this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
+Services.scriptloader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
 
 function checkInstallConfirmation(...urls) {
   let nurls = urls.length;
 
   let notificationCount = 0;
   let observer = {
     observe(aSubject, aTopic, aData) {
       var installInfo = aSubject.wrappedJSObject;
@@ -63,30 +61,26 @@ function checkInstallConfirmation(...url
         return;
       }
       windows.delete(window);
 
       if (windows.size > 0 || urls.length > 0) {
         return;
       }
 
-      let wm = Cc["@mozilla.org/appshell/window-mediator;1"]
-                           .getService(Ci.nsIWindowMediator);
-      wm.removeListener(listener);
+      Services.wm.removeListener(listener);
 
       is(notificationCount, nurls, `Saw ${nurls} addon-install-started notifications`);
       Services.obs.removeObserver(observer, "addon-install-started");
 
       executeSoon(run_next_test);
     }
   };
 
-  let wm = Cc["@mozilla.org/appshell/window-mediator;1"]
-                       .getService(Ci.nsIWindowMediator);
-  wm.addListener(listener);
+  Services.wm.addListener(listener);
 }
 
 function test() {
   waitForExplicitFinish();
 
   open_manager("addons://list/extension", function(aWindow) {
     gManagerWindow = aWindow;
     run_next_test();
--- a/toolkit/mozapps/extensions/test/browser/browser_list.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_list.js
@@ -734,48 +734,45 @@ add_task(async function() {
     return false;
   }
 
   // Ignore the OSX full keyboard access setting
   Services.prefs.setBoolPref("accessibility.tabfocus_applies_to_xul", false);
 
   let items = get_test_items();
 
-  var fm = Cc["@mozilla.org/focus-manager;1"].
-           getService(Ci.nsIFocusManager);
-
   let addon = items["Test add-on 6"];
   addon.parentNode.ensureElementIsVisible(addon);
   EventUtils.synthesizeMouseAtCenter(addon, { }, gManagerWindow);
-  is(fm.focusedElement, addon.parentNode, "Focus should have moved to the list");
+  is(Services.focus.focusedElement, addon.parentNode, "Focus should have moved to the list");
 
   EventUtils.synthesizeKey("VK_TAB", { }, gManagerWindow);
-  is(fm.focusedElement, get_node(addon, "details-btn"), "Focus should have moved to the more button");
+  is(Services.focus.focusedElement, get_node(addon, "details-btn"), "Focus should have moved to the more button");
 
   EventUtils.synthesizeKey("VK_TAB", { }, gManagerWindow);
-  is(fm.focusedElement, get_node(addon, "disable-btn"), "Focus should have moved to the disable button");
+  is(Services.focus.focusedElement, get_node(addon, "disable-btn"), "Focus should have moved to the disable button");
 
   EventUtils.synthesizeKey("VK_TAB", { }, gManagerWindow);
-  is(fm.focusedElement, get_node(addon, "remove-btn"), "Focus should have moved to the remove button");
+  is(Services.focus.focusedElement, get_node(addon, "remove-btn"), "Focus should have moved to the remove button");
 
   EventUtils.synthesizeKey("VK_TAB", { }, gManagerWindow);
-  ok(!is_node_in_list(fm.focusedElement), "Focus should be outside the list");
+  ok(!is_node_in_list(Services.focus.focusedElement), "Focus should be outside the list");
 
   EventUtils.synthesizeKey("VK_TAB", { shiftKey: true }, gManagerWindow);
-  is(fm.focusedElement, get_node(addon, "remove-btn"), "Focus should have moved to the remove button");
+  is(Services.focus.focusedElement, get_node(addon, "remove-btn"), "Focus should have moved to the remove button");
 
   EventUtils.synthesizeKey("VK_TAB", { shiftKey: true }, gManagerWindow);
   EventUtils.synthesizeKey("VK_TAB", { shiftKey: true }, gManagerWindow);
-  is(fm.focusedElement, get_node(addon, "details-btn"), "Focus should have moved to the more button");
+  is(Services.focus.focusedElement, get_node(addon, "details-btn"), "Focus should have moved to the more button");
 
   EventUtils.synthesizeKey("VK_TAB", { shiftKey: true }, gManagerWindow);
-  is(fm.focusedElement, addon.parentNode, "Focus should have moved to the list");
+  is(Services.focus.focusedElement, addon.parentNode, "Focus should have moved to the list");
 
   EventUtils.synthesizeKey("VK_TAB", { shiftKey: true }, gManagerWindow);
-  ok(!is_node_in_list(fm.focusedElement), "Focus should be outside the list");
+  ok(!is_node_in_list(Services.focus.focusedElement), "Focus should be outside the list");
 
   try {
     Services.prefs.clearUserPref("accessibility.tabfocus_applies_to_xul");
   } catch (e) { }
 });
 
 
 add_task(async function() {
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/browser/browser_webext_options_addon_reload.js
@@ -0,0 +1,105 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+const {AddonTestUtils} = Cu.import("resource://testing-common/AddonTestUtils.jsm", {});
+const {ExtensionParent} = Cu.import("resource://gre/modules/ExtensionParent.jsm", {});
+
+// This test function helps to detect when an addon options browser have been inserted
+// in the about:addons page.
+function waitOptionsBrowserInserted() {
+  return new Promise(resolve => {
+    async function listener(eventName, browser) {
+      // wait for a webextension XUL browser element that is owned by the "about:addons" page.
+      if (browser.ownerDocument.location.href == "about:addons") {
+        ExtensionParent.apiManager.off("extension-browser-inserted", listener);
+
+        resolve(browser);
+      }
+    }
+    ExtensionParent.apiManager.on("extension-browser-inserted", listener);
+  });
+}
+
+add_task(async function test_options_on_addon_reload() {
+  const ID = "@test-options-on-addon-reload";
+
+  function backgroundScript() {
+    const {browser} = window;
+    browser.runtime.openOptionsPage();
+  }
+
+  let extensionDefinition = {
+    useAddonManager: "temporary",
+
+    manifest: {
+      "options_ui": {
+        "page": "options.html",
+      },
+      "applications": {
+        "gecko": {
+          "id": ID,
+        },
+      },
+    },
+    files: {
+      "options.html": `<!DOCTYPE html>
+        <html>
+          <head>
+            <meta charset="utf-8">
+          </head>
+          <body>
+            Extension Options UI
+          </body>
+        </html>`,
+    },
+    background: backgroundScript,
+  };
+
+  await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:addons");
+
+  const extension = ExtensionTestUtils.loadExtension(extensionDefinition);
+
+  const onceOptionsBrowserInserted = waitOptionsBrowserInserted();
+
+  await extension.startup();
+
+  info("Wait the options_ui page XUL browser to be created");
+  await onceOptionsBrowserInserted;
+
+  const aboutAddonsDocument = gBrowser.selectedBrowser.contentDocument;
+
+  Assert.equal(aboutAddonsDocument.location.href, "about:addons",
+               "The about:addons page is the currently selected tab");
+
+  const optionsBrowsers = aboutAddonsDocument.querySelectorAll("#addon-options");
+  Assert.equal(optionsBrowsers.length, 1, "Got a single XUL browser for the addon options_ui page");
+
+  // Reload the addon five times in a row, and then check that there is still one addon options browser.
+
+  let addon = await AddonManager.getAddonByID(ID);
+
+  for (let i = 0; i < 5; i++) {
+    const onceOptionsReloaded = Promise.all([
+      // Reloading the addon currently prevents the extension.awaitMessage test helper to be able
+      // to receive test messages from the reloaded extension, this test function helps to wait
+      // the extension has been restarted on addon reload.
+      AddonTestUtils.promiseWebExtensionStartup(),
+      TestUtils.topicObserved(AddonManager.OPTIONS_NOTIFICATION_DISPLAYED,
+                              (subject, data) => data == extension.id),
+    ]);
+
+    await addon.reload();
+
+    info("Wait the new options_ui page XUL browser to be created");
+    await onceOptionsReloaded;
+
+    let optionsBrowsers = aboutAddonsDocument.querySelectorAll("#addon-options");
+
+    Assert.equal(optionsBrowsers.length, 1, "Got a single XUL browser for the addon options_ui page");
+  }
+
+  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+  await extension.unload();
+});
--- a/toolkit/mozapps/extensions/test/mochitest/test_bug687194.html
+++ b/toolkit/mozapps/extensions/test/mochitest/test_bug687194.html
@@ -15,28 +15,28 @@
     SimpleTest.waitForExplicitFinish();
 
     const childFrameURL =
       "data:text/html,<!DOCTYPE HTML><html><body></body></html>";
 
     function childFrameScript() {
       "use strict";
 
-      var ios =
-          Components.classes["@mozilla.org/network/io-service;1"]
-                    .getService(Components.interfaces.nsIIOService);
-      /* global Ci, addMessageListener */
+      /* global Ci, Cu, addMessageListener */
+
+      Cu.import("resource://gre/modules/Services.jsm");
+
       let cr =
           Components.classes["@mozilla.org/chrome/chrome-registry;1"]
                     .getService(Ci.nsIXULChromeRegistry);
       addMessageListener("test687194:resolveChromeURI", function(message) {
         let result;
         let threw = false;
         try {
-            let uri = ios.newURI(message.data.URI);
+            let uri = Services.io.newURI(message.data.URI);
             result = cr.convertChromeURL(uri).spec;
         } catch (e) {
             threw = true;
             result = "EXCEPTION: " + e;
         }
 
         message.target.sendAsyncMessage("test687194:resolveChromeURI:Answer",
                                         { threw, result });
--- a/toolkit/mozapps/extensions/test/xpcshell/test_LightweightThemeManager.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_LightweightThemeManager.js
@@ -375,56 +375,54 @@ function run_test() {
   } catch (e) {
     // Expected exception
   }
 
   do_check_eq(ltm.usedThemes.length, 0);
   do_check_eq(ltm.currentTheme, null);
 
   // Force the theme into the prefs anyway
-  let prefs = Cc["@mozilla.org/preferences-service;1"].
-              getService(Ci.nsIPrefBranch);
   let themes = [data];
-  prefs.setCharPref("lightweightThemes.usedThemes", JSON.stringify(themes));
+  Services.prefs.setCharPref("lightweightThemes.usedThemes", JSON.stringify(themes));
   do_check_eq(ltm.usedThemes.length, 1);
 
   // This should silently drop the bad theme.
   ltm.currentTheme = dummy();
   do_check_eq(ltm.usedThemes.length, 1);
   ltm.forgetUsedTheme(ltm.currentTheme.id);
   do_check_eq(ltm.usedThemes.length, 0);
   do_check_eq(ltm.currentTheme, null);
 
   // Add one broken and some working.
   themes = [data, dummy("x1"), dummy("x2")];
-  prefs.setCharPref("lightweightThemes.usedThemes", JSON.stringify(themes));
+  Services.prefs.setCharPref("lightweightThemes.usedThemes", JSON.stringify(themes));
   do_check_eq(ltm.usedThemes.length, 3);
 
   // Switching to an existing theme should drop the bad theme.
   ltm.currentTheme = ltm.getUsedTheme("x1");
   do_check_eq(ltm.usedThemes.length, 2);
   ltm.forgetUsedTheme("x1");
   ltm.forgetUsedTheme("x2");
   do_check_eq(ltm.usedThemes.length, 0);
   do_check_eq(ltm.currentTheme, null);
 
-  prefs.setCharPref("lightweightThemes.usedThemes", JSON.stringify(themes));
+  Services.prefs.setCharPref("lightweightThemes.usedThemes", JSON.stringify(themes));
   do_check_eq(ltm.usedThemes.length, 3);
 
   // Forgetting an existing theme should drop the bad theme.
   ltm.forgetUsedTheme("x1");
   do_check_eq(ltm.usedThemes.length, 1);
   ltm.forgetUsedTheme("x2");
   do_check_eq(ltm.usedThemes.length, 0);
   do_check_eq(ltm.currentTheme, null);
 
   // Test whether a JSON set with setCharPref can be retrieved with usedThemes
   ltm.currentTheme = dummy("x0");
   ltm.currentTheme = dummy("x1");
-  prefs.setCharPref("lightweightThemes.usedThemes", JSON.stringify(ltm.usedThemes));
+  Services.prefs.setCharPref("lightweightThemes.usedThemes", JSON.stringify(ltm.usedThemes));
   do_check_eq(ltm.usedThemes.length, 2);
   do_check_eq(ltm.currentTheme.id, "x1");
   do_check_eq(ltm.usedThemes[1].id, "x0");
   do_check_eq(ltm.usedThemes[0].id, "x1");
 
   ltm.forgetUsedTheme("x0");
   do_check_eq(ltm.usedThemes.length, 1);
   do_check_neq(ltm.currentTheme, null);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_prefs.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_prefs.js
@@ -4,21 +4,16 @@
 
 // Tests resetting of preferences in blocklist entry when an add-on is blocked.
 // See bug 802434.
 
 var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 const URI_EXTENSION_BLOCKLIST_DIALOG = "chrome://mozapps/content/extensions/blocklist.xul";
 
-XPCOMUtils.defineLazyGetter(this, "gPref", function bls_gPref() {
-  return Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService).
-         QueryInterface(Ci.nsIPrefBranch);
-});
-
 Cu.import("resource://testing-common/httpd.js");
 Cu.import("resource://testing-common/MockRegistrar.jsm");
 var testserver = new HttpServer();
 testserver.start(-1);
 gPort = testserver.identity.primaryPort;
 
 // register static files with server and interpolate port numbers in them
 mapFile("/data/test_blocklist_prefs_1.xml", testserver);
@@ -99,33 +94,33 @@ function run_test() {
     targetApplications: [{
       id: "xpcshell@tests.mozilla.org",
       minVersion: "1",
       maxVersion: "3"
     }]
   }, profileDir);
 
   // Pre-set the preferences that we expect to get reset.
-  gPref.setIntPref("test.blocklist.pref1", 15);
-  gPref.setIntPref("test.blocklist.pref2", 15);
-  gPref.setBoolPref("test.blocklist.pref3", true);
-  gPref.setBoolPref("test.blocklist.pref4", true);
+  Services.prefs.setIntPref("test.blocklist.pref1", 15);
+  Services.prefs.setIntPref("test.blocklist.pref2", 15);
+  Services.prefs.setBoolPref("test.blocklist.pref3", true);
+  Services.prefs.setBoolPref("test.blocklist.pref4", true);
 
   startupManager();
 
   // Before blocklist is loaded.
   AddonManager.getAddonsByIDs(["block1@tests.mozilla.org",
                                "block2@tests.mozilla.org"], function([a1, a2]) {
     do_check_eq(a1.blocklistState, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
     do_check_eq(a2.blocklistState, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
 
-    do_check_eq(gPref.getIntPref("test.blocklist.pref1"), 15);
-    do_check_eq(gPref.getIntPref("test.blocklist.pref2"), 15);
-    do_check_eq(gPref.getBoolPref("test.blocklist.pref3"), true);
-    do_check_eq(gPref.getBoolPref("test.blocklist.pref4"), true);
+    do_check_eq(Services.prefs.getIntPref("test.blocklist.pref1"), 15);
+    do_check_eq(Services.prefs.getIntPref("test.blocklist.pref2"), 15);
+    do_check_eq(Services.prefs.getBoolPref("test.blocklist.pref3"), true);
+    do_check_eq(Services.prefs.getBoolPref("test.blocklist.pref4"), true);
     run_test_1();
   });
 }
 
 function run_test_1() {
   load_blocklist("test_blocklist_prefs_1.xml", function() {
     restartManager();
 
@@ -133,16 +128,16 @@ function run_test_1() {
     AddonManager.getAddonsByIDs(["block1@tests.mozilla.org",
                                  "block2@tests.mozilla.org"], function([a1, a2]) {
       do_check_neq(a1, null);
       do_check_eq(a1.blocklistState, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
       do_check_neq(a2, null);
       do_check_eq(a2.blocklistState, Ci.nsIBlocklistService.STATE_BLOCKED);
 
       // All these prefs must be reset to defaults.
-      do_check_eq(gPref.prefHasUserValue("test.blocklist.pref1"), false);
-      do_check_eq(gPref.prefHasUserValue("test.blocklist.pref2"), false);
-      do_check_eq(gPref.prefHasUserValue("test.blocklist.pref3"), false);
-      do_check_eq(gPref.prefHasUserValue("test.blocklist.pref4"), false);
+      do_check_eq(Services.prefs.prefHasUserValue("test.blocklist.pref1"), false);
+      do_check_eq(Services.prefs.prefHasUserValue("test.blocklist.pref2"), false);
+      do_check_eq(Services.prefs.prefHasUserValue("test.blocklist.pref3"), false);
+      do_check_eq(Services.prefs.prefHasUserValue("test.blocklist.pref4"), false);
       end_test();
     });
   });
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug393285.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug393285.js
@@ -264,53 +264,50 @@ function run_test() {
     run_test_1();
   });
 }
 
 function run_test_1() {
   load_blocklist("test_bug393285.xml", function() {
     restartManager();
 
-    var blocklist = Cc["@mozilla.org/extensions/blocklist;1"]
-                    .getService(Ci.nsIBlocklistService);
-
     AddonManager.getAddonsByIDs(addonIDs,
                                function([a1, a2, a3, a4, a5, a6, a7, a8, a9, a10,
                                          a11, a12, a13, a14, a15]) {
       // No info in blocklist, shouldn't be blocked
-      do_check_false(blocklist.isAddonBlocklisted(a1, "1", "1.9"));
+      do_check_false(Services.blocklist.isAddonBlocklisted(a1, "1", "1.9"));
 
       // Should always be blocked
-      do_check_true(blocklist.isAddonBlocklisted(a2, "1", "1.9"));
+      do_check_true(Services.blocklist.isAddonBlocklisted(a2, "1", "1.9"));
 
       // Only version 1 should be blocked
-      do_check_true(blocklist.isAddonBlocklisted(a3, "1", "1.9"));
-      do_check_false(blocklist.isAddonBlocklisted(a4, "1", "1.9"));
+      do_check_true(Services.blocklist.isAddonBlocklisted(a3, "1", "1.9"));
+      do_check_false(Services.blocklist.isAddonBlocklisted(a4, "1", "1.9"));
 
       // Should be blocked for app version 1
-      do_check_true(blocklist.isAddonBlocklisted(a5, "1", "1.9"));
-      do_check_false(blocklist.isAddonBlocklisted(a5, "2", "1.9"));
+      do_check_true(Services.blocklist.isAddonBlocklisted(a5, "1", "1.9"));
+      do_check_false(Services.blocklist.isAddonBlocklisted(a5, "2", "1.9"));
 
       // Not blocklisted because we are a different OS
-      do_check_false(blocklist.isAddonBlocklisted(a6, "2", "1.9"));
+      do_check_false(Services.blocklist.isAddonBlocklisted(a6, "2", "1.9"));
 
       // Blocklisted based on OS
-      do_check_true(blocklist.isAddonBlocklisted(a7, "2", "1.9"));
-      do_check_true(blocklist.isAddonBlocklisted(a8, "2", "1.9"));
+      do_check_true(Services.blocklist.isAddonBlocklisted(a7, "2", "1.9"));
+      do_check_true(Services.blocklist.isAddonBlocklisted(a8, "2", "1.9"));
 
       // Not blocklisted because we are a different ABI
-      do_check_false(blocklist.isAddonBlocklisted(a9, "2", "1.9"));
+      do_check_false(Services.blocklist.isAddonBlocklisted(a9, "2", "1.9"));
 
       // Blocklisted based on ABI
-      do_check_true(blocklist.isAddonBlocklisted(a10, "2", "1.9"));
-      do_check_true(blocklist.isAddonBlocklisted(a11, "2", "1.9"));
+      do_check_true(Services.blocklist.isAddonBlocklisted(a10, "2", "1.9"));
+      do_check_true(Services.blocklist.isAddonBlocklisted(a11, "2", "1.9"));
 
       // Doesnt match both os and abi so not blocked
-      do_check_false(blocklist.isAddonBlocklisted(a12, "2", "1.9"));
-      do_check_false(blocklist.isAddonBlocklisted(a13, "2", "1.9"));
-      do_check_false(blocklist.isAddonBlocklisted(a14, "2", "1.9"));
+      do_check_false(Services.blocklist.isAddonBlocklisted(a12, "2", "1.9"));
+      do_check_false(Services.blocklist.isAddonBlocklisted(a13, "2", "1.9"));
+      do_check_false(Services.blocklist.isAddonBlocklisted(a14, "2", "1.9"));
 
       // Matches both os and abi so blocked
-      do_check_true(blocklist.isAddonBlocklisted(a15, "2", "1.9"));
+      do_check_true(Services.blocklist.isAddonBlocklisted(a15, "2", "1.9"));
       end_test();
     });
   });
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug406118.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug406118.js
@@ -131,25 +131,22 @@ function run_test() {
     run_test_1();
   });
 }
 
 function run_test_1() {
   load_blocklist("test_bug393285.xml", function() {
     restartManager();
 
-    var blocklist = Cc["@mozilla.org/extensions/blocklist;1"]
-                    .getService(Ci.nsIBlocklistService);
-
     AddonManager.getAddonsByIDs(addonIDs,
                                function([a1, a2, a3, a4]) {
       // No info in blocklist, shouldn't be blocked
-      do_check_false(blocklist.isAddonBlocklisted(a1, null, null));
+      do_check_false(Services.blocklist.isAddonBlocklisted(a1, null, null));
 
       // All these should be blocklisted for the current app.
-      do_check_true(blocklist.isAddonBlocklisted(a2, null, null));
-      do_check_true(blocklist.isAddonBlocklisted(a3, null, null));
-      do_check_true(blocklist.isAddonBlocklisted(a4, null, null));
+      do_check_true(Services.blocklist.isAddonBlocklisted(a2, null, null));
+      do_check_true(Services.blocklist.isAddonBlocklisted(a3, null, null));
+      do_check_true(Services.blocklist.isAddonBlocklisted(a4, null, null));
 
       end_test();
     });
   });
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug430120.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug430120.js
@@ -72,42 +72,38 @@ function pathHandler(metadata, response)
               gOSVersion + "&1.9&distribution&distribution-version");
   gBlocklist.observe(null, "quit-application", "");
   gBlocklist.observe(null, "xpcom-shutdown", "");
   testserver.stop(do_test_finished);
 }
 
 fun