merge autoland to mozilla-central a=merge
authorIris Hsiao <ihsiao@mozilla.com>
Mon, 08 May 2017 11:10:13 +0800
changeset 356898 22aaf8bad4df8e5f8c976f1521c213cb37e2dff5
parent 356820 17d8a1e278a9c54a6fdda9d390abce4077e55b20 (current diff)
parent 356897 74ff52c38236a889ac008c612e4754f5dc68602a (diff)
child 356967 c3e5497cff1c995821b1c9320fa71f1ef9a8c30e
push id31775
push userihsiao@mozilla.com
push dateMon, 08 May 2017 03:10:38 +0000
treeherdermozilla-central@22aaf8bad4df [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge autoland to mozilla-central a=merge
parser/html/nsHtml5AtomList.h
parser/html/nsHtml5Atoms.cpp
parser/html/nsHtml5Atoms.h
toolkit/themes/linux/mozapps/extensions/dictionaryGeneric-16.png
toolkit/themes/linux/mozapps/extensions/themeGeneric-16.png
toolkit/themes/osx/mozapps/extensions/dictionaryGeneric-16.png
toolkit/themes/osx/mozapps/extensions/search.png
toolkit/themes/osx/mozapps/extensions/themeGeneric-16.png
toolkit/themes/windows/mozapps/extensions/dictionaryGeneric-16.png
toolkit/themes/windows/mozapps/extensions/themeGeneric-16.png
--- a/browser/base/content/browser-addons.js
+++ b/browser/base/content/browser-addons.js
@@ -495,75 +495,68 @@ const gExtensionsNotifications = {
     // uninit() can race ahead of init() in some cases, if that happens,
     // we have no handler to remove.
     if (!this.initialized) {
       return;
     }
     ExtensionsUI.off("change", this.boundUpdate);
   },
 
+  _createAddonButton(text, icon, callback) {
+    let button = document.createElement("toolbarbutton");
+    button.setAttribute("label", text);
+    const DEFAULT_EXTENSION_ICON =
+      "chrome://mozapps/skin/extensions/extensionGeneric.svg";
+    button.setAttribute("image", icon || DEFAULT_EXTENSION_ICON);
+    button.className = "addon-banner-item";
+
+    button.addEventListener("click", callback);
+    PanelUI.addonNotificationContainer.appendChild(button);
+  },
+
   updateAlerts() {
     let sideloaded = ExtensionsUI.sideloaded;
     let updates = ExtensionsUI.updates;
     if (sideloaded.size + updates.size == 0) {
       PanelUI.removeNotification("addon-alert");
     } else {
       PanelUI.showBadgeOnlyNotification("addon-alert");
     }
 
-    let container = document.getElementById("PanelUI-footer-addons");
+    let container = PanelUI.addonNotificationContainer;
 
     while (container.firstChild) {
       container.firstChild.remove();
     }
 
-    const DEFAULT_EXTENSION_ICON =
-      "chrome://mozapps/skin/extensions/extensionGeneric.svg";
     let items = 0;
     for (let update of updates) {
       if (++items > 4) {
         break;
       }
-
-      let button = document.createElement("toolbarbutton");
       let text = gNavigatorBundle.getFormattedString("webextPerms.updateMenuItem", [update.addon.name]);
-      button.setAttribute("label", text);
-
-      let icon = update.addon.iconURL || DEFAULT_EXTENSION_ICON;
-      button.setAttribute("image", icon);
-
-      button.addEventListener("click", evt => {
+      this._createAddonButton(text, update.addon.iconURL, evt => {
         ExtensionsUI.showUpdate(gBrowser, update);
       });
-
-      container.appendChild(button);
     }
 
     let appName;
     for (let addon of sideloaded) {
       if (++items > 4) {
         break;
       }
       if (!appName) {
         let brandBundle = document.getElementById("bundle_brand");
         appName = brandBundle.getString("brandShortName");
       }
 
-      let button = document.createElement("toolbarbutton");
       let text = gNavigatorBundle.getFormattedString("webextPerms.sideloadMenuItem", [addon.name, appName]);
-      button.setAttribute("label", text);
-
-      let icon = addon.iconURL || DEFAULT_EXTENSION_ICON;
-      button.setAttribute("image", icon);
-
-      button.addEventListener("click", evt => {
+      this._createAddonButton(text, addon.iconURL, evt => {
         ExtensionsUI.showSideloaded(gBrowser, addon);
       });
-
-      container.appendChild(button);
     }
   },
 };
 
 var LightWeightThemeWebInstaller = {
   init() {
     let mm = window.messageManager;
     mm.addMessageListener("LightWeightThemeWebInstaller:Install", this);
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -1182,17 +1182,18 @@ toolbarpaletteitem[place="palette"][hidd
 
 #customization-palette .toolbarpaletteitem-box {
   -moz-box-pack: center;
   -moz-box-flex: 1;
   width: 10em;
   max-width: 10em;
 }
 
-#main-window[customizing=true] .PanelUI-notification-menu-item {
+#main-window[customizing=true] .addon-banner-item,
+#main-window[customizing=true] .panel-banner-item {
   display: none;
 }
 
 /* UI Tour */
 
 @keyframes uitour-wobble {
   from {
     transform: rotate(0deg) translateX(3px) rotate(0deg);
--- a/browser/base/content/test/appUpdate/head.js
+++ b/browser/base/content/test/appUpdate/head.js
@@ -203,17 +203,17 @@ function processStep({notificationId, bu
     is(shownNotification, notificationId, "The right notification showed up.");
     if (shownNotification != notificationId) {
       if (cleanup) {
         yield cleanup();
       }
       return;
     }
 
-    let notification = document.getElementById(`PanelUI-${notificationId}-notification`);
+    let notification = document.getElementById(`appMenu-${notificationId}-notification`);
     is(notification.hidden, false, `${notificationId} notification is showing`);
     if (beforeClick) {
       yield Task.spawn(beforeClick);
     }
 
     let buttonEl = document.getAnonymousElementByAttribute(notification, "anonid", button);
 
     buttonEl.click();
--- a/browser/base/content/test/static/browser_all_files_referenced.js
+++ b/browser/base/content/test/static/browser_all_files_referenced.js
@@ -209,20 +209,16 @@ var whitelist = new Set([
   {file: "chrome://global/skin/tree/sort-dsc.png", platforms: ["linux"]},
   // Bug 1344267
   {file: "chrome://marionette/content/test_anonymous_content.xul"},
   {file: "chrome://marionette/content/test_dialog.properties"},
   {file: "chrome://marionette/content/test_dialog.xul"},
   // Bug 1348533
   {file: "chrome://mozapps/skin/downloads/buttons.png", platforms: ["macosx"]},
   {file: "chrome://mozapps/skin/downloads/downloadButtons.png", platforms: ["linux", "win"]},
-  // Bug 1348555
-  {file: "chrome://mozapps/skin/extensions/dictionaryGeneric-16.png"},
-  {file: "chrome://mozapps/skin/extensions/search.png", platforms: ["macosx"]},
-  {file: "chrome://mozapps/skin/extensions/themeGeneric-16.png"},
   // Bug 1348556
   {file: "chrome://mozapps/skin/plugins/pluginBlocked.png"},
   // Bug 1348558
   {file: "chrome://mozapps/skin/update/downloadButtons.png",
    platforms: ["linux"]},
   // Bug 1348559
   {file: "chrome://pippki/content/resetpassword.xul"},
   // Bug 1351078
@@ -230,18 +226,16 @@ var whitelist = new Set([
   // Bug 1351070
   {file: "resource://gre/modules/ContentPrefInstance.jsm"},
   // Bug 1351079
   {file: "resource://gre/modules/ISO8601DateUtils.jsm"},
   // Bug 1337345
   {file: "resource://gre/modules/Manifest.jsm"},
   // Bug 1351089
   {file: "resource://gre/modules/PresentationDeviceInfoManager.jsm"},
-  // Bug 1351658
-  {file: "resource://gre/modules/PropertyListUtils.jsm", platforms: ["linux", "win"]},
   // Bug 1351097
   {file: "resource://gre/modules/accessibility/AccessFu.jsm"},
   // Bug 1351637
   {file: "resource://gre/modules/sdk/bootstrap.js"},
 
 ].filter(item =>
   ("isFromDevTools" in item) == isDevtools &&
   (!item.skipNightly || !AppConstants.NIGHTLY_BUILD) &&
--- a/browser/base/content/test/webextensions/browser_extension_sideloading.js
+++ b/browser/base/content/test/webextensions/browser_extension_sideloading.js
@@ -180,17 +180,17 @@ add_task(function* () {
 
   // Check for the addons badge on the hamburger menu
   let menuButton = document.getElementById("PanelUI-menu-button");
   is(menuButton.getAttribute("badge-status"), "addon-alert", "Should have addon alert badge");
 
   // Find the menu entries for sideloaded extensions
   yield PanelUI.show();
 
-  let addons = document.getElementById("PanelUI-footer-addons");
+  let addons = PanelUI.addonNotificationContainer;
   is(addons.children.length, 4, "Have 4 menu entries for sideloaded extensions");
 
   // Click the first sideloaded extension
   let popupPromise = promisePopupNotificationShown("addon-webext-permissions");
   addons.children[0].click();
 
   // When we get the permissions prompt, we should be at the extensions
   // list in about:addons
@@ -221,17 +221,17 @@ add_task(function* () {
   is(addon3.userDisabled, true, "Addon 3 should still be disabled");
   is(addon4.userDisabled, true, "Addon 4 should still be disabled");
 
   yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
 
   // Should still have 3 entries in the hamburger menu
   yield PanelUI.show();
 
-  addons = document.getElementById("PanelUI-footer-addons");
+  addons = PanelUI.addonNotificationContainer;
   is(addons.children.length, 3, "Have 3 menu entries for sideloaded extensions");
 
   // Click the second sideloaded extension and wait for the notification
   popupPromise = promisePopupNotificationShown("addon-webext-permissions");
   addons.children[0].click();
   panel = yield popupPromise;
 
   // Again we should be at the extentions list in about:addons
@@ -255,17 +255,17 @@ add_task(function* () {
   is(addon1.userDisabled, true, "Addon 1 should still be disabled");
   is(addon2.userDisabled, false, "Addon 2 should now be enabled");
   is(addon3.userDisabled, true, "Addon 3 should still be disabled");
   is(addon4.userDisabled, true, "Addon 4 should still be disabled");
 
   // Should still have 2 entries in the hamburger menu
   yield PanelUI.show();
 
-  addons = document.getElementById("PanelUI-footer-addons");
+  addons = PanelUI.addonNotificationContainer;
   is(addons.children.length, 2, "Have 2 menu entries for sideloaded extensions");
 
   // Close the hamburger menu and go directly to the addons manager
   yield PanelUI.hide();
 
   win = yield BrowserOpenAddonsMgr(VIEW);
 
   let list = win.document.getElementById("addon-list");
@@ -294,17 +294,17 @@ add_task(function* () {
   is(value, false, "userDisabled should be set on addon 3");
 
   addon3 = yield AddonManager.getAddonByID(ID3);
   is(addon3.userDisabled, false, "Addon 3 should be enabled");
 
   // Should still have 1 entry in the hamburger menu
   yield PanelUI.show();
 
-  addons = document.getElementById("PanelUI-footer-addons");
+  addons = PanelUI.addonNotificationContainer;
   is(addons.children.length, 1, "Have 1 menu entry for sideloaded extensions");
 
   // Close the hamburger menu and go to the detail page for this addon
   yield PanelUI.hide();
 
   win = yield BrowserOpenAddonsMgr(`addons://detail/${encodeURIComponent(ID4)}`);
   let button = win.document.getElementById("detail-enable-btn");
 
--- a/browser/base/content/test/webextensions/browser_extension_update_background.js
+++ b/browser/base/content/test/webextensions/browser_extension_update_background.js
@@ -76,17 +76,17 @@ function* backgroundUpdateTest(url, id, 
   AddonManagerPrivate.backgroundUpdateCheck();
   yield updatePromise;
 
   is(getBadgeStatus(), "addon-alert", "Should have addon alert badge");
 
   // Find the menu entry for the update
   yield PanelUI.show();
 
-  let addons = document.getElementById("PanelUI-footer-addons");
+  let addons = PanelUI.addonNotificationContainer;
   is(addons.children.length, 1, "Have a menu entry for the update");
 
   // Click the menu item
   let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, "about:addons");
   let popupPromise = promisePopupNotificationShown("addon-webext-permissions");
   addons.children[0].click();
 
   // about:addons should load and go to the list of extensions
@@ -116,31 +116,31 @@ function* backgroundUpdateTest(url, id, 
   is(addon.version, "1.0", "Should still be running the old version");
 
   yield BrowserTestUtils.removeTab(tab);
 
   // Alert badge and hamburger menu items should be gone
   is(getBadgeStatus(), "", "Addon alert badge should be gone");
 
   yield PanelUI.show();
-  addons = document.getElementById("PanelUI-footer-addons");
+  addons = PanelUI.addonNotificationContainer;
   is(addons.children.length, 0, "Update menu entries should be gone");
   yield PanelUI.hide();
 
   // Re-check for an update
   updatePromise = promiseInstallEvent(addon, "onDownloadEnded");
   yield AddonManagerPrivate.backgroundUpdateCheck();
   yield updatePromise;
 
   is(getBadgeStatus(), "addon-alert", "Should have addon alert badge");
 
   // Find the menu entry for the update
   yield PanelUI.show();
 
-  addons = document.getElementById("PanelUI-footer-addons");
+  addons = PanelUI.addonNotificationContainer;
   is(addons.children.length, 1, "Have a menu entry for the update");
 
   // Click the menu item
   tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, "about:addons");
   popupPromise = promisePopupNotificationShown("addon-webext-permissions");
   addons.children[0].click();
 
   // Wait for about:addons to load
--- a/browser/base/content/test/webextensions/browser_extension_update_background_noprompt.js
+++ b/browser/base/content/test/webextensions/browser_extension_update_background_noprompt.js
@@ -54,17 +54,17 @@ async function testNoPrompt(origUrl, id)
   let updatePromise = waitForUpdate(addon);
   AddonManagerPrivate.backgroundUpdateCheck();
   await updatePromise;
 
   // There should be no notifications about the update
   is(getBadgeStatus(), "", "Should not have addon alert badge");
 
   await PanelUI.show();
-  let addons = document.getElementById("PanelUI-footer-addons");
+  let addons = PanelUI.addonNotificationContainer;
   is(addons.children.length, 0, "Have 0 updates in the PanelUI menu");
   await PanelUI.hide();
 
   ok(!sawPopup, "Should not have seen permissions notification");
 
   addon = await AddonManager.getAddonByID(id);
   is(addon.version, "2.0", "Update should have applied");
 
--- a/browser/base/content/test/webrtc/browser.ini
+++ b/browser/base/content/test/webrtc/browser.ini
@@ -7,15 +7,14 @@ support-files =
 
 [browser_devices_get_user_media.js]
 skip-if = (os == "linux" && debug) # linux: bug 976544
 [browser_devices_get_user_media_anim.js]
 [browser_devices_get_user_media_in_frame.js]
 [browser_devices_get_user_media_multi_process.js]
 skip-if = e10s && (asan || debug) # bug 1347625
 [browser_devices_get_user_media_screen.js]
-skip-if = (os == "linux") || (os == "win" && !debug) # bug 1320994 for linux opt, bug 1338038 for windows and linux debug
 [browser_devices_get_user_media_tear_off_tab.js]
 [browser_devices_get_user_media_unprompted_access.js]
 [browser_devices_get_user_media_unprompted_access_in_frame.js]
 [browser_devices_get_user_media_unprompted_access_tear_off_tab.js]
-skip-if = (os == "linux") || (os == "win" && bits == 64) # linux: bug 1331616, win8: bug 1334752
+skip-if = (os == "win" && bits == 64) # win8: bug 1334752
 [browser_webrtc_hooks.js]
--- a/browser/base/content/test/webrtc/browser_devices_get_user_media_screen.js
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_screen.js
@@ -50,17 +50,17 @@ var gTests = [
       is(item.getAttribute("devicetype"), "Screen", "the devicetype attribute is set correctly");
       ok(item.scary, "the screen item is marked as scary");
     }
 
     // Select a screen, a preview with a scary warning should appear.
     menulist.getItemAtIndex(2).doCommand();
     ok(!document.getElementById("webRTC-all-windows-shared").hidden,
        "the 'all windows will be shared' warning should now be visible");
-    yield promiseWaitForCondition(() => !document.getElementById("webRTC-preview").hidden);
+    yield promiseWaitForCondition(() => !document.getElementById("webRTC-preview").hidden, 100);
     ok(!document.getElementById("webRTC-preview").hidden,
        "the preview area is visible");
     ok(!document.getElementById("webRTC-previewWarning").hidden,
        "the scary warning is visible");
 
     // Select the 'No Screen' item again, the preview should be hidden.
     menulist.getItemAtIndex(0).doCommand();
     ok(document.getElementById("webRTC-all-windows-shared").hidden,
@@ -405,30 +405,30 @@ var gTests = [
     yield share(false, true);
     yield check({video: true, audio: true, screen: "Screen"});
 
     info("Stop the screen share, mic+cam should continue");
     yield stopSharing("screen", true);
     yield check({video: true, audio: true});
 
     info("Stop the camera, everything should stop.");
-    yield stopSharing("camera", false, true);
+    yield stopSharing("camera");
 
     info("Now, share only the screen...");
     indicator = promiseIndicatorWindow();
     yield share(false, false, true);
     yield indicator;
     yield check({screen: "Screen"});
 
     info("... and add camera and microphone in a second request.");
     yield share(true, true);
     yield check({video: true, audio: true, screen: "Screen"});
 
     info("Stop the camera, this should stop everything.");
-    yield stopSharing("camera", false, true);
+    yield stopSharing("camera");
   }
 },
 
 {
   desc: "getUserMedia screen: reloading the page removes all gUM UI",
   run: function* checkReloading() {
     let promise = promisePopupNotificationShown("webRTC-shareDevices");
     yield promiseRequestDevice(false, true, null, "screen");
--- a/browser/base/content/test/webrtc/browser_devices_get_user_media_unprompted_access.js
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_unprompted_access.js
@@ -57,17 +57,17 @@ var gTests = [
     });
 
     yield expectObserverCalled("getUserMedia:response:deny");
     SitePermissions.remove(null, "screen", gBrowser.selectedBrowser);
     SitePermissions.remove(null, "camera", gBrowser.selectedBrowser);
     SitePermissions.remove(null, "microphone", gBrowser.selectedBrowser);
 
     // After closing all streams, gUM(audio+camera) causes a prompt.
-    yield closeStream(false, 0, 2);
+    yield closeStream();
     promise = promisePopupNotificationShown("webRTC-shareDevices");
     yield promiseRequestDevice(true, true);
     yield promise;
     yield expectObserverCalled("getUserMedia:request");
     checkDeviceSelectors(true, true);
 
     yield promiseMessage(permissionError, () => {
       activateSecondaryAction(kActionDeny);
@@ -164,17 +164,17 @@ var gTests = [
     yield expectObserverCalled("recording-device-events");
 
     Assert.deepEqual((yield getMediaCaptureState()), {video: true},
                      "expected camera to be shared");
 
     yield checkSharingUI({audio: false, video: true});
 
     // close all streams
-    yield closeStream(false, 0, 2);
+    yield closeStream();
   }
 },
 
 {
   desc: "getUserMedia audio",
   run: function* checkAudioVideoWhileLiveTracksExist_audio() {
     let promise = promisePopupNotificationShown("webRTC-shareDevices");
     yield promiseRequestDevice(true, false);
@@ -236,17 +236,17 @@ var gTests = [
     yield expectObserverCalled("recording-device-events");
 
     Assert.deepEqual((yield getMediaCaptureState()), {audio: true},
                      "expected microphone to be shared");
 
     yield checkSharingUI({audio: true, video: false});
 
     // close all streams
-    yield closeStream(false, 0, 2);
+    yield closeStream();
   }
 }
 
 ];
 
 add_task(async function test() {
   await runTests(gTests);
 });
--- a/browser/base/content/test/webrtc/browser_devices_get_user_media_unprompted_access_in_frame.js
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_unprompted_access_in_frame.js
@@ -52,17 +52,17 @@ var gTests = [
     yield promiseRequestDevice(true, true, "frame1");
     yield promise;
     yield expectObserverCalled("getUserMedia:request");
     yield promiseNoPopupNotification("webRTC-shareDevices");
     yield expectObserverCalled("getUserMedia:response:allow");
     yield expectObserverCalled("recording-device-events");
 
     // close the stream
-    yield closeStream(false, "frame1", 2);
+    yield closeStream(false, "frame1");
   }
 },
 
 {
   desc: "getUserMedia audio+camera in frame 1 - part II",
   run: function* checkAudioVideoWhileLiveTracksExist_frame_partII() {
     let promise = promisePopupNotificationShown("webRTC-shareDevices");
     yield promiseRequestDevice(true, true, "frame1");
@@ -192,17 +192,17 @@ var gTests = [
     yield promiseMessage(permissionError, () => {
       activateSecondaryAction(kActionDeny);
     });
 
     yield expectObserverCalled("getUserMedia:response:deny");
     yield expectObserverCalled("recording-window-ended");
 
     // close the stream
-    yield closeStream(false);
+    yield closeStream();
     SitePermissions.remove(null, "screen", gBrowser.selectedBrowser);
     SitePermissions.remove(null, "camera", gBrowser.selectedBrowser);
     SitePermissions.remove(null, "microphone", gBrowser.selectedBrowser);
   }
 }
 
 ];
 
--- a/browser/base/content/test/webrtc/browser_devices_get_user_media_unprompted_access_tear_off_tab.js
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_unprompted_access_tear_off_tab.js
@@ -36,17 +36,16 @@ var gTests = [
     yield promiseRequestDevice(true, true, null, null, win.gBrowser.selectedBrowser);
     yield promiseObserverCalled("getUserMedia:request");
     let promises = [promiseNoPopupNotification("webRTC-shareDevices"),
                     promiseObserverCalled("getUserMedia:response:allow"),
                     promiseObserverCalled("recording-device-events")];
     yield Promise.all(promises);
 
     promises = [promiseObserverCalled("recording-device-events"),
-                promiseObserverCalled("recording-device-events"),
                 promiseObserverCalled("recording-window-ended")];
     yield BrowserTestUtils.closeWindow(win);
     yield Promise.all(promises);
 
     yield checkNotSharing();
   }
 }
 
--- a/browser/base/content/test/webrtc/head.js
+++ b/browser/base/content/test/webrtc/head.js
@@ -25,19 +25,19 @@ function waitForCondition(condition, nex
     if (conditionPassed) {
       moveOn();
     }
     tries++;
   }, 100);
   var moveOn = function() { clearInterval(interval); nextTest(); };
 }
 
-function promiseWaitForCondition(aConditionFn) {
+function promiseWaitForCondition(aConditionFn, retryTimes) {
   let deferred = Promise.defer();
-  waitForCondition(aConditionFn, deferred.resolve, "Condition didn't pass.");
+  waitForCondition(aConditionFn, deferred.resolve, "Condition didn't pass.", retryTimes);
   return deferred.promise;
 }
 
 /**
  * Waits for a window with the given URL to exist.
  *
  * @param url
  *        The url of the window.
@@ -210,35 +210,27 @@ function expectObserverCalled(aTopic) {
       is(data.count, 1, "expected notification " + aTopic);
       mm.removeMessageListener("Test:ExpectObserverCalled:Reply", listener);
       resolve();
     });
     mm.sendAsyncMessage("Test:ExpectObserverCalled", aTopic);
   });
 }
 
-function expectNoObserverCalled(aIgnoreDeviceEvents = false) {
+function expectNoObserverCalled() {
   return new Promise(resolve => {
     let mm = _mm();
     mm.addMessageListener("Test:ExpectNoObserverCalled:Reply",
                           function listener({data}) {
       mm.removeMessageListener("Test:ExpectNoObserverCalled:Reply", listener);
       for (let topic in data) {
         if (!data[topic])
           continue;
 
-        // If we are stopping tracks that were created from 2 different
-        // getUserMedia calls, the "recording-device-events" notification is
-        // fired twice on Windows and Mac, and intermittently twice on Linux.
-        if (topic == "recording-device-events" && aIgnoreDeviceEvents) {
-          todo(false, "Got " + data[topic] + " unexpected " + topic +
-               " notifications, see bug 1320994");
-        } else {
-          is(data[topic], 0, topic + " notification unexpected");
-        }
+        is(data[topic], 0, topic + " notification unexpected");
       }
       resolve();
     });
     mm.sendAsyncMessage("Test:ExpectNoObserverCalled");
   });
 }
 
 function ignoreObserversCalled() {
@@ -349,35 +341,34 @@ function getMediaCaptureState() {
     let mm = _mm();
     mm.addMessageListener("Test:MediaCaptureState", ({data}) => {
       resolve(data);
     });
     mm.sendAsyncMessage("Test:GetMediaCaptureState");
   });
 }
 
-function* stopSharing(aType = "camera", aShouldKeepSharing = false,
-                      aExpectDoubleRecordingEvent = false) {
+function* stopSharing(aType = "camera", aShouldKeepSharing = false) {
   let promiseRecordingEvent = promiseObserverCalled("recording-device-events");
   gIdentityHandler._identityBox.click();
   let permissions = document.getElementById("identity-popup-permission-list");
   let cancelButton =
     permissions.querySelector(".identity-popup-permission-icon." + aType + "-icon ~ " +
                               ".identity-popup-permission-remove-button");
   cancelButton.click();
   gIdentityHandler._identityPopup.hidden = true;
   yield promiseRecordingEvent;
   yield expectObserverCalled("getUserMedia:revoke");
 
   // If we are stopping screen sharing and expect to still have another stream,
   // "recording-window-ended" won't be fired.
   if (!aShouldKeepSharing)
     yield expectObserverCalled("recording-window-ended");
 
-  yield expectNoObserverCalled(aExpectDoubleRecordingEvent);
+  yield expectNoObserverCalled();
 
   if (!aShouldKeepSharing)
     yield* checkNotSharing();
 }
 
 function promiseRequestDevice(aRequestAudio, aRequestVideo, aFrameId, aType,
                               aBrowser = gBrowser.selectedBrowser) {
   info("requesting devices");
@@ -386,26 +377,23 @@ function promiseRequestDevice(aRequestAu
                            function*(args) {
     let global = content.wrappedJSObject;
     if (args.aFrameId)
       global = global.document.getElementById(args.aFrameId).contentWindow;
     global.requestDevice(args.aRequestAudio, args.aRequestVideo, args.aType);
   });
 }
 
-function* closeStream(aAlreadyClosed, aFrameId, aStreamCount = 1) {
+function* closeStream(aAlreadyClosed, aFrameId) {
   yield expectNoObserverCalled();
 
   let promises;
   if (!aAlreadyClosed) {
-    promises = [];
-    for (let i = 0; i < aStreamCount; i++) {
-      promises.push(promiseObserverCalled("recording-device-events"));
-    }
-    promises.push(promiseObserverCalled("recording-window-ended"));
+    promises = [promiseObserverCalled("recording-device-events"),
+                promiseObserverCalled("recording-window-ended")];
   }
 
   info("closing the stream");
   yield ContentTask.spawn(gBrowser.selectedBrowser, aFrameId, function*(contentFrameId) {
     let global = content.wrappedJSObject;
     if (contentFrameId)
       global = global.document.getElementById(contentFrameId).contentWindow;
     global.closeStream();
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -12,27 +12,22 @@
   <panelmultiview id="PanelUI-multiView" mainViewId="PanelUI-mainView">
     <panelview id="PanelUI-mainView" context="customizationPanelContextMenu">
       <vbox id="PanelUI-contents-scroller">
         <vbox id="PanelUI-contents" class="panelUI-grid"/>
       </vbox>
 
       <footer id="PanelUI-footer">
         <vbox id="PanelUI-footer-addons"></vbox>
-        <toolbarbutton id="PanelUI-update-available-menu-item"
-                       wrap="true"
-                       label="&updateAvailable.panelUI.label;"
-                       hidden="true"/>
-        <toolbarbutton id="PanelUI-update-manual-menu-item"
+        <toolbarbutton class="panel-banner-item"
+                       label-update-available="&updateAvailable.panelUI.label;"
+                       label-update-manual="&updateManual.panelUI.label;"
+                       label-update-restart="&updateRestart.panelUI.label;"
+                       oncommand="PanelUI._onBannerItemSelected(event)"
                        wrap="true"
-                       label="&updateManual.panelUI.label;"
-                       hidden="true"/>
-        <toolbarbutton id="PanelUI-update-restart-menu-item"
-                       wrap="true"
-                       label="&updateRestart.panelUI.label;"
                        hidden="true"/>
         <hbox id="PanelUI-footer-fxa">
           <hbox id="PanelUI-fxa-status"
                 label="&fxaSignedIn.tooltip;"
                 defaultlabel="&fxaSignIn.label;"
                 signedinTooltiptext="&fxaSignedIn.tooltip;"
                 tooltiptext="&fxaSignedIn.tooltip;"
                 errorlabel="&fxaSignInError.label;"
@@ -426,27 +421,27 @@
       <description>&panicButton.thankyou.msg2;</description>
     </vbox>
   </hbox>
   <button label="&panicButton.thankyou.buttonlabel;"
           id="panic-button-success-closebutton"
           oncommand="PanicButtonNotifier.close()"/>
 </panel>
 
-<panel id="PanelUI-notification-popup"
+<panel id="appMenu-notification-popup"
        class="popup-notification-panel"
        type="arrow"
        position="after_start"
        hidden="true"
        orient="vertical"
        noautofocus="true"
        noautohide="true"
        nopreventnavboxhide="true"
        role="alert">
-  <popupnotification id="PanelUI-update-available-notification"
+  <popupnotification id="appMenu-update-available-notification"
                      popupid="update-available"
                      label="&updateAvailable.header.message;"
                      buttonlabel="&updateAvailable.acceptButton.label;"
                      buttonaccesskey="&updateAvailable.acceptButton.accesskey;"
                      closebuttonhidden="true"
                      secondarybuttonlabel="&updateAvailable.cancelButton.label;"
                      secondarybuttonaccesskey="&updateAvailable.cancelButton.accesskey;"
                      dropmarkerhidden="true"
@@ -454,17 +449,17 @@
                      hidden="true">
     <popupnotificationcontent id="update-available-notification-content" orient="vertical">
       <description id="update-available-description">&updateAvailable.message;
         <label id="update-available-whats-new" class="text-link" value="&updateAvailable.whatsnew.label;" />
       </description>
     </popupnotificationcontent>
   </popupnotification>
 
-  <popupnotification id="PanelUI-update-manual-notification"
+  <popupnotification id="appMenu-update-manual-notification"
                      popupid="update-manual"
                      label="&updateManual.header.message;"
                      buttonlabel="&updateManual.acceptButton.label;"
                      buttonaccesskey="&updateManual.acceptButton.accesskey;"
                      closebuttonhidden="true"
                      secondarybuttonlabel="&updateManual.cancelButton.label;"
                      secondarybuttonaccesskey="&updateManual.cancelButton.accesskey;"
                      dropmarkerhidden="true"
@@ -472,17 +467,17 @@
                      hidden="true">
     <popupnotificationcontent id="update-manual-notification-content" orient="vertical">
       <description id="update-manual-description">&updateManual.message;
         <label id="update-manual-whats-new" class="text-link" value="&updateManual.whatsnew.label;" />
       </description>
     </popupnotificationcontent>
   </popupnotification>
 
-  <popupnotification id="PanelUI-update-restart-notification"
+  <popupnotification id="appMenu-update-restart-notification"
                      popupid="update-restart"
                      label="&updateRestart.header.message2;"
                      buttonlabel="&updateRestart.acceptButton.label;"
                      buttonaccesskey="&updateRestart.acceptButton.accesskey;"
                      closebuttonhidden="true"
                      secondarybuttonlabel="&updateRestart.cancelButton.label;"
                      secondarybuttonaccesskey="&updateRestart.cancelButton.accesskey;"
                      dropmarkerhidden="true"
@@ -500,16 +495,24 @@
        type="arrow"
        hidden="true"
        flip="slide"
        position="bottomcenter topright"
        noautofocus="true">
   <panelmultiview id="appMenu-multiView" mainViewId="appMenu-mainView">
     <panelview id="appMenu-mainView" class="cui-widget-panelview PanelUI-subView">
       <vbox class="panel-subview-body">
+        <vbox id="appMenu-addon-banners"/>
+        <toolbarbutton class="panel-banner-item"
+                       label-update-available="&updateAvailable.panelUI.label;"
+                       label-update-manual="&updateManual.panelUI.label;"
+                       label-update-restart="&updateRestart.panelUI.label;"
+                       oncommand="PanelUI._onBannerItemSelected(event)"
+                       wrap="true"
+                       hidden="true"/>
         <toolbarbutton id="appMenu-new-window-button"
                        class="subviewbutton subviewbutton-iconic"
                        label="&newNavigatorCmd.label;"
                        key="key_newNavigator"
                        command="cmd_newNavigator"/>
         <toolbarbutton id="appMenu-private-window-button"
                        class="subviewbutton subviewbutton-iconic"
                        label="&newPrivateWindow.label;"
--- a/browser/components/customizableui/content/panelUI.js
+++ b/browser/components/customizableui/content/panelUI.js
@@ -30,19 +30,19 @@ const PanelUI = {
   get kElements() {
     return {
       contents: "PanelUI-contents",
       mainView: gPhotonStructure ? "appMenu-mainView" : "PanelUI-mainView",
       multiView: gPhotonStructure ? "appMenu-multiView" : "PanelUI-multiView",
       helpView: "PanelUI-helpView",
       menuButton: "PanelUI-menu-button",
       panel: gPhotonStructure ? "appMenu-popup" : "PanelUI-popup",
-      notificationPanel: "PanelUI-notification-popup",
+      notificationPanel: "appMenu-notification-popup",
       scroller: "PanelUI-contents-scroller",
-      footer: "PanelUI-footer",
+      addonNotificationContainer: gPhotonStructure ? "appMenu-addon-banners" : "PanelUI-footer-addons",
 
       overflowFixedList: gPhotonStructure ? "widget-overflow-fixed-list" : "",
     };
   },
 
   _initialized: false,
   init() {
     for (let [k, v] of Object.entries(this.kElements)) {
@@ -698,31 +698,31 @@ const PanelUI = {
 
     if (this.panel.state == "showing" || this.panel.state == "open") {
       // If the menu is already showing, then we need to dismiss all notifications
       // since we don't want their doorhangers competing for attention
       doorhangers.forEach(n => { n.dismissed = true; })
       this._hidePopup();
       this._clearBadge();
       if (!this.notifications[0].options.badgeOnly) {
-        this._showMenuItem(this.notifications[0]);
+        this._showBannerItem(this.notifications[0]);
       }
     } else if (doorhangers.length > 0) {
       if (window.fullScreen) {
         this._hidePopup();
         this._showBadge(doorhangers[0]);
-        this._showMenuItem(doorhangers[0]);
+        this._showBannerItem(doorhangers[0]);
       } else {
         this._clearBadge();
         this._showNotificationPanel(doorhangers[0]);
       }
     } else {
       this._hidePopup();
       this._showBadge(this.notifications[0]);
-      this._showMenuItem(this.notifications[0]);
+      this._showBannerItem(this.notifications[0]);
     }
   },
 
   _showNotificationPanel(notification) {
     this._refreshNotificationPanel(notification);
 
     if (this.isNotificationPanelOpen) {
       return;
@@ -739,17 +739,17 @@ const PanelUI = {
       popupnotification.hidden = true;
       popupnotification.notification = null;
     }
   },
 
   _clearAllNotifications() {
     this._clearNotificationPanel();
     this._clearBadge();
-    this._clearMenuItems();
+    this._clearBannerItem();
   },
 
   _refreshNotificationPanel(notification) {
     this._clearNotificationPanel();
 
     let popupnotificationID = this._getPopupId(notification);
     let popupnotification = document.getElementById(popupnotificationID);
 
@@ -761,42 +761,41 @@ const PanelUI = {
     popupnotification.hidden = false;
   },
 
   _showBadge(notification) {
     let badgeStatus = this._getBadgeStatus(notification);
     this.menuButton.setAttribute("badge-status", badgeStatus);
   },
 
-  // "Menu item" here refers to an item in the hamburger panel menu. They will
-  // typically show up as a colored row near the bottom of the panel.
-  _showMenuItem(notification) {
-    this._clearMenuItems();
-
-    let menuItemId = this._getMenuItemId(notification);
-    let menuItem = document.getElementById(menuItemId);
-    if (menuItem) {
-      menuItem.notification = notification;
-      menuItem.setAttribute("oncommand", "PanelUI._onNotificationMenuItemSelected(event)");
-      menuItem.classList.add("PanelUI-notification-menu-item");
-      menuItem.hidden = false;
-      menuItem.fromPanelUINotifications = true;
+  // "Banner item" here refers to an item in the hamburger panel menu. They will
+  // typically show up as a colored row in the panel.
+  _showBannerItem(notification) {
+    if (!this._panelBannerItem) {
+      this._panelBannerItem = this.mainView.querySelector(".panel-banner-item");
     }
+    let label = this._panelBannerItem.getAttribute("label-" + notification.id);
+    // Ignore items we don't know about.
+    if (!label) {
+      return;
+    }
+    this._panelBannerItem.setAttribute("notificationid", notification.id);
+    this._panelBannerItem.setAttribute("label", label);
+    this._panelBannerItem.hidden = false;
+    this._panelBannerItem.notification = notification;
   },
 
   _clearBadge() {
     this.menuButton.removeAttribute("badge-status");
   },
 
-  _clearMenuItems() {
-    for (let child of this.footer.children) {
-      if (child.fromPanelUINotifications) {
-        child.notification = null;
-        child.hidden = true;
-      }
+  _clearBannerItem() {
+    if (this._panelBannerItem) {
+      this._panelBannerItem.notification = null;
+      this._panelBannerItem.hidden = true;
     }
   },
 
   _removeNotification(notification) {
     // This notification may already be removed, in which case let's just fail
     // silently.
     let notifications = this.notifications;
     if (!notifications)
@@ -847,17 +846,17 @@ const PanelUI = {
       notification.dismissed = true;
       this._notify(notification.id, "dismissed");
     } else {
       this._removeNotification(notification);
     }
     this._updateNotifications();
   },
 
-  _onNotificationMenuItemSelected(event) {
+  _onBannerItemSelected(event) {
     let target = event.originalTarget;
     if (!target.notification)
       throw "menucommand target has no associated action/notification";
 
     event.stopPropagation();
 
     try {
       target.notification.mainAction.callback(false);
@@ -865,22 +864,20 @@ const PanelUI = {
     } catch (error) {
       Cu.reportError(error);
     }
 
     this._removeNotification(target.notification);
     this._updateNotifications();
   },
 
-  _getPopupId(notification) { return "PanelUI-" + notification.id + "-notification"; },
+  _getPopupId(notification) { return "appMenu-" + notification.id + "-notification"; },
 
   _getBadgeStatus(notification) { return notification.id; },
 
-  _getMenuItemId(notification) { return "PanelUI-" + notification.id + "-menu-item"; },
-
   _getPanelAnchor(candidate) {
     let iconAnchor =
       document.getAnonymousElementByAttribute(candidate, "class",
                                               "toolbarbutton-icon");
     return iconAnchor || candidate;
   },
 
   _addedShortcuts: false,
--- a/browser/components/customizableui/test/browser_panelUINotifications.js
+++ b/browser/components/customizableui/test/browser_panelUINotifications.js
@@ -27,17 +27,17 @@ add_task(function* testMainActionCalled(
       callback: () => { extraMainActionCalled = true; }
     };
     extraWindow.PanelUI.showNotification("update-manual", extraMainAction)
 
     isnot(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is showing.");
     let notifications = [...PanelUI.notificationPanel.children].filter(n => !n.hidden);
     is(notifications.length, 1, "PanelUI doorhanger is only displaying one notification.");
     let doorhanger = notifications[0];
-    is(doorhanger.id, "PanelUI-update-manual-notification", "PanelUI is displaying the update-manual notification.");
+    is(doorhanger.id, "appMenu-update-manual-notification", "PanelUI is displaying the update-manual notification.");
 
     let mainActionButton = doc.getAnonymousElementByAttribute(doorhanger, "anonid", "button");
     mainActionButton.click();
 
     ok(mainActionCalled, "Main action callback was called");
     isnot(extraMainActionCalled, true, "Extra window's main action callback was not called");
     is(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is closed.");
     is(extraWindow.PanelUI.notificationPanel.state, "closed", "Extra window's update-manual doorhanger is closed.");
@@ -77,29 +77,30 @@ add_task(function* testSecondaryActionWo
       callback: () => { extraMainActionCalled = true; }
     };
     extraWindow.PanelUI.showNotification("update-manual", extraMainAction)
 
     isnot(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is showing.");
     let notifications = [...PanelUI.notificationPanel.children].filter(n => !n.hidden);
     is(notifications.length, 1, "PanelUI doorhanger is only displaying one notification.");
     let doorhanger = notifications[0];
-    is(doorhanger.id, "PanelUI-update-manual-notification", "PanelUI is displaying the update-manual notification.");
+    is(doorhanger.id, "appMenu-update-manual-notification", "PanelUI is displaying the update-manual notification.");
 
     let secondaryActionButton = doc.getAnonymousElementByAttribute(doorhanger, "anonid", "secondarybutton");
     secondaryActionButton.click();
 
     is(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is closed.");
     is(extraWindow.PanelUI.notificationPanel.state, "closed", "Extra window's update-manual doorhanger is closed.");
 
     is(PanelUI.menuButton.getAttribute("badge-status"), "update-manual", "Badge is displaying on PanelUI button.");
 
     yield PanelUI.show();
     isnot(PanelUI.menuButton.getAttribute("badge-status"), "update-manual", "Badge is hidden on PanelUI button.");
-    let menuItem = doc.getElementById("PanelUI-update-manual-menu-item");
+    let menuItem = PanelUI.mainView.querySelector(".panel-banner-item");
+    is(menuItem.label, menuItem.getAttribute("label-update-manual"), "Showing correct label");
     is(menuItem.hidden, false, "update-manual menu item is showing.");
 
     yield PanelUI.hide();
     is(PanelUI.menuButton.getAttribute("badge-status"), "update-manual", "Badge is shown on PanelUI button.");
 
     yield PanelUI.show();
     menuItem.click();
     ok(mainActionCalled, "Main action callback was called");
@@ -131,28 +132,29 @@ add_task(function* testInteractionWithBa
     };
     PanelUI.showNotification("update-manual", mainAction);
 
     isnot(PanelUI.menuButton.getAttribute("badge-status"), "fxa-needs-authentication", "Fxa badge is hidden on PanelUI button.");
     isnot(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is showing.");
     let notifications = [...PanelUI.notificationPanel.children].filter(n => !n.hidden);
     is(notifications.length, 1, "PanelUI doorhanger is only displaying one notification.");
     let doorhanger = notifications[0];
-    is(doorhanger.id, "PanelUI-update-manual-notification", "PanelUI is displaying the update-manual notification.");
+    is(doorhanger.id, "appMenu-update-manual-notification", "PanelUI is displaying the update-manual notification.");
 
     let secondaryActionButton = doc.getAnonymousElementByAttribute(doorhanger, "anonid", "secondarybutton");
     secondaryActionButton.click();
 
     is(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is closed.");
 
     is(PanelUI.menuButton.getAttribute("badge-status"), "update-manual", "Badge is displaying on PanelUI button.");
 
     yield PanelUI.show();
     isnot(PanelUI.menuButton.getAttribute("badge-status"), "update-manual", "Badge is hidden on PanelUI button.");
-    let menuItem = doc.getElementById("PanelUI-update-manual-menu-item");
+    let menuItem = PanelUI.mainView.querySelector(".panel-banner-item");
+    is(menuItem.label, menuItem.getAttribute("label-update-manual"), "Showing correct label");
     is(menuItem.hidden, false, "update-manual menu item is showing.");
 
     menuItem.click();
     ok(mainActionCalled, "Main action callback was called");
 
     is(PanelUI.menuButton.getAttribute("badge-status"), "fxa-needs-authentication", "Fxa badge is shown on PanelUI button.");
     PanelUI.removeNotification(/.*/);
     is(PanelUI.menuButton.hasAttribute("badge-status"), false, "Should not have a badge status");
@@ -174,17 +176,17 @@ add_task(function* testAddingBadgeWhileD
     PanelUI.showNotification("update-manual", mainAction);
     PanelUI.showBadgeOnlyNotification("fxa-needs-authentication");
 
     isnot(PanelUI.menuButton.getAttribute("badge-status"), "fxa-needs-authentication", "Fxa badge is hidden on PanelUI button.");
     isnot(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is showing.");
     let notifications = [...PanelUI.notificationPanel.children].filter(n => !n.hidden);
     is(notifications.length, 1, "PanelUI doorhanger is only displaying one notification.");
     let doorhanger = notifications[0];
-    is(doorhanger.id, "PanelUI-update-manual-notification", "PanelUI is displaying the update-manual notification.");
+    is(doorhanger.id, "appMenu-update-manual-notification", "PanelUI is displaying the update-manual notification.");
 
     let mainActionButton = doc.getAnonymousElementByAttribute(doorhanger, "anonid", "button");
     mainActionButton.click();
 
     ok(mainActionCalled, "Main action callback was called");
     is(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is closed.");
     is(PanelUI.menuButton.getAttribute("badge-status"), "fxa-needs-authentication", "Fxa badge is shown on PanelUI button.");
     PanelUI.removeNotification(/.*/);
@@ -260,48 +262,47 @@ add_task(function* testMultipleNonBadges
 
     let notifications;
     let doorhanger;
 
     isnot(PanelUI.notificationPanel.state, "closed", "Doorhanger is showing.");
     notifications = [...PanelUI.notificationPanel.children].filter(n => !n.hidden);
     is(notifications.length, 1, "PanelUI doorhanger is only displaying one notification.");
     doorhanger = notifications[0];
-    is(doorhanger.id, "PanelUI-update-manual-notification", "PanelUI is displaying the update-manual notification.");
+    is(doorhanger.id, "appMenu-update-manual-notification", "PanelUI is displaying the update-manual notification.");
 
     PanelUI.showNotification("update-restart", updateRestartAction);
 
     isnot(PanelUI.notificationPanel.state, "closed", "Doorhanger is showing.");
     notifications = [...PanelUI.notificationPanel.children].filter(n => !n.hidden);
     is(notifications.length, 1, "PanelUI doorhanger is only displaying one notification.");
     doorhanger = notifications[0];
-    is(doorhanger.id, "PanelUI-update-restart-notification", "PanelUI is displaying the update-restart notification.");
+    is(doorhanger.id, "appMenu-update-restart-notification", "PanelUI is displaying the update-restart notification.");
 
     let secondaryActionButton = doc.getAnonymousElementByAttribute(doorhanger, "anonid", "secondarybutton");
     secondaryActionButton.click();
 
     is(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is closed.");
     is(PanelUI.menuButton.getAttribute("badge-status"), "update-restart", "update-restart badge is displaying on PanelUI button.");
 
-    let menuItem;
-
     yield PanelUI.show();
     isnot(PanelUI.menuButton.getAttribute("badge-status"), "update-restart", "update-restart badge is hidden on PanelUI button.");
-    menuItem = doc.getElementById("PanelUI-update-restart-menu-item");
+    let menuItem = PanelUI.mainView.querySelector(".panel-banner-item");
+    is(menuItem.label, menuItem.getAttribute("label-update-restart"), "Showing correct label");
     is(menuItem.hidden, false, "update-restart menu item is showing.");
 
     menuItem.click();
     ok(updateRestartAction.called, "update-restart main action callback was called");
 
     is(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is closed.");
     is(PanelUI.menuButton.getAttribute("badge-status"), "update-manual", "update-manual badge is displaying on PanelUI button.");
 
     yield PanelUI.show();
     isnot(PanelUI.menuButton.getAttribute("badge-status"), "update-manual", "update-manual badge is hidden on PanelUI button.");
-    menuItem = doc.getElementById("PanelUI-update-manual-menu-item");
+    is(menuItem.label, menuItem.getAttribute("label-update-manual"), "Showing correct label");
     is(menuItem.hidden, false, "update-manual menu item is showing.");
 
     menuItem.click();
     ok(updateManualAction.called, "update-manual main action callback was called");
   });
 });
 
 add_task(function* testFullscreen() {
@@ -313,17 +314,17 @@ add_task(function* testFullscreen() {
     callback: () => { mainActionCalled = true; }
   };
   PanelUI.showNotification("update-manual", mainAction);
 
   isnot(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is showing.");
   let notifications = [...PanelUI.notificationPanel.children].filter(n => !n.hidden);
   is(notifications.length, 1, "PanelUI doorhanger is only displaying one notification.");
   let doorhanger = notifications[0];
-  is(doorhanger.id, "PanelUI-update-manual-notification", "PanelUI is displaying the update-manual notification.");
+  is(doorhanger.id, "appMenu-update-manual-notification", "PanelUI is displaying the update-manual notification.");
 
   let popuphiddenPromise = BrowserTestUtils.waitForEvent(PanelUI.notificationPanel, "popuphidden");
   EventUtils.synthesizeKey("VK_F11", {});
   yield popuphiddenPromise;
   yield new Promise(executeSoon);
   is(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is closed.");
 
   window.FullScreen.showNavToolbox();
--- a/browser/components/preferences/in-content/findInPage.js
+++ b/browser/components/preferences/in-content/findInPage.js
@@ -198,25 +198,26 @@ var gSearchResultsPane = {
       gotoPref("paneSearchResults");
 
       this.searchResultsCategory.hidden = false;
 
       let resultsFound = false;
 
       // Building the range for highlighted areas
       let rootPreferences = document.getElementById("mainPrefPane")
-      let rootPreferencesChildren = rootPreferences.children;
+      let rootPreferencesChildren = rootPreferences
+        .querySelectorAll(":not([data-hidden-from-search])");
 
       // Showing all the children to bind JS, Access Keys, etc
-      for (let i = 0; i < rootPreferences.childElementCount; i++) {
+      for (let i = 0; i < rootPreferencesChildren.length; i++) {
         rootPreferencesChildren[i].hidden = false;
       }
 
       // Showing or Hiding specific section depending on if words in query are found
-      for (let i = 0; i < rootPreferences.childElementCount; i++) {
+      for (let i = 0; i < rootPreferencesChildren.length; i++) {
         if (rootPreferencesChildren[i].className != "header" &&
             rootPreferencesChildren[i].className != "no-results-message" &&
             this.searchWithinNode(rootPreferencesChildren[i], query)) {
           rootPreferencesChildren[i].hidden = false;
           resultsFound = true;
         } else {
           rootPreferencesChildren[i].hidden = true;
         }
@@ -290,16 +291,16 @@ var gSearchResultsPane = {
         valueResult = this.stringMatchesFilters(nodeObject.getAttribute("value"), searchPhrase);
       }
 
       matchesFound = matchesFound || complexTextNodesResult || labelResult || valueResult;
     }
 
     for (let i = 0; i < nodeObject.childNodes.length; i++) {
       // Search only if child node is not hidden
-      if (!nodeObject.childNodes[i].hidden) {
+      if (!nodeObject.childNodes[i].hidden && nodeObject.getAttribute("data-hidden-from-search") !== "true") {
         let result = this.searchWithinNode(nodeObject.childNodes[i], searchPhrase);
         matchesFound = matchesFound || result;
       }
     }
     return matchesFound;
   }
 }
--- a/browser/components/preferences/in-content/tests/browser_search_within_preferences.js
+++ b/browser/components/preferences/in-content/tests/browser_search_within_preferences.js
@@ -166,8 +166,40 @@ add_task(function*() {
   searchInput.value = "";
   searchInput.doCommand()
 
   // Checks if back to normal
   is_element_visible(generalPane, "Should be in generalPane");
 
   yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
+
+/**
+ * Test for "Site Data" case, verifying elements with data-hidden-from-search = true
+ * are hidden in search result.
+ */
+add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({"set": [["browser.storageManager.enabled", false]]});
+  yield openPreferencesViaOpenPreferencesAPI("privacy", {leaveOpen: true});
+  let generalPane = gBrowser.contentDocument.getElementById("header-general");
+
+  is_element_hidden(generalPane, "Should not be in general");
+
+  // Performs search
+  let searchInput = gBrowser.contentDocument.getElementById("searchInput");
+  searchInput.doCommand()
+  searchInput.value = "site data";
+  searchInput.doCommand()
+
+  let mainPrefTag = gBrowser.contentDocument.getElementById("mainPrefPane");
+
+  let child = mainPrefTag.querySelector("#siteDataGroup");
+  is_element_hidden(child, "Should be hidden in search results");
+
+  // Takes search off
+  searchInput.value = "";
+  searchInput.doCommand()
+
+  // Checks if back to normal
+  is_element_visible(generalPane, "Should be in generalPane");
+
+  yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
--- a/browser/themes/shared/customizableui/panelUI.inc.css
+++ b/browser/themes/shared/customizableui/panelUI.inc.css
@@ -119,26 +119,22 @@
   border: 1px solid -moz-dialog;
   /* "!important" is necessary to override the rule in toolbarbutton.css */
   margin: -9px 0 0 !important;
   margin-inline-end: -6px !important;
   min-width: 16px;
   min-height: 16px;
 }
 
-#PanelUI-update-restart-menu-item::after,
-#PanelUI-update-available-menu-item::after,
-#PanelUI-update-manual-menu-item::after {
+.panel-banner-item[notificationid^=update]::after {
   background: #74BF43 url(chrome://browser/skin/update-badge.svg) no-repeat center;
   border-radius: 50%;
 }
 
-#PanelUI-update-restart-menu-item,
-#PanelUI-update-available-menu-item,
-#PanelUI-update-manual-menu-item {
+.panel-banner-item[notificationid^=update] {
   list-style-image: url(chrome://branding/content/icon16.png);
 }
 
 #PanelUI-menu-button[badge-status="download-warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
 #PanelUI-menu-button[badge-status="fxa-needs-authentication"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
   box-shadow: none;
   filter: drop-shadow(0 1px 0 hsla(206, 50%, 10%, .15));
 }
@@ -313,17 +309,17 @@ panelmultiview[nosubviews=true] > .panel
   padding: 0;
 }
 
 panelview[id^=PanelUI-webext-] {
   overflow: hidden;
 }
 
 panelview:not([mainview]) .toolbarbutton-text,
-.cui-widget-panel toolbarbutton > .toolbarbutton-text {
+.cui-widget-panel toolbarbutton:not([wrap]) > .toolbarbutton-text {
   text-align: start;
   display: -moz-box;
 }
 
 .cui-widget-panel > .panel-arrowcontainer > .panel-arrowcontent {
   padding: 4px 0;
 }
 
@@ -460,17 +456,17 @@ toolbaritem[cui-areatype="menu-panel"][s
 }
 
 #PanelUI-multiView[viewtype="subview"] > .panel-viewcontainer > .panel-viewstack > .panel-mainview >  #PanelUI-mainView {
   background-color: var(--arrowpanel-dimmed);
 }
 
 #PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-contents-scroller > #PanelUI-contents > .panel-wide-item,
 #PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-contents-scroller > #PanelUI-contents > .toolbarbutton-1:not([panel-multiview-anchor="true"]),
-#PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-footer > .PanelUI-notification-menu-item,
+#PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-footer > .panel-banner-item,
 #PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-footer > #PanelUI-footer-fxa > #PanelUI-fxa-status > #PanelUI-fxa-avatar,
 #PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-footer > #PanelUI-footer-fxa > #PanelUI-fxa-status > #PanelUI-fxa-label,
 #PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-footer > #PanelUI-footer-fxa > #PanelUI-fxa-icon,
 #PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-footer > #PanelUI-footer-inner > #PanelUI-footer-inner > toolbarseparator,
 #PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-footer > #PanelUI-footer-inner > #PanelUI-customize,
 #PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-footer > #PanelUI-footer-inner > #PanelUI-help:not([panel-multiview-anchor="true"]) {
   opacity: .5;
 }
@@ -582,50 +578,50 @@ toolbarpaletteitem[place="palette"] > to
   width: 47px;
   padding-top: 1px;
   display: block;
   text-align: center;
   position: relative;
   top: 25%;
 }
 
-#PanelUI-footer-addons > toolbarbutton::after,
-.PanelUI-notification-menu-item::after {
+.addon-banner-item::after,
+.panel-banner-item::after {
   content: "";
   width: 16px;
   height: 16px;
   margin-inline-end: 16.5px;
   display: -moz-box;
 }
 
-#PanelUI-footer-addons > toolbarbutton {
+.addon-banner-item {
   background-color: #FFEFBF;
-  /* Force border to override `#PanelUI-footer-addons > toolbarbutton` selector below */
+  /* Force border to override `.addon-banner-item` selector below */
   border-top: 1px solid hsl(45, 100%, 77%) !important;
   display: flex;
   flex: 1 1 0%;
   width: calc(@menuPanelWidth@ + 30px);
   padding-inline-start: 15px;
   border-inline-start-style: none;
 }
 
-#PanelUI-footer-addons > toolbarbutton:hover {
+.addon-banner-item:hover {
   background-color: #FFE8A2;
 }
 
-#PanelUI-footer-addons > toolbarbutton:active {
+.addon-banner-item:active {
   background-color: #FFE38F;
 }
 
-#PanelUI-footer-addons > toolbarbutton > .toolbarbutton-icon {
+.addon-banner-item > .toolbarbutton-icon {
   width: 14px;
   height: 14px;
 }
 
-#PanelUI-footer-addons > toolbarbutton::after {
+.addon-banner-item::after {
   background: #FFBF00 url(chrome://browser/skin/update-badge-failed.svg) no-repeat center;
 }
 
 #PanelUI-fxa-status {
   display: flex;
   flex: 1 1 0%;
   width: 1px;
 }
@@ -649,94 +645,103 @@ toolbarpaletteitem[place="palette"] > to
   -moz-appearance: none;
 }
 
 #PanelUI-footer-inner:hover > toolbarseparator,
 #PanelUI-footer-fxa:hover > toolbarseparator {
   margin: 0;
 }
 
-.PanelUI-notification-menu-item,
+.addon-banner-item,
+.panel-banner-item,
 #PanelUI-help,
 #PanelUI-fxa-label,
 #PanelUI-fxa-icon,
-#PanelUI-footer-addons > toolbarbutton,
 #PanelUI-customize,
 #PanelUI-quit {
   margin: 0;
   padding: 11px 0;
   box-sizing: border-box;
   min-height: 40px;
   -moz-appearance: none;
   box-shadow: none;
   border: none;
   border-radius: 0;
   transition: background-color;
   -moz-box-orient: horizontal;
 }
 
-.PanelUI-notification-menu-item {
+.panel-banner-item {
   border-top: 1px solid var(--panel-separator-color);
   border-bottom: 1px solid transparent;
   margin-bottom: -1px;
 }
 
-.PanelUI-notification-menu-item > .toolbarbutton-text {
+/* in Photon, we have a bottom border as well. Reconcile with the above rule
+ * after photon launch. */
+#appMenu-mainView > .panel-subview-body > .panel-banner-item {
+  border-bottom: 1px solid var(--panel-separator-color);
+  margin-bottom: 3px;
+  padding-inline-start: 10px;
+}
+
+
+.panel-banner-item > .toolbarbutton-text {
   width: 0; /* Fancy cropping solution for flexbox. */
 }
 
 #PanelUI-help,
 #PanelUI-quit {
   min-width: 46px;
 }
 
-.PanelUI-notification-menu-item > .toolbarbutton-text,
+.addon-banner-item > .toolbarbutton-text,
+.panel-banner-item > .toolbarbutton-text,
 #PanelUI-fxa-label > .toolbarbutton-text,
-#PanelUI-footer-addons > toolbarbutton > .toolbarbutton-text,
 #PanelUI-customize > .toolbarbutton-text {
   margin: 0;
   padding: 0 6px;
   text-align: start;
 }
 
 #PanelUI-help > .toolbarbutton-text,
 #PanelUI-quit > .toolbarbutton-text,
 #PanelUI-fxa-avatar > .toolbarbutton-text {
   display: none;
 }
 
-.PanelUI-notification-menu-item > .toolbarbutton-icon,
+.panel-banner-item > .toolbarbutton-icon,
 #PanelUI-fxa-label > .toolbarbutton-icon,
 #PanelUI-fxa-icon > .toolbarbutton-icon,
 #PanelUI-customize > .toolbarbutton-icon,
 #PanelUI-help > .toolbarbutton-icon,
 #PanelUI-quit > .toolbarbutton-icon {
   margin-inline-end: 0;
 }
 
 #PanelUI-fxa-icon {
   padding-inline-start: 15px;
   padding-inline-end: 15px;
 }
 
 #PanelUI-fxa-label,
-#PanelUI-footer-addons > toolbarbutton,
+.addon-banner-item,
 #PanelUI-customize {
   flex: 1;
   padding-inline-start: 15px;
   border-inline-start-style: none;
 }
 
 #PanelUI-footer-fxa[fxastatus="signedin"] > #PanelUI-fxa-status > #PanelUI-fxa-label {
   padding-inline-start: 0px;
 }
 
 /* descend from #PanelUI-footer to add specificity, or else the
    padding-inline-start will be overridden */
-#PanelUI-footer > .PanelUI-notification-menu-item {
+#PanelUI-footer > .panel-banner-item {
   width: calc(@menuPanelWidth@ + 30px);
   padding-inline-start: 15px;
   border-inline-start-style: none;
 }
 
 #PanelUI-fxa-label,
 #PanelUI-fxa-icon {
   list-style-image: url(chrome://browser/skin/sync-horizontalbar.png);
@@ -873,17 +878,17 @@ toolbarpaletteitem[place="palette"] > to
 
 #PanelUI-quit {
   border-inline-end-style: none;
   list-style-image: url(chrome://browser/skin/menuPanel-exit.png);
 }
 
 #PanelUI-fxa-label,
 #PanelUI-fxa-icon,
-#PanelUI-footer-addons > toolbarbutton,
+.addon-banner-item,
 #PanelUI-customize,
 #PanelUI-help,
 #PanelUI-quit {
   -moz-image-region: rect(0, 16px, 16px, 0);
 }
 
 #PanelUI-footer-fxa[fxastatus="signedin"] > #PanelUI-fxa-status > #PanelUI-fxa-label > .toolbarbutton-icon,
 #PanelUI-footer-fxa:not([fxastatus="signedin"]) > #PanelUI-fxa-status > #PanelUI-fxa-avatar {
@@ -967,26 +972,26 @@ toolbarpaletteitem[place="palette"] > to
 }
 
 #PanelUI-footer-fxa[fxastatus="login-failed"] > #PanelUI-fxa-status:hover:active,
 #PanelUI-footer-fxa[fxastatus="unverified"] > #PanelUI-fxa-status:hover:active {
   background-color: hsl(42,94%,82%);
   box-shadow: 0 1px 0 hsla(210,4%,10%,.05) inset;
 }
 
-.PanelUI-notification-menu-item {
+.panel-banner-item {
   color: black;
   background-color: hsla(96,65%,75%,.5);
 }
 
-.PanelUI-notification-menu-item:not([disabled]):hover {
+.panel-banner-item:not([disabled]):hover {
   background-color: hsla(96,65%,75%,.8);
 }
 
-.PanelUI-notification-menu-item:not([disabled]):hover:active {
+.panel-banner-item:not([disabled]):hover:active {
   background-color: hsl(96,65%,75%);
 }
 
 #PanelUI-quit:not([disabled]):hover {
   background-color: #d94141;
   outline-color: #c23a3a;
 }
 
@@ -1692,19 +1697,17 @@ menuitem[checked="true"].subviewbutton >
   }
 
   #PanelUI-help[panel-multiview-anchor="true"]:-moz-locale-dir(rtl)::after,
   toolbarbutton[panel-multiview-anchor="true"]:-moz-locale-dir(rtl) {
     background-image: url(chrome://browser/skin/customizableui/subView-arrow-back-inverted-rtl@2x.png),
                       linear-gradient(rgba(255,255,255,0.3), transparent);
   }
 
-  #PanelUI-update-restart-menu-item,
-  #PanelUI-update-available-menu-item,
-  #PanelUI-update-manual-menu-item {
+  .panel-banner-item[notificationid^=update] {
     list-style-image: url(chrome://branding/content/icon32.png);
   }
 
   #PanelUI-fxa-label,
   #PanelUI-fxa-icon {
     list-style-image: url(chrome://browser/skin/sync-horizontalbar@2x.png);
   }
 
@@ -1731,17 +1734,17 @@ menuitem[checked="true"].subviewbutton >
   #PanelUI-fxa-label,
   #PanelUI-fxa-icon,
   #PanelUI-customize,
   #PanelUI-help,
   #PanelUI-quit {
     -moz-image-region: rect(0, 32px, 32px, 0);
   }
 
-  .PanelUI-notification-menu-item > .toolbarbutton-icon,
+  .panel-banner-item > .toolbarbutton-icon,
   #PanelUI-fxa-label > .toolbarbutton-icon,
   #PanelUI-fxa-icon > .toolbarbutton-icon,
   #PanelUI-customize > .toolbarbutton-icon,
   #PanelUI-help > .toolbarbutton-icon,
   #PanelUI-quit > .toolbarbutton-icon {
     width: 16px;
   }
 
--- a/config/external/moz.build
+++ b/config/external/moz.build
@@ -28,16 +28,19 @@ if CONFIG['MOZ_TREMOR']:
     external_dirs += ['media/libtremor']
 
 if CONFIG['MOZ_WEBM_ENCODER']:
     external_dirs += ['media/libmkv']
 
 if not CONFIG['MOZ_SYSTEM_LIBVPX']:
     external_dirs += ['media/libvpx']
 
+if CONFIG['MOZ_AV1']:
+    external_dirs += ['media/libaom']
+
 if not CONFIG['MOZ_SYSTEM_PNG']:
     external_dirs += ['media/libpng']
 
 if CONFIG['CPU_ARCH'] == 'arm':
     external_dirs += ['media/openmax_dl']
 
 if CONFIG['MOZ_WEBSPEECH_POCKETSPHINX']:
     external_dirs += [
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -195,16 +195,17 @@ GK_ATOM(children, "children")
 GK_ATOM(childList, "childList")
 GK_ATOM(choose, "choose")
 GK_ATOM(chromemargin, "chromemargin")
 GK_ATOM(chromeOnlyContent, "chromeOnlyContent")
 GK_ATOM(exposeToUntrustedContent, "exposeToUntrustedContent")
 GK_ATOM(circ, "circ")
 GK_ATOM(circle, "circle")
 GK_ATOM(cite, "cite")
+GK_ATOM(cjkDecimal, "cjk-decimal")
 GK_ATOM(_class, "class")
 GK_ATOM(classid, "classid")
 GK_ATOM(clear, "clear")
 GK_ATOM(click, "click")
 GK_ATOM(clickcount, "clickcount")
 GK_ATOM(clickthrough, "clickthrough")
 GK_ATOM(movetoclick, "movetoclick")
 GK_ATOM(clip, "clip")
@@ -268,16 +269,17 @@ GK_ATOM(datalist, "datalist")
 GK_ATOM(dataType, "data-type")
 GK_ATOM(dateTime, "date-time")
 GK_ATOM(datasources, "datasources")
 GK_ATOM(datetime, "datetime")
 GK_ATOM(datetimebox, "datetimebox")
 GK_ATOM(dblclick, "dblclick")
 GK_ATOM(dd, "dd")
 GK_ATOM(debug, "debug")
+GK_ATOM(decimal, "decimal")
 GK_ATOM(decimalFormat, "decimal-format")
 GK_ATOM(decimalSeparator, "decimal-separator")
 GK_ATOM(deck, "deck")
 GK_ATOM(declare, "declare")
 GK_ATOM(decoderDoctor, "decoder-doctor")
 GK_ATOM(decrement, "decrement")
 GK_ATOM(_default, "default")
 GK_ATOM(headerDefaultStyle, "default-style")
@@ -557,17 +559,19 @@ GK_ATOM(listitem, "listitem")
 GK_ATOM(listrows, "listrows")
 GK_ATOM(load, "load")
 GK_ATOM(loadingprincipal, "loadingprincipal")
 GK_ATOM(localedir, "localedir")
 GK_ATOM(localName, "local-name")
 GK_ATOM(longdesc, "longdesc")
 GK_ATOM(loop, "loop")
 GK_ATOM(low, "low")
+GK_ATOM(lowerAlpha, "lower-alpha")
 GK_ATOM(lowerFirst, "lower-first")
+GK_ATOM(lowerRoman, "lower-roman")
 GK_ATOM(lowest, "lowest")
 GK_ATOM(lowsrc, "lowsrc")
 GK_ATOM(ltr, "ltr")
 GK_ATOM(lwtheme, "lwtheme")
 GK_ATOM(lwthemetextcolor, "lwthemetextcolor")
 GK_ATOM(main, "main")
 GK_ATOM(map, "map")
 GK_ATOM(manifest, "manifest")
@@ -1283,17 +1287,19 @@ GK_ATOM(tv, "tv")
 GK_ATOM(type, "type")
 GK_ATOM(typemustmatch, "typemustmatch")
 GK_ATOM(u, "u")
 GK_ATOM(ul, "ul")
 GK_ATOM(underflow, "underflow")
 GK_ATOM(undetermined, "undetermined")
 GK_ATOM(unload, "unload")
 GK_ATOM(unparsedEntityUri, "unparsed-entity-uri")
+GK_ATOM(upperAlpha, "upper-alpha")
 GK_ATOM(upperFirst, "upper-first")
+GK_ATOM(upperRoman, "upper-roman")
 GK_ATOM(uri, "uri")
 GK_ATOM(use, "use")
 GK_ATOM(useAttributeSets, "use-attribute-sets")
 GK_ATOM(usemap, "usemap")
 GK_ATOM(user_scalable, "user-scalable")
 GK_ATOM(userInput, "userInput")
 GK_ATOM(validate, "validate")
 GK_ATOM(valign, "valign")
@@ -1387,16 +1393,17 @@ GK_ATOM(d, "d")
 GK_ATOM(darken, "darken")
 GK_ATOM(defs, "defs")
 GK_ATOM(deg, "deg")
 GK_ATOM(desc, "desc")
 GK_ATOM(diffuseConstant, "diffuseConstant")
 GK_ATOM(dilate, "dilate")
 GK_ATOM(direction, "direction")
 GK_ATOM(disable, "disable")
+GK_ATOM(disc, "disc")
 GK_ATOM(discrete, "discrete")
 GK_ATOM(divisor, "divisor")
 GK_ATOM(dominant_baseline, "dominant-baseline")
 GK_ATOM(duplicate, "duplicate")
 GK_ATOM(dx, "dx")
 GK_ATOM(dy, "dy")
 GK_ATOM(edgeMode, "edgeMode")
 GK_ATOM(ellipse, "ellipse")
@@ -2389,8 +2396,272 @@ GK_ATOM(nsuri_svg, "http://www.w3.org/20
 GK_ATOM(onsourceopen, "onsourceopen")
 GK_ATOM(onsourceended, "onsourceended")
 GK_ATOM(onsourceclosed, "onsourceclosed")
 GK_ATOM(onupdatestart, "onupdatestart")
 GK_ATOM(onupdate, "onupdate")
 GK_ATOM(onupdateend, "onupdateend")
 GK_ATOM(onaddsourcebuffer, "onaddsourcebuffer")
 GK_ATOM(onremovesourcebuffer, "onremovesourcebuffer")
+
+// THE REST OF THE FILE IS GENERATED BY THE HTML PARSER TRANSLATOR AND
+// WILL BE OVERWRITTEN!
+// Please put manually-added atoms above this section and please avoid #ifdefing
+// them so that the translator doesn't need to learn to deal with conditionally
+// present manual atoms.
+// BEGIN GENERATED
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(xlink, "xlink")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(xml_space, "xml:space")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(xml_lang, "xml:lang")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(xml_base, "xml:base")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(aria_grab, "aria-grab")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(aria_channel, "aria-channel")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(aria_secret, "aria-secret")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(aria_templateid, "aria-templateid")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(aria_datatype, "aria-datatype")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(local, "local")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(xchannelselector, "xchannelselector")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(ychannelselector, "ychannelselector")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(enable_background, "enable-background")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(calcmode, "calcmode")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(specularexponent, "specularexponent")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(specularconstant, "specularconstant")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(gradienttransform, "gradienttransform")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(gradientunits, "gradientunits")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(rendering_intent, "rendering-intent")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(stddeviation, "stddeviation")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(basefrequency, "basefrequency")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(baseprofile, "baseprofile")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(baseProfile, "baseProfile")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(edgemode, "edgemode")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(repeatcount, "repeatcount")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(repeatdur, "repeatdur")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(spreadmethod, "spreadmethod")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(diffuseconstant, "diffuseconstant")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(surfacescale, "surfacescale")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(lengthadjust, "lengthadjust")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(origin, "origin")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(targetx, "targetx")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(targety, "targety")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(pathlength, "pathlength")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(definitionurl, "definitionurl")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(limitingconeangle, "limitingconeangle")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(markerheight, "markerheight")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(markerwidth, "markerwidth")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(maskunits, "maskunits")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(markerunits, "markerunits")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(maskcontentunits, "maskcontentunits")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(tablevalues, "tablevalues")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(primitiveunits, "primitiveunits")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(zoomandpan, "zoomandpan")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(kernelmatrix, "kernelmatrix")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(kerning, "kerning")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(kernelunitlength, "kernelunitlength")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(pointsatx, "pointsatx")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(pointsaty, "pointsaty")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(pointsatz, "pointsatz")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(xlink_href, "xlink:href")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(xlink_title, "xlink:title")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(xlink_role, "xlink:role")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(xlink_arcrole, "xlink:arcrole")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(arcrole, "arcrole")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(xmlns_xlink, "xmlns:xlink")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(xlink_type, "xlink:type")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(xlink_show, "xlink:show")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(xlink_actuate, "xlink:actuate")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(color_rendering, "color-rendering")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(numoctaves, "numoctaves")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(onmousewheel, "onmousewheel")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(clippathunits, "clippathunits")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(glyph_orientation_vertical, "glyph-orientation-vertical")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(glyph_orientation_horizontal, "glyph-orientation-horizontal")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(glyphref, "glyphref")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(keypoints, "keypoints")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(attributename, "attributename")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(attributetype, "attributetype")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(startoffset, "startoffset")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(keysplines, "keysplines")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(preservealpha, "preservealpha")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(preserveaspectratio, "preserveaspectratio")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(alttext, "alttext")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(filterunits, "filterunits")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(keytimes, "keytimes")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(patterntransform, "patterntransform")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(patternunits, "patternunits")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(patterncontentunits, "patterncontentunits")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(stitchtiles, "stitchtiles")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(systemlanguage, "systemlanguage")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(textlength, "textlength")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(requiredfeatures, "requiredfeatures")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(requiredextensions, "requiredextensions")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(viewtarget, "viewtarget")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(viewbox, "viewbox")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(refx, "refx")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(refy, "refy")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(isindex, "isindex")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(fefunca, "fefunca")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(fefuncb, "fefuncb")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(feblend, "feblend")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(feflood, "feflood")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(feturbulence, "feturbulence")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(femergenode, "femergenode")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(feimage, "feimage")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(femerge, "femerge")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(fetile, "fetile")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(fecomposite, "fecomposite")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(altglyphdef, "altglyphdef")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(altGlyphDef, "altGlyphDef")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(fefuncg, "fefuncg")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(fediffuselighting, "fediffuselighting")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(fespecularlighting, "fespecularlighting")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(altglyph, "altglyph")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(altGlyph, "altGlyph")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(clippath, "clippath")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(textpath, "textpath")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(altglyphitem, "altglyphitem")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(altGlyphItem, "altGlyphItem")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(animatetransform, "animatetransform")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(animatemotion, "animatemotion")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(fedisplacementmap, "fedisplacementmap")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(animatecolor, "animatecolor")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(fefuncr, "fefuncr")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(fecomponenttransfer, "fecomponenttransfer")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(fegaussianblur, "fegaussianblur")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(foreignobject, "foreignobject")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(feoffset, "feoffset")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(fespotlight, "fespotlight")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(fepointlight, "fepointlight")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(fedistantlight, "fedistantlight")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(lineargradient, "lineargradient")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(radialgradient, "radialgradient")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(fedropshadow, "fedropshadow")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(fecolormatrix, "fecolormatrix")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(feconvolvematrix, "feconvolvematrix")
+// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
+GK_ATOM(femorphology, "femorphology")
+// END GENERATED ATOMS, DO NOT ADD CODE BELOW THIS LINE
--- a/dom/base/test/test_navigator_hardwareConcurrency.html
+++ b/dom/base/test/test_navigator_hardwareConcurrency.html
@@ -1,21 +1,39 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <meta charset="utf-8">
   <title>Test for Navigator.hardwareConcurrency</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript">
+  "use strict";
+
+  function resistFingerprinting(value) {
+    return SpecialPowers.pushPrefEnv({"set": [["privacy.resistFingerprinting", value]]});
+  }
 
   var x = navigator.hardwareConcurrency;
   is(typeof x, "number", "hardwareConcurrency should be a number.");
   ok(x > 0, "hardwareConcurrency should be greater than 0.");
 
+  SimpleTest.waitForExplicitFinish();
+
+  resistFingerprinting(true).then(() => {
+    const y = navigator.hardwareConcurrency;
+    ok(y === 2, "hardwareConcurrency should always be 2 when we're resisting fingerprinting.");
+
+    resistFingerprinting(false).then(() => {
+      const z = navigator.hardwareConcurrency;
+      ok(z === x, "hardwareConcurrency should be the same as before we were resisting fingerprinting.");
+      SimpleTest.finish();
+    });
+  });
+
   </script>
 </head>
 <body>
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -37,17 +37,16 @@
 #include "ProfilerMarkerPayload.h"
 #endif
 #include "nsCOMArray.h"
 #include "nsCOMPtr.h"
 #include "nsContentUtils.h"
 #include "nsDOMCID.h"
 #include "nsError.h"
 #include "nsGkAtoms.h"
-#include "nsHtml5Atoms.h"
 #include "nsIContent.h"
 #include "nsIContentSecurityPolicy.h"
 #include "nsIDocument.h"
 #include "nsIDOMEventListener.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsISupports.h"
 #include "nsIXPConnect.h"
 #include "nsJSUtils.h"
@@ -1789,19 +1788,18 @@ bool
 EventListenerManager::IsApzAwareListener(Listener* aListener)
 {
   return !aListener->mFlags.mPassive && IsApzAwareEvent(aListener->mTypeAtom);
 }
 
 bool
 EventListenerManager::IsApzAwareEvent(nsIAtom* aEvent)
 {
-  if (aEvent == nsGkAtoms::onwheel ||
-      aEvent == nsGkAtoms::onDOMMouseScroll ||
-      aEvent == nsHtml5Atoms::onmousewheel ||
+  if (aEvent == nsGkAtoms::onwheel || aEvent == nsGkAtoms::onDOMMouseScroll ||
+      aEvent == nsGkAtoms::onmousewheel ||
       aEvent == nsGkAtoms::onMozMousePixelScroll) {
     return true;
   }
   // In theory we should schedule a repaint if the touch event pref changes,
   // because the event regions might be out of date. In practice that seems like
   // overkill because users generally shouldn't be flipping this pref, much
   // less expecting touch listeners on the page to immediately start preventing
   // scrolling without so much as a repaint. Tests that we write can work
--- a/dom/filesystem/compat/FileSystemDirectoryReader.h
+++ b/dom/filesystem/compat/FileSystemDirectoryReader.h
@@ -5,16 +5,17 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_FileSystemDirectoryReader_h
 #define mozilla_dom_FileSystemDirectoryReader_h
 
 #include "mozilla/Attributes.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/FileSystemDirectoryEntry.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsWrapperCache.h"
 
 namespace mozilla {
 namespace dom {
 
 class Directory;
 class FileSystem;
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -17,16 +17,17 @@
 #include "nsAttrValueInlines.h"
 #include "nsCRTGlue.h"
 
 #include "nsIDOMHTMLInputElement.h"
 #include "nsITextControlElement.h"
 #include "nsIDOMNSEditableElement.h"
 #include "nsIRadioVisitor.h"
 #include "nsIPhonetic.h"
+#include "InputType.h"
 
 #include "HTMLFormSubmissionConstants.h"
 #include "mozilla/Telemetry.h"
 #include "nsIControllers.h"
 #include "nsIStringBundle.h"
 #include "nsFocusManager.h"
 #include "nsColorControlFrame.h"
 #include "nsNumberControlFrame.h"
@@ -45,17 +46,16 @@
 #include "nsIPresShell.h"
 #include "nsIFormControlFrame.h"
 #include "nsITextControlFrame.h"
 #include "nsIFrame.h"
 #include "nsRangeFrame.h"
 #include "nsIServiceManager.h"
 #include "nsError.h"
 #include "nsIEditor.h"
-#include "nsIIOService.h"
 #include "nsDocument.h"
 #include "nsAttrValueOrString.h"
 #include "nsDateTimeControlFrame.h"
 
 #include "nsPresState.h"
 #include "nsIDOMEvent.h"
 #include "nsIDOMNodeList.h"
 #include "nsIDOMHTMLCollection.h"
@@ -80,18 +80,16 @@
 #include "nsIRadioGroupContainer.h"
 
 // input type=file
 #include "mozilla/dom/FileSystemEntry.h"
 #include "mozilla/dom/FileSystem.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/FileList.h"
 #include "nsIFile.h"
-#include "nsNetCID.h"
-#include "nsNetUtil.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsIContentPrefService.h"
 #include "nsIMIMEService.h"
 #include "nsIObserverService.h"
 #include "nsIPopupWindowManager.h"
 #include "nsGlobalWindow.h"
 
 // input type=image
@@ -124,24 +122,16 @@
 #include "js/Date.h"
 
 NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(Input)
 
 // XXX align=left, hspace, vspace, border? other nav4 attrs
 
 static NS_DEFINE_CID(kXULControllersCID,  NS_XULCONTROLLERS_CID);
 
-// This must come outside of any namespace, or else it won't overload with the
-// double based version in nsMathUtils.h
-inline mozilla::Decimal
-NS_floorModulo(mozilla::Decimal x, mozilla::Decimal y)
-{
-  return (x - y * (x / y).floor());
-}
-
 namespace mozilla {
 namespace dom {
 
 // First bits are needed for the control type.
 #define NS_OUTER_ACTIVATE_EVENT   (1 << 9)
 #define NS_ORIGINAL_CHECKED_VALUE (1 << 10)
 #define NS_NO_CONTENT_DISPATCH    (1 << 11)
 #define NS_ORIGINAL_INDETERMINATE_VALUE (1 << 12)
@@ -1177,16 +1167,19 @@ HTMLInputElement::HTMLInputElement(alrea
   static_assert(sizeof(HTMLInputElement) <= 512,
                 "Keep the size of HTMLInputElement under 512 to avoid "
                 "performance regression!");
 
   // We are in a type=text so we now we currenty need a nsTextEditorState.
   mInputData.mState =
     nsTextEditorState::Construct(this, &sCachedTextEditorState);
 
+  void* memory = mInputTypeMem;
+  mInputType = InputType::Create(this, mType, memory);
+
   if (!gUploadLastDir)
     HTMLInputElement::InitUploadLastDir();
 
   // Set up our default state.  By default we're enabled (since we're
   // a control type that can be disabled but not actually disabled
   // right now), optional, and valid.  We are NOT readwrite by default
   // until someone calls UpdateEditableState on us, apparently!  Also
   // by default we don't have to show validity UI and so forth.
@@ -1211,16 +1204,21 @@ HTMLInputElement::FreeData()
   if (!IsSingleLineTextControl(false)) {
     free(mInputData.mValue);
     mInputData.mValue = nullptr;
   } else {
     UnbindFromFrame(nullptr);
     ReleaseTextEditorState(mInputData.mState);
     mInputData.mState = nullptr;
   }
+
+  if (mInputType) {
+    mInputType->DropReference();
+    mInputType = nullptr;
+  }
 }
 
 nsTextEditorState*
 HTMLInputElement::GetEditorState() const
 {
   if (!IsSingleLineTextControl(false)) {
     return nullptr;
   }
@@ -1452,79 +1450,51 @@ HTMLInputElement::AfterSetAttr(int32_t a
     if (aName == nsGkAtoms::required || aName == nsGkAtoms::disabled ||
         aName == nsGkAtoms::readonly) {
       UpdateValueMissingValidityState();
 
       // This *has* to be called *after* validity has changed.
       if (aName == nsGkAtoms::readonly || aName == nsGkAtoms::disabled) {
         UpdateBarredFromConstraintValidation();
       }
-    } else if (MinOrMaxLengthApplies() && aName == nsGkAtoms::maxlength) {
+    } else if (aName == nsGkAtoms::maxlength) {
       UpdateTooLongValidityState();
-    } else if (MinOrMaxLengthApplies() && aName == nsGkAtoms::minlength) {
+    } else if (aName == nsGkAtoms::minlength) {
       UpdateTooShortValidityState();
     } else if (aName == nsGkAtoms::pattern && mDoneCreating) {
       UpdatePatternMismatchValidityState();
     } else if (aName == nsGkAtoms::multiple) {
       UpdateTypeMismatchValidityState();
     } else if (aName == nsGkAtoms::max) {
       UpdateHasRange();
-      if (mType == NS_FORM_INPUT_RANGE) {
-        // The value may need to change when @max changes since the value may
-        // have been invalid and can now change to a valid value, or vice
-        // versa. For example, consider:
-        // <input type=range value=-1 max=1 step=3>. The valid range is 0 to 1
-        // while the nearest valid steps are -1 and 2 (the max value having
-        // prevented there being a valid step in range). Changing @max to/from
-        // 1 and a number greater than on equal to 3 should change whether we
-        // have a step mismatch or not.
-        // The value may also need to change between a value that results in
-        // a step mismatch and a value that results in overflow. For example,
-        // if @max in the example above were to change from 1 to -1.
-        nsAutoString value;
-        GetNonFileValueInternal(value);
-        nsresult rv =
-          SetValueInternal(value, nsTextEditorState::eSetValue_Internal);
-        NS_ENSURE_SUCCESS(rv, rv);
-      }
-      // Validity state must be updated *after* the SetValueInternal call above
-      // or else the following assert will not be valid.
+      nsresult rv = mInputType->MinMaxStepAttrChanged();
+      NS_ENSURE_SUCCESS(rv, rv);
+      // Validity state must be updated *after* the UpdateValueDueToAttrChange
+      // call above or else the following assert will not be valid.
       // We don't assert the state of underflow during creation since
       // DoneCreatingElement sanitizes.
       UpdateRangeOverflowValidityState();
       MOZ_ASSERT(!mDoneCreating ||
                  mType != NS_FORM_INPUT_RANGE ||
                  !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
                  "HTML5 spec does not allow underflow for type=range");
     } else if (aName == nsGkAtoms::min) {
       UpdateHasRange();
-      if (mType == NS_FORM_INPUT_RANGE) {
-        // See @max comment
-        nsAutoString value;
-        GetNonFileValueInternal(value);
-        nsresult rv =
-          SetValueInternal(value, nsTextEditorState::eSetValue_Internal);
-        NS_ENSURE_SUCCESS(rv, rv);
-      }
+      nsresult rv = mInputType->MinMaxStepAttrChanged();
+      NS_ENSURE_SUCCESS(rv, rv);
       // See corresponding @max comment
       UpdateRangeUnderflowValidityState();
       UpdateStepMismatchValidityState();
       MOZ_ASSERT(!mDoneCreating ||
                  mType != NS_FORM_INPUT_RANGE ||
                  !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
                  "HTML5 spec does not allow underflow for type=range");
     } else if (aName == nsGkAtoms::step) {
-      if (mType == NS_FORM_INPUT_RANGE) {
-        // See @max comment
-        nsAutoString value;
-        GetNonFileValueInternal(value);
-        nsresult rv =
-          SetValueInternal(value, nsTextEditorState::eSetValue_Internal);
-        NS_ENSURE_SUCCESS(rv, rv);
-      }
+      nsresult rv = mInputType->MinMaxStepAttrChanged();
+      NS_ENSURE_SUCCESS(rv, rv);
       // See corresponding @max comment
       UpdateStepMismatchValidityState();
       MOZ_ASSERT(!mDoneCreating ||
                  mType != NS_FORM_INPUT_RANGE ||
                  !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
                  "HTML5 spec does not allow underflow for type=range");
     } else if (aName == nsGkAtoms::dir &&
                aValue && aValue->Equals(nsGkAtoms::_auto, eIgnoreCase)) {
@@ -5237,16 +5207,18 @@ HTMLInputElement::HandleTypeChange(uint8
   if (GetEditorState()) {
     mInputData.mState->SyncUpSelectionPropertiesBeforeDestruction();
     sp = mInputData.mState->GetSelectionProperties();
   }
 
   // We already have a copy of the value, lets free it and changes the type.
   FreeData();
   mType = aNewType;
+  void* memory = mInputTypeMem;
+  mInputType = InputType::Create(this, mType, memory);
 
   if (IsSingleLineTextControl()) {
 
     mInputData.mState =
       nsTextEditorState::Construct(this, &sCachedTextEditorState);
     if (!sp.IsDefault()) {
       mInputData.mState->SetSelectionProperties(sp);
     }
@@ -7367,27 +7339,16 @@ HTMLInputElement::PlaceholderApplies() c
   if (IsDateTimeInputType(mType)) {
     return false;
   }
 
   return IsSingleLineTextControl(false);
 }
 
 bool
-HTMLInputElement::DoesPatternApply() const
-{
-  // TODO: temporary until bug 773205 is fixed.
-  if (IsExperimentalMobileType(mType) || IsDateTimeInputType(mType)) {
-    return false;
-  }
-
-  return IsSingleLineTextControl(false);
-}
-
-bool
 HTMLInputElement::DoesMinMaxApply() const
 {
   switch (mType)
   {
     case NS_FORM_INPUT_NUMBER:
     case NS_FORM_INPUT_DATE:
     case NS_FORM_INPUT_TIME:
     case NS_FORM_INPUT_RANGE:
@@ -7504,305 +7465,77 @@ HTMLInputElement::SetCustomValidity(cons
 
   return NS_OK;
 }
 
 bool
 HTMLInputElement::IsTooLong()
 {
   if (!mValueChanged ||
-      !mLastValueChangeWasInteractive ||
-      !MinOrMaxLengthApplies() ||
-      !HasAttr(kNameSpaceID_None, nsGkAtoms::maxlength)) {
+      !mLastValueChangeWasInteractive) {
     return false;
   }
 
-  int32_t maxLength = MaxLength();
-
-  // Maxlength of -1 means parsing error.
-  if (maxLength == -1) {
-    return false;
-  }
-
-  return InputTextLength(CallerType::System) > maxLength;
+  return mInputType->IsTooLong();
 }
 
 bool
 HTMLInputElement::IsTooShort()
 {
   if (!mValueChanged ||
-      !mLastValueChangeWasInteractive ||
-      !MinOrMaxLengthApplies() ||
-      !HasAttr(kNameSpaceID_None, nsGkAtoms::minlength)) {
+      !mLastValueChangeWasInteractive) {
     return false;
   }
 
-  int32_t minLength = MinLength();
-
-  // Minlength of -1 means parsing error.
-  if (minLength == -1) {
-    return false;
-  }
-
-  int32_t textLength = InputTextLength(CallerType::System);
-
-  return textLength && textLength < minLength;
+  return mInputType->IsTooShort();
 }
 
 bool
 HTMLInputElement::IsValueMissing() const
 {
   // Should use UpdateValueMissingValidityStateForRadio() for type radio.
   MOZ_ASSERT(mType != NS_FORM_INPUT_RADIO);
 
-  if (!HasAttr(kNameSpaceID_None, nsGkAtoms::required) ||
-      !DoesRequiredApply()) {
-    return false;
-  }
-
-  if (!IsMutable()) {
-    return false;
-  }
-
-  switch (GetValueMode()) {
-    case VALUE_MODE_VALUE:
-      return IsValueEmpty();
-
-    case VALUE_MODE_FILENAME:
-      return GetFilesOrDirectoriesInternal().IsEmpty();
-
-    case VALUE_MODE_DEFAULT_ON:
-      // This should not be used for type radio.
-      // See the MOZ_ASSERT at the beginning of the method.
-      return !mChecked;
-
-    case VALUE_MODE_DEFAULT:
-    default:
-      return false;
-  }
+  return mInputType->IsValueMissing();
 }
 
 bool
 HTMLInputElement::HasTypeMismatch() const
 {
-  if (mType != NS_FORM_INPUT_EMAIL && mType != NS_FORM_INPUT_URL) {
-    return false;
-  }
-
-  nsAutoString value;
-  GetNonFileValueInternal(value);
-
-  if (value.IsEmpty()) {
-    return false;
-  }
-
-  if (mType == NS_FORM_INPUT_EMAIL) {
-    return HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)
-             ? !IsValidEmailAddressList(value) : !IsValidEmailAddress(value);
-  } else if (mType == NS_FORM_INPUT_URL) {
-    /**
-     * TODO:
-     * The URL is not checked as the HTML5 specifications want it to be because
-     * there is no code to check for a valid URI/IRI according to 3986 and 3987
-     * RFC's at the moment, see bug 561586.
-     *
-     * RFC 3987 (IRI) implementation: bug 42899
-     *
-     * HTML5 specifications:
-     * http://dev.w3.org/html5/spec/infrastructure.html#valid-url
-     */
-    nsCOMPtr<nsIIOService> ioService = do_GetIOService();
-    nsCOMPtr<nsIURI> uri;
-
-    return !NS_SUCCEEDED(ioService->NewURI(NS_ConvertUTF16toUTF8(value), nullptr,
-                                           nullptr, getter_AddRefs(uri)));
-  }
-
-  return false;
+  return mInputType->HasTypeMismatch();
 }
 
 bool
 HTMLInputElement::HasPatternMismatch() const
 {
-  if (!DoesPatternApply() ||
-      !HasAttr(kNameSpaceID_None, nsGkAtoms::pattern)) {
-    return false;
-  }
-
-  nsAutoString pattern;
-  GetAttr(kNameSpaceID_None, nsGkAtoms::pattern, pattern);
-
-  nsAutoString value;
-  GetNonFileValueInternal(value);
-
-  if (value.IsEmpty()) {
-    return false;
-  }
-
-  nsIDocument* doc = OwnerDoc();
-
-  return !nsContentUtils::IsPatternMatching(value, pattern, doc);
+  return mInputType->HasPatternMismatch();
 }
 
 bool
 HTMLInputElement::IsRangeOverflow() const
 {
-  if (!DoesMinMaxApply()) {
-    return false;
-  }
-
-  Decimal maximum = GetMaximum();
-  if (maximum.isNaN()) {
-    return false;
-  }
-
-  Decimal value = GetValueAsDecimal();
-  if (value.isNaN()) {
-    return false;
-  }
-
-  return value > maximum;
+  return mInputType->IsRangeOverflow();
 }
 
 bool
 HTMLInputElement::IsRangeUnderflow() const
 {
-  if (!DoesMinMaxApply()) {
-    return false;
-  }
-
-  Decimal minimum = GetMinimum();
-  if (minimum.isNaN()) {
-    return false;
-  }
-
-  Decimal value = GetValueAsDecimal();
-  if (value.isNaN()) {
-    return false;
-  }
-
-  return value < minimum;
+  return mInputType->IsRangeUnderflow();
 }
 
 bool
 HTMLInputElement::HasStepMismatch(bool aUseZeroIfValueNaN) const
 {
-  if (!DoesStepApply()) {
-    return false;
-  }
-
-  Decimal value = GetValueAsDecimal();
-  if (value.isNaN()) {
-    if (aUseZeroIfValueNaN) {
-      value = Decimal(0);
-    } else {
-      // The element can't suffer from step mismatch if it's value isn't a number.
-      return false;
-    }
-  }
-
-  Decimal step = GetStep();
-  if (step == kStepAny) {
-    return false;
-  }
-
-  // Value has to be an integral multiple of step.
-  return NS_floorModulo(value - GetStepBase(), step) != Decimal(0);
-}
-
-/**
- * Takes aEmail and attempts to convert everything after the first "@"
- * character (if anything) to punycode before returning the complete result via
- * the aEncodedEmail out-param. The aIndexOfAt out-param is set to the index of
- * the "@" character.
- *
- * If no "@" is found in aEmail, aEncodedEmail is simply set to aEmail and
- * the aIndexOfAt out-param is set to kNotFound.
- *
- * Returns true in all cases unless an attempt to punycode encode fails. If
- * false is returned, aEncodedEmail has not been set.
- *
- * This function exists because ConvertUTF8toACE() splits on ".", meaning that
- * for 'user.name@sld.tld' it would treat "name@sld" as a label. We want to
- * encode the domain part only.
- */
-static bool PunycodeEncodeEmailAddress(const nsAString& aEmail,
-                                       nsAutoCString& aEncodedEmail,
-                                       uint32_t* aIndexOfAt)
-{
-  nsAutoCString value = NS_ConvertUTF16toUTF8(aEmail);
-  *aIndexOfAt = (uint32_t)value.FindChar('@');
-
-  if (*aIndexOfAt == (uint32_t)kNotFound ||
-      *aIndexOfAt == value.Length() - 1) {
-    aEncodedEmail = value;
-    return true;
-  }
-
-  nsCOMPtr<nsIIDNService> idnSrv = do_GetService(NS_IDNSERVICE_CONTRACTID);
-  if (!idnSrv) {
-    NS_ERROR("nsIIDNService isn't present!");
-    return false;
-  }
-
-  uint32_t indexOfDomain = *aIndexOfAt + 1;
-
-  const nsDependentCSubstring domain = Substring(value, indexOfDomain);
-  bool ace;
-  if (NS_SUCCEEDED(idnSrv->IsACE(domain, &ace)) && !ace) {
-    nsAutoCString domainACE;
-    if (NS_FAILED(idnSrv->ConvertUTF8toACE(domain, domainACE))) {
-      return false;
-    }
-    value.Replace(indexOfDomain, domain.Length(), domainACE);
-  }
-
-  aEncodedEmail = value;
-  return true;
+  return mInputType->HasStepMismatch(aUseZeroIfValueNaN);
 }
 
 bool
 HTMLInputElement::HasBadInput() const
 {
-  if (mType == NS_FORM_INPUT_NUMBER) {
-    nsAutoString value;
-    GetNonFileValueInternal(value);
-    if (!value.IsEmpty()) {
-      // The input can't be bad, otherwise it would have been sanitized to the
-      // empty string.
-      NS_ASSERTION(!GetValueAsDecimal().isNaN(), "Should have sanitized");
-      return false;
-    }
-    nsNumberControlFrame* numberControlFrame =
-      do_QueryFrame(GetPrimaryFrame());
-    if (numberControlFrame &&
-        !numberControlFrame->AnonTextControlIsEmpty()) {
-      // The input the user entered failed to parse as a number.
-      return true;
-    }
-    return false;
-  }
-  if (mType == NS_FORM_INPUT_EMAIL) {
-    // With regards to suffering from bad input the spec says that only the
-    // punycode conversion works, so we don't care whether the email address is
-    // valid or not here. (If the email address is invalid then we will be
-    // suffering from a type mismatch.)
-    nsAutoString value;
-    nsAutoCString unused;
-    uint32_t unused2;
-    GetNonFileValueInternal(value);
-    HTMLSplitOnSpacesTokenizer tokenizer(value, ',');
-    while (tokenizer.hasMoreTokens()) {
-      if (!PunycodeEncodeEmailAddress(tokenizer.nextToken(), unused, &unused2)) {
-        return true;
-      }
-    }
-    return false;
-  }
-  return false;
+  return mInputType->HasBadInput();
 }
 
 void
 HTMLInputElement::UpdateTooLongValidityState()
 {
   SetValidityState(VALIDITY_STATE_TOO_LONG, IsTooLong());
 }
 
@@ -8187,98 +7920,16 @@ HTMLInputElement::GetValidationMessage(n
     }
     default:
       rv = nsIConstraintValidation::GetValidationMessage(aValidationMessage, aType);
   }
 
   return rv;
 }
 
-//static
-bool
-HTMLInputElement::IsValidEmailAddressList(const nsAString& aValue)
-{
-  HTMLSplitOnSpacesTokenizer tokenizer(aValue, ',');
-
-  while (tokenizer.hasMoreTokens()) {
-    if (!IsValidEmailAddress(tokenizer.nextToken())) {
-      return false;
-    }
-  }
-
-  return !tokenizer.separatorAfterCurrentToken();
-}
-
-//static
-bool
-HTMLInputElement::IsValidEmailAddress(const nsAString& aValue)
-{
-  // Email addresses can't be empty and can't end with a '.' or '-'.
-  if (aValue.IsEmpty() || aValue.Last() == '.' || aValue.Last() == '-') {
-    return false;
-  }
-
-  uint32_t atPos;
-  nsAutoCString value;
-  if (!PunycodeEncodeEmailAddress(aValue, value, &atPos) ||
-      atPos == (uint32_t)kNotFound || atPos == 0 || atPos == value.Length() - 1) {
-    // Could not encode, or "@" was not found, or it was at the start or end
-    // of the input - in all cases, not a valid email address.
-    return false;
-  }
-
-  uint32_t length = value.Length();
-  uint32_t i = 0;
-
-  // Parsing the username.
-  for (; i < atPos; ++i) {
-    char16_t c = value[i];
-
-    // The username characters have to be in this list to be valid.
-    if (!(nsCRT::IsAsciiAlpha(c) || nsCRT::IsAsciiDigit(c) ||
-          c == '.' || c == '!' || c == '#' || c == '$' || c == '%' ||
-          c == '&' || c == '\''|| c == '*' || c == '+' || c == '-' ||
-          c == '/' || c == '=' || c == '?' || c == '^' || c == '_' ||
-          c == '`' || c == '{' || c == '|' || c == '}' || c == '~' )) {
-      return false;
-    }
-  }
-
-  // Skip the '@'.
-  ++i;
-
-  // The domain name can't begin with a dot or a dash.
-  if (value[i] == '.' || value[i] == '-') {
-    return false;
-  }
-
-  // Parsing the domain name.
-  for (; i < length; ++i) {
-    char16_t c = value[i];
-
-    if (c == '.') {
-      // A dot can't follow a dot or a dash.
-      if (value[i-1] == '.' || value[i-1] == '-') {
-        return false;
-      }
-    } else if (c == '-'){
-      // A dash can't follow a dot.
-      if (value[i-1] == '.') {
-        return false;
-      }
-    } else if (!(nsCRT::IsAsciiAlpha(c) || nsCRT::IsAsciiDigit(c) ||
-                 c == '-')) {
-      // The domain characters have to be in this list to be valid.
-      return false;
-    }
-  }
-
-  return true;
-}
-
 NS_IMETHODIMP_(bool)
 HTMLInputElement::IsSingleLineTextControl() const
 {
   return IsSingleLineTextControl(false);
 }
 
 NS_IMETHODIMP_(bool)
 HTMLInputElement::IsTextArea() const
--- a/dom/html/HTMLInputElement.h
+++ b/dom/html/HTMLInputElement.h
@@ -12,27 +12,48 @@
 #include "nsImageLoadingContent.h"
 #include "nsIDOMHTMLInputElement.h"
 #include "nsITextControlElement.h"
 #include "nsITimer.h"
 #include "nsIPhonetic.h"
 #include "nsIDOMNSEditableElement.h"
 #include "nsCOMPtr.h"
 #include "nsIConstraintValidation.h"
+#include "mozilla/UniquePtr.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/HTMLFormElement.h" // for HasEverTriedInvalidSubmit()
 #include "mozilla/dom/HTMLInputElementBinding.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/UnionTypes.h"
 #include "nsIFilePicker.h"
 #include "nsIContentPrefService2.h"
 #include "mozilla/Decimal.h"
 #include "nsContentUtils.h"
 #include "nsTextEditorState.h"
+#include "mozilla/Variant.h"
+#include "SingleLineTextInputTypes.h"
+#include "NumericInputTypes.h"
+#include "CheckableInputTypes.h"
+#include "ButtonInputTypes.h"
+#include "DateTimeInputTypes.h"
+#include "ColorInputType.h"
+#include "FileInputType.h"
+#include "HiddenInputType.h"
 
+static constexpr size_t INPUT_TYPE_SIZE = sizeof(
+  mozilla::Variant<TextInputType, SearchInputType, TelInputType, URLInputType,
+                   EmailInputType, PasswordInputType, NumberInputType,
+                   RangeInputType, RadioInputType, CheckboxInputType,
+                   ButtonInputType, ImageInputType, ResetInputType,
+                   SubmitInputType, DateInputType, TimeInputType, WeekInputType,
+                   MonthInputType, DateTimeLocalInputType, FileInputType,
+                   ColorInputType, HiddenInputType> );
+
+class InputType;
+struct DoNotDelete;
 class nsIRadioGroupContainer;
 class nsIRadioVisitor;
 
 namespace mozilla {
 
 class EventChainPostVisitor;
 class EventChainPreVisitor;
 
@@ -108,16 +129,17 @@ class HTMLInputElement final : public ns
                                public nsIDOMHTMLInputElement,
                                public nsITextControlElement,
                                public nsIPhonetic,
                                public nsIDOMNSEditableElement,
                                public nsIConstraintValidation
 {
   friend class AfterSetFilesOrDirectoriesCallback;
   friend class DispatchChangeEventCallback;
+  friend class ::InputType;
 
 public:
   using nsIConstraintValidation::GetValidationMessage;
   using nsIConstraintValidation::CheckValidity;
   using nsIConstraintValidation::ReportValidity;
   using nsIConstraintValidation::WillValidate;
   using nsIConstraintValidation::Validity;
   using nsGenericHTMLFormElementWithState::GetForm;
@@ -898,38 +920,16 @@ protected:
     // On getting, returns "C:\fakepath\" followed by the file name of the
     // first file of the selected files if any.
     // On setting the empty string, empties the selected files list, otherwise
     // throw the INVALID_STATE_ERR exception.
     VALUE_MODE_FILENAME
   };
 
   /**
-   * This helper method returns true if aValue is a valid email address.
-   * This is following the HTML5 specification:
-   * http://dev.w3.org/html5/spec/forms.html#valid-e-mail-address
-   *
-   * @param aValue  the email address to check.
-   * @result        whether the given string is a valid email address.
-   */
-  static bool IsValidEmailAddress(const nsAString& aValue);
-
-  /**
-   * This helper method returns true if aValue is a valid email address list.
-   * Email address list is a list of email address separated by comas (,) which
-   * can be surrounded by space charecters.
-   * This is following the HTML5 specification:
-   * http://dev.w3.org/html5/spec/forms.html#valid-e-mail-address-list
-   *
-   * @param aValue  the email address list to check.
-   * @result        whether the given string is a valid email address list.
-   */
-  static bool IsValidEmailAddressList(const nsAString& aValue);
-
-  /**
    * This helper method convert a sub-string that contains only digits to a
    * number (unsigned int given that it can't contain a minus sign).
    * This method will return whether the sub-string is correctly formatted
    * (ie. contains only digit) and it can be successfuly parsed to generate a
    * number).
    * If the method returns true, |aResult| will contained the parsed number.
    *
    * @param aValue  the string on which the sub-string will be extracted and parsed.
@@ -1077,21 +1077,16 @@ protected:
   bool DoesReadOnlyApply() const;
 
   /**
    * Returns if the required attribute applies for the current type.
    */
   bool DoesRequiredApply() const;
 
   /**
-   * Returns if the pattern attribute applies for the current type.
-   */
-  bool DoesPatternApply() const;
-
-  /**
    * Returns if the min and max attributes apply for the current type.
    */
   bool DoesMinMaxApply() const;
 
   /**
    * Returns if the step attribute apply for the current type.
    */
   bool DoesStepApply() const { return DoesMinMaxApply(); }
@@ -1106,21 +1101,16 @@ protected:
    */
   bool DoesValueAsNumberApply() const { return DoesMinMaxApply(); }
 
   /**
    * Returns if autocomplete attribute applies for the current type.
    */
   bool DoesAutocompleteApply() const;
 
-  /**
-   * Returns if the minlength or maxlength attributes apply for the current type.
-   */
-  bool MinOrMaxLengthApplies() const { return IsSingleLineTextControl(false, mType); }
-
   void FreeData();
   nsTextEditorState *GetEditorState() const;
 
   /**
    * Manages the internal data storage across type changes.
    */
   void HandleTypeChange(uint8_t aNewType, bool aNotify);
 
@@ -1562,16 +1552,24 @@ protected:
 
   /**
    * The selection properties cache for number controls.  This is needed because
    * the number controls don't recycle their text field, so the normal cache in
    * nsTextEditorState cannot do its job.
    */
   nsTextEditorState::SelectionProperties mSelectionProperties;
 
+  /*
+   * InputType object created based on input type.
+   */
+  UniquePtr<InputType, DoNotDelete> mInputType;
+
+  // Memory allocated for mInputType, reused when type changes.
+  char mInputTypeMem[INPUT_TYPE_SIZE];
+
   // Step scale factor values, for input types that have one.
   static const Decimal kStepScaleFactorDate;
   static const Decimal kStepScaleFactorNumberRange;
   static const Decimal kStepScaleFactorTime;
   static const Decimal kStepScaleFactorMonth;
   static const Decimal kStepScaleFactorWeek;
 
   // Default step base value when a type do not have specific one.
new file mode 100644
--- /dev/null
+++ b/dom/html/input/ButtonInputTypes.h
@@ -0,0 +1,87 @@
+/* -*- 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 ButtonInputTypes_h__
+#define ButtonInputTypes_h__
+
+#include "InputType.h"
+
+class ButtonInputTypeBase : public ::InputType
+{
+public:
+  ~ButtonInputTypeBase() override {}
+
+protected:
+  explicit ButtonInputTypeBase(mozilla::dom::HTMLInputElement* aInputElement)
+    : InputType(aInputElement)
+  {}
+};
+
+// input type=button
+class ButtonInputType : public ButtonInputTypeBase
+{
+public:
+  static InputType*
+  Create(mozilla::dom::HTMLInputElement* aInputElement, void* aMemory)
+  {
+    return new (aMemory) ButtonInputType(aInputElement);
+  }
+
+private:
+  explicit ButtonInputType(mozilla::dom::HTMLInputElement* aInputElement)
+    : ButtonInputTypeBase(aInputElement)
+  {}
+};
+
+// input type=image
+class ImageInputType : public ButtonInputTypeBase
+{
+public:
+  static InputType*
+  Create(mozilla::dom::HTMLInputElement* aInputElement, void* aMemory)
+  {
+    return new (aMemory) ImageInputType(aInputElement);
+  }
+
+private:
+  explicit ImageInputType(mozilla::dom::HTMLInputElement* aInputElement)
+    : ButtonInputTypeBase(aInputElement)
+  {}
+};
+
+// input type=reset
+class ResetInputType : public ButtonInputTypeBase
+{
+public:
+  static InputType*
+  Create(mozilla::dom::HTMLInputElement* aInputElement, void* aMemory)
+  {
+    return new (aMemory) ResetInputType(aInputElement);
+  }
+
+private:
+  explicit ResetInputType(mozilla::dom::HTMLInputElement* aInputElement)
+    : ButtonInputTypeBase(aInputElement)
+  {}
+};
+
+// input type=submit
+class SubmitInputType : public ButtonInputTypeBase
+{
+public:
+  static InputType*
+  Create(mozilla::dom::HTMLInputElement* aInputElement, void* aMemory)
+  {
+    return new (aMemory) SubmitInputType(aInputElement);
+  }
+
+private:
+  explicit SubmitInputType(mozilla::dom::HTMLInputElement* aInputElement)
+    : ButtonInputTypeBase(aInputElement)
+  {}
+};
+
+#endif /* ButtonInputTypes_h__ */
new file mode 100644
--- /dev/null
+++ b/dom/html/input/CheckableInputTypes.cpp
@@ -0,0 +1,23 @@
+/* -*- 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 "CheckableInputTypes.h"
+
+#include "mozilla/dom/HTMLInputElement.h"
+
+bool
+CheckboxInputType::IsValueMissing() const
+{
+  if (!mInputElement->HasAttr(kNameSpaceID_None, nsGkAtoms::required)) {
+    return false;
+  }
+
+  if (!IsMutable()) {
+    return false;
+  }
+
+  return !mInputElement->Checked();
+}
new file mode 100644
--- /dev/null
+++ b/dom/html/input/CheckableInputTypes.h
@@ -0,0 +1,57 @@
+/* -*- 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 CheckableInputTypes_h__
+#define CheckableInputTypes_h__
+
+#include "InputType.h"
+
+class CheckableInputTypeBase : public ::InputType
+{
+public:
+  ~CheckableInputTypeBase() override {}
+
+protected:
+  explicit CheckableInputTypeBase(mozilla::dom::HTMLInputElement* aInputElement)
+    : InputType(aInputElement)
+  {}
+};
+
+// input type=checkbox
+class CheckboxInputType : public CheckableInputTypeBase
+{
+public:
+  static InputType*
+  Create(mozilla::dom::HTMLInputElement* aInputElement, void* aMemory)
+  {
+    return new (aMemory) CheckboxInputType(aInputElement);
+  }
+
+  bool IsValueMissing() const override;
+
+private:
+  explicit CheckboxInputType(mozilla::dom::HTMLInputElement* aInputElement)
+    : CheckableInputTypeBase(aInputElement)
+  {}
+};
+
+// input type=radio
+class RadioInputType : public CheckableInputTypeBase
+{
+public:
+  static InputType*
+  Create(mozilla::dom::HTMLInputElement* aInputElement, void* aMemory)
+  {
+    return new (aMemory) RadioInputType(aInputElement);
+  }
+
+private:
+  explicit RadioInputType(mozilla::dom::HTMLInputElement* aInputElement)
+    : CheckableInputTypeBase(aInputElement)
+  {}
+};
+
+#endif /* CheckableInputTypes_h__ */
new file mode 100644
--- /dev/null
+++ b/dom/html/input/ColorInputType.h
@@ -0,0 +1,28 @@
+/* -*- 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 ColorInputType_h__
+#define ColorInputType_h__
+
+#include "InputType.h"
+
+// input type=color
+class ColorInputType : public ::InputType
+{
+public:
+  static InputType*
+  Create(mozilla::dom::HTMLInputElement* aInputElement, void* aMemory)
+  {
+    return new (aMemory) ColorInputType(aInputElement);
+  }
+
+private:
+  explicit ColorInputType(mozilla::dom::HTMLInputElement* aInputElement)
+    : InputType(aInputElement)
+  {}
+};
+
+#endif /* ColorInputType_h__ */
new file mode 100644
--- /dev/null
+++ b/dom/html/input/DateTimeInputTypes.cpp
@@ -0,0 +1,84 @@
+/* -*- 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 "DateTimeInputTypes.h"
+
+#include "mozilla/dom/HTMLInputElement.h"
+
+bool
+DateTimeInputTypeBase::IsMutable() const
+{
+  return !mInputElement->IsDisabled() &&
+         !mInputElement->HasAttr(kNameSpaceID_None, nsGkAtoms::readonly);
+}
+
+bool
+DateTimeInputTypeBase::IsValueMissing() const
+{
+  if (!mInputElement->HasAttr(kNameSpaceID_None, nsGkAtoms::required)) {
+    return false;
+  }
+
+  if (!IsMutable()) {
+    return false;
+  }
+
+  return IsValueEmpty();
+}
+
+bool
+DateTimeInputTypeBase::IsRangeOverflow() const
+{
+  mozilla::Decimal maximum = mInputElement->GetMaximum();
+  if (maximum.isNaN()) {
+    return false;
+  }
+
+  mozilla::Decimal value = mInputElement->GetValueAsDecimal();
+  if (value.isNaN()) {
+    return false;
+  }
+
+  return value > maximum;
+}
+
+bool
+DateTimeInputTypeBase::IsRangeUnderflow() const
+{
+  mozilla::Decimal minimum = mInputElement->GetMinimum();
+  if (minimum.isNaN()) {
+    return false;
+  }
+
+  mozilla::Decimal value = mInputElement->GetValueAsDecimal();
+  if (value.isNaN()) {
+    return false;
+  }
+
+  return value < minimum;
+}
+
+bool
+DateTimeInputTypeBase::HasStepMismatch(bool aUseZeroIfValueNaN) const
+{
+  mozilla::Decimal value = mInputElement->GetValueAsDecimal();
+  if (value.isNaN()) {
+    if (aUseZeroIfValueNaN) {
+      value = mozilla::Decimal(0);
+    } else {
+      // The element can't suffer from step mismatch if it's value isn't a number.
+      return false;
+    }
+  }
+
+  mozilla::Decimal step = mInputElement->GetStep();
+  if (step == kStepAny) {
+    return false;
+  }
+
+  // Value has to be an integral multiple of step.
+  return NS_floorModulo(value - GetStepBase(), step) != mozilla::Decimal(0);
+}
new file mode 100644
--- /dev/null
+++ b/dom/html/input/DateTimeInputTypes.h
@@ -0,0 +1,111 @@
+/* -*- 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 DateTimeInputTypes_h__
+#define DateTimeInputTypes_h__
+
+#include "InputType.h"
+
+class DateTimeInputTypeBase : public ::InputType
+{
+public:
+  ~DateTimeInputTypeBase() override {}
+
+  bool IsValueMissing() const override;
+  bool IsRangeOverflow() const override;
+  bool IsRangeUnderflow() const override;
+  bool HasStepMismatch(bool aUseZeroIfValueNaN) const override;
+
+protected:
+  explicit DateTimeInputTypeBase(mozilla::dom::HTMLInputElement* aInputElement)
+    : InputType(aInputElement)
+  {}
+
+  bool IsMutable() const override;
+};
+
+// input type=date
+class DateInputType : public DateTimeInputTypeBase
+{
+public:
+  static InputType*
+  Create(mozilla::dom::HTMLInputElement* aInputElement, void* aMemory)
+  {
+    return new (aMemory) DateInputType(aInputElement);
+  }
+
+private:
+  explicit DateInputType(mozilla::dom::HTMLInputElement* aInputElement)
+    : DateTimeInputTypeBase(aInputElement)
+  {}
+};
+
+// input type=time
+class TimeInputType : public DateTimeInputTypeBase
+{
+public:
+  static InputType*
+  Create(mozilla::dom::HTMLInputElement* aInputElement, void* aMemory)
+  {
+    return new (aMemory) TimeInputType(aInputElement);
+  }
+
+private:
+  explicit TimeInputType(mozilla::dom::HTMLInputElement* aInputElement)
+    : DateTimeInputTypeBase(aInputElement)
+  {}
+};
+
+// input type=week
+class WeekInputType : public DateTimeInputTypeBase
+{
+public:
+  static InputType*
+  Create(mozilla::dom::HTMLInputElement* aInputElement, void* aMemory)
+  {
+    return new (aMemory) WeekInputType(aInputElement);
+  }
+
+private:
+  explicit WeekInputType(mozilla::dom::HTMLInputElement* aInputElement)
+    : DateTimeInputTypeBase(aInputElement)
+  {}
+};
+
+// input type=month
+class MonthInputType : public DateTimeInputTypeBase
+{
+public:
+  static InputType*
+  Create(mozilla::dom::HTMLInputElement* aInputElement, void* aMemory)
+  {
+    return new (aMemory) MonthInputType(aInputElement);
+  }
+
+private:
+  explicit MonthInputType(mozilla::dom::HTMLInputElement* aInputElement)
+    : DateTimeInputTypeBase(aInputElement)
+  {}
+};
+
+// input type=datetime-local
+class DateTimeLocalInputType : public DateTimeInputTypeBase
+{
+public:
+  static InputType*
+  Create(mozilla::dom::HTMLInputElement* aInputElement, void* aMemory)
+  {
+    return new (aMemory) DateTimeLocalInputType(aInputElement);
+  }
+
+private:
+  explicit DateTimeLocalInputType(mozilla::dom::HTMLInputElement* aInputElement)
+    : DateTimeInputTypeBase(aInputElement)
+  {}
+};
+
+
+#endif /* DateTimeInputTypes_h__ */
new file mode 100644
--- /dev/null
+++ b/dom/html/input/FileInputType.cpp
@@ -0,0 +1,23 @@
+/* -*- 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 "FileInputType.h"
+
+#include "mozilla/dom/HTMLInputElement.h"
+
+bool
+FileInputType::IsValueMissing() const
+{
+  if (!mInputElement->HasAttr(kNameSpaceID_None, nsGkAtoms::required)) {
+    return false;
+  }
+
+  if (!IsMutable()) {
+    return false;
+  }
+
+  return mInputElement->GetFilesOrDirectoriesInternal().IsEmpty();
+}
new file mode 100644
--- /dev/null
+++ b/dom/html/input/FileInputType.h
@@ -0,0 +1,30 @@
+/* -*- 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 FileInputType_h__
+#define FileInputType_h__
+
+#include "InputType.h"
+
+// input type=file
+class FileInputType : public ::InputType
+{
+public:
+  static InputType*
+  Create(mozilla::dom::HTMLInputElement* aInputElement, void* aMemory)
+  {
+    return new (aMemory) FileInputType(aInputElement);
+  }
+
+  bool IsValueMissing() const override;
+
+private:
+  explicit FileInputType(mozilla::dom::HTMLInputElement* aInputElement)
+    : InputType(aInputElement)
+  {}
+};
+
+#endif /* FileInputType_h__ */
new file mode 100644
--- /dev/null
+++ b/dom/html/input/HiddenInputType.h
@@ -0,0 +1,28 @@
+/* -*- 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 HiddenInputType_h__
+#define HiddenInputType_h__
+
+#include "InputType.h"
+
+// input type=hidden
+class HiddenInputType : public ::InputType
+{
+public:
+  static InputType*
+  Create(mozilla::dom::HTMLInputElement* aInputElement, void* aMemory)
+  {
+    return new (aMemory) HiddenInputType(aInputElement);
+  }
+
+private:
+  explicit HiddenInputType(mozilla::dom::HTMLInputElement* aInputElement)
+    : InputType(aInputElement)
+  {}
+};
+
+#endif /* HiddenInputType_h__ */
new file mode 100644
--- /dev/null
+++ b/dom/html/input/InputType.cpp
@@ -0,0 +1,207 @@
+/* -*- 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 "InputType.h"
+
+#include "nsIFormControl.h"
+#include "ButtonInputTypes.h"
+#include "CheckableInputTypes.h"
+#include "ColorInputType.h"
+#include "DateTimeInputTypes.h"
+#include "FileInputType.h"
+#include "HiddenInputType.h"
+#include "NumericInputTypes.h"
+#include "SingleLineTextInputTypes.h"
+
+const mozilla::Decimal InputType::kStepAny = mozilla::Decimal(0);
+
+/* static */ mozilla::UniquePtr<InputType, DoNotDelete>
+InputType::Create(mozilla::dom::HTMLInputElement* aInputElement, uint8_t aType,
+                  void* aMemory)
+{
+  mozilla::UniquePtr<InputType, DoNotDelete> inputType;
+  switch(aType) {
+    // Single line text
+    case NS_FORM_INPUT_TEXT:
+      inputType.reset(TextInputType::Create(aInputElement, aMemory));
+      break;
+    case NS_FORM_INPUT_TEL:
+      inputType.reset(TelInputType::Create(aInputElement, aMemory));
+      break;
+    case NS_FORM_INPUT_EMAIL:
+      inputType.reset(EmailInputType::Create(aInputElement, aMemory));
+      break;
+    case NS_FORM_INPUT_SEARCH:
+      inputType.reset(SearchInputType::Create(aInputElement, aMemory));
+      break;
+    case NS_FORM_INPUT_PASSWORD:
+      inputType.reset(PasswordInputType::Create(aInputElement, aMemory));
+      break;
+    case NS_FORM_INPUT_URL:
+      inputType.reset(URLInputType::Create(aInputElement, aMemory));
+      break;
+    // Button
+    case NS_FORM_INPUT_BUTTON:
+      inputType.reset(ButtonInputType::Create(aInputElement, aMemory));
+      break;
+    case NS_FORM_INPUT_SUBMIT:
+      inputType.reset(SubmitInputType::Create(aInputElement, aMemory));
+      break;
+    case NS_FORM_INPUT_IMAGE:
+      inputType.reset(ImageInputType::Create(aInputElement, aMemory));
+      break;
+    case NS_FORM_INPUT_RESET:
+      inputType.reset(ResetInputType::Create(aInputElement, aMemory));
+      break;
+    // Checkable
+    case NS_FORM_INPUT_CHECKBOX:
+      inputType.reset(CheckboxInputType::Create(aInputElement, aMemory));
+      break;
+    case NS_FORM_INPUT_RADIO:
+      inputType.reset(RadioInputType::Create(aInputElement, aMemory));
+      break;
+    // Numeric
+    case NS_FORM_INPUT_NUMBER:
+      inputType.reset(NumberInputType::Create(aInputElement, aMemory));
+      break;
+    case NS_FORM_INPUT_RANGE:
+      inputType.reset(RangeInputType::Create(aInputElement, aMemory));
+      break;
+    // DateTime
+    case NS_FORM_INPUT_DATE:
+      inputType.reset(DateInputType::Create(aInputElement, aMemory));
+      break;
+    case NS_FORM_INPUT_TIME:
+      inputType.reset(TimeInputType::Create(aInputElement, aMemory));
+      break;
+    case NS_FORM_INPUT_MONTH:
+      inputType.reset(MonthInputType::Create(aInputElement, aMemory));
+      break;
+    case NS_FORM_INPUT_WEEK:
+      inputType.reset(WeekInputType::Create(aInputElement, aMemory));
+      break;
+    case NS_FORM_INPUT_DATETIME_LOCAL:
+      inputType.reset(DateTimeLocalInputType::Create(aInputElement, aMemory));
+      break;
+    // Others
+    case NS_FORM_INPUT_COLOR:
+      inputType.reset(ColorInputType::Create(aInputElement, aMemory));
+      break;
+    case NS_FORM_INPUT_FILE:
+      inputType.reset(FileInputType::Create(aInputElement, aMemory));
+      break;
+    case NS_FORM_INPUT_HIDDEN:
+      inputType.reset(HiddenInputType::Create(aInputElement, aMemory));
+      break;
+    default:
+      inputType.reset(TextInputType::Create(aInputElement, aMemory));
+  }
+
+  return inputType;
+}
+
+bool
+InputType::IsMutable() const
+{
+  return !mInputElement->IsDisabled();
+}
+
+bool
+InputType::IsValueEmpty() const
+{
+  return mInputElement->IsValueEmpty();
+}
+
+void
+InputType::GetNonFileValueInternal(nsAString& aValue) const
+{
+  return mInputElement->GetNonFileValueInternal(aValue);
+}
+
+nsresult
+InputType::SetValueInternal(const nsAString& aValue, uint32_t aFlags)
+{
+  return mInputElement->SetValueInternal(aValue, aFlags);
+}
+
+mozilla::Decimal
+InputType::GetStepBase() const
+{
+  return mInputElement->GetStepBase();
+}
+
+nsIFrame*
+InputType::GetPrimaryFrame() const
+{
+  return mInputElement->GetPrimaryFrame();
+}
+
+void
+InputType::DropReference()
+{
+  // Drop our (non ref-counted) reference.
+  mInputElement = nullptr;
+}
+
+bool
+InputType::IsTooLong() const
+{
+  return false;
+}
+
+bool
+InputType::IsTooShort() const
+{
+  return false;
+}
+
+bool
+InputType::IsValueMissing() const
+{
+  return false;
+}
+
+bool
+InputType::HasTypeMismatch() const
+{
+  return false;
+}
+
+bool
+InputType::HasPatternMismatch() const
+{
+  return false;
+}
+
+bool
+InputType::IsRangeOverflow() const
+{
+  return false;
+}
+
+bool
+InputType::IsRangeUnderflow() const
+{
+  return false;
+}
+
+bool
+InputType::HasStepMismatch(bool aUseZeroIfValueNaN) const
+{
+  return false;
+}
+
+bool
+InputType::HasBadInput() const
+{
+  return false;
+}
+
+nsresult
+InputType::MinMaxStepAttrChanged()
+{
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/dom/html/input/InputType.h
@@ -0,0 +1,120 @@
+/* -*- 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 InputType_h__
+#define InputType_h__
+
+#include <stdint.h>
+#include "mozilla/Decimal.h"
+#include "mozilla/UniquePtr.h"
+#include "nsString.h"
+#include "nsError.h"
+
+// This must come outside of any namespace, or else it won't overload with the
+// double based version in nsMathUtils.h
+inline mozilla::Decimal
+NS_floorModulo(mozilla::Decimal x, mozilla::Decimal y)
+{
+  return (x - y * (x / y).floor());
+}
+
+namespace mozilla {
+namespace dom {
+class HTMLInputElement;
+} // namespace dom
+} // namespace mozilla
+
+struct DoNotDelete;
+class nsIFrame;
+
+/**
+ * A common superclass for different types of a HTMLInputElement.
+ */
+class InputType
+{
+public:
+  static mozilla::UniquePtr<InputType, DoNotDelete>
+  Create(mozilla::dom::HTMLInputElement* aInputElement, uint8_t aType,
+         void* aMemory);
+
+  virtual ~InputType() {}
+
+  // Float value returned by GetStep() when the step attribute is set to 'any'.
+  static const mozilla::Decimal kStepAny;
+
+  /**
+   * Drop the reference to the input element.
+   */
+  void DropReference();
+
+  virtual bool IsTooLong() const;
+  virtual bool IsTooShort() const;
+  virtual bool IsValueMissing() const;
+  virtual bool HasTypeMismatch() const;
+  virtual bool HasPatternMismatch() const;
+  virtual bool IsRangeOverflow() const;
+  virtual bool IsRangeUnderflow() const;
+  virtual bool HasStepMismatch(bool aUseZeroIfValueNaN) const;
+  virtual bool HasBadInput() const;
+
+  virtual nsresult MinMaxStepAttrChanged();
+
+protected:
+  explicit InputType(mozilla::dom::HTMLInputElement* aInputElement)
+    : mInputElement(aInputElement)
+  {}
+
+  /**
+   * Get the mutable state of the element.
+   * When the element isn't mutable (immutable), the value or checkedness
+   * should not be changed by the user.
+   *
+   * See: https://html.spec.whatwg.org/multipage/forms.html#the-input-element:concept-fe-mutable
+   */
+  virtual bool IsMutable() const;
+
+  /**
+   * Returns whether the input element's current value is the empty string.
+   * This only makes sense for some input types; does NOT make sense for file
+   * inputs.
+   *
+   * @return whether the input element's current value is the empty string.
+   */
+  bool IsValueEmpty() const;
+
+  // A getter for callers that know we're not dealing with a file input, so they
+  // don't have to think about the caller type.
+  void GetNonFileValueInternal(nsAString& aValue) const;
+
+  /**
+   * Setting the input element's value.
+   *
+   * @param aValue      String to set.
+   * @param aFlags      See nsTextEditorState::SetValueFlags.
+   */
+  nsresult SetValueInternal(const nsAString& aValue, uint32_t aFlags);
+
+  /**
+   * Return the base used to compute if a value matches step.
+   * Basically, it's the min attribute if present and a default value otherwise.
+   *
+   * @return The step base.
+   */
+  mozilla::Decimal GetStepBase() const;
+
+  /**
+   * Get the primary frame for the input element.
+   */
+  nsIFrame* GetPrimaryFrame() const;
+
+  mozilla::dom::HTMLInputElement* mInputElement;
+};
+
+// Custom deleter for UniquePtr<InputType> to avoid freeing memory pre-allocated
+// for InputType, but we still need to call the destructor explictly.
+struct DoNotDelete { void operator()(::InputType* p) { p->~InputType(); } };
+
+#endif /* InputType_h__ */
new file mode 100644
--- /dev/null
+++ b/dom/html/input/NumericInputTypes.cpp
@@ -0,0 +1,129 @@
+/* -*- 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 "NumericInputTypes.h"
+
+#include "mozilla/dom/HTMLInputElement.h"
+#include "nsNumberControlFrame.h"
+#include "nsTextEditorState.h"
+
+bool
+NumberInputType::IsMutable() const
+{
+  return !mInputElement->IsDisabled() &&
+         !mInputElement->HasAttr(kNameSpaceID_None, nsGkAtoms::readonly);
+}
+
+bool
+NumericInputTypeBase::IsRangeOverflow() const
+{
+  mozilla::Decimal maximum = mInputElement->GetMaximum();
+  if (maximum.isNaN()) {
+    return false;
+  }
+
+  mozilla::Decimal value = mInputElement->GetValueAsDecimal();
+  if (value.isNaN()) {
+    return false;
+  }
+
+  return value > maximum;
+}
+
+bool
+NumericInputTypeBase::IsRangeUnderflow() const
+{
+  mozilla::Decimal minimum = mInputElement->GetMinimum();
+  if (minimum.isNaN()) {
+    return false;
+  }
+
+  mozilla::Decimal value = mInputElement->GetValueAsDecimal();
+  if (value.isNaN()) {
+    return false;
+  }
+
+  return value < minimum;
+}
+
+bool
+NumericInputTypeBase::HasStepMismatch(bool aUseZeroIfValueNaN) const
+{
+  mozilla::Decimal value = mInputElement->GetValueAsDecimal();
+  if (value.isNaN()) {
+    if (aUseZeroIfValueNaN) {
+      value = mozilla::Decimal(0);
+    } else {
+      // The element can't suffer from step mismatch if it's value isn't a number.
+      return false;
+    }
+  }
+
+  mozilla::Decimal step = mInputElement->GetStep();
+  if (step == kStepAny) {
+    return false;
+  }
+
+  // Value has to be an integral multiple of step.
+  return NS_floorModulo(value - GetStepBase(), step) != mozilla::Decimal(0);
+}
+
+/* input type=numer */
+
+bool
+NumberInputType::IsValueMissing() const
+{
+  if (!mInputElement->HasAttr(kNameSpaceID_None, nsGkAtoms::required)) {
+    return false;
+  }
+
+  if (!IsMutable()) {
+    return false;
+  }
+
+  return IsValueEmpty();
+}
+
+bool
+NumberInputType::HasBadInput() const
+{
+  nsAutoString value;
+  GetNonFileValueInternal(value);
+  if (!value.IsEmpty()) {
+    // The input can't be bad, otherwise it would have been sanitized to the
+    // empty string.
+    NS_ASSERTION(!mInputElement->GetValueAsDecimal().isNaN(),
+                 "Should have sanitized");
+    return false;
+  }
+  nsNumberControlFrame* numberControlFrame =
+    do_QueryFrame(GetPrimaryFrame());
+  if (numberControlFrame &&
+      !numberControlFrame->AnonTextControlIsEmpty()) {
+    // The input the user entered failed to parse as a number.
+    return true;
+  }
+  return false;
+}
+
+/* input type=range */
+nsresult
+RangeInputType::MinMaxStepAttrChanged()
+{
+  // The value may need to change when @min/max/step changes since the value may
+  // have been invalid and can now change to a valid value, or vice versa. For
+  // example, consider: <input type=range value=-1 max=1 step=3>. The valid
+  // range is 0 to 1 while the nearest valid steps are -1 and 2 (the max value
+  // having prevented there being a valid step in range). Changing @max to/from
+  // 1 and a number greater than on equal to 3 should change whether we have a
+  // step mismatch or not.
+  // The value may also need to change between a value that results in a step
+  // mismatch and a value that results in overflow. For example, if @max in the
+  // example above were to change from 1 to -1.
+  nsAutoString value;
+  GetNonFileValueInternal(value);
+  return SetValueInternal(value, nsTextEditorState::eSetValue_Internal);
+}
new file mode 100644
--- /dev/null
+++ b/dom/html/input/NumericInputTypes.h
@@ -0,0 +1,66 @@
+/* -*- 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 NumericInputTypes_h__
+#define NumericInputTypes_h__
+
+#include "InputType.h"
+
+class NumericInputTypeBase : public ::InputType
+{
+public:
+  ~NumericInputTypeBase() override {}
+
+  bool IsRangeOverflow() const override;
+  bool IsRangeUnderflow() const override;
+  bool HasStepMismatch(bool aUseZeroIfValueNaN) const override;
+
+protected:
+  explicit NumericInputTypeBase(mozilla::dom::HTMLInputElement* aInputElement)
+    : InputType(aInputElement)
+  {}
+};
+
+// input type=number
+class NumberInputType : public NumericInputTypeBase
+{
+public:
+  static InputType*
+  Create(mozilla::dom::HTMLInputElement* aInputElement, void* aMemory)
+  {
+    return new (aMemory) NumberInputType(aInputElement);
+  }
+
+  bool IsValueMissing() const override;
+  bool HasBadInput() const override;
+
+private:
+  explicit NumberInputType(mozilla::dom::HTMLInputElement* aInputElement)
+    : NumericInputTypeBase(aInputElement)
+  {}
+
+  bool IsMutable() const override;
+};
+
+// input type=range
+class RangeInputType : public NumericInputTypeBase
+{
+public:
+  static InputType*
+  Create(mozilla::dom::HTMLInputElement* aInputElement, void* aMemory)
+  {
+    return new (aMemory) RangeInputType(aInputElement);
+  }
+
+  nsresult MinMaxStepAttrChanged() override;
+
+private:
+  explicit RangeInputType(mozilla::dom::HTMLInputElement* aInputElement)
+    : NumericInputTypeBase(aInputElement)
+  {}
+};
+
+#endif /* NumericInputTypes_h__ */
new file mode 100644
--- /dev/null
+++ b/dom/html/input/SingleLineTextInputTypes.cpp
@@ -0,0 +1,273 @@
+/* -*- 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 "SingleLineTextInputTypes.h"
+
+#include "mozilla/dom/HTMLInputElement.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "HTMLSplitOnSpacesTokenizer.h"
+#include "nsContentUtils.h"
+#include "nsCRTGlue.h"
+#include "nsIIDNService.h"
+#include "nsIIOService.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+
+bool
+SingleLineTextInputTypeBase::IsMutable() const
+{
+  return !mInputElement->IsDisabled() &&
+         !mInputElement->HasAttr(kNameSpaceID_None, nsGkAtoms::readonly);
+}
+
+bool
+SingleLineTextInputTypeBase::IsTooLong() const
+{
+  int32_t maxLength = mInputElement->MaxLength();
+
+  // Maxlength of -1 means attribute isn't set or parsing error.
+  if (maxLength == -1) {
+   return false;
+  }
+
+  int32_t textLength =
+    mInputElement->InputTextLength(mozilla::dom::CallerType::System);
+
+  return textLength > maxLength;
+}
+
+bool
+SingleLineTextInputTypeBase::IsTooShort() const
+{
+  int32_t minLength = mInputElement->MinLength();
+
+  // Minlength of -1 means attribute isn't set or parsing error.
+  if (minLength == -1) {
+    return false;
+  }
+
+  int32_t textLength =
+    mInputElement->InputTextLength(mozilla::dom::CallerType::System);
+
+  return textLength && textLength < minLength;
+}
+
+bool
+SingleLineTextInputTypeBase::IsValueMissing() const
+{
+  if (!mInputElement->HasAttr(kNameSpaceID_None, nsGkAtoms::required)) {
+    return false;
+  }
+
+  if (!IsMutable()) {
+    return false;
+  }
+
+  return IsValueEmpty();
+}
+
+bool
+SingleLineTextInputTypeBase::HasPatternMismatch() const
+{
+  nsAutoString pattern;
+ if (!mInputElement->GetAttr(kNameSpaceID_None, nsGkAtoms::pattern, pattern)) {
+    return false;
+  }
+
+  nsAutoString value;
+  GetNonFileValueInternal(value);
+
+  if (value.IsEmpty()) {
+    return false;
+  }
+
+  nsIDocument* doc = mInputElement->OwnerDoc();
+
+  return !nsContentUtils::IsPatternMatching(value, pattern, doc);
+}
+
+/* input type=url */
+
+bool
+URLInputType::HasTypeMismatch() const
+{
+  nsAutoString value;
+  GetNonFileValueInternal(value);
+
+  if (value.IsEmpty()) {
+    return false;
+  }
+
+  /**
+   * TODO:
+   * The URL is not checked as the HTML5 specifications want it to be because
+   * there is no code to check for a valid URI/IRI according to 3986 and 3987
+   * RFC's at the moment, see bug 561586.
+   *
+   * RFC 3987 (IRI) implementation: bug 42899
+   *
+   * HTML5 specifications:
+   * http://dev.w3.org/html5/spec/infrastructure.html#valid-url
+   */
+  nsCOMPtr<nsIIOService> ioService = do_GetIOService();
+  nsCOMPtr<nsIURI> uri;
+
+  return !NS_SUCCEEDED(ioService->NewURI(NS_ConvertUTF16toUTF8(value), nullptr,
+                                         nullptr, getter_AddRefs(uri)));
+
+}
+
+/* input type=email */
+
+bool
+EmailInputType::HasTypeMismatch() const
+{
+  nsAutoString value;
+  GetNonFileValueInternal(value);
+
+  if (value.IsEmpty()) {
+    return false;
+  }
+
+  return mInputElement->HasAttr(kNameSpaceID_None, nsGkAtoms::multiple) ?
+    !IsValidEmailAddressList(value) : !IsValidEmailAddress(value);
+}
+
+bool
+EmailInputType::HasBadInput() const
+{
+  // With regards to suffering from bad input the spec says that only the
+  // punycode conversion works, so we don't care whether the email address is
+  // valid or not here. (If the email address is invalid then we will be
+  // suffering from a type mismatch.)
+  nsAutoString value;
+  nsAutoCString unused;
+  uint32_t unused2;
+  GetNonFileValueInternal(value);
+  HTMLSplitOnSpacesTokenizer tokenizer(value, ',');
+  while (tokenizer.hasMoreTokens()) {
+    if (!PunycodeEncodeEmailAddress(tokenizer.nextToken(), unused, &unused2)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+/* static */ bool
+EmailInputType::IsValidEmailAddressList(const nsAString& aValue)
+{
+  HTMLSplitOnSpacesTokenizer tokenizer(aValue, ',');
+
+  while (tokenizer.hasMoreTokens()) {
+    if (!IsValidEmailAddress(tokenizer.nextToken())) {
+      return false;
+    }
+  }
+
+  return !tokenizer.separatorAfterCurrentToken();
+}
+
+/* static */ bool
+EmailInputType::IsValidEmailAddress(const nsAString& aValue)
+{
+  // Email addresses can't be empty and can't end with a '.' or '-'.
+  if (aValue.IsEmpty() || aValue.Last() == '.' || aValue.Last() == '-') {
+    return false;
+  }
+
+  uint32_t atPos;
+  nsAutoCString value;
+  if (!PunycodeEncodeEmailAddress(aValue, value, &atPos) ||
+      atPos == (uint32_t)kNotFound || atPos == 0 || atPos == value.Length() - 1) {
+    // Could not encode, or "@" was not found, or it was at the start or end
+    // of the input - in all cases, not a valid email address.
+    return false;
+  }
+
+  uint32_t length = value.Length();
+  uint32_t i = 0;
+
+  // Parsing the username.
+  for (; i < atPos; ++i) {
+    char16_t c = value[i];
+
+    // The username characters have to be in this list to be valid.
+    if (!(nsCRT::IsAsciiAlpha(c) || nsCRT::IsAsciiDigit(c) ||
+          c == '.' || c == '!' || c == '#' || c == '$' || c == '%' ||
+          c == '&' || c == '\''|| c == '*' || c == '+' || c == '-' ||
+          c == '/' || c == '=' || c == '?' || c == '^' || c == '_' ||
+          c == '`' || c == '{' || c == '|' || c == '}' || c == '~' )) {
+      return false;
+    }
+  }
+
+  // Skip the '@'.
+  ++i;
+
+  // The domain name can't begin with a dot or a dash.
+  if (value[i] == '.' || value[i] == '-') {
+    return false;
+  }
+
+  // Parsing the domain name.
+  for (; i < length; ++i) {
+    char16_t c = value[i];
+
+    if (c == '.') {
+      // A dot can't follow a dot or a dash.
+      if (value[i-1] == '.' || value[i-1] == '-') {
+        return false;
+      }
+    } else if (c == '-'){
+      // A dash can't follow a dot.
+      if (value[i-1] == '.') {
+        return false;
+      }
+    } else if (!(nsCRT::IsAsciiAlpha(c) || nsCRT::IsAsciiDigit(c) ||
+                 c == '-')) {
+      // The domain characters have to be in this list to be valid.
+      return false;
+    }
+  }
+
+  return true;
+}
+
+/* static */ bool
+EmailInputType::PunycodeEncodeEmailAddress(const nsAString& aEmail,
+                                           nsAutoCString& aEncodedEmail,
+                                           uint32_t* aIndexOfAt)
+{
+  nsAutoCString value = NS_ConvertUTF16toUTF8(aEmail);
+  *aIndexOfAt = (uint32_t)value.FindChar('@');
+
+  if (*aIndexOfAt == (uint32_t)kNotFound ||
+      *aIndexOfAt == value.Length() - 1) {
+    aEncodedEmail = value;
+    return true;
+  }
+
+  nsCOMPtr<nsIIDNService> idnSrv = do_GetService(NS_IDNSERVICE_CONTRACTID);
+  if (!idnSrv) {
+    NS_ERROR("nsIIDNService isn't present!");
+    return false;
+  }
+
+  uint32_t indexOfDomain = *aIndexOfAt + 1;
+
+  const nsDependentCSubstring domain = Substring(value, indexOfDomain);
+  bool ace;
+  if (NS_SUCCEEDED(idnSrv->IsACE(domain, &ace)) && !ace) {
+    nsAutoCString domainACE;
+    if (NS_FAILED(idnSrv->ConvertUTF8toACE(domain, domainACE))) {
+      return false;
+    }
+    value.Replace(indexOfDomain, domain.Length(), domainACE);
+  }
+
+  aEncodedEmail = value;
+  return true;
+}
new file mode 100644
--- /dev/null
+++ b/dom/html/input/SingleLineTextInputTypes.h
@@ -0,0 +1,174 @@
+/* -*- 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 SingleLineTextInputTypes_h__
+#define SingleLineTextInputTypes_h__
+
+#include "InputType.h"
+
+class SingleLineTextInputTypeBase : public ::InputType
+{
+public:
+  ~SingleLineTextInputTypeBase() override {}
+
+  bool IsTooLong() const override;
+  bool IsTooShort() const override;
+  bool IsValueMissing() const override;
+  bool HasPatternMismatch() const override;
+
+protected:
+  explicit SingleLineTextInputTypeBase(
+    mozilla::dom::HTMLInputElement* aInputElement)
+      : InputType(aInputElement)
+  {}
+
+  bool IsMutable() const override;
+};
+
+// input type=text
+class TextInputType : public SingleLineTextInputTypeBase
+{
+public:
+  static InputType*
+  Create(mozilla::dom::HTMLInputElement* aInputElement, void* aMemory)
+  {
+    return new (aMemory) TextInputType(aInputElement);
+  }
+
+private:
+  explicit TextInputType(mozilla::dom::HTMLInputElement* aInputElement)
+    : SingleLineTextInputTypeBase(aInputElement)
+  {}
+};
+
+// input type=search
+class SearchInputType : public SingleLineTextInputTypeBase
+{
+public:
+  static InputType*
+  Create(mozilla::dom::HTMLInputElement* aInputElement, void* aMemory)
+  {
+    return new (aMemory) SearchInputType(aInputElement);
+  }
+
+private:
+  explicit SearchInputType(mozilla::dom::HTMLInputElement* aInputElement)
+    : SingleLineTextInputTypeBase(aInputElement)
+  {}
+};
+
+// input type=tel
+class TelInputType : public SingleLineTextInputTypeBase
+{
+public:
+  static InputType*
+  Create(mozilla::dom::HTMLInputElement* aInputElement, void* aMemory)
+  {
+    return new (aMemory) TelInputType(aInputElement);
+  }
+
+private:
+  explicit TelInputType(mozilla::dom::HTMLInputElement* aInputElement)
+    : SingleLineTextInputTypeBase(aInputElement)
+  {}
+};
+
+// input type=url
+class URLInputType : public SingleLineTextInputTypeBase
+{
+public:
+  static InputType*
+  Create(mozilla::dom::HTMLInputElement* aInputElement, void* aMemory)
+  {
+    return new (aMemory) URLInputType(aInputElement);
+  }
+
+  bool HasTypeMismatch() const override;
+
+private:
+  explicit URLInputType(mozilla::dom::HTMLInputElement* aInputElement)
+    : SingleLineTextInputTypeBase(aInputElement)
+  {}
+};
+
+// input type=email
+class EmailInputType : public SingleLineTextInputTypeBase
+{
+public:
+  static InputType*
+  Create(mozilla::dom::HTMLInputElement* aInputElement, void* aMemory)
+  {
+    return new (aMemory) EmailInputType(aInputElement);
+  }
+
+  bool HasTypeMismatch() const override;
+  bool HasBadInput() const override;
+
+private:
+  explicit EmailInputType(mozilla::dom::HTMLInputElement* aInputElement)
+    : SingleLineTextInputTypeBase(aInputElement)
+  {}
+
+  /**
+   * This helper method returns true if aValue is a valid email address.
+   * This is following the HTML5 specification:
+   * http://dev.w3.org/html5/spec/forms.html#valid-e-mail-address
+   *
+   * @param aValue  the email address to check.
+   * @result        whether the given string is a valid email address.
+   */
+  static bool IsValidEmailAddress(const nsAString& aValue);
+
+  /**
+   * This helper method returns true if aValue is a valid email address list.
+   * Email address list is a list of email address separated by comas (,) which
+   * can be surrounded by space charecters.
+   * This is following the HTML5 specification:
+   * http://dev.w3.org/html5/spec/forms.html#valid-e-mail-address-list
+   *
+   * @param aValue  the email address list to check.
+   * @result        whether the given string is a valid email address list.
+   */
+  static bool IsValidEmailAddressList(const nsAString& aValue);
+
+  /**
+   * Takes aEmail and attempts to convert everything after the first "@"
+   * character (if anything) to punycode before returning the complete result
+   * via the aEncodedEmail out-param. The aIndexOfAt out-param is set to the
+   * index of the "@" character.
+   *
+   * If no "@" is found in aEmail, aEncodedEmail is simply set to aEmail and
+   * the aIndexOfAt out-param is set to kNotFound.
+   *
+   * Returns true in all cases unless an attempt to punycode encode fails. If
+   * false is returned, aEncodedEmail has not been set.
+   *
+   * This function exists because ConvertUTF8toACE() splits on ".", meaning that
+   * for 'user.name@sld.tld' it would treat "name@sld" as a label. We want to
+   * encode the domain part only.
+   */
+ static bool PunycodeEncodeEmailAddress(const nsAString& aEmail,
+                                        nsAutoCString& aEncodedEmail,
+                                        uint32_t* aIndexOfAt);
+};
+
+// input type=password
+class PasswordInputType : public SingleLineTextInputTypeBase
+{
+public:
+  static InputType*
+  Create(mozilla::dom::HTMLInputElement* aInputElement, void* aMemory)
+  {
+    return new (aMemory) PasswordInputType(aInputElement);
+  }
+
+private:
+  explicit PasswordInputType(mozilla::dom::HTMLInputElement* aInputElement)
+    : SingleLineTextInputTypeBase(aInputElement)
+  {}
+};
+
+#endif /* SingleLineTextInputTypes_h__ */
new file mode 100644
--- /dev/null
+++ b/dom/html/input/moz.build
@@ -0,0 +1,40 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS += [
+    'ButtonInputTypes.h',
+    'CheckableInputTypes.h',
+    'ColorInputType.h',
+    'DateTimeInputTypes.h',
+    'FileInputType.h',
+    'HiddenInputType.h',
+    'InputType.h',
+    'NumericInputTypes.h',
+    'SingleLineTextInputTypes.h',
+]
+
+UNIFIED_SOURCES += [
+    'CheckableInputTypes.cpp',
+    'DateTimeInputTypes.cpp',
+    'FileInputType.cpp',
+    'InputType.cpp',
+    'NumericInputTypes.cpp',
+    'SingleLineTextInputTypes.cpp',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+LOCAL_INCLUDES += [
+    '/dom/base',
+    '/dom/html',
+    '/layout/forms',
+]
+
+FINAL_LIBRARY = 'xul'
+
+if CONFIG['GNU_CXX']:
+    CXXFLAGS += ['-Wno-error=shadow']
+
--- a/dom/html/moz.build
+++ b/dom/html/moz.build
@@ -2,16 +2,18 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 with Files("**"):
     BUG_COMPONENT = ("Core", "DOM")
 
+DIRS += ['input']
+
 MOCHITEST_MANIFESTS += [
     'test/forms/mochitest.ini',
     'test/imports/mochitest.ini',
     'test/mochitest.ini',
 ]
 
 MOCHITEST_CHROME_MANIFESTS += [
     'test/chrome.ini',
@@ -227,16 +229,17 @@ EXTRA_COMPONENTS += [
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 LOCAL_INCLUDES += [
     '/caps',
     '/docshell/base',
     '/dom/base',
     '/dom/canvas',
+    '/dom/html/input',
     '/dom/media/',
     '/dom/xbl',
     '/dom/xul',
     '/editor/txmgr',
     '/image',
     '/layout/forms',
     '/layout/generic',
     '/layout/style',
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -152,474 +152,514 @@ using dom::Promise;
 using dom::Sequence;
 using media::NewRunnableFrom;
 using media::NewTaskFrom;
 using media::Pledge;
 using media::Refcountable;
 
 static Atomic<bool> sInShutdown;
 
+typedef media::Pledge<bool, dom::MediaStreamError*> PledgeVoid;
+
 static bool
 HostIsHttps(nsIURI &docURI)
 {
   bool isHttps;
   nsresult rv = docURI.SchemeIs("https", &isHttps);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return false;
   }
   return isHttps;
 }
 
-/**
- * This class is an implementation of MediaStreamListener. This is used
- * to Start() and Stop() the underlying MediaEngineSource when MediaStreams
- * are assigned and deassigned in content.
- */
-class GetUserMediaCallbackMediaStreamListener : public MediaStreamListener
-{
-  friend MediaManager;
+class SourceListener : public MediaStreamListener {
 public:
-  // Create in an inactive state
-  GetUserMediaCallbackMediaStreamListener(base::Thread *aThread,
-    uint64_t aWindowID,
-    const PrincipalHandle& aPrincipalHandle)
-    : mMediaThread(aThread)
-    , mMainThreadCheck(nullptr)
-    , mWindowID(aWindowID)
-    , mPrincipalHandle(aPrincipalHandle)
-    , mStopped(false)
-    , mFinished(false)
-    , mRemoved(false)
-    , mAudioStopped(false)
-    , mAudioStopPending(false)
-    , mVideoStopped(false)
-    , mVideoStopPending(false)
-    , mChromeNotificationTaskPosted(false)
-  {}
-
-  ~GetUserMediaCallbackMediaStreamListener()
-  {
-    Unused << mMediaThread;
-    // It's OK to release mStream on any thread; they have thread-safe
-    // refcounts.
-  }
-
-  void Activate(already_AddRefed<SourceMediaStream> aStream,
+  SourceListener();
+
+  /**
+   * Registers this source listener as belonging to the given window listener.
+   */
+  void Register(GetUserMediaWindowListener* aListener);
+
+  /**
+   * Marks this listener as active and adds itself as a listener to aStream.
+   */
+  void Activate(SourceMediaStream* aStream,
                 AudioDevice* aAudioDevice,
-                VideoDevice* aVideoDevice)
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    mMainThreadCheck = PR_GetCurrentThread();
-    mStream = aStream;
-    mAudioDevice = aAudioDevice;
-    mVideoDevice = aVideoDevice;
-
-    mStream->AddListener(this);
-  }
-
-  MediaStream *Stream() // Can be used to test if Activate was called
+                VideoDevice* aVideoDevice);
+
+  /**
+   * Stops all live tracks, finishes the associated MediaStream and cleans up.
+   */
+  void Stop();
+
+  /**
+   * Removes this SourceListener from its associated MediaStream and marks it
+   * removed. Also removes the weak reference to the associated window listener.
+   */
+  void Remove();
+
+  /**
+   * Posts a task to stop the device associated with aTrackID and notifies the
+   * associated window listener that a track was stopped.
+   * Should this track be the last live one to be stopped, we'll also clean up.
+   */
+  void StopTrack(TrackID aTrackID);
+
+  /**
+   * Stops all screen/app/window/audioCapture sharing, but not camera or
+   * microphone.
+   */
+  void StopSharing();
+
+  MediaStream* Stream() const
   {
     return mStream;
   }
-  SourceMediaStream *GetSourceStream()
+
+  SourceMediaStream* GetSourceStream();
+
+  AudioDevice* GetAudioDevice() const
   {
-    NS_ASSERTION(mStream,"Getting stream from never-activated GUMCMSListener");
-    if (!mStream) {
-      return nullptr;
-    }
-    return mStream->AsSourceStream();
+    return mAudioDevice;
+  }
+
+  VideoDevice* GetVideoDevice() const
+  {
+    return mVideoDevice;
   }
 
-  void StopSharing();
-
-  void StopTrack(TrackID aID);
-
-  void NotifyChromeOfTrackStops();
-
-  typedef media::Pledge<bool, dom::MediaStreamError*> PledgeVoid;
+  void GetSettings(dom::MediaTrackSettings& aOutSettings, TrackID aTrackID);
+
+  void NotifyPull(MediaStreamGraph* aGraph,
+                  StreamTime aDesiredTime) override;
+
+  void NotifyEvent(MediaStreamGraph* aGraph,
+                   MediaStreamGraphEvent aEvent) override;
+
+  void NotifyFinished();
+
+  /**
+   * this can be in response to our own RemoveListener() (via ::Remove()), or
+   * because the DOM GC'd the DOMLocalMediaStream/etc we're attached to.
+   */
+  void NotifyRemoved();
+
+  void NotifyDirectListeners(MediaStreamGraph* aGraph, bool aHasListeners);
+
+  bool Activated() const
+  {
+    return mActivated;
+  }
+
+  bool Stopped() const
+  {
+    return mStopped;
+  }
+
+  bool CapturingVideo() const;
+
+  bool CapturingAudio() const;
+
+  bool CapturingScreen() const;
+
+  bool CapturingWindow() const;
+
+  bool CapturingApplication() const;
+
+  bool CapturingBrowser() const;
 
   already_AddRefed<PledgeVoid>
   ApplyConstraintsToTrack(nsPIDOMWindowInner* aWindow,
-                          TrackID aID,
+                          TrackID aTrackID,
                           const dom::MediaTrackConstraints& aConstraints,
                           dom::CallerType aCallerType);
 
-  // mVideo/AudioDevice are set by Activate(), so we assume they're capturing
-  // if set and represent a real capture device.
-  bool CapturingVideo()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    return mVideoDevice && !mStopped &&
-           !mVideoDevice->GetSource()->IsAvailable() &&
-           mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Camera &&
-           (!mVideoDevice->GetSource()->IsFake() ||
-            Preferences::GetBool("media.navigator.permission.fake"));
-  }
-  bool CapturingAudio()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    return mAudioDevice && !mStopped &&
-           !mAudioDevice->GetSource()->IsAvailable() &&
-           (!mAudioDevice->GetSource()->IsFake() ||
-            Preferences::GetBool("media.navigator.permission.fake"));
-  }
-  bool CapturingScreen()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    return mVideoDevice && !mStopped &&
-           !mVideoDevice->GetSource()->IsAvailable() &&
-           mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Screen;
-  }
-  bool CapturingWindow()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    return mVideoDevice && !mStopped &&
-           !mVideoDevice->GetSource()->IsAvailable() &&
-           mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Window;
-  }
-  bool CapturingApplication()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    return mVideoDevice && !mStopped &&
-           !mVideoDevice->GetSource()->IsAvailable() &&
-           mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Application;
-  }
-  bool CapturingBrowser()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    return mVideoDevice && !mStopped &&
-           mVideoDevice->GetSource()->IsAvailable() &&
-           mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Browser;
-  }
-
-  void GetSettings(dom::MediaTrackSettings& aOutSettings, TrackID aTrackID)
-  {
-    switch (aTrackID) {
-      case kVideoTrack:
-        if (mVideoDevice) {
-          mVideoDevice->GetSource()->GetSettings(aOutSettings);
-        }
-        break;
-
-      case kAudioTrack:
-        if (mAudioDevice) {
-          mAudioDevice->GetSource()->GetSettings(aOutSettings);
-        }
-        break;
-    }
-  }
-
-  // implement in .cpp to avoid circular dependency with MediaOperationTask
-  // Can be invoked from EITHER MainThread or MSG thread
-  void Stop();
-
-  void
-  AudioConfig(bool aEchoOn, uint32_t aEcho,
-              bool aAgcOn, uint32_t aAGC,
-              bool aNoiseOn, uint32_t aNoise,
-              int32_t aPlayoutDelay);
-
-  void
-  Remove()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    // allow calling even if inactive (!mStream) for easier cleanup
-    // Caller holds strong reference to us, so no death grip required
-    if (mStream && !mRemoved) {
-      MM_LOG(("Listener removed on purpose, mFinished = %d", (int) mFinished));
-      mRemoved = true; // RemoveListener is async, avoid races
-      // If it's destroyed, don't call - listener will be removed and we'll be notified!
-      if (!mStream->IsDestroyed()) {
-        mStream->RemoveListener(this);
-      }
-    }
-  }
-
-  // Proxy NotifyPull() to sources
-  void
-  NotifyPull(MediaStreamGraph* aGraph, StreamTime aDesiredTime) override
-  {
-    // Currently audio sources ignore NotifyPull, but they could
-    // watch it especially for fake audio.
-    if (mAudioDevice) {
-      mAudioDevice->GetSource()->NotifyPull(aGraph, mStream, kAudioTrack,
-                                            aDesiredTime, mPrincipalHandle);
-    }
-    if (mVideoDevice) {
-      mVideoDevice->GetSource()->NotifyPull(aGraph, mStream, kVideoTrack,
-                                            aDesiredTime, mPrincipalHandle);
-    }
-  }
-
-  void
-  NotifyEvent(MediaStreamGraph* aGraph,
-              MediaStreamGraphEvent aEvent) override
-  {
-    nsresult rv;
-    nsCOMPtr<nsIThread> thread;
-
-    switch (aEvent) {
-      case MediaStreamGraphEvent::EVENT_FINISHED:
-        rv = NS_GetMainThread(getter_AddRefs(thread));
-        if (NS_WARN_IF(NS_FAILED(rv))) {
-          NS_ASSERTION(false, "Mainthread not available; running on current thread");
-          // Ensure this really *was* MainThread (NS_GetCurrentThread won't work)
-          MOZ_RELEASE_ASSERT(mMainThreadCheck == PR_GetCurrentThread());
-          NotifyFinished();
-          return;
-        }
-        thread->Dispatch(NewRunnableMethod(this, &GetUserMediaCallbackMediaStreamListener::NotifyFinished),
-                         NS_DISPATCH_NORMAL);
-        break;
-      case MediaStreamGraphEvent::EVENT_REMOVED:
-        rv = NS_GetMainThread(getter_AddRefs(thread));
-        if (NS_WARN_IF(NS_FAILED(rv))) {
-          NS_ASSERTION(false, "Mainthread not available; running on current thread");
-          // Ensure this really *was* MainThread (NS_GetCurrentThread won't work)
-          MOZ_RELEASE_ASSERT(mMainThreadCheck == PR_GetCurrentThread());
-          NotifyRemoved();
-          return;
-        }
-        thread->Dispatch(NewRunnableMethod(this, &GetUserMediaCallbackMediaStreamListener::NotifyRemoved),
-                         NS_DISPATCH_NORMAL);
-        break;
-      case MediaStreamGraphEvent::EVENT_HAS_DIRECT_LISTENERS:
-        NotifyDirectListeners(aGraph, true);
-        break;
-      case MediaStreamGraphEvent::EVENT_HAS_NO_DIRECT_LISTENERS:
-        NotifyDirectListeners(aGraph, false);
-        break;
-      default:
-        break;
-    }
-  }
-
-  void
-  NotifyFinished();
-
-  void
-  NotifyRemoved();
-
-  void
-  NotifyDirectListeners(MediaStreamGraph* aGraph, bool aHasListeners);
-
-  PrincipalHandle GetPrincipalHandle() const { return mPrincipalHandle; }
+  PrincipalHandle GetPrincipalHandle() const;
 
 private:
-  // Set at construction
-  base::Thread* mMediaThread;
-  // never ever indirect off this; just for assertions
-  PRThread* mMainThreadCheck;
-
-  uint64_t mWindowID;
-  const PrincipalHandle mPrincipalHandle;
-
-  // true after this listener has sent MEDIA_STOP. MainThread only.
+  // true after this listener has been Activate()d in a WindowListener.
+  // MainThread only.
+  bool mActivated;
+
+  // true after this listener has had all devices stopped. MainThread only.
   bool mStopped;
 
   // true after the stream this listener is listening to has finished in the
   // MediaStreamGraph. MainThread only.
   bool mFinished;
 
   // true after this listener has been removed from its MediaStream.
   // MainThread only.
   bool mRemoved;
 
-  // true if we have sent MEDIA_STOP or MEDIA_STOP_TRACK for mAudioDevice.
-  // MainThread only.
+  // true if we have stopped mAudioDevice. MainThread only.
   bool mAudioStopped;
 
-  // true if we have scheduled MEDIA_STOP or MEDIA_STOP_TRACK for mAudioDevice.
-  // MainThread only.
-  bool mAudioStopPending;
-
-  // true if we have sent MEDIA_STOP or MEDIA_STOP_TRACK for mVideoDevice.
-  // MainThread only.
+  // true if we have stopped mVideoDevice. MainThread only.
   bool mVideoStopped;
 
-  // true if we have scheduled MEDIA_STOP or MEDIA_STOP_TRACK for mVideoDevice.
-  // MainThread only.
-  bool mVideoStopPending;
-
-  // true if we have scheduled a task to notify chrome in the next stable state.
-  // The task will reset this to false. MainThread only.
-  bool mChromeNotificationTaskPosted;
+  // never ever indirect off this; just for assertions
+  PRThread* mMainThreadCheck;
+
+  // Set in Register() on main thread, then read from any thread.
+  PrincipalHandle mPrincipalHandle;
+
+  // Weak pointer to the window listener that owns us. MainThread only.
+  GetUserMediaWindowListener* mWindowListener;
 
   // Set at Activate on MainThread
 
   // Accessed from MediaStreamGraph thread, MediaManager thread, and MainThread
   // No locking needed as they're only addrefed except on the MediaManager thread
   RefPtr<AudioDevice> mAudioDevice; // threadsafe refcnt
   RefPtr<VideoDevice> mVideoDevice; // threadsafe refcnt
   RefPtr<SourceMediaStream> mStream; // threadsafe refcnt
 };
 
-// Generic class for running long media operations like Start off the main
-// thread, and then (because nsDOMMediaStreams aren't threadsafe),
-// ProxyReleases mStream since it's cycle collected.
-class MediaOperationTask : public Runnable
+/**
+ * This class represents a WindowID and handles all MediaStreamListeners
+ * (here subclassed as SourceListeners) used to feed GetUserMedia source
+ * streams. It proxies feedback from them into messages for browser chrome.
+ * The SourceListeners are used to Start() and Stop() the underlying
+ * MediaEngineSource when MediaStreams are assigned and deassigned in content.
+ */
+class GetUserMediaWindowListener
 {
+  friend MediaManager;
 public:
-  // so we can send Stop without AddRef()ing from the MSG thread
-  MediaOperationTask(MediaOperation aType,
-    GetUserMediaCallbackMediaStreamListener* aListener,
-    DOMMediaStream* aStream,
-    OnTracksAvailableCallback* aOnTracksAvailableCallback,
-    AudioDevice* aAudioDevice,
-    VideoDevice* aVideoDevice,
-    bool aBool,
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GetUserMediaWindowListener)
+
+  // Create in an inactive state
+  GetUserMediaWindowListener(base::Thread *aThread,
     uint64_t aWindowID,
-    already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError,
-    const dom::MediaTrackConstraints& aConstraints = dom::MediaTrackConstraints())
-    : mType(aType)
-    , mStream(aStream)
-    , mOnTracksAvailableCallback(aOnTracksAvailableCallback)
-    , mAudioDevice(aAudioDevice)
-    , mVideoDevice(aVideoDevice)
-    , mListener(aListener)
-    , mBool(aBool)
+    const PrincipalHandle& aPrincipalHandle)
+    : mMediaThread(aThread)
     , mWindowID(aWindowID)
-    , mOnFailure(aError)
-    , mConstraints(aConstraints)
+    , mPrincipalHandle(aPrincipalHandle)
+    , mChromeNotificationTaskPosted(false)
   {}
 
-  ~MediaOperationTask()
+  /**
+   * Registers an inactive gUM source listener for this WindowListener.
+   */
+  void Register(SourceListener* aListener)
   {
-    // MediaStreams can be released on any thread.
+    MOZ_ASSERT(NS_IsMainThread());
+    if (!aListener || aListener->Activated()) {
+      MOZ_ASSERT(false, "Invalid listener");
+      return;
+    }
+    if (mInactiveListeners.Contains(aListener)) {
+      MOZ_ASSERT(false, "Already registered");
+      return;
+    }
+    if (mActiveListeners.Contains(aListener)) {
+      MOZ_ASSERT(false, "Already activated");
+      return;
+    }
+
+    aListener->Register(this);
+    mInactiveListeners.AppendElement(aListener);
   }
 
-  void
-  ReturnCallbackError(nsresult rv, const char* errorLog);
-
-  NS_IMETHOD
-  Run() override
+  /**
+   * Activates an already registered and inactive gUM source listener for this
+   * WindowListener.
+   */
+  void Activate(SourceListener* aListener,
+                SourceMediaStream* aStream,
+                AudioDevice* aAudioDevice,
+                VideoDevice* aVideoDevice)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    if (!aListener || aListener->Activated()) {
+      MOZ_ASSERT(false, "Cannot activate already activated source listener");
+      return;
+    }
+
+    if (!mInactiveListeners.RemoveElement(aListener)) {
+      MOZ_ASSERT(false, "Cannot activate non-registered source listener");
+      return;
+    }
+
+    RefPtr<SourceListener> listener = aListener;
+    listener->Activate(aStream, aAudioDevice, aVideoDevice);
+    mActiveListeners.AppendElement(listener.forget());
+  }
+
+  // Can be invoked from EITHER MainThread or MSG thread
+  void Stop()
   {
-    SourceMediaStream *source = mListener->GetSourceStream();
-    // No locking between these is required as all the callbacks for the
-    // same MediaStream will occur on the same thread.
-    if (!source) // means the stream was never Activated()
-      return NS_OK;
-
-    switch (mType) {
-      case MEDIA_START:
-        {
-          NS_ASSERTION(!NS_IsMainThread(), "Never call on main thread");
-          nsresult rv;
-
-          if (mAudioDevice) {
-            rv = mAudioDevice->GetSource()->Start(source, kAudioTrack,
-                                                  mListener->GetPrincipalHandle());
-            if (NS_FAILED(rv)) {
-              ReturnCallbackError(rv, "Starting audio failed");
-              return NS_OK;
-            }
-          }
-          if (mVideoDevice) {
-            rv = mVideoDevice->GetSource()->Start(source, kVideoTrack,
-                                                  mListener->GetPrincipalHandle());
-            if (NS_FAILED(rv)) {
-              ReturnCallbackError(rv, "Starting video failed");
-              return NS_OK;
-            }
-          }
-          // Start() queued the tracks to be added synchronously to avoid races
-          source->FinishAddTracks();
-
-          source->SetPullEnabled(true);
-          source->AdvanceKnownTracksTime(STREAM_TIME_MAX);
-
-          MM_LOG(("started all sources"));
-          // Forward mOnTracksAvailableCallback to GetUserMediaNotificationEvent,
-          // because mOnTracksAvailableCallback needs to be added to mStream
-          // on the main thread.
-          nsIRunnable *event =
-            new GetUserMediaNotificationEvent(GetUserMediaNotificationEvent::STARTING,
-                                              mStream.forget(),
-                                              mOnTracksAvailableCallback.forget(),
-                                              mAudioDevice != nullptr,
-                                              mVideoDevice != nullptr,
-                                              mWindowID, mOnFailure.forget());
-          // event must always be released on mainthread due to the JS callbacks
-          // in the TracksAvailableCallback
-          NS_DispatchToMainThread(event);
-        }
-        break;
-
-      case MEDIA_STOP:
-      case MEDIA_STOP_TRACK:
-        {
-          NS_ASSERTION(!NS_IsMainThread(), "Never call on main thread");
-          if (mAudioDevice) {
-            mAudioDevice->GetSource()->Stop(source, kAudioTrack);
-            mAudioDevice->Deallocate();
-          }
-          if (mVideoDevice) {
-            mVideoDevice->GetSource()->Stop(source, kVideoTrack);
-            mVideoDevice->Deallocate();
-          }
-          if (mType == MEDIA_STOP) {
-            source->EndAllTrackAndFinish();
-          }
-
-          nsIRunnable *event =
-            new GetUserMediaNotificationEvent(mListener,
-                                              mType == MEDIA_STOP ?
-                                              GetUserMediaNotificationEvent::STOPPING :
-                                              GetUserMediaNotificationEvent::STOPPED_TRACK,
-                                              mAudioDevice != nullptr,
-                                              mVideoDevice != nullptr,
-                                              mWindowID);
-          // event must always be released on mainthread due to the JS callbacks
-          // in the TracksAvailableCallback
-          NS_DispatchToMainThread(event);
-        }
-        break;
-
-      case MEDIA_DIRECT_LISTENERS:
-        {
-          NS_ASSERTION(!NS_IsMainThread(), "Never call on main thread");
-          if (mVideoDevice) {
-            mVideoDevice->GetSource()->SetDirectListeners(mBool);
+    MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
+
+    for (auto& source : mActiveListeners) {
+      source->Stop();
+    }
+
+    // Once all tracks have stopped, that will trigger the chrome notification
+  }
+
+  /**
+   * Removes all SourceListeners from this window listener.
+   * Removes this window listener from the list of active windows, so callers
+   * need to make sure to hold a strong reference.
+   */
+  void RemoveAll()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    // Shallow copy since SourceListener::Remove() will modify the arrays.
+    nsTArray<RefPtr<SourceListener>> listeners(mInactiveListeners.Length()
+                                               + mActiveListeners.Length());
+    listeners.AppendElements(mInactiveListeners);
+    listeners.AppendElements(mActiveListeners);
+    for (auto& l : listeners) {
+      Remove(l);
+    }
+
+    MOZ_ASSERT(mInactiveListeners.Length() == 0);
+    MOZ_ASSERT(mActiveListeners.Length() == 0);
+
+    RefPtr<MediaManager> mgr = MediaManager::GetIfExists();
+    if (!mgr) {
+      MOZ_ASSERT(false, "MediaManager should stay until everything is removed");
+      return;
+    }
+    GetUserMediaWindowListener* windowListener =
+      mgr->GetWindowListener(mWindowID);
+
+    if (!windowListener) {
+      nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+      auto* globalWindow = nsGlobalWindow::GetInnerWindowWithId(mWindowID);
+      if (globalWindow) {
+        RefPtr<GetUserMediaRequest> req =
+          new GetUserMediaRequest(globalWindow->AsInner(),
+                                  NullString(), NullString());
+        obs->NotifyObservers(req, "recording-device-stopped", nullptr);
+      }
+      return;
+    }
+
+    MOZ_ASSERT(windowListener == this,
+               "There should only be one window listener per window ID");
+
+    LOG(("GUMWindowListener %p removing windowID %" PRIu64, this, mWindowID));
+    mgr->RemoveWindowID(mWindowID);
+  }
+
+  bool Remove(SourceListener* aListener)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    if (!mInactiveListeners.RemoveElement(aListener) &&
+        !mActiveListeners.RemoveElement(aListener)) {
+      return false;
+    }
+
+    MOZ_ASSERT(!mInactiveListeners.Contains(aListener),
+               "A SourceListener should only be once in one of "
+               "mInactiveListeners and mActiveListeners");
+    MOZ_ASSERT(!mActiveListeners.Contains(aListener),
+               "A SourceListener should only be once in one of "
+               "mInactiveListeners and mActiveListeners");
+
+    LOG(("GUMWindowListener %p removing SourceListener %p.", this, aListener));
+    aListener->Remove();
+
+    if (VideoDevice* removedDevice = aListener->GetVideoDevice()) {
+      bool revokeVideoPermission = true;
+      nsString removedRawId;
+      nsString removedSourceType;
+      removedDevice->GetRawId(removedRawId);
+      removedDevice->GetMediaSource(removedSourceType);
+      for (const auto& l : mActiveListeners) {
+        if (VideoDevice* device = l->GetVideoDevice()) {
+          nsString rawId;
+          device->GetRawId(rawId);
+          if (removedRawId.Equals(rawId)) {
+            revokeVideoPermission = false;
+            break;
           }
         }
-        break;
-
-      default:
-        MOZ_ASSERT(false,"invalid MediaManager operation");
-        break;
+      }
+
+      if (revokeVideoPermission) {
+        nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+        auto* globalWindow = nsGlobalWindow::GetInnerWindowWithId(mWindowID);
+        nsPIDOMWindowInner* window = globalWindow ? globalWindow->AsInner()
+                                                  : nullptr;
+        RefPtr<GetUserMediaRequest> req =
+          new GetUserMediaRequest(window, removedRawId, removedSourceType);
+        obs->NotifyObservers(req, "recording-device-stopped", nullptr);
+      }
+    }
+
+    if (AudioDevice* removedDevice = aListener->GetAudioDevice()) {
+      bool revokeAudioPermission = true;
+      nsString removedRawId;
+      nsString removedSourceType;
+      removedDevice->GetRawId(removedRawId);
+      removedDevice->GetMediaSource(removedSourceType);
+      for (const auto& l : mActiveListeners) {
+        if (AudioDevice* device = l->GetAudioDevice()) {
+          nsString rawId;
+          device->GetRawId(rawId);
+          if (removedRawId.Equals(rawId)) {
+            revokeAudioPermission = false;
+            break;
+          }
+        }
+      }
+
+      if (revokeAudioPermission) {
+        nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+        auto* globalWindow = nsGlobalWindow::GetInnerWindowWithId(mWindowID);
+        nsPIDOMWindowInner* window = globalWindow ? globalWindow->AsInner()
+                                                  : nullptr;
+        RefPtr<GetUserMediaRequest> req =
+          new GetUserMediaRequest(window, removedRawId, removedSourceType);
+        obs->NotifyObservers(req, "recording-device-stopped", nullptr);
+      }
+    }
+
+    if (mInactiveListeners.Length() == 0 &&
+        mActiveListeners.Length() == 0) {
+      LOG(("GUMWindowListener %p Removed the last SourceListener. "
+           "Cleaning up.", this));
+      RemoveAll();
     }
 
-    return NS_OK;
+    return true;
+  }
+
+  void StopSharing();
+
+  /**
+   * Called by one of our SourceListeners when one of its tracks has stopped.
+   * Schedules an event for the next stable state to update chrome.
+   */
+  void NotifySourceTrackStopped();
+
+  /**
+   * Called in stable state to send a notification to update chrome.
+   */
+  void NotifyChromeOfTrackStops();
+
+  bool CapturingVideo() const
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    for (auto& l : mActiveListeners) {
+      if (l->CapturingVideo()) {
+        return true;
+      }
+    }
+    return false;
+  }
+  bool CapturingAudio() const
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    for (auto& l : mActiveListeners) {
+      if (l->CapturingAudio()) {
+        return true;
+      }
+    }
+    return false;
   }
+  bool CapturingScreen() const
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    for (auto& l : mActiveListeners) {
+      if (l->CapturingScreen()) {
+        return true;
+      }
+    }
+    return false;
+  }
+  bool CapturingWindow() const
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    for (auto& l : mActiveListeners) {
+      if (l->CapturingWindow()) {
+        return true;
+      }
+    }
+    return false;
+  }
+  bool CapturingApplication() const
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    for (auto& l : mActiveListeners) {
+      if (l->CapturingApplication()) {
+        return true;
+      }
+    }
+    return false;
+  }
+  bool CapturingBrowser() const
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    for (auto& l : mActiveListeners) {
+      if (l->CapturingBrowser()) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  uint64_t WindowID() const
+  {
+    return mWindowID;
+  }
+
+  PrincipalHandle GetPrincipalHandle() const { return mPrincipalHandle; }
 
 private:
-  MediaOperation mType;
-  RefPtr<DOMMediaStream> mStream;
-  nsAutoPtr<OnTracksAvailableCallback> mOnTracksAvailableCallback;
-  RefPtr<AudioDevice> mAudioDevice; // threadsafe
-  RefPtr<VideoDevice> mVideoDevice; // threadsafe
-  RefPtr<GetUserMediaCallbackMediaStreamListener> mListener; // threadsafe
-  bool mBool;
+  ~GetUserMediaWindowListener()
+  {
+    for (auto& l : mInactiveListeners) {
+      l->NotifyRemoved();
+    }
+    mInactiveListeners.Clear();
+    for (auto& l : mActiveListeners) {
+      l->NotifyRemoved();
+    }
+    mActiveListeners.Clear();
+    Unused << mMediaThread;
+    // It's OK to release mStream on any thread; they have thread-safe
+    // refcounts.
+  }
+
+  // Set at construction
+  base::Thread* mMediaThread;
+
   uint64_t mWindowID;
-  nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mOnFailure;
-  dom::MediaTrackConstraints mConstraints;
+  const PrincipalHandle mPrincipalHandle;
+
+  // true if we have scheduled a task to notify chrome in the next stable state.
+  // The task will reset this to false. MainThread only.
+  bool mChromeNotificationTaskPosted;
+
+  nsTArray<RefPtr<SourceListener>> mInactiveListeners;
+  nsTArray<RefPtr<SourceListener>> mActiveListeners;
 };
 
 /**
  * Send an error back to content.
  * Do this only on the main thread. The onSuccess callback is also passed here
  * so it can be released correctly.
  */
 template<class SuccessCallbackType>
 class ErrorCallbackRunnable : public Runnable
 {
 public:
   ErrorCallbackRunnable(
-    nsCOMPtr<SuccessCallbackType>& aOnSuccess,
-    nsCOMPtr<nsIDOMGetUserMediaErrorCallback>& aOnFailure,
+    nsCOMPtr<SuccessCallbackType>&& aOnSuccess,
+    nsCOMPtr<nsIDOMGetUserMediaErrorCallback>&& aOnFailure,
     MediaMgrError& aError,
     uint64_t aWindowID)
     : mError(&aError)
     , mWindowID(aWindowID)
     , mManager(MediaManager::GetInstance())
   {
     mOnSuccess.swap(aOnSuccess);
     mOnFailure.swap(aOnFailure);
@@ -654,39 +694,16 @@ private:
 
   nsCOMPtr<SuccessCallbackType> mOnSuccess;
   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mOnFailure;
   RefPtr<MediaMgrError> mError;
   uint64_t mWindowID;
   RefPtr<MediaManager> mManager; // get ref to this when creating the runnable
 };
 
-// Handle removing GetUserMediaCallbackMediaStreamListener from main thread
-class GetUserMediaListenerRemove: public Runnable
-{
-public:
-  GetUserMediaListenerRemove(uint64_t aWindowID,
-    GetUserMediaCallbackMediaStreamListener *aListener)
-    : mWindowID(aWindowID)
-    , mListener(aListener) {}
-
-  NS_IMETHOD
-  Run() override
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    RefPtr<MediaManager> manager(MediaManager::GetInstance());
-    manager->RemoveFromWindowList(mWindowID, mListener);
-    return NS_OK;
-  }
-
-protected:
-  uint64_t mWindowID;
-  RefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
-};
-
 /**
  * nsIMediaDevice implementation.
  */
 NS_IMPL_ISUPPORTS(MediaDevice, nsIMediaDevice)
 
 MediaDevice::MediaDevice(MediaEngineSource* aSource, bool aIsVideo)
   : mScary(aSource->GetScary())
   , mMediaSource(aSource->GetMediaSource())
@@ -889,35 +906,16 @@ nsresult MediaDevice::Restart(const dom:
   return GetSource()->Restart(mAllocationHandle, aConstraints, aPrefs, mID,
                               aOutBadConstraint);
 }
 
 nsresult MediaDevice::Deallocate() {
   return GetSource()->Deallocate(mAllocationHandle);
 }
 
-void
-MediaOperationTask::ReturnCallbackError(nsresult rv, const char* errorLog)
-{
-  MM_LOG(("%s , rv=%" PRIu32, errorLog, static_cast<uint32_t>(rv)));
-  NS_DispatchToMainThread(do_AddRef(new ReleaseMediaOperationResource(mStream.forget(),
-    mOnTracksAvailableCallback.forget())));
-  nsString log;
-
-  log.AssignASCII(errorLog);
-  nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> onSuccess;
-  RefPtr<MediaMgrError> error = new MediaMgrError(
-    NS_LITERAL_STRING("InternalError"), log);
-  NS_DispatchToMainThread(do_AddRef(
-    new ErrorCallbackRunnable<nsIDOMGetUserMediaSuccessCallback>(onSuccess,
-                                                                 mOnFailure,
-                                                                 *error,
-                                                                 mWindowID)));
-}
-
 static bool
 IsOn(const OwningBooleanOrMediaTrackConstraints &aUnion) {
   return !aUnion.IsBoolean() || aUnion.GetAsBoolean();
 }
 
 static const MediaTrackConstraints&
 GetInvariant(const OwningBooleanOrMediaTrackConstraints &aUnion) {
   static const MediaTrackConstraints empty;
@@ -982,42 +980,44 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED(FakeT
  */
 class GetUserMediaStreamRunnable : public Runnable
 {
 public:
   GetUserMediaStreamRunnable(
     nsCOMPtr<nsIDOMGetUserMediaSuccessCallback>& aOnSuccess,
     nsCOMPtr<nsIDOMGetUserMediaErrorCallback>& aOnFailure,
     uint64_t aWindowID,
-    GetUserMediaCallbackMediaStreamListener* aListener,
+    GetUserMediaWindowListener* aWindowListener,
+    SourceListener* aSourceListener,
     const ipc::PrincipalInfo& aPrincipalInfo,
     const MediaStreamConstraints& aConstraints,
     AudioDevice* aAudioDevice,
     VideoDevice* aVideoDevice,
     PeerIdentity* aPeerIdentity)
     : mConstraints(aConstraints)
     , mAudioDevice(aAudioDevice)
     , mVideoDevice(aVideoDevice)
     , mWindowID(aWindowID)
-    , mListener(aListener)
+    , mWindowListener(aWindowListener)
+    , mSourceListener(aSourceListener)
     , mPrincipalInfo(aPrincipalInfo)
     , mPeerIdentity(aPeerIdentity)
     , mManager(MediaManager::GetInstance())
   {
     mOnSuccess.swap(aOnSuccess);
     mOnFailure.swap(aOnFailure);
   }
 
   ~GetUserMediaStreamRunnable() {}
 
   class TracksAvailableCallback : public OnTracksAvailableCallback
   {
   public:
     TracksAvailableCallback(MediaManager* aManager,
-                            nsIDOMGetUserMediaSuccessCallback* aSuccess,
+                            already_AddRefed<nsIDOMGetUserMediaSuccessCallback> aSuccess,
                             uint64_t aWindowID,
                             DOMMediaStream* aStream)
       : mWindowID(aWindowID), mOnSuccess(aSuccess), mManager(aManager),
         mStream(aStream) {}
     void NotifyTracksAvailable(DOMMediaStream* aStream) override
     {
       // We're in the main thread, so no worries here.
       if (!(mManager->IsWindowStillActive(mWindowID))) {
@@ -1051,18 +1051,19 @@ public:
   Run() override
   {
     MOZ_ASSERT(NS_IsMainThread());
     nsGlobalWindow* globalWindow = nsGlobalWindow::GetInnerWindowWithId(mWindowID);
     nsPIDOMWindowInner* window = globalWindow ? globalWindow->AsInner() : nullptr;
 
     // We're on main-thread, and the windowlist can only
     // be invalidated from the main-thread (see OnNavigation)
-    StreamListeners* listeners = mManager->GetWindowListeners(mWindowID);
-    if (!listeners || !window || !window->GetExtantDoc()) {
+    GetUserMediaWindowListener* listener =
+      mManager->GetWindowListener(mWindowID);
+    if (!listener || !window || !window->GetExtantDoc()) {
       // This window is no longer live.  mListener has already been removed
       return NS_OK;
     }
 
     MediaStreamGraph::GraphDriverType graphDriverType =
       mAudioDevice ? MediaStreamGraph::AUDIO_THREAD_DRIVER
                    : MediaStreamGraph::SYSTEM_THREAD_DRIVER;
     MediaStreamGraph* msg =
@@ -1089,33 +1090,34 @@ public:
             mWindowID, domStream->GetInputStream()->AsProcessedStream());
       window->SetAudioCapture(true);
     } else {
       class LocalTrackSource : public MediaStreamTrackSource
       {
       public:
         LocalTrackSource(nsIPrincipal* aPrincipal,
                          const nsString& aLabel,
-                         GetUserMediaCallbackMediaStreamListener* aListener,
+                         SourceListener* aListener,
                          const MediaSourceEnum aSource,
                          const TrackID aTrackID,
                          const PeerIdentity* aPeerIdentity)
           : MediaStreamTrackSource(aPrincipal, aLabel), mListener(aListener),
             mSource(aSource), mTrackID(aTrackID), mPeerIdentity(aPeerIdentity) {}
 
         MediaSourceEnum GetMediaSource() const override
         {
           return mSource;
         }
 
         const PeerIdentity* GetPeerIdentity() const override
         {
           return mPeerIdentity;
         }
 
+
         already_AddRefed<PledgeVoid>
         ApplyConstraints(nsPIDOMWindowInner* aWindow,
                          const MediaTrackConstraints& aConstraints,
                          dom::CallerType aCallerType) override
         {
           if (sInShutdown || !mListener) {
             // Track has been stopped, or we are in shutdown. In either case
             // there's no observable outcome, so pretend we succeeded.
@@ -1141,17 +1143,17 @@ public:
             mListener->StopTrack(mTrackID);
             mListener = nullptr;
           }
         }
 
       protected:
         ~LocalTrackSource() {}
 
-        RefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
+        RefPtr<SourceListener> mListener;
         const MediaSourceEnum mSource;
         const TrackID mTrackID;
         const RefPtr<const PeerIdentity> mPeerIdentity;
       };
 
       nsCOMPtr<nsIPrincipal> principal;
       if (mPeerIdentity) {
         principal = NullPrincipal::CreateWithInheritedAttributes(window->GetExtantDoc()->NodePrincipal());
@@ -1160,85 +1162,142 @@ public:
       }
 
       // Normal case, connect the source stream to the track union stream to
       // avoid us blocking. Pass a simple TrackSourceGetter for potential
       // fake tracks. Apart from them gUM never adds tracks dynamically.
       domStream =
         DOMLocalMediaStream::CreateSourceStreamAsInput(window, msg,
                                                        new FakeTrackSourceGetter(principal));
+      stream = domStream->GetInputStream()->AsSourceStream();
 
       if (mAudioDevice) {
         nsString audioDeviceName;
         mAudioDevice->GetName(audioDeviceName);
         const MediaSourceEnum source =
           mAudioDevice->GetSource()->GetMediaSource();
         RefPtr<MediaStreamTrackSource> audioSource =
-          new LocalTrackSource(principal, audioDeviceName, mListener, source,
-                               kAudioTrack, mPeerIdentity);
+          new LocalTrackSource(principal, audioDeviceName, mSourceListener,
+                               source, kAudioTrack, mPeerIdentity);
         MOZ_ASSERT(IsOn(mConstraints.mAudio));
         RefPtr<MediaStreamTrack> track =
           domStream->CreateDOMTrack(kAudioTrack, MediaSegment::AUDIO, audioSource,
                                     GetInvariant(mConstraints.mAudio));
         domStream->AddTrackInternal(track);
       }
       if (mVideoDevice) {
         nsString videoDeviceName;
         mVideoDevice->GetName(videoDeviceName);
         const MediaSourceEnum source =
           mVideoDevice->GetSource()->GetMediaSource();
         RefPtr<MediaStreamTrackSource> videoSource =
-          new LocalTrackSource(principal, videoDeviceName, mListener, source,
-                               kVideoTrack, mPeerIdentity);
+          new LocalTrackSource(principal, videoDeviceName, mSourceListener,
+                               source, kVideoTrack, mPeerIdentity);
         MOZ_ASSERT(IsOn(mConstraints.mVideo));
         RefPtr<MediaStreamTrack> track =
           domStream->CreateDOMTrack(kVideoTrack, MediaSegment::VIDEO, videoSource,
                                     GetInvariant(mConstraints.mVideo));
         domStream->AddTrackInternal(track);
       }
-      stream = domStream->GetInputStream()->AsSourceStream();
     }
 
-    if (!domStream || sInShutdown) {
+    if (!domStream || !stream || sInShutdown) {
       nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onFailure = mOnFailure.forget();
       LOG(("Returning error for getUserMedia() - no stream"));
 
       if (auto* window = nsGlobalWindow::GetInnerWindowWithId(mWindowID)) {
         RefPtr<MediaStreamError> error = new MediaStreamError(window->AsInner(),
             NS_LITERAL_STRING("InternalError"),
             sInShutdown ? NS_LITERAL_STRING("In shutdown") :
                           NS_LITERAL_STRING("No stream."));
         onFailure->OnError(error);
       }
       return NS_OK;
     }
 
-    // The listener was added at the beginning in an inactive state.
-    // Activate our listener. We'll call Start() on the source when get a callback
-    // that the MediaStream has started consuming. The listener is freed
-    // when the page is invalidated (on navigation or close).
-    MOZ_ASSERT(stream);
-    mListener->Activate(stream.forget(), mAudioDevice, mVideoDevice);
+    // Activate our source listener. We'll call Start() on the source when we
+    // get a callback that the MediaStream has started consuming. The listener
+    // is freed when the page is invalidated (on navigation or close).
+    mWindowListener->Activate(mSourceListener, stream, mAudioDevice, mVideoDevice);
 
     // Note: includes JS callbacks; must be released on MainThread
-    TracksAvailableCallback* tracksAvailableCallback =
-      new TracksAvailableCallback(mManager, mOnSuccess, mWindowID, domStream);
+    auto callback = MakeRefPtr<Refcountable<UniquePtr<OnTracksAvailableCallback>>>(
+        new TracksAvailableCallback(mManager, mOnSuccess.forget(), mWindowID, domStream));
 
     // Dispatch to the media thread to ask it to start the sources,
     // because that can take a while.
-    // Pass ownership of domStream to the MediaOperationTask
-    // to ensure it's kept alive until the MediaOperationTask runs (at least).
-    RefPtr<Runnable> mediaOperation =
-        new MediaOperationTask(MEDIA_START, mListener, domStream,
-                               tracksAvailableCallback,
-                               mAudioDevice, mVideoDevice,
-                               false, mWindowID, mOnFailure.forget());
-    MediaManager::PostTask(mediaOperation.forget());
-    // We won't need mOnFailure now.
-    mOnFailure = nullptr;
+    // Pass ownership of domStream through the lambda to
+    // GetUserMediaNotificationEvent to ensure it's kept alive until the
+    // GetUserMediaNotificationEvent runs or is discarded.
+    RefPtr<GetUserMediaStreamRunnable> self = this;
+    MediaManager::PostTask(NewTaskFrom([self, domStream, callback]() mutable {
+      MOZ_ASSERT(MediaManager::IsInMediaThread());
+      SourceMediaStream* source = self->mSourceListener->GetSourceStream();
+
+      RefPtr<MediaMgrError> error = nullptr;
+      if (self->mAudioDevice) {
+        nsresult rv =
+          self->mAudioDevice->GetSource()->Start(source, kAudioTrack,
+                                                 self->mSourceListener->GetPrincipalHandle());
+        if (NS_FAILED(rv)) {
+          nsString log;
+          log.AssignASCII("Starting audio failed");
+          error = new MediaMgrError(NS_LITERAL_STRING("InternalError"), log);
+        }
+      }
+
+      if (!error && self->mVideoDevice) {
+        nsresult rv =
+          self->mVideoDevice->GetSource()->Start(source, kVideoTrack,
+                                                 self->mSourceListener->GetPrincipalHandle());
+        if (NS_FAILED(rv)) {
+          nsString log;
+          log.AssignASCII("Starting video failed");
+          error = new MediaMgrError(NS_LITERAL_STRING("InternalError"), log);
+        }
+      }
+
+      if (error) {
+        // The DOM stream and track callback must be released on main thread.
+        NS_DispatchToMainThread(do_AddRef(new ReleaseMediaOperationResource(
+          domStream.forget(), callback.forget())));
+
+        // Dispatch the error callback on main thread.
+        nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> onSuccess;
+        NS_DispatchToMainThread(do_AddRef(
+          new ErrorCallbackRunnable<nsIDOMGetUserMediaSuccessCallback>(
+            Move(onSuccess), Move(self->mOnFailure), *error, self->mWindowID)));
+
+        // This should be empty now
+        MOZ_ASSERT(!self->mOnFailure);
+        return NS_OK;
+      }
+
+      // Start() queued the tracks to be added synchronously to avoid races
+      source->FinishAddTracks();
+
+      source->SetPullEnabled(true);
+      source->AdvanceKnownTracksTime(STREAM_TIME_MAX);
+
+      LOG(("started all sources"));
+
+      // Forward onTracksAvailableCallback to GetUserMediaNotificationEvent,
+      // because onTracksAvailableCallback needs to be added to domStream
+      // on the main thread.
+      // The event runnable must always be released on mainthread due to the JS
+      // callbacks in the TracksAvailableCallback.
+      NS_DispatchToMainThread(do_AddRef(
+        new GetUserMediaNotificationEvent(
+          GetUserMediaNotificationEvent::STARTING,
+          domStream.forget(),
+          callback.forget(),
+          self->mWindowID,
+          self->mOnFailure.forget())));
+      return NS_OK;
+    }));
 
     if (!IsPincipalInfoPrivate(mPrincipalInfo)) {
       // Call GetPrincipalKey again, this time w/persist = true, to promote
       // deviceIds to persistent, in case they're not already. Fire'n'forget.
       RefPtr<Pledge<nsCString>> p =
         media::GetPrincipalKey(mPrincipalInfo, true);
     }
     return NS_OK;
@@ -1246,17 +1305,18 @@ public:
 
 private:
   nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> mOnSuccess;
   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mOnFailure;
   MediaStreamConstraints mConstraints;
   RefPtr<AudioDevice> mAudioDevice;
   RefPtr<VideoDevice> mVideoDevice;
   uint64_t mWindowID;
-  RefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
+  RefPtr<GetUserMediaWindowListener> mWindowListener;
+  RefPtr<SourceListener> mSourceListener;
   ipc::PrincipalInfo mPrincipalInfo;
   RefPtr<PeerIdentity> mPeerIdentity;
   RefPtr<MediaManager> mManager; // get ref to this when creating the runnable
 };
 
 // Source getter returning full list
 
 template<class DeviceType>
@@ -1379,51 +1439,53 @@ MediaManager::SelectSettings(
  */
 class GetUserMediaTask : public Runnable
 {
 public:
   GetUserMediaTask(
     const MediaStreamConstraints& aConstraints,
     already_AddRefed<nsIDOMGetUserMediaSuccessCallback> aOnSuccess,
     already_AddRefed<nsIDOMGetUserMediaErrorCallback> aOnFailure,
-    uint64_t aWindowID, GetUserMediaCallbackMediaStreamListener *aListener,
-    MediaEnginePrefs &aPrefs,
+    uint64_t aWindowID, GetUserMediaWindowListener *aWindowListener,
+    SourceListener *aSourceListener, MediaEnginePrefs &aPrefs,
     const ipc::PrincipalInfo& aPrincipalInfo,
     bool aIsChrome,
     MediaManager::SourceSet* aSourceSet)
     : mConstraints(aConstraints)
     , mOnSuccess(aOnSuccess)
     , mOnFailure(aOnFailure)
     , mWindowID(aWindowID)
-    , mListener(aListener)
+    , mWindowListener(aWindowListener)
+    , mSourceListener(aSourceListener)
     , mPrefs(aPrefs)
     , mPrincipalInfo(aPrincipalInfo)
     , mIsChrome(aIsChrome)
     , mDeviceChosen(false)
     , mSourceSet(aSourceSet)
     , mManager(MediaManager::GetInstance())
   {}
 
   ~GetUserMediaTask() {
   }
 
   void
   Fail(const nsAString& aName,
        const nsAString& aMessage = EmptyString(),
        const nsAString& aConstraint = EmptyString()) {
     RefPtr<MediaMgrError> error = new MediaMgrError(aName, aMessage, aConstraint);
-    auto runnable = MakeRefPtr<ErrorCallbackRunnable<nsIDOMGetUserMediaSuccessCallback>>(
-        mOnSuccess, mOnFailure, *error, mWindowID);
+    auto errorRunnable = MakeRefPtr<ErrorCallbackRunnable<nsIDOMGetUserMediaSuccessCallback>>(
+        Move(mOnSuccess), Move(mOnFailure), *error, mWindowID);
     // These should be empty now
     MOZ_ASSERT(!mOnSuccess);
     MOZ_ASSERT(!mOnFailure);
 
-    NS_DispatchToMainThread(runnable.forget());
+    NS_DispatchToMainThread(errorRunnable.forget());
     // Do after ErrorCallbackRunnable Run()s, as it checks active window list
-    NS_DispatchToMainThread(do_AddRef(new GetUserMediaListenerRemove(mWindowID, mListener)));
+    NS_DispatchToMainThread(NewRunnableMethod<RefPtr<SourceListener>>(
+      mWindowListener, &GetUserMediaWindowListener::Remove, mSourceListener));
   }
 
   NS_IMETHOD
   Run() override
   {
     MOZ_ASSERT(!NS_IsMainThread());
     MOZ_ASSERT(mOnSuccess);
     MOZ_ASSERT(mOnFailure);
@@ -1481,18 +1543,19 @@ public:
     }
     PeerIdentity* peerIdentity = nullptr;
     if (!mConstraints.mPeerIdentity.IsEmpty()) {
       peerIdentity = new PeerIdentity(mConstraints.mPeerIdentity);
     }
 
     NS_DispatchToMainThread(do_AddRef(
         new GetUserMediaStreamRunnable(mOnSuccess, mOnFailure, mWindowID,
-                                       mListener, mPrincipalInfo,
-                                       mConstraints, mAudioDevice, mVideoDevice,
+                                       mWindowListener, mSourceListener,
+                                       mPrincipalInfo, mConstraints,
+                                       mAudioDevice, mVideoDevice,
                                        peerIdentity)));
     MOZ_ASSERT(!mOnSuccess);
     MOZ_ASSERT(!mOnFailure);
     return NS_OK;
   }
 
   nsresult
   Denied(const nsAString& aName,
@@ -1510,18 +1573,17 @@ public:
       nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onFailure = mOnFailure.forget();
 
       if (auto* window = nsGlobalWindow::GetInnerWindowWithId(mWindowID)) {
         RefPtr<MediaStreamError> error = new MediaStreamError(window->AsInner(),
                                                               aName, aMessage);
         onFailure->OnError(error);
       }
       // Should happen *after* error runs for consistency, but may not matter
-      RefPtr<MediaManager> manager(MediaManager::GetInstance());
-      manager->RemoveFromWindowList(mWindowID, mListener);
+      mWindowListener->Remove(mSourceListener);
     } else {
       // This will re-check the window being alive on main-thread
       // and remove the listener on MainThread as well
       Fail(aName, aMessage);
     }
 
     MOZ_ASSERT(!mOnSuccess);
     MOZ_ASSERT(!mOnFailure);
@@ -1559,17 +1621,18 @@ public:
   }
 
 private:
   MediaStreamConstraints mConstraints;
 
   nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> mOnSuccess;
   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mOnFailure;
   uint64_t mWindowID;
-  RefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
+  RefPtr<GetUserMediaWindowListener> mWindowListener;
+  RefPtr<SourceListener> mSourceListener;
   RefPtr<AudioDevice> mAudioDevice;
   RefPtr<VideoDevice> mVideoDevice;
   MediaEnginePrefs mPrefs;
   ipc::PrincipalInfo mPrincipalInfo;
   bool mIsChrome;
 
   bool mDeviceChosen;
 public:
@@ -1890,57 +1953,43 @@ MediaManager::PostTask(already_AddRefed<
   }
   NS_ASSERTION(Get(), "MediaManager singleton?");
   NS_ASSERTION(Get()->mMediaThread, "No thread yet");
   Get()->mMediaThread->message_loop()->PostTask(Move(task));
 }
 
 /* static */ nsresult
 MediaManager::NotifyRecordingStatusChange(nsPIDOMWindowInner* aWindow,
-                                          const nsString& aMsg,
-                                          const bool& aIsAudio,
-                                          const bool& aIsVideo)
+                                          const nsString& aMsg)
 {
   NS_ENSURE_ARG(aWindow);
 
   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   if (!obs) {
     NS_WARNING("Could not get the Observer service for GetUserMedia recording notification.");
     return NS_ERROR_FAILURE;
   }
 
   RefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
-  props->SetPropertyAsBool(NS_LITERAL_STRING("isAudio"), aIsAudio);
-  props->SetPropertyAsBool(NS_LITERAL_STRING("isVideo"), aIsVideo);
 
   nsCString pageURL;
   nsCOMPtr<nsIURI> docURI = aWindow->GetDocumentURI();
   NS_ENSURE_TRUE(docURI, NS_ERROR_FAILURE);
 
   nsresult rv = docURI->GetSpec(pageURL);
   NS_ENSURE_SUCCESS(rv, rv);
 
   NS_ConvertUTF8toUTF16 requestURL(pageURL);
 
   props->SetPropertyAsAString(NS_LITERAL_STRING("requestURL"), requestURL);
 
   obs->NotifyObservers(static_cast<nsIPropertyBag2*>(props),
                        "recording-device-events",
                        aMsg.get());
 
-  // Forward recording events to parent process.
-  // The events are gathered in chrome process and used for recording indicator
-  if (!XRE_IsParentProcess()) {
-    Unused <<
-      dom::ContentChild::GetSingleton()->SendRecordingDeviceEvents(aMsg,
-                                                                   requestURL,
-                                                                   aIsAudio,
-                                                                   aIsVideo);
-  }
-
   return NS_OK;
 }
 
 int MediaManager::AddDeviceChangeCallback(DeviceChangeCallback* aCallback)
 {
   bool fakeDeviceChangeEventOn = mPrefs.mFakeDeviceChangeEventOn;
   MediaManager::PostTask(NewTaskFrom([fakeDeviceChangeEventOn]() {
     RefPtr<MediaManager> manager = MediaManager_GetInstance();
@@ -2227,25 +2276,30 @@ if (privileged) {
           cs.mMediaSource = ac.mMediaSource;
         }
       }
     }
   } else if (IsOn(c.mAudio)) {
    audioType = MediaSourceEnum::Microphone;
   }
 
-  StreamListeners* listeners = AddWindowID(windowID);
-
-  // Create a disabled listener to act as a placeholder
-  RefPtr<GetUserMediaCallbackMediaStreamListener> listener =
-    new GetUserMediaCallbackMediaStreamListener(mMediaThread, windowID,
-                                                MakePrincipalHandle(principal));
-
-  // No need for locking because we always do this in the main thread.
-  listeners->AppendElement(listener);
+  // Create a window listener if it doesn't already exist.
+  RefPtr<GetUserMediaWindowListener> windowListener =
+    GetWindowListener(windowID);
+  if (windowListener) {
+    PrincipalHandle existingPrincipalHandle = windowListener->GetPrincipalHandle();
+    MOZ_ASSERT(PrincipalHandleMatches(existingPrincipalHandle, principal));
+  } else {
+    windowListener = new GetUserMediaWindowListener(mMediaThread, windowID,
+                                                    MakePrincipalHandle(principal));
+    AddWindowID(windowID, windowListener);
+  }
+
+  RefPtr<SourceListener> sourceListener = new SourceListener();
+  windowListener->Register(sourceListener);
 
   if (!privileged) {
     // Check if this site has had persistent permissions denied.
     nsCOMPtr<nsIPermissionManager> permManager =
       do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
     uint32_t audioPerm = nsIPermissionManager::UNKNOWN_ACTION;
@@ -2264,17 +2318,17 @@ if (privileged) {
     }
 
     if ((!IsOn(c.mAudio) && !IsOn(c.mVideo)) ||
         (IsOn(c.mAudio) && audioPerm == nsIPermissionManager::DENY_ACTION) ||
         (IsOn(c.mVideo) && videoPerm == nsIPermissionManager::DENY_ACTION)) {
       RefPtr<MediaStreamError> error =
           new MediaStreamError(aWindow, NS_LITERAL_STRING("NotAllowedError"));
       onFailure->OnError(error);
-      RemoveFromWindowList(windowID, listener);
+      windowListener->Remove(sourceListener);
       return NS_OK;
     }
   }
 
   // Get list of all devices, with origin-specific device ids.
 
   MediaEnginePrefs prefs = mPrefs;
 
@@ -2287,33 +2341,34 @@ if (privileged) {
 
   bool askPermission =
       (!privileged || Preferences::GetBool("media.navigator.permission.force")) &&
       (!fake || Preferences::GetBool("media.navigator.permission.fake"));
 
   RefPtr<PledgeSourceSet> p = EnumerateDevicesImpl(windowID, videoType,
                                                    audioType, fake);
   RefPtr<MediaManager> self = this;
-  p->Then([self, onSuccess, onFailure, windowID, c, listener, askPermission,
-           prefs, isHTTPS, callID, principalInfo,
+  p->Then([self, onSuccess, onFailure, windowID, c, windowListener,
+           sourceListener, askPermission, prefs, isHTTPS, callID, principalInfo,
            isChrome](SourceSet*& aDevices) mutable {
     // grab result
     auto devices = MakeRefPtr<Refcountable<UniquePtr<SourceSet>>>(aDevices);
 
     // Ensure that our windowID is still good.
     if (!nsGlobalWindow::GetInnerWindowWithId(windowID)) {
       return;
     }
 
     // Apply any constraints. This modifies the passed-in list.
     RefPtr<PledgeChar> p2 = self->SelectSettings(c, isChrome, devices);
 
     p2->Then([self, onSuccess, onFailure, windowID, c,
-              listener, askPermission, prefs, isHTTPS, callID, principalInfo,
-              isChrome, devices](const char*& badConstraint) mutable {
+              windowListener, sourceListener, askPermission, prefs, isHTTPS,
+              callID, principalInfo, isChrome, devices
+             ](const char*& badConstraint) mutable {
 
       // Ensure that the captured 'this' pointer and our windowID are still good.
       auto* globalWindow = nsGlobalWindow::GetInnerWindowWithId(windowID);
       RefPtr<nsPIDOMWindowInner> window = globalWindow ? globalWindow->AsInner()
                                                        : nullptr;
       if (!MediaManager::Exists() || !window) {
         return;
       }
@@ -2341,21 +2396,25 @@ if (privileged) {
         for (auto& device : **devices) {
           nsresult rv = devicesCopy->AppendElement(device, /*weak =*/ false);
           if (NS_WARN_IF(NS_FAILED(rv))) {
             return;
           }
         }
       }
 
-      // Pass callbacks and MediaStreamListener along to GetUserMediaTask.
-      RefPtr<GetUserMediaTask> task (new GetUserMediaTask(c, onSuccess.forget(),
+      // Pass callbacks and listeners along to GetUserMediaTask.
+      RefPtr<GetUserMediaTask> task (new GetUserMediaTask(c,
+                                                          onSuccess.forget(),
                                                           onFailure.forget(),
-                                                          windowID, listener,
-                                                          prefs, principalInfo,
+                                                          windowID,
+                                                          windowListener,
+                                                          sourceListener,
+                                                          prefs,
+                                                          principalInfo,
                                                           isChrome,
                                                           devices->release()));
       // Store the task w/callbacks.
       self->mActiveCallbacks.Put(callID, task.forget());
 
       // Add a WindowID cross-reference so OnNavigation can tear things down
       nsTArray<nsString>* array;
       if (!self->mCallIds.Get(windowID, &array)) {
@@ -2545,43 +2604,50 @@ MediaManager::EnumerateDevices(nsPIDOMWi
                                nsIDOMGetUserMediaErrorCallback* aOnFailure)
 {
   MOZ_ASSERT(NS_IsMainThread());
   NS_ENSURE_TRUE(!sInShutdown, NS_ERROR_FAILURE);
   nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> onSuccess(aOnSuccess);
   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onFailure(aOnFailure);
   uint64_t windowId = aWindow->WindowID();
 
-  StreamListeners* listeners = AddWindowID(windowId);
-
   nsIPrincipal* principal = aWindow->GetExtantDoc()->NodePrincipal();
 
-  // Create a disabled listener to act as a placeholder
-  RefPtr<GetUserMediaCallbackMediaStreamListener> listener =
-    new GetUserMediaCallbackMediaStreamListener(mMediaThread, windowId,
-                                                MakePrincipalHandle(principal));
-
-  // No need for locking because we always do this in the main thread.
-  listeners->AppendElement(listener);
+  RefPtr<GetUserMediaWindowListener> windowListener =
+    GetWindowListener(windowId);
+  if (windowListener) {
+    PrincipalHandle existingPrincipalHandle =
+      windowListener->GetPrincipalHandle();
+    MOZ_ASSERT(PrincipalHandleMatches(existingPrincipalHandle, principal));
+  } else {
+    windowListener = new GetUserMediaWindowListener(mMediaThread, windowId,
+                                                    MakePrincipalHandle(principal));
+    AddWindowID(windowId, windowListener);
+  }
+
+  // Create an inactive SourceListener to act as a placeholder, so the
+  // window listener doesn't clean itself up until we're done.
+  RefPtr<SourceListener> sourceListener = new SourceListener();
+  windowListener->Register(sourceListener);
 
   bool fake = Preferences::GetBool("media.navigator.streams.fake");
 
   RefPtr<PledgeSourceSet> p = EnumerateDevicesImpl(windowId,
                                                    MediaSourceEnum::Camera,
                                                    MediaSourceEnum::Microphone,
                                                    fake);
-  p->Then([onSuccess, windowId, listener](SourceSet*& aDevices) mutable {
+  p->Then([onSuccess, windowListener, sourceListener](SourceSet*& aDevices) mutable {
     UniquePtr<SourceSet> devices(aDevices); // grab result
-    RefPtr<MediaManager> mgr = MediaManager_GetInstance();
-    mgr->RemoveFromWindowList(windowId, listener);
+    DebugOnly<bool> rv = windowListener->Remove(sourceListener);
+    MOZ_ASSERT(rv);
     nsCOMPtr<nsIWritableVariant> array = MediaManager_ToJSArray(*devices);
     onSuccess->OnSuccess(array);
-  }, [onFailure, windowId, listener](MediaStreamError*& reason) mutable {
-    RefPtr<MediaManager> mgr = MediaManager_GetInstance();
-    mgr->RemoveFromWindowList(windowId, listener);
+  }, [onFailure, windowListener, sourceListener](MediaStreamError*& reason) mutable {
+    DebugOnly<bool> rv = windowListener->Remove(sourceListener);
+    MOZ_ASSERT(rv);
     onFailure->OnError(reason);
   });
   return NS_OK;
 }
 
 /*
  * GetUserMediaDevices - called by the UI-part of getUserMedia from chrome JS.
  */
@@ -2637,34 +2703,31 @@ MediaManager::GetBackend(uint64_t aWindo
 #endif
   }
   return mBackend;
 }
 
 static void
 StopSharingCallback(MediaManager *aThis,
                     uint64_t aWindowID,
-                    StreamListeners *aListeners,
+                    GetUserMediaWindowListener *aListener,
                     void *aData)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  if (aListeners) {
-    auto length = aListeners->Length();
-    for (size_t i = 0; i < length; ++i) {
-      GetUserMediaCallbackMediaStreamListener *listener = aListeners->ElementAt(i);
-
-      if (listener->Stream()) { // aka HasBeenActivate()ed
-        listener->Stop();
-      }
-      listener->Remove();
-      listener->StopSharing();
-    }
-    aListeners->Clear();
-    aThis->RemoveWindowID(aWindowID);
+
+  // Grab a strong ref since RemoveAll() might destroy the listener mid-way
+  // when clearing the mActiveWindows reference.
+  RefPtr<GetUserMediaWindowListener> listener(aListener);
+  if (!listener) {
+    return;
   }
+
+  listener->Stop();
+  listener->RemoveAll();
+  MOZ_ASSERT(!aThis->GetWindowListener(aWindowID));
 }
 
 
 void
 MediaManager::OnNavigation(uint64_t aWindowID)
 {
   MOZ_ASSERT(NS_IsMainThread());
   LOG(("OnNavigation for %" PRIu64, aWindowID));
@@ -2683,16 +2746,17 @@ MediaManager::OnNavigation(uint64_t aWin
   // This is safe since we're on main-thread, and the windowlist can only
   // be added to from the main-thread
   auto* window = nsGlobalWindow::GetInnerWindowWithId(aWindowID);
   if (window) {
     IterateWindowListeners(window->AsInner(), StopSharingCallback, nullptr);
   } else {
     RemoveWindowID(aWindowID);
   }
+  MOZ_ASSERT(!GetWindowListener(aWindowID));
 
   RemoveMediaDevicesCallback(aWindowID);
 }
 
 void
 MediaManager::RemoveMediaDevicesCallback(uint64_t aWindowID)
 {
   MutexAutoLock lock(mCallbackMutex);
@@ -2706,30 +2770,30 @@ MediaManager::RemoveMediaDevicesCallback
       if (window && window->WindowID() == aWindowID) {
         DeviceChangeCallback::RemoveDeviceChangeCallbackLocked(observer);
         return;
       }
     }
   }
 }
 
-StreamListeners*
-MediaManager::AddWindowID(uint64_t aWindowId)
+void
+MediaManager::AddWindowID(uint64_t aWindowId,
+                          GetUserMediaWindowListener* aListener)
 {
   MOZ_ASSERT(NS_IsMainThread());
   // Store the WindowID in a hash table and mark as active. The entry is removed
   // when this window is closed or navigated away from.
   // This is safe since we're on main-thread, and the windowlist can only
   // be invalidated from the main-thread (see OnNavigation)
-  StreamListeners* listeners = GetActiveWindows()->Get(aWindowId);
-  if (!listeners) {
-    listeners = new StreamListeners;
-    GetActiveWindows()->Put(aWindowId, listeners);
+  if (IsWindowStillActive(aWindowId)) {
+    MOZ_ASSERT(false, "Window already added");
+    return;
   }
-  return listeners;
+  GetActiveWindows()->Put(aWindowId, aListener);
 }
 
 void
 MediaManager::RemoveWindowID(uint64_t aWindowId)
 {
   mActiveWindows.Remove(aWindowId);
 
   // get outer windowID
@@ -2754,118 +2818,16 @@ MediaManager::RemoveWindowID(uint64_t aW
 
   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   obs->NotifyObservers(nullptr, "recording-window-ended", data.get());
   LOG(("Sent recording-window-ended for window %" PRIu64 " (outer %" PRIu64 ")",
        aWindowId, outerID));
 }
 
 void
-MediaManager::RemoveFromWindowList(uint64_t aWindowID,
-  GetUserMediaCallbackMediaStreamListener *aListener)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  nsString videoRawId;
-  nsString audioRawId;
-  nsString videoSourceType;
-  nsString audioSourceType;
-  bool hasVideoDevice = aListener->mVideoDevice;
-  bool hasAudioDevice = aListener->mAudioDevice;
-
-  if (hasVideoDevice) {
-    aListener->mVideoDevice->GetRawId(videoRawId);
-    aListener->mVideoDevice->GetMediaSource(videoSourceType);
-  }
-  if (hasAudioDevice) {
-    aListener->mAudioDevice->GetRawId(audioRawId);
-    aListener->mAudioDevice->GetMediaSource(audioSourceType);
-  }
-
-  // This is defined as safe on an inactive GUMCMSListener
-  aListener->Remove(); // really queues the remove
-
-  StreamListeners* listeners = GetWindowListeners(aWindowID);
-  if (!listeners) {
-    nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
-    auto* globalWindow = nsGlobalWindow::GetInnerWindowWithId(aWindowID);
-    RefPtr<nsPIDOMWindowInner> window = globalWindow ? globalWindow->AsInner()
-                                                     : nullptr;
-    if (window != nullptr) {
-      RefPtr<GetUserMediaRequest> req =
-        new GetUserMediaRequest(window, NullString(), NullString());
-      obs->NotifyObservers(req, "recording-device-stopped", nullptr);
-    }
-    return;
-  }
-  listeners->RemoveElement(aListener);
-
-  uint32_t length = listeners->Length();
-
-  if (hasVideoDevice) {
-    bool revokeVideoPermission = true;
-
-    for (uint32_t i = 0; i < length; ++i) {
-      RefPtr<GetUserMediaCallbackMediaStreamListener> listener =
-        listeners->ElementAt(i);
-      if (hasVideoDevice && listener->mVideoDevice) {
-        nsString rawId;
-        listener->mVideoDevice->GetRawId(rawId);
-        if (videoRawId.Equals(rawId)) {
-          revokeVideoPermission = false;
-          break;
-        }
-      }
-    }
-
-    if (revokeVideoPermission) {
-      nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
-      auto* globalWindow = nsGlobalWindow::GetInnerWindowWithId(aWindowID);
-      RefPtr<nsPIDOMWindowInner> window = globalWindow ? globalWindow->AsInner()
-                                                       : nullptr;
-      RefPtr<GetUserMediaRequest> req =
-        new GetUserMediaRequest(window, videoRawId, videoSourceType);
-      obs->NotifyObservers(req, "recording-device-stopped", nullptr);
-    }
-  }
-
-  if (hasAudioDevice) {
-    bool revokeAudioPermission = true;
-
-    for (uint32_t i = 0; i < length; ++i) {
-      RefPtr<GetUserMediaCallbackMediaStreamListener> listener =
-        listeners->ElementAt(i);
-      if (hasAudioDevice && listener->mAudioDevice) {
-        nsString rawId;
-        listener->mAudioDevice->GetRawId(rawId);
-        if (audioRawId.Equals(rawId)) {
-          revokeAudioPermission = false;
-          break;
-        }
-      }
-    }
-
-    if (revokeAudioPermission) {
-      nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
-      auto* globalWindow = nsGlobalWindow::GetInnerWindowWithId(aWindowID);
-      RefPtr<nsPIDOMWindowInner> window = globalWindow ? globalWindow->AsInner()
-                                                       : nullptr;
-      RefPtr<GetUserMediaRequest> req =
-        new GetUserMediaRequest(window, audioRawId, audioSourceType);
-      obs->NotifyObservers(req, "recording-device-stopped", nullptr);
-    }
-  }
-
-  if (length == 0) {
-    RemoveWindowID(aWindowID);
-    // listeners has been deleted here
-  }
-}
-
-void
 MediaManager::GetPref(nsIPrefBranch *aBranch, const char *aPref,
                       const char *aData, int32_t *aVal)
 {
   int32_t temp;
   if (aData == nullptr || strcmp(aPref,aData) == 0) {
     if (NS_SUCCEEDED(aBranch->GetIntPref(aPref, &temp))) {
       *aVal = temp;
     }
@@ -3156,43 +3118,32 @@ nsresult
 MediaManager::GetActiveMediaCaptureWindows(nsIArray** aArray)
 {
   MOZ_ASSERT(aArray);
 
   nsCOMPtr<nsIMutableArray> array = nsArray::Create();
 
   for (auto iter = mActiveWindows.Iter(); !iter.Done(); iter.Next()) {
     const uint64_t& id = iter.Key();
-    StreamListeners* listeners = iter.UserData();
+    RefPtr<GetUserMediaWindowListener> winListener = iter.UserData();
+    if (!winListener) {
+      continue;
+    }
 
     nsPIDOMWindowInner* window =
       nsGlobalWindow::GetInnerWindowWithId(id)->AsInner();
     MOZ_ASSERT(window);
     // XXXkhuey ...
     if (!window) {
       continue;
     }
-    // mActiveWindows contains both windows that have requested device
-    // access and windows that are currently capturing media. We want
-    // to return only the latter. See bug 975177.
-    bool capturing = false;
-    if (listeners) {
-      uint32_t length = listeners->Length();
-      for (uint32_t i = 0; i < length; ++i) {
-        RefPtr<GetUserMediaCallbackMediaStreamListener> listener =
-          listeners->ElementAt(i);
-        if (listener->CapturingVideo() || listener->CapturingAudio() ||
-            listener->CapturingScreen() || listener->CapturingWindow() ||
-            listener->CapturingApplication()) {
-          capturing = true;
-          break;
-        }
-      }
-    }
-    if (capturing) {
+
+    if (winListener->CapturingVideo() || winListener->CapturingAudio() ||
+        winListener->CapturingScreen() || winListener->CapturingWindow() ||
+        winListener->CapturingApplication()) {
       array->AppendElement(window, /*weak =*/ false);
     }
   }
 
   array.forget(aArray);
   return NS_OK;
 }
 
@@ -3204,49 +3155,45 @@ struct CaptureWindowStateData {
   bool *mWindowShare;
   bool *mAppShare;
   bool *mBrowserShare;
 };
 
 static void
 CaptureWindowStateCallback(MediaManager *aThis,
                            uint64_t aWindowID,
-                           StreamListeners *aListeners,
+                           GetUserMediaWindowListener  *aListener,
                            void *aData)
 {
   struct CaptureWindowStateData *data = (struct CaptureWindowStateData *) aData;
 
-  if (aListeners) {
-    auto length = aListeners->Length();
-    for (size_t i = 0; i < length; ++i) {
-      GetUserMediaCallbackMediaStreamListener *listener = aListeners->ElementAt(i);
-
-      if (listener->CapturingVideo()) {
-        *data->mVideo = true;
-      }
-      if (listener->CapturingAudio()) {
-        *data->mAudio = true;
-      }
-      if (listener->CapturingScreen()) {
-        *data->mScreenShare = true;
-      }
-      if (listener->CapturingWindow()) {
-        *data->mWindowShare = true;
-      }
-      if (listener->CapturingApplication()) {
-        *data->mAppShare = true;
-      }
-      if (listener->CapturingBrowser()) {
-        *data->mBrowserShare = true;
-      }
-    }
+  if (!aListener) {
+    return;
+  }
+
+  if (aListener->CapturingVideo()) {
+    *data->mVideo = true;
+  }
+  if (aListener->CapturingAudio()) {
+    *data->mAudio = true;
+  }
+  if (aListener->CapturingScreen()) {
+    *data->mScreenShare = true;
+  }
+  if (aListener->CapturingWindow()) {
+    *data->mWindowShare = true;
+  }
+  if (aListener->CapturingApplication()) {
+    *data->mAppShare = true;
+  }
+  if (aListener->CapturingBrowser()) {
+    *data->mBrowserShare = true;
   }
 }
 
-
 NS_IMETHODIMP
 MediaManager::MediaCaptureWindowState(nsIDOMWindow* aWindow, bool* aVideo,
                                       bool* aAudio, bool *aScreenShare,
                                       bool* aWindowShare, bool *aAppShare,
                                       bool *aBrowserShare)
 {
   MOZ_ASSERT(NS_IsMainThread());
   struct CaptureWindowStateData data;
@@ -3285,25 +3232,24 @@ MediaManager::SanitizeDeviceIds(int64_t 
 
   media::SanitizeOriginKeys(aSinceWhen, false); // we fire and forget
   return NS_OK;
 }
 
 static void
 StopScreensharingCallback(MediaManager *aThis,
                           uint64_t aWindowID,
-                          StreamListeners *aListeners,
+                          GetUserMediaWindowListener *aListener,
                           void *aData)
 {
-  if (aListeners) {
-    auto length = aListeners->Length();
-    for (size_t i = 0; i < length; ++i) {
-      aListeners->ElementAt(i)->StopSharing();
-    }
+  if (!aListener) {
+    return;
   }
+
+  aListener->StopSharing();
 }
 
 void
 MediaManager::StopScreensharing(uint64_t aWindowID)
 {
   // We need to stop window/screensharing for all streams in all innerwindows that
   // correspond to that outerwindow.
 
@@ -3318,20 +3264,22 @@ MediaManager::StopScreensharing(uint64_t
 void
 MediaManager::IterateWindowListeners(nsPIDOMWindowInner* aWindow,
                                      WindowListenerCallback aCallback,
                                      void *aData)
 {
   // Iterate the docshell tree to find all the child windows, and for each
   // invoke the callback
   if (aWindow) {
-    uint64_t windowID = aWindow->WindowID();
-    StreamListeners* listeners = GetActiveWindows()->Get(windowID);
-    // pass listeners so it can modify/delete the list
-    (*aCallback)(this, windowID, listeners, aData);
+    {
+      uint64_t windowID = aWindow->WindowID();
+      GetUserMediaWindowListener* listener = GetWindowListener(windowID);
+      (*aCallback)(this, windowID, listener, aData);
+      // NB: `listener` might have been destroyed.
+    }
 
     // iterate any children of *this* window (iframes, etc)
     nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
     if (docShell) {
       int32_t i, count;
       docShell->GetChildCount(&count);
       for (i = 0; i < count; ++i) {
         nsCOMPtr<nsIDocShellTreeItem> item;
@@ -3409,72 +3357,416 @@ MediaManager::IsActivelyCapturingOrHasAP
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return false;
     }
   }
   return audio == nsIPermissionManager::ALLOW_ACTION ||
          video == nsIPermissionManager::ALLOW_ACTION;
 }
 
+SourceListener::SourceListener()
+  : mActivated(false)
+  , mStopped(false)
+  , mFinished(false)
+  , mRemoved(false)
+  , mAudioStopped(false)
+  , mVideoStopped(false)
+  , mMainThreadCheck(nullptr)
+  , mPrincipalHandle(PRINCIPAL_HANDLE_NONE)
+  , mWindowListener(nullptr)
+{}
+
 void
-GetUserMediaCallbackMediaStreamListener::Stop()
+SourceListener::Register(GetUserMediaWindowListener* aListener)
+{
+  LOG(("SourceListener %p registering with window listener %p", this, aListener));
+
+  if (mWindowListener) {
+    MOZ_ASSERT(false, "Already registered");
+    return;
+  }
+  if (mActivated) {
+    MOZ_ASSERT(false, "Already activated");
+    return;
+  }
+  if (!aListener) {
+    MOZ_ASSERT(false, "No listener");
+    return;
+  }
+  mPrincipalHandle = aListener->GetPrincipalHandle();
+  mWindowListener = aListener;
+}
+
+void
+SourceListener::Activate(SourceMediaStream* aStream,
+                         AudioDevice* aAudioDevice,
+                         VideoDevice* aVideoDevice)
 {
   MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
+
+  LOG(("SourceListener %p activating audio=%p video=%p", this, aAudioDevice, aVideoDevice));
+
+  if (mActivated) {
+    MOZ_ASSERT(false, "Already activated");
+    return;
+  }
+
+  mActivated = true;
+  mMainThreadCheck = PR_GetCurrentThread();
+  mStream = aStream;
+  mAudioDevice = aAudioDevice;
+  mVideoDevice = aVideoDevice;
+  mStream->AddListener(this);
+}
+
+void
+SourceListener::Stop()
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
+
   if (mStopped) {
     return;
   }
 
-  // We can't take a chance on blocking here, so proxy this to another
-  // thread.
-  // Pass a ref to us (which is threadsafe) so it can query us for the
-  // source stream info.
-  RefPtr<MediaOperationTask> mediaOperation =
-    new MediaOperationTask(MEDIA_STOP,
-                           this, nullptr, nullptr,
-                           !mAudioStopped ? mAudioDevice.get() : nullptr,
-                           !mVideoStopped ? mVideoDevice.get() : nullptr,
-                           false, mWindowID, nullptr);
-  MediaManager::PostTask(mediaOperation.forget());
-  mStopped = mAudioStopped = mVideoStopped = true;
+  LOG(("SourceListener %p stopping", this));
+
+  // StopSharing() has some special logic, at least for audio capture.
+  // It must be called when all tracks have stopped, before setting mStopped.
+  StopSharing();
+
+  mStopped = true;
+
+  if (mAudioDevice && !mAudioStopped) {
+    StopTrack(kAudioTrack);
+  }
+  if (mVideoDevice && !mVideoStopped) {
+    StopTrack(kVideoTrack);
+  }
+  RefPtr<SourceMediaStream> source = GetSourceStream();
+  MediaManager::PostTask(NewTaskFrom([source]() {
+    MOZ_ASSERT(MediaManager::IsInMediaThread());
+    source->EndAllTrackAndFinish();
+  }));
 }
 
-// Doesn't kill audio
 void
-GetUserMediaCallbackMediaStreamListener::StopSharing()
+SourceListener::Remove()
 {
   MOZ_ASSERT(NS_IsMainThread());
+  if (!mStream || mRemoved) {
+    return;
+  }
+
+  LOG(("SourceListener %p removed on purpose, mFinished = %d", this, (int) mFinished));
+  mRemoved = true; // RemoveListener is async, avoid races
+  mWindowListener = nullptr;
+
+  // If it's destroyed, don't call - listener will be removed and we'll be notified!
+  if (!mStream->IsDestroyed()) {
+    mStream->RemoveListener(this);
+  }
+}
+
+void
+SourceListener::StopTrack(TrackID aTrackID)
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
+
+  RefPtr<MediaDevice> device;
+  RefPtr<SourceMediaStream> source;
+
+  switch (aTrackID) {
+    case kAudioTrack: {
+      LOG(("SourceListener %p stopping audio track %d", this, aTrackID));
+      if (!mAudioDevice) {
+        NS_ASSERTION(false, "Can't stop audio. No device.");
+        return;
+      }
+      if (mAudioStopped) {
+        // Audio already stopped
+        return;
+      }
+      device = mAudioDevice;
+      source = GetSourceStream();
+      mAudioStopped = true;
+      break;
+    }
+    case kVideoTrack: {
+      LOG(("SourceListener %p stopping video track %d", this, aTrackID));
+      if (!mVideoDevice) {
+        NS_ASSERTION(false, "Can't stop video. No device.");
+        return;
+      }
+      if (mVideoStopped) {
+        // Video already stopped
+        return;
+      }
+      device = mVideoDevice;
+      source = GetSourceStream();
+      mVideoStopped = true;
+      break;
+    }
+    default: {
+      MOZ_ASSERT(false, "Unknown track id");
+      return;
+    }
+  }
+
+  MediaManager::PostTask(NewTaskFrom([device, source, aTrackID]() {
+    device->GetSource()->Stop(source, aTrackID);
+    device->Deallocate();
+  }));
+
+  if ((!mAudioDevice || mAudioStopped) &&
+      (!mVideoDevice || mVideoStopped)) {
+    LOG(("SourceListener %p this was the last track stopped", this));
+    Stop();
+  }
+
+  if (!mWindowListener) {
+    MOZ_ASSERT(false, "Should still have window listener");
+    return;
+  }
+  mWindowListener->NotifySourceTrackStopped();
+}
+
+void
+SourceListener::StopSharing()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_RELEASE_ASSERT(mWindowListener);
+
+  if (mStopped) {
+    return;
+  }
+
+  LOG(("SourceListener %p StopSharing", this));
+
   if (mVideoDevice &&
       (mVideoDevice->GetMediaSource() == MediaSourceEnum::Screen ||
        mVideoDevice->GetMediaSource() == MediaSourceEnum::Application ||
        mVideoDevice->GetMediaSource() == MediaSourceEnum::Window)) {
     // We want to stop the whole stream if there's no audio;
     // just the video track if we have both.
     // StopTrack figures this out for us.
     StopTrack(kVideoTrack);
-  } else if (mAudioDevice &&
-             mAudioDevice->GetMediaSource() == MediaSourceEnum::AudioCapture) {
-    nsCOMPtr<nsPIDOMWindowInner> window = nsGlobalWindow::GetInnerWindowWithId(mWindowID)->AsInner();
-    MOZ_ASSERT(window);
+  }
+  if (mAudioDevice &&
+      mAudioDevice->GetMediaSource() == MediaSourceEnum::AudioCapture) {
+    uint64_t windowID = mWindowListener->WindowID();
+    nsCOMPtr<nsPIDOMWindowInner> window = nsGlobalWindow::GetInnerWindowWithId(windowID)->AsInner();
+    MOZ_RELEASE_ASSERT(window);
     window->SetAudioCapture(false);
     MediaStreamGraph* graph =
       MediaStreamGraph::GetInstance(MediaStreamGraph::AUDIO_THREAD_DRIVER,
                                     dom::AudioChannel::Normal);
-    graph->UnregisterCaptureStreamForWindow(mWindowID);
+    graph->UnregisterCaptureStreamForWindow(windowID);
     mStream->Destroy();
   }
 }
 
-// ApplyConstraints for track
-
-auto
-GetUserMediaCallbackMediaStreamListener::ApplyConstraintsToTrack(
-    nsPIDOMWindowInner* aWindow,
-    TrackID aTrackID,
-    const MediaTrackConstraints& aConstraints,
-    dom::CallerType aCallerType) -> already_AddRefed<PledgeVoid>
+SourceMediaStream*
+SourceListener::GetSourceStream()
+{
+  NS_ASSERTION(mStream,"Getting stream from never-activated SourceListener");
+  if (!mStream) {
+    return nullptr;
+  }
+  return mStream->AsSourceStream();
+}
+
+void
+SourceListener::GetSettings(dom::MediaTrackSettings& aOutSettings, TrackID aTrackID)
+{
+  switch (aTrackID) {
+    case kVideoTrack: {
+      if (mVideoDevice) {
+        mVideoDevice->GetSource()->GetSettings(aOutSettings);
+      }
+      break;
+    }
+    case kAudioTrack: {
+      if (mAudioDevice) {
+        mAudioDevice->GetSource()->GetSettings(aOutSettings);
+      }
+      break;
+    }
+    default: {
+      MOZ_ASSERT(false, "Unknown track id");
+    }
+  }
+}
+
+// Proxy NotifyPull() to sources
+void
+SourceListener::NotifyPull(MediaStreamGraph* aGraph,
+                           StreamTime aDesiredTime)
+{
+  // Currently audio sources ignore NotifyPull, but they could
+  // watch it especially for fake audio.
+  if (mAudioDevice) {
+    mAudioDevice->GetSource()->NotifyPull(aGraph, mStream, kAudioTrack,
+                                          aDesiredTime, mPrincipalHandle);
+  }
+  if (mVideoDevice) {
+    mVideoDevice->GetSource()->NotifyPull(aGraph, mStream, kVideoTrack,
+                                          aDesiredTime, mPrincipalHandle);
+  }
+}
+
+void
+SourceListener::NotifyEvent(MediaStreamGraph* aGraph,
+                            MediaStreamGraphEvent aEvent)
+{
+  nsresult rv;
+  nsCOMPtr<nsIThread> thread;
+
+  switch (aEvent) {
+    case MediaStreamGraphEvent::EVENT_FINISHED:
+      rv = NS_GetMainThread(getter_AddRefs(thread));
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        NS_ASSERTION(false, "Mainthread not available; running on current thread");
+        // Ensure this really *was* MainThread (NS_GetCurrentThread won't work)
+        MOZ_RELEASE_ASSERT(mMainThreadCheck == PR_GetCurrentThread());
+        NotifyFinished();
+        return;
+      }
+      thread->Dispatch(NewRunnableMethod(this, &SourceListener::NotifyFinished),
+                       NS_DISPATCH_NORMAL);
+      break;
+    case MediaStreamGraphEvent::EVENT_REMOVED:
+      rv = NS_GetMainThread(getter_AddRefs(thread));
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        NS_ASSERTION(false, "Mainthread not available; running on current thread");
+        // Ensure this really *was* MainThread (NS_GetCurrentThread won't work)
+        MOZ_RELEASE_ASSERT(mMainThreadCheck == PR_GetCurrentThread());
+        NotifyRemoved();
+        return;
+      }
+      thread->Dispatch(NewRunnableMethod(this, &SourceListener::NotifyRemoved),
+                       NS_DISPATCH_NORMAL);
+      break;
+    case MediaStreamGraphEvent::EVENT_HAS_DIRECT_LISTENERS:
+      NotifyDirectListeners(aGraph, true);
+      break;
+    case MediaStreamGraphEvent::EVENT_HAS_NO_DIRECT_LISTENERS:
+      NotifyDirectListeners(aGraph, false);
+      break;
+    default:
+      break;
+  }
+}
+
+void
+SourceListener::NotifyFinished()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  mFinished = true;
+  if (!mWindowListener) {
+    // Removed explicitly before finished.
+    return;
+  }
+
+  LOG(("SourceListener %p NotifyFinished", this));
+
+  Stop(); // we know it's been activated
+  mWindowListener->Remove(this);
+}
+
+void
+SourceListener::NotifyRemoved()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  LOG(("SourceListener removed, mFinished = %d", (int) mFinished));
+  mRemoved = true;
+
+  if (!mFinished) {
+    NotifyFinished();
+  }
+
+  mWindowListener = nullptr;
+}
+
+void
+SourceListener::NotifyDirectListeners(MediaStreamGraph* aGraph,
+                                      bool aHasListeners)
+{
+  if (!mVideoDevice) {
+    return;
+  }
+
+  auto& videoDevice = mVideoDevice;
+  MediaManager::PostTask(NewTaskFrom([videoDevice, aHasListeners]() {
+    videoDevice->GetSource()->SetDirectListeners(aHasListeners);
+    return NS_OK;
+  }));
+}
+
+bool
+SourceListener::CapturingVideo() const
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  return mActivated && mVideoDevice && !mVideoStopped &&
+         !mVideoDevice->GetSource()->IsAvailable() &&
+         mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Camera &&
+         (!mVideoDevice->GetSource()->IsFake() ||
+          Preferences::GetBool("media.navigator.permission.fake"));
+}
+
+bool
+SourceListener::CapturingAudio() const
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  return mActivated && mAudioDevice && !mAudioStopped &&
+         !mAudioDevice->GetSource()->IsAvailable() &&
+         (!mAudioDevice->GetSource()->IsFake() ||
+          Preferences::GetBool("media.navigator.permission.fake"));
+}
+
+bool
+SourceListener::CapturingScreen() const
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  return mActivated && mVideoDevice && !mVideoStopped &&
+         !mVideoDevice->GetSource()->IsAvailable() &&
+         mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Screen;
+}
+
+bool
+SourceListener::CapturingWindow() const
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  return mActivated && mVideoDevice && !mVideoStopped &&
+         !mVideoDevice->GetSource()->IsAvailable() &&
+         mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Window;
+}
+
+bool
+SourceListener::CapturingApplication() const
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  return mActivated && mVideoDevice && !mVideoStopped &&
+         !mVideoDevice->GetSource()->IsAvailable() &&
+         mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Application;
+}
+
+bool
+SourceListener::CapturingBrowser() const
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  return mActivated && mVideoDevice && !mVideoStopped &&
+         !mVideoDevice->GetSource()->IsAvailable() &&
+         mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Browser;
+}
+
+already_AddRefed<PledgeVoid>
+SourceListener::ApplyConstraintsToTrack(nsPIDOMWindowInner* aWindow,
+                                        TrackID aTrackID,
+                                        const dom::MediaTrackConstraints& aConstraints,
+                                        dom::CallerType aCallerType)
 {
   MOZ_ASSERT(NS_IsMainThread());
   RefPtr<PledgeVoid> p = new PledgeVoid();
 
   // XXX to support multiple tracks of a type in a stream, this should key off
   // the TrackID and not just the type
   RefPtr<AudioDevice> audioDevice =
     aTrackID == kAudioTrack ? mAudioDevice.get() : nullptr;
@@ -3552,167 +3844,76 @@ GetUserMediaCallbackMediaStreamListener:
         }
       }
       return NS_OK;
     }));
   }));
   return p.forget();
 }
 
-// Stop backend for track
+PrincipalHandle
+SourceListener::GetPrincipalHandle() const
+{
+  return mPrincipalHandle;
+}
+
+// Doesn't kill audio
+void
+GetUserMediaWindowListener::StopSharing()
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
+
+  for (auto& source : mActiveListeners) {
+    source->StopSharing();
+  }
+}
 
 void
-GetUserMediaCallbackMediaStreamListener::StopTrack(TrackID aTrackID)
+GetUserMediaWindowListener::NotifySourceTrackStopped()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aTrackID == kAudioTrack || aTrackID == kVideoTrack);
-
-  // XXX to support multiple tracks of a type in a stream, this should key off
-  // the TrackID and not just hard coded values.
-
-  bool stopAudio = aTrackID == kAudioTrack;
-  bool stopVideo = aTrackID == kVideoTrack;
-
-  if (mStopped ||
-      (stopAudio && (mAudioStopped || !mAudioDevice)) ||
-      (stopVideo && (mVideoStopped || !mVideoDevice)))
-  {
-    LOG(("Can't stop gUM track %d (%s), exists=%d, stopped=%d",
-         aTrackID,
-         stopAudio ? "audio" : "video",
-         stopAudio ? !!mAudioDevice : !!mVideoDevice,
-         stopAudio ? mAudioStopped : mVideoStopped));
-    return;
-  }
-
-  if ((stopAudio || mAudioStopped || !mAudioDevice) &&
-      (stopVideo || mVideoStopped || !mVideoDevice)) {
-    Stop();
-    return;
-  }
 
   // We wait until stable state before notifying chrome so chrome only does one
   // update if more tracks are stopped in this event loop.
 
-  mAudioStopPending |= stopAudio;
-  mVideoStopPending |= stopVideo;
-
   if (mChromeNotificationTaskPosted) {
     return;
   }
 
   nsCOMPtr<nsIRunnable> runnable =
-    NewRunnableMethod(this, &GetUserMediaCallbackMediaStreamListener::NotifyChromeOfTrackStops);
+    NewRunnableMethod(this, &GetUserMediaWindowListener::NotifyChromeOfTrackStops);
   nsContentUtils::RunInStableState(runnable.forget());
   mChromeNotificationTaskPosted = true;
 }
 
 void
-GetUserMediaCallbackMediaStreamListener::NotifyChromeOfTrackStops()
+GetUserMediaWindowListener::NotifyChromeOfTrackStops()
 {
   MOZ_ASSERT(mChromeNotificationTaskPosted);
   mChromeNotificationTaskPosted = false;
 
-  // We make sure these are always reset.
-  bool stopAudio = mAudioStopPending;
-  bool stopVideo = mVideoStopPending;
-  mAudioStopPending = false;
-  mVideoStopPending = false;
-
-  if (mStopped) {
-    // The entire capture was stopped while we were waiting for stable state.
-    return;
-  }
-
-  MOZ_ASSERT(stopAudio || stopVideo);
-  MOZ_ASSERT(!stopAudio || !mAudioStopped,
-             "If there's a pending stop for audio, audio must not have been stopped");
-  MOZ_ASSERT(!stopAudio || mAudioDevice,
-             "If there's a pending stop for audio, there must be an audio device");
-  MOZ_ASSERT(!stopVideo || !mVideoStopped,
-             "If there's a pending stop for video, video must not have been stopped");
-  MOZ_ASSERT(!stopVideo || mVideoDevice,
-             "If there's a pending stop for video, there must be a video device");
-
-  if ((stopAudio || mAudioStopped || !mAudioDevice) &&
-      (stopVideo || mVideoStopped || !mVideoDevice)) {
-    // All tracks stopped.
-    Stop();
-    return;
-  }
-
-  mAudioStopped |= stopAudio;
-  mVideoStopped |= stopVideo;
-
-  RefPtr<MediaOperationTask> mediaOperation =
-    new MediaOperationTask(MEDIA_STOP_TRACK,
-                           this, nullptr, nullptr,
-                           stopAudio ? mAudioDevice.get() : nullptr,
-                           stopVideo ? mVideoDevice.get() : nullptr,
-                           false , mWindowID, nullptr);
-  MediaManager::PostTask(mediaOperation.forget());
-}
-
-void
-GetUserMediaCallbackMediaStreamListener::NotifyFinished()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  mFinished = true;
-  Stop(); // we know it's been activated
-
-  RefPtr<MediaManager> manager(MediaManager::GetIfExists());
-  if (manager) {
-    manager->RemoveFromWindowList(mWindowID, this);
-  } else {
-    NS_WARNING("Late NotifyFinished after MediaManager shutdown");
-  }
-}
-
-// Called from the MediaStreamGraph thread
-void
-GetUserMediaCallbackMediaStreamListener::NotifyDirectListeners(MediaStreamGraph* aGraph,
-                                                               bool aHasListeners)
-{
-  RefPtr<MediaOperationTask> mediaOperation =
-    new MediaOperationTask(MEDIA_DIRECT_LISTENERS,
-                           this, nullptr, nullptr,
-                           mAudioDevice, mVideoDevice,
-                           aHasListeners, mWindowID, nullptr);
-  MediaManager::PostTask(mediaOperation.forget());
-}
-
-// this can be in response to our own RemoveListener() (via ::Remove()), or
-// because the DOM GC'd the DOMLocalMediaStream/etc we're attached to.
-void
-GetUserMediaCallbackMediaStreamListener::NotifyRemoved()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MM_LOG(("Listener removed by DOM Destroy(), mFinished = %d", (int) mFinished));
-  mRemoved = true;
-
-  if (!mFinished) {
-    NotifyFinished();
-  }
+  NS_DispatchToMainThread(do_AddRef(new GetUserMediaNotificationEvent(
+    GetUserMediaNotificationEvent::STOPPING, mWindowID)));
 }
 
 GetUserMediaNotificationEvent::GetUserMediaNotificationEvent(
-    GetUserMediaCallbackMediaStreamListener* aListener,
     GetUserMediaStatus aStatus,
-    bool aIsAudio, bool aIsVideo, uint64_t aWindowID)
-: mListener(aListener) , mStatus(aStatus) , mIsAudio(aIsAudio)
-, mIsVideo(aIsVideo), mWindowID(aWindowID) {}
+    uint64_t aWindowID)
+: mStatus(aStatus), mWindowID(aWindowID) {}
 
 GetUserMediaNotificationEvent::GetUserMediaNotificationEvent(
     GetUserMediaStatus aStatus,
     already_AddRefed<DOMMediaStream> aStream,
-    OnTracksAvailableCallback* aOnTracksAvailableCallback,
-    bool aIsAudio, bool aIsVideo, uint64_t aWindowID,
+    already_AddRefed<Refcountable<UniquePtr<OnTracksAvailableCallback>>> aOnTracksAvailableCallback,
+    uint64_t aWindowID,
     already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError)
-: mStream(aStream), mOnTracksAvailableCallback(aOnTracksAvailableCallback),
-  mStatus(aStatus), mIsAudio(aIsAudio), mIsVideo(aIsVideo), mWindowID(aWindowID),
+: mStream(aStream),
+  mOnTracksAvailableCallback(aOnTracksAvailableCallback),
+  mStatus(aStatus),
+  mWindowID(aWindowID),
   mOnFailure(aError) {}
 GetUserMediaNotificationEvent::~GetUserMediaNotificationEvent()
 {
 }
 
 NS_IMETHODIMP
 GetUserMediaNotificationEvent::Run()
 {
@@ -3722,23 +3923,22 @@ GetUserMediaNotificationEvent::Run()
   // Otherwise this object might be destroyed off the main thread,
   // releasing DOMMediaStream off the main thread, which is not allowed.
   RefPtr<DOMMediaStream> stream = mStream.forget();
 
   nsString msg;
   switch (mStatus) {
   case STARTING:
     msg = NS_LITERAL_STRING("starting");
-    stream->OnTracksAvailable(mOnTracksAvailableCallback.forget());
+    stream->OnTracksAvailable(mOnTracksAvailableCallback->release());
     break;
   case STOPPING:
-  case STOPPED_TRACK:
     msg = NS_LITERAL_STRING("shutdown");
     break;
   }
 
   RefPtr<nsGlobalWindow> window = nsGlobalWindow::GetInnerWindowWithId(mWindowID);
   NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
 
-  return MediaManager::NotifyRecordingStatusChange(window->AsInner(), msg, mIsAudio, mIsVideo);
+  return MediaManager::NotifyRecordingStatusChange(window->AsInner(), msg);
 }
 
 } // namespace mozilla
--- a/dom/media/MediaManager.h
+++ b/dom/media/MediaManager.h
@@ -50,22 +50,20 @@ struct MediaTrackConstraints;
 struct MediaTrackConstraintSet;
 enum class CallerType : uint32_t;
 } // namespace dom
 
 namespace ipc {
 class PrincipalInfo;
 }
 
+class GetUserMediaTask;
+class GetUserMediaWindowListener;
 class MediaManager;
-class GetUserMediaCallbackMediaStreamListener;
-class GetUserMediaTask;
-
-extern LogModule* GetMediaManagerLog();
-#define MM_LOG(msg) MOZ_LOG(GetMediaManagerLog(), mozilla::LogLevel::Debug, msg)
+class SourceListener;
 
 class MediaDevice : public nsIMediaDevice
 {
 public:
   typedef MediaEngineSource Source;
 
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIMEDIADEVICE
@@ -131,76 +129,71 @@ public:
 };
 
 class GetUserMediaNotificationEvent: public Runnable
 {
   public:
     enum GetUserMediaStatus {
       STARTING,
       STOPPING,
-      STOPPED_TRACK,
     };
-    GetUserMediaNotificationEvent(GetUserMediaCallbackMediaStreamListener* aListener,
-                                  GetUserMediaStatus aStatus,
-                                  bool aIsAudio, bool aIsVideo, uint64_t aWindowID);
+    GetUserMediaNotificationEvent(GetUserMediaStatus aStatus,
+                                  uint64_t aWindowID);
 
     GetUserMediaNotificationEvent(GetUserMediaStatus aStatus,
                                   already_AddRefed<DOMMediaStream> aStream,
-                                  OnTracksAvailableCallback* aOnTracksAvailableCallback,
-                                  bool aIsAudio, bool aIsVideo, uint64_t aWindowID,
+                                  already_AddRefed<media::Refcountable<UniquePtr<OnTracksAvailableCallback>>> aOnTracksAvailableCallback,
+                                  uint64_t aWindowID,
                                   already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError);
     virtual ~GetUserMediaNotificationEvent();
 
     NS_IMETHOD Run() override;
 
   protected:
-    RefPtr<GetUserMediaCallbackMediaStreamListener> mListener; // threadsafe
+    RefPtr<GetUserMediaWindowListener> mListener; // threadsafe
     RefPtr<DOMMediaStream> mStream;
-    nsAutoPtr<OnTracksAvailableCallback> mOnTracksAvailableCallback;
+    RefPtr<media::Refcountable<UniquePtr<OnTracksAvailableCallback>>> mOnTracksAvailableCallback;
     GetUserMediaStatus mStatus;
-    bool mIsAudio;
-    bool mIsVideo;
     uint64_t mWindowID;
     RefPtr<nsIDOMGetUserMediaErrorCallback> mOnFailure;
 };
 
 typedef enum {
-  MEDIA_START,
   MEDIA_STOP,
   MEDIA_STOP_TRACK,
   MEDIA_DIRECT_LISTENERS,
 } MediaOperation;
 
 class ReleaseMediaOperationResource : public Runnable
 {
 public:
-  ReleaseMediaOperationResource(already_AddRefed<DOMMediaStream> aStream,
-    OnTracksAvailableCallback* aOnTracksAvailableCallback):
+  ReleaseMediaOperationResource(
+    already_AddRefed<DOMMediaStream> aStream,
+    already_AddRefed<media::Refcountable<UniquePtr<OnTracksAvailableCallback>>> aOnTracksAvailableCallback):
     mStream(aStream),
     mOnTracksAvailableCallback(aOnTracksAvailableCallback) {}
   NS_IMETHOD Run() override {return NS_OK;}
 private:
   RefPtr<DOMMediaStream> mStream;
-  nsAutoPtr<OnTracksAvailableCallback> mOnTracksAvailableCallback;
+  RefPtr<media::Refcountable<UniquePtr<OnTracksAvailableCallback>>> mOnTracksAvailableCallback;
 };
 
-typedef nsTArray<RefPtr<GetUserMediaCallbackMediaStreamListener> > StreamListeners;
-typedef nsClassHashtable<nsUint64HashKey, StreamListeners> WindowTable;
+typedef nsRefPtrHashtable<nsUint64HashKey, GetUserMediaWindowListener> WindowTable;
 
 // we could add MediaManager if needed
 typedef void (*WindowListenerCallback)(MediaManager *aThis,
                                        uint64_t aWindowID,
-                                       StreamListeners *aListeners,
+                                       GetUserMediaWindowListener *aListener,
                                        void *aData);
 
 class MediaManager final : public nsIMediaManagerService,
                            public nsIObserver
                           ,public DeviceChangeCallback
 {
-  friend GetUserMediaCallbackMediaStreamListener;
+  friend SourceListener;
 public:
   static already_AddRefed<MediaManager> GetInstance();
 
   // NOTE: never Dispatch(....,NS_DISPATCH_SYNC) to the MediaManager
   // thread from the MainThread, as we NS_DISPATCH_SYNC to MainThread
   // from MediaManager thread.
   static MediaManager* Get();
   static MediaManager* GetIfExists();
@@ -211,37 +204,41 @@ public:
 #endif
 
   static bool Exists()
   {
     return !!sSingleton;
   }
 
   static nsresult NotifyRecordingStatusChange(nsPIDOMWindowInner* aWindow,
-                                              const nsString& aMsg,
-                                              const bool& aIsAudio,
-                                              const bool& aIsVideo);
+                                              const nsString& aMsg);
 
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIOBSERVER
   NS_DECL_NSIMEDIAMANAGERSERVICE
 
   media::Parent<media::NonE10s>* GetNonE10sParent();
   MediaEngine* GetBackend(uint64_t aWindowId = 0);
-  StreamListeners *GetWindowListeners(uint64_t aWindowId) {
+
+  WindowTable *GetActiveWindows() {
     MOZ_ASSERT(NS_IsMainThread());
-    return mActiveWindows.Get(aWindowId);
+    return &mActiveWindows;
   }
+  GetUserMediaWindowListener *GetWindowListener(uint64_t aWindowId) {
+    MOZ_ASSERT(NS_IsMainThread());
+    return mActiveWindows.GetWeak(aWindowId);
+  }
+  void AddWindowID(uint64_t aWindowId, GetUserMediaWindowListener *aListener);
   void RemoveWindowID(uint64_t aWindowId);
   bool IsWindowStillActive(uint64_t aWindowId) {
-    return !!GetWindowListeners(aWindowId);
+    return !!GetWindowListener(aWindowId);
   }
   // Note: also calls aListener->Remove(), even if inactive
   void RemoveFromWindowList(uint64_t aWindowID,
-    GetUserMediaCallbackMediaStreamListener *aListener);
+    GetUserMediaWindowListener *aListener);
 
   nsresult GetUserMedia(
     nsPIDOMWindowInner* aWindow,
     const dom::MediaStreamConstraints& aConstraints,
     nsIDOMGetUserMediaSuccessCallback* onSuccess,
     nsIDOMGetUserMediaErrorCallback* onError,
     dom::CallerType aCallerType);
 
@@ -288,22 +285,16 @@ private:
                        dom::MediaSourceEnum aAudioSrcType,
                        bool aFake = false);
   already_AddRefed<PledgeChar>
   SelectSettings(
       dom::MediaStreamConstraints& aConstraints,
       bool aIsChrome,
       RefPtr<media::Refcountable<UniquePtr<SourceSet>>>& aSources);
 
-  StreamListeners* AddWindowID(uint64_t aWindowId);
-  WindowTable *GetActiveWindows() {
-    MOZ_ASSERT(NS_IsMainThread());
-    return &mActiveWindows;
-  }
-
   void GetPref(nsIPrefBranch *aBranch, const char *aPref,
                const char *aData, int32_t *aVal);
   void GetPrefBool(nsIPrefBranch *aBranch, const char *aPref,
                    const char *aData, bool *aVal);
   void GetPrefs(nsIPrefBranch *aBranch, const char *aData);
 
   // Make private because we want only one instance of this class
   MediaManager();
new file mode 100644
--- /dev/null
+++ b/dom/media/platforms/agnostic/AOMDecoder.cpp
@@ -0,0 +1,260 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 "AOMDecoder.h"
+#include "MediaResult.h"
+#include "TimeUnits.h"
+#include "aom/aomdx.h"
+#include "gfx2DGlue.h"
+#include "mozilla/PodOperations.h"
+#include "mozilla/SyncRunnable.h"
+#include "nsError.h"
+#include "prsystem.h"
+
+#include <algorithm>
+
+#undef LOG
+#define LOG(arg, ...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, ("AOMDecoder(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
+#define LOG_RESULT(code, message, ...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, ("AOMDecoder::%s: %s (code %d) " message, __func__, aom_codec_err_to_string(code), (int)code, ##__VA_ARGS__))
+
+namespace mozilla {
+
+using namespace gfx;
+using namespace layers;
+
+
+static MediaResult
+InitContext(aom_codec_ctx_t* aCtx,
+            const VideoInfo& aInfo)
+{
+  aom_codec_iface_t* dx = aom_codec_av1_dx();
+  if (!dx) {
+    return MediaResult(NS_ERROR_FAILURE,
+                       RESULT_DETAIL("Couldn't get AV1 decoder interface."));
+  }
+
+  int decode_threads = 2;
+  if (aInfo.mDisplay.width >= 2048) {
+    decode_threads = 8;
+  }
+  else if (aInfo.mDisplay.width >= 1024) {
+    decode_threads = 4;
+  }
+  decode_threads = std::min(decode_threads, PR_GetNumberOfProcessors());
+
+  aom_codec_dec_cfg_t config;
+  PodZero(&config);
+  config.threads = decode_threads;
+  config.w = config.h = 0; // set after decode
+
+  aom_codec_flags_t flags = 0;
+
+  auto res = aom_codec_dec_init(aCtx, dx, &config, flags);
+  if (res != AOM_CODEC_OK) {
+    LOG_RESULT(res, "Codec initialization failed!");
+    return MediaResult(NS_ERROR_FAILURE,
+                       RESULT_DETAIL("AOM error initializing AV1 decoder: %s",
+                                     aom_codec_err_to_string(res)));
+  }
+  return NS_OK;
+}
+
+AOMDecoder::AOMDecoder(const CreateDecoderParams& aParams)
+  : mImageContainer(aParams.mImageContainer)
+  , mTaskQueue(aParams.mTaskQueue)
+  , mInfo(aParams.VideoConfig())
+{
+  PodZero(&mCodec);
+}
+
+AOMDecoder::~AOMDecoder()
+{
+}
+
+RefPtr<ShutdownPromise>
+AOMDecoder::Shutdown()
+{
+  RefPtr<AOMDecoder> self = this;
+  return InvokeAsync(mTaskQueue, __func__, [self, this]() {
+    auto res = aom_codec_destroy(&mCodec);
+    if (res != AOM_CODEC_OK) {
+      LOG_RESULT(res, "aom_codec_destroy");
+    }
+    return ShutdownPromise::CreateAndResolve(true, __func__);
+  });
+}
+
+RefPtr<MediaDataDecoder::InitPromise>
+AOMDecoder::Init()
+{
+  if (NS_FAILED(InitContext(&mCodec, mInfo))) {
+    return AOMDecoder::InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                                                    __func__);
+  }
+  return AOMDecoder::InitPromise::CreateAndResolve(TrackInfo::kVideoTrack,
+                                                   __func__);
+}
+
+RefPtr<MediaDataDecoder::FlushPromise>
+AOMDecoder::Flush()
+{
+  return InvokeAsync(mTaskQueue, __func__, []() {
+    return FlushPromise::CreateAndResolve(true, __func__);
+  });
+}
+
+RefPtr<MediaDataDecoder::DecodePromise>
+AOMDecoder::ProcessDecode(MediaRawData* aSample)
+{
+  MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+
+#if defined(DEBUG)
+  NS_ASSERTION(IsKeyframe(*aSample) == aSample->mKeyframe,
+               "AOM Decode Keyframe error sample->mKeyframe and si.si_kf out of sync");
+#endif
+
+  if (aom_codec_err_t r = aom_codec_decode(&mCodec, aSample->Data(), aSample->Size(), nullptr, 0)) {
+    LOG_RESULT(r, "Decode error!");
+    return DecodePromise::CreateAndReject(
+      MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
+                  RESULT_DETAIL("AOM error decoding AV1 sample: %s",
+                                aom_codec_err_to_string(r))),
+      __func__);
+  }
+
+  aom_codec_iter_t iter = nullptr;
+  aom_image_t *img;
+  DecodedData results;
+
+  while ((img = aom_codec_get_frame(&mCodec, &iter))) {
+    NS_ASSERTION(img->fmt == AOM_IMG_FMT_I420 ||
+                 img->fmt == AOM_IMG_FMT_I444,
+                 "WebM image format not I420 or I444");
+
+    // Chroma shifts are rounded down as per the decoding examples in the SDK
+    VideoData::YCbCrBuffer b;
+    b.mPlanes[0].mData = img->planes[0];
+    b.mPlanes[0].mStride = img->stride[0];
+    b.mPlanes[0].mHeight = img->d_h;
+    b.mPlanes[0].mWidth = img->d_w;
+    b.mPlanes[0].mOffset = b.mPlanes[0].mSkip = 0;
+
+    b.mPlanes[1].mData = img->planes[1];
+    b.mPlanes[1].mStride = img->stride[1];
+    b.mPlanes[1].mOffset = b.mPlanes[1].mSkip = 0;
+
+    b.mPlanes[2].mData = img->planes[2];
+    b.mPlanes[2].mStride = img->stride[2];
+    b.mPlanes[2].mOffset = b.mPlanes[2].mSkip = 0;
+
+    if (img->fmt == AOM_IMG_FMT_I420) {
+      b.mPlanes[1].mHeight = (img->d_h + 1) >> img->y_chroma_shift;
+      b.mPlanes[1].mWidth = (img->d_w + 1) >> img->x_chroma_shift;
+
+      b.mPlanes[2].mHeight = (img->d_h + 1) >> img->y_chroma_shift;
+      b.mPlanes[2].mWidth = (img->d_w + 1) >> img->x_chroma_shift;
+    } else if (img->fmt == AOM_IMG_FMT_I444) {
+      b.mPlanes[1].mHeight = img->d_h;
+      b.mPlanes[1].mWidth = img->d_w;
+
+      b.mPlanes[2].mHeight = img->d_h;
+      b.mPlanes[2].mWidth = img->d_w;
+    } else {
+      LOG("AOM Unknown image format");
+      return DecodePromise::CreateAndReject(
+        MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
+                    RESULT_DETAIL("AOM Unknown image format")),
+        __func__);
+    }
+
+    RefPtr<VideoData> v;
+    v = VideoData::CreateAndCopyData(mInfo,
+                                     mImageContainer,
+                                     aSample->mOffset,
+                                     aSample->mTime,
+                                     aSample->mDuration,
+                                     b,
+                                     aSample->mKeyframe,
+                                     aSample->mTimecode,
+                                     mInfo.ScaledImageRect(img->d_w,
+                                                           img->d_h));
+
+    if (!v) {
+      LOG(
+        "Image allocation error source %ux%u display %ux%u picture %ux%u",
+        img->d_w, img->d_h, mInfo.mDisplay.width, mInfo.mDisplay.height,
+        mInfo.mImage.width, mInfo.mImage.height);
+      return DecodePromise::CreateAndReject(
+        MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__), __func__);
+    }
+    results.AppendElement(Move(v));
+  }
+  return DecodePromise::CreateAndResolve(Move(results), __func__);
+}
+
+RefPtr<MediaDataDecoder::DecodePromise>
+AOMDecoder::Decode(MediaRawData* aSample)
+{
+  return InvokeAsync<MediaRawData*>(mTaskQueue, this, __func__,
+                                    &AOMDecoder::ProcessDecode, aSample);
+}
+
+RefPtr<MediaDataDecoder::DecodePromise>
+AOMDecoder::Drain()
+{
+  return InvokeAsync(mTaskQueue, __func__, [] {
+    return DecodePromise::CreateAndResolve(DecodedData(), __func__);
+  });
+}
+
+
+/* static */
+bool
+AOMDecoder::IsAV1(const nsACString& aMimeType)
+{
+  return aMimeType.EqualsLiteral("video/webm; codecs=av1")
+         || aMimeType.EqualsLiteral("video/av1");
+}
+
+/* static */
+bool
+AOMDecoder::IsKeyframe(Span<const uint8_t> aBuffer) {
+  aom_codec_stream_info_t info;
+  PodZero(&info);
+  info.sz = sizeof(info);
+
+  auto res = aom_codec_peek_stream_info(aom_codec_av1_dx(),
+                                        aBuffer.Elements(),
+                                        aBuffer.Length(),
+                                        &info);
+  if (res != AOM_CODEC_OK) {
+    LOG_RESULT(res, "couldn't get keyframe flag with aom_codec_peek_stream_info");
+    return false;
+  }
+
+  return bool(info.is_kf);
+}
+
+/* static */
+nsIntSize
+AOMDecoder::GetFrameSize(Span<const uint8_t> aBuffer) {
+  aom_codec_stream_info_t info;
+  PodZero(&info);
+  info.sz = sizeof(info);
+
+  auto res = aom_codec_peek_stream_info(aom_codec_av1_dx(),
+                                        aBuffer.Elements(),
+                                        aBuffer.Length(),
+                                        &info);
+  if (res != AOM_CODEC_OK) {
+    LOG_RESULT(res, "couldn't get frame size with aom_codec_peek_stream_info");
+  }
+
+  return nsIntSize(info.w, info.h);
+}
+
+} // namespace mozilla
+#undef LOG
new file mode 100644
--- /dev/null
+++ b/dom/media/platforms/agnostic/AOMDecoder.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+#if !defined(AOMDecoder_h_)
+#define AOMDecoder_h_
+
+#include "PlatformDecoderModule.h"
+#include "mozilla/Span.h"
+
+#include <stdint.h>
+#include "aom/aom_decoder.h"
+
+namespace mozilla {
+
+class AOMDecoder : public MediaDataDecoder
+{
+public:
+  explicit AOMDecoder(const CreateDecoderParams& aParams);
+
+  RefPtr<InitPromise> Init() override;
+  RefPtr<DecodePromise> Decode(MediaRawData* aSample) override;
+  RefPtr<DecodePromise> Drain() override;
+  RefPtr<FlushPromise> Flush() override;
+  RefPtr<ShutdownPromise> Shutdown() override;
+  const char* GetDescriptionName() const override
+  {
+    return "libaom (AV1) video decoder";
+  }
+
+  // Return true if aMimeType is a one of the strings used
+  // by our demuxers to identify AV1 streams.
+  static bool IsAV1(const nsACString& aMimeType);
+
+  // Return true if a sample is a keyframe.
+  static bool IsKeyframe(Span<const uint8_t> aBuffer);
+
+  // Return the frame dimensions for a sample.
+  static nsIntSize GetFrameSize(Span<const uint8_t> aBuffer);
+
+private:
+  ~AOMDecoder();
+  RefPtr<DecodePromise> ProcessDecode(MediaRawData* aSample);
+
+  const RefPtr<layers::ImageContainer> mImageContainer;
+  const RefPtr<TaskQueue> mTaskQueue;
+
+  // AOM decoder state
+  aom_codec_ctx_t mCodec;
+
+  const VideoInfo& mInfo;
+};
+
+} // namespace mozilla
+
+#endif // AOMDecoder_h_
--- a/dom/media/platforms/agnostic/AgnosticDecoderModule.cpp
+++ b/dom/media/platforms/agnostic/AgnosticDecoderModule.cpp
@@ -7,42 +7,55 @@
 #include "AgnosticDecoderModule.h"
 #include "OpusDecoder.h"
 #include "TheoraDecoder.h"
 #include "VPXDecoder.h"
 #include "VorbisDecoder.h"
 #include "WAVDecoder.h"
 #include "mozilla/Logging.h"
 
+#ifdef MOZ_AV1
+#include "AOMDecoder.h"
+#endif
+
 namespace mozilla {
 
 bool
 AgnosticDecoderModule::SupportsMimeType(
   const nsACString& aMimeType,
   DecoderDoctorDiagnostics* aDiagnostics) const
 {
   bool supports =
     VPXDecoder::IsVPX(aMimeType)
+#ifdef MOZ_AV1
+    || AOMDecoder::IsAV1(aMimeType)
+#endif
     || OpusDataDecoder::IsOpus(aMimeType)
     || VorbisDataDecoder::IsVorbis(aMimeType)
     || WaveDataDecoder::IsWave(aMimeType)
     || TheoraDecoder::IsTheora(aMimeType);
   MOZ_LOG(sPDMLog, LogLevel::Debug, ("Agnostic decoder %s requested type",
         supports ? "supports" : "rejects"));
   return supports;
 }
 
 already_AddRefed<MediaDataDecoder>
 AgnosticDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams)
 {
   RefPtr<MediaDataDecoder> m;
 
   if (VPXDecoder::IsVPX(aParams.mConfig.mMimeType)) {
     m = new VPXDecoder(aParams);
-  } else if (TheoraDecoder::IsTheora(aParams.mConfig.mMimeType)) {
+  }
+#ifdef MOZ_AV1
+  else if (AOMDecoder::IsAV1(aParams.mConfig.mMimeType)) {
+    m = new AOMDecoder(aParams);
+  }
+#endif
+  else if (TheoraDecoder::IsTheora(aParams.mConfig.mMimeType)) {
     m = new TheoraDecoder(aParams);
   }
 
   return m.forget();
 }
 
 already_AddRefed<MediaDataDecoder>
 AgnosticDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams)
--- a/dom/media/platforms/moz.build
+++ b/dom/media/platforms/moz.build
@@ -55,16 +55,24 @@ if CONFIG['MOZ_FFVPX']:
         'ffmpeg/ffvpx',
     ]
 
 if CONFIG['MOZ_FFMPEG']:
     DIRS += [
         'ffmpeg',
     ]
 
+if CONFIG['MOZ_AV1']:
+    EXPORTS += [
+        'agnostic/AOMDecoder.h',
+    ]
+    UNIFIED_SOURCES += [
+        'agnostic/AOMDecoder.cpp',
+    ]
+
 if CONFIG['MOZ_APPLEMEDIA']:
   EXPORTS += [
       'apple/AppleDecoderModule.h',
   ]
   UNIFIED_SOURCES += [
       'apple/AppleATDecoder.cpp',
       'apple/AppleCMLinker.cpp',
       'apple/AppleDecoderModule.cpp',
--- a/dom/media/webm/WebMDemuxer.cpp
+++ b/dom/media/webm/WebMDemuxer.cpp
@@ -3,16 +3,19 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsError.h"
 #include "MediaDecoderStateMachine.h"
 #include "AbstractMediaDecoder.h"
 #include "MediaResource.h"
+#ifdef MOZ_AV1
+#include "AOMDecoder.h"
+#endif
 #include "OpusDecoder.h"
 #include "VPXDecoder.h"
 #include "WebMDemuxer.h"
 #include "WebMBufferedParser.h"
 #include "gfx2DGlue.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/EndianUtils.h"
 #include "mozilla/SharedThreadPool.h"
@@ -322,16 +325,19 @@ WebMDemuxer::ReadMetadata()
       mVideoCodec = nestegg_track_codec_id(context, track);
       switch(mVideoCodec) {
         case NESTEGG_CODEC_VP8:
           mInfo.mVideo.mMimeType = "video/webm; codecs=vp8";
           break;
         case NESTEGG_CODEC_VP9:
           mInfo.mVideo.mMimeType = "video/webm; codecs=vp9";
           break;
+        case NESTEGG_CODEC_AV1:
+          mInfo.mVideo.mMimeType = "video/webm; codecs=av1";
+          break;
         default:
           NS_WARNING("Unknown WebM video codec");
           return NS_ERROR_FAILURE;
       }
       // Picture region, taking into account cropping, before scaling
       // to the display size.
       unsigned int cropH = params.crop_right + params.crop_left;
       unsigned int cropV = params.crop_bottom + params.crop_top;
@@ -677,27 +683,42 @@ WebMDemuxer::GetNextPacket(TrackInfo::Tr
         auto sample = MakeSpan(data, length);
         switch (mVideoCodec) {
         case NESTEGG_CODEC_VP8:
           isKeyframe = VPXDecoder::IsKeyframe(sample, VPXDecoder::Codec::VP8);
           break;
         case NESTEGG_CODEC_VP9:
           isKeyframe = VPXDecoder::IsKeyframe(sample, VPXDecoder::Codec::VP9);
           break;
+#ifdef MOZ_AV1
+        case NESTEGG_CODEC_AV1:
+          isKeyframe = AOMDecoder::IsKeyframe(sample);
+          break;
+#endif
         default:
           NS_WARNING("Cannot detect keyframes in unknown WebM video codec");
           return NS_ERROR_FAILURE;
         }
         if (isKeyframe) {
           // For both VP8 and VP9, we only look for resolution changes
           // on keyframes. Other resolution changes are invalid.
-          auto codec = mVideoCodec == NESTEGG_CODEC_VP8
-                       ? VPXDecoder::Codec::VP8
-                       : VPXDecoder::Codec::VP9;
-          auto dimensions = VPXDecoder::GetFrameSize(sample, codec);
+          auto dimensions = nsIntSize(0, 0);
+          switch (mVideoCodec) {
+          case NESTEGG_CODEC_VP8:
+            dimensions = VPXDecoder::GetFrameSize(sample, VPXDecoder::Codec::VP8);
+            break;
+          case NESTEGG_CODEC_VP9:
+            dimensions = VPXDecoder::GetFrameSize(sample, VPXDecoder::Codec::VP9);
+            break;
+#ifdef MOZ_AV1
+          case NESTEGG_CODEC_AV1:
+            dimensions = AOMDecoder::GetFrameSize(sample);
+            break;
+#endif
+          }
           if (mLastSeenFrameSize.isSome()
               && (dimensions != mLastSeenFrameSize.value())) {
             mInfo.mVideo.mDisplay = dimensions;
             mSharedVideoTrackInfo =
               new TrackInfoSharedPtr(mInfo.mVideo, ++sStreamSourceID);
           }
           mLastSeenFrameSize = Some(dimensions);
         }
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -2704,16 +2704,23 @@ void
 RuntimeService::MemoryPressureAllWorkers()
 {
   BROADCAST_ALL_WORKERS(MemoryPressure, /* dummy = */ false);
 }
 
 uint32_t
 RuntimeService::ClampedHardwareConcurrency() const
 {
+  // The Firefox Hardware Report says 70% of Firefox users have exactly 2 cores.
+  // When the resistFingerprinting pref is set, we want to blend into the crowd
+  // so spoof navigator.hardwareConcurrency = 2 to reduce user uniqueness.
+  if (MOZ_UNLIKELY(nsContentUtils::ShouldResistFingerprinting())) {
+    return 2;
+  }
+
   // This needs to be atomic, because multiple workers, and even mainthread,
   // could race to initialize it at once.
   static Atomic<uint32_t> clampedHardwareConcurrency;
 
   // No need to loop here: if compareExchange fails, that just means that some
   // other worker has initialized numberOfProcessors, so we're good to go.
   if (!clampedHardwareConcurrency) {
     int32_t numberOfProcessors = PR_GetNumberOfProcessors();
--- a/dom/workers/test/test_navigator_workers_hardwareConcurrency.html
+++ b/dom/workers/test/test_navigator_workers_hardwareConcurrency.html
@@ -1,27 +1,53 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <meta charset="utf-8">
   <title>Test for Navigator.hardwareConcurrency</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript">
+  "use strict";
 
   SimpleTest.waitForExplicitFinish();
-  var script = "postMessage(navigator.hardwareConcurrency)";
-  var url = URL.createObjectURL(new Blob([script]));
-  var w = new Worker(url);
-  w.onmessage = function(e) {
+
+  function getWorkerHardwareConcurrency(onmessage) {
+    var script = "postMessage(navigator.hardwareConcurrency)";
+    var url = URL.createObjectURL(new Blob([script]));
+    var w = new Worker(url);
+    w.onmessage = onmessage;
+  }
+
+  function resistFingerprinting(value) {
+    return SpecialPowers.pushPrefEnv({"set": [["privacy.resistFingerprinting", value]]});
+  }
+
+  getWorkerHardwareConcurrency(e => {
     var x = e.data;
     is(typeof x, "number", "hardwareConcurrency should be a number.");
     ok(x > 0, "hardwareConcurrency should be greater than 0.");
-    SimpleTest.finish();
-  }
+
+    resistFingerprinting(true).then(() => {
+      getWorkerHardwareConcurrency(e => {
+        const y = e.data;
+        ok(y === 2, "hardwareConcurrency should always be 2 when we're resisting fingerprinting.");
+
+        resistFingerprinting(false).then(() => {
+          getWorkerHardwareConcurrency(e => {
+            const z = e.data;
+            ok(z === x, "hardwareConcurrency should be the same as before we were resisting fingerprinting.");
+
+            SimpleTest.finish();
+          });
+        });
+      });
+    });
+  });
+
   </script>
 </head>
 <body>
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -7311,16 +7311,20 @@ BytecodeEmitter::emitForOf(ParseNode* fo
 
     JumpList beq;
     JumpTarget breakTarget{ -1 };
     {
 #ifdef DEBUG
         auto loopDepth = this->stackDepth;
 #endif
 
+        // Make sure this code is attributed to the "for".
+        if (!updateSourceCoordNotes(forOfHead->pn_pos.begin))
+            return false;
+
         if (!emit1(JSOP_POP))                             // ITER
             return false;
         if (!emit1(JSOP_DUP))                             // ITER ITER
             return false;
 
         if (!emitIteratorNext(forOfHead, iterKind, allowSelfHostedIter))
             return false;                                 // ITER RESULT
 
@@ -7519,16 +7523,20 @@ BytecodeEmitter::emitForIn(ParseNode* fo
     // Perform the loop body.
     ParseNode* forBody = forInLoop->pn_right;
     if (!emitTree(forBody))                               // ITER ITERVAL
         return false;
 
     // Set offset for continues.
     loopInfo.continueTarget = { offset() };
 
+    // Make sure this code is attributed to the "for".
+    if (!updateSourceCoordNotes(forInHead->pn_pos.begin))
+        return false;
+
     if (!emitLoopEntry(nullptr, initialJump))             // ITER ITERVAL
         return false;
     if (!emit1(JSOP_POP))                                 // ITER
         return false;
     if (!emit1(JSOP_MOREITER))                            // ITER NEXTITERVAL?
         return false;
     if (!emit1(JSOP_ISNOITER))                            // ITER NEXTITERVAL? ISNOITER
         return false;
--- a/js/src/gc/Tracer.cpp
+++ b/js/src/gc/Tracer.cpp
@@ -459,16 +459,23 @@ JS_GetTraceThingInfo(char* buf, size_t b
                     snprintf(buf, bufsize, "<nonlinear desc>");
                 }
             } else {
                 snprintf(buf, bufsize, "<null>");
             }
             break;
           }
 
+          case JS::TraceKind::Scope:
+          {
+            js::Scope* scope = static_cast<js::Scope*>(thing);
+            snprintf(buf, bufsize, " %s", js::ScopeKindString(scope->kind()));
+            break;
+          }
+
           default:
             break;
         }
     }
     buf[bufsize - 1] = '\0';
 }
 
 JS::CallbackTracer::CallbackTracer(JSContext* cx, WeakMapTraceKind weakTraceKind)
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-17.js
@@ -0,0 +1,31 @@
+var g = newGlobal();
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+var log;
+var previous;
+
+dbg.onDebuggerStatement = function (frame) {
+  let debugLine = frame.script.getOffsetLocation(frame.offset).lineNumber;
+  log = '';
+  previous = '';
+  frame.onStep = function() {
+    let foundLine = this.script.getOffsetLocation(this.offset).lineNumber;
+    if (this.script.getLineOffsets(foundLine).indexOf(this.offset) >= 0) {
+      let thisline = (foundLine - debugLine).toString(16);
+      if (thisline !== previous) {
+        log += thisline;
+        previous = thisline;
+      }
+    }
+  };
+};
+
+function testOne(loopKind) {
+  let body = "var array = [2, 4, 6];\ndebugger;\nfor (let iter " +
+      loopKind + " array) {\n  print(iter);\n}\n";
+  g.eval(body);
+  assertEq(log, "12121212");
+}
+
+testOne("in");
+testOne("of");
--- a/layout/base/nsCounterManager.cpp
+++ b/layout/base/nsCounterManager.cpp
@@ -44,20 +44,18 @@ nsCounterUseNode::InitTextFrame(nsGenCon
 }
 
 CounterStyle*
 nsCounterUseNode::GetCounterStyle()
 {
     if (!mCounterStyle) {
         const nsCSSValue& style = mCounterFunction->Item(mAllCounters ? 2 : 1);
         CounterStyleManager* manager = mPresContext->CounterStyleManager();
-        if (style.GetUnit() == eCSSUnit_Ident) {
-            nsString ident;
-            style.GetStringValue(ident);
-            mCounterStyle = manager->BuildCounterStyle(ident);
+        if (style.GetUnit() == eCSSUnit_AtomIdent) {
+            mCounterStyle = manager->BuildCounterStyle(style.GetAtomValue());
         } else if (style.GetUnit() == eCSSUnit_Symbols) {
             mCounterStyle = new AnonymousCounterStyle(style.GetArrayValue());
         } else {
             NS_NOTREACHED("Unknown counter style");
             mCounterStyle = CounterStyleManager::GetDecimalStyle();
         }
     }
     return mCounterStyle;
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -25,17 +25,16 @@
 #include "nsDocument.h"
 #include "nsFontMetrics.h"
 #include "nsPresContext.h"
 #include "nsIContent.h"
 #include "nsIDOMHTMLDocument.h"
 #include "nsIDOMHTMLElement.h"
 #include "nsFrameList.h"
 #include "nsGkAtoms.h"
-#include "nsHtml5Atoms.h"
 #include "nsIAtom.h"
 #include "nsCaret.h"
 #include "nsCSSPseudoElements.h"
 #include "nsCSSAnonBoxes.h"
 #include "nsCSSColorUtils.h"
 #include "nsView.h"
 #include "nsViewManager.h"
 #include "nsPlaceholderFrame.h"
@@ -117,16 +116,17 @@
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/RuleNodeCacheConditions.h"
 #include "mozilla/StyleAnimationValue.h"
 #include "mozilla/StyleSetHandle.h"
 #include "mozilla/StyleSetHandleInlines.h"
 #include "RegionBuilder.h"
 #include "SVGSVGElement.h"
+#include "DisplayItemClip.h"
 
 #ifdef MOZ_XUL
 #include "nsXULPopupManager.h"
 #endif
 
 #include "GeckoProfiler.h"
 #include "nsAnimationManager.h"
 #include "nsTransitionManager.h"
@@ -8933,26 +8933,33 @@ nsLayoutUtils::GetTouchActionFromFrame(n
 
 /* static */  void
 nsLayoutUtils::TransformToAncestorAndCombineRegions(
   const nsRegion& aRegion,
   nsIFrame* aFrame,
   const nsIFrame* aAncestorFrame,
   nsRegion* aPreciseTargetDest,
   nsRegion* aImpreciseTargetDest,
-  Maybe<Matrix4x4>* aMatrixCache)
+  Maybe<Matrix4x4>* aMatrixCache,
+  const DisplayItemClip* aClip)
 {
   if (aRegion.IsEmpty()) {
     return;
   }
   bool isPrecise;
   RegionBuilder<nsRegion> transformedRegion;
   for (nsRegion::RectIterator it = aRegion.RectIter(); !it.Done(); it.Next()) {
     nsRect transformed = TransformFrameRectToAncestor(
       aFrame, it.Get(), aAncestorFrame, &isPrecise, aMatrixCache);
+    if (aClip) {
+      transformed = aClip->ApplyNonRoundedIntersection(transformed);
+      if (aClip->GetRoundedRectCount() > 0) {
+        isPrecise = false;
+      }
+    }
     transformedRegion.OrWith(transformed);
   }
   nsRegion* dest = isPrecise ? aPreciseTargetDest : aImpreciseTargetDest;
   dest->OrWith(transformedRegion.ToRegion());
 }
 
 /* static */ bool
 nsLayoutUtils::ShouldUseNoScriptSheet(nsIDocument* aDocument)
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -65,16 +65,17 @@ struct nsOverflowAreas;
 
 namespace mozilla {
 enum class CSSPseudoElementType : uint8_t;
 class EventListenerManager;
 enum class LayoutFrameType : uint8_t;
 struct IntrinsicSize;
 struct ContainerLayerParameters;
 class WritingMode;
+class DisplayItemClip;
 namespace dom {
 class CanvasRenderingContext2D;
 class DOMRectList;
 class Element;
 class HTMLImageElement;
 class HTMLCanvasElement;
 class HTMLVideoElement;
 class OffscreenCanvas;
@@ -2584,26 +2585,29 @@ public:
    * Helper method to get touch action behaviour from the frame
    */
   static uint32_t
   GetTouchActionFromFrame(nsIFrame* aFrame);
 
   /**
    * Helper method to transform |aBounds| from aFrame to aAncestorFrame,
    * and combine it with |aPreciseTargetDest| if it is axis-aligned, or
-   * combine it with |aImpreciseTargetDest| if not.
+   * combine it with |aImpreciseTargetDest| if not. The transformed rect is
+   * clipped to |aClip|; if |aClip| has rounded corners, that also causes
+   * the imprecise target to be used.
    */
   static void
   TransformToAncestorAndCombineRegions(
     const nsRegion& aRegion,
     nsIFrame* aFrame,
     const nsIFrame* aAncestorFrame,
     nsRegion* aPreciseTargetDest,
     nsRegion* aImpreciseTargetDest,
-    mozilla::Maybe<Matrix4x4>* aMatrixCache);
+    mozilla::Maybe<Matrix4x4>* aMatrixCache,
+    const mozilla::DisplayItemClip* aClip);
 
   /**
    * Populate aOutSize with the size of the content viewer corresponding
    * to the given prescontext. Return true if the size was set, false
    * otherwise.
    */
   static bool
   GetContentViewerSize(nsPresContext* aPresContext,
--- a/layout/build/nsLayoutStatics.cpp
+++ b/layout/build/nsLayoutStatics.cpp
@@ -46,17 +46,16 @@
 #include "nsTreeSanitizer.h"
 #include "nsCellMap.h"
 #include "nsTextFrame.h"
 #include "nsCCUncollectableMarker.h"
 #include "nsTextFragment.h"
 #include "nsCSSRuleProcessor.h"
 #include "nsCORSListenerProxy.h"
 #include "nsHTMLDNSPrefetch.h"
-#include "nsHtml5Atoms.h"
 #include "nsHtml5Module.h"
 #include "nsHTMLTags.h"
 #include "nsIRDFContentSink.h"	// for RDF atom initialization
 #include "mozilla/dom/FallbackEncoding.h"
 #include "nsFocusManager.h"
 #include "nsListControlFrame.h"
 #include "mozilla/dom/HTMLInputElement.h"
 #include "SVGElementFactory.h"
@@ -153,17 +152,16 @@ nsLayoutStatics::Initialize()
   // Register all of our atoms once
   nsCSSAnonBoxes::AddRefAtoms();
   nsCSSPseudoClasses::AddRefAtoms();
   nsCSSPseudoElements::AddRefAtoms();
   nsCSSKeywords::AddRefTable();
   nsCSSProps::AddRefTable();
   nsColorNames::AddRefTable();
   nsGkAtoms::AddRefAtoms();
-  nsHtml5Atoms::AddRefAtoms();
   nsTextServicesDocument::RegisterAtoms();
   nsHTMLTags::RegisterAtoms();
   nsRDFAtoms::RegisterAtoms();
 
   NS_SealStaticAtomTable();
 
   StartupJSEnvironment();
   rv = nsRegion::InitStatic();
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -46,17 +46,16 @@
 #include "mozilla/RestyleManager.h"
 #include "mozilla/RestyleManagerInlines.h"
 
 #include "nsIDOMNode.h"
 #include "nsISelection.h"
 #include "nsISelectionPrivate.h"
 #include "nsFrameSelection.h"
 #include "nsGkAtoms.h"
-#include "nsHtml5Atoms.h"
 #include "nsCSSAnonBoxes.h"
 
 #include "nsFrameTraversal.h"
 #include "nsRange.h"
 #include "nsITextControlFrame.h"
 #include "nsNameSpaceManager.h"
 #include "nsIPercentBSizeObserver.h"
 #include "nsStyleStructInlines.h"
--- a/layout/painting/FrameLayerBuilder.cpp
+++ b/layout/painting/FrameLayerBuilder.cpp
@@ -113,16 +113,18 @@ static inline MaskLayerImageCache* GetMa
     gMaskLayerImageCache = new MaskLayerImageCache();
   }
 
   return gMaskLayerImageCache;
 }
 
 FrameLayerBuilder::FrameLayerBuilder()
   : mRetainingManager(nullptr)
+  , mContainingPaintedLayer(nullptr)
+  , mInactiveLayerClip(nullptr)
   , mDetectedDOMModification(false)
   , mInvalidateAllLayers(false)
   , mInLayerTreeCompressionMode(false)
   , mContainerLayerGeneration(0)
   , mMaxContainerLayerGeneration(0)
 {
   MOZ_COUNT_CTOR(FrameLayerBuilder);
 }
@@ -1783,24 +1785,26 @@ FrameLayerBuilder::Shutdown()
   if (gMaskLayerImageCache) {
     delete gMaskLayerImageCache;
     gMaskLayerImageCache = nullptr;
   }
 }
 
 void
 FrameLayerBuilder::Init(nsDisplayListBuilder* aBuilder, LayerManager* aManager,
-                        PaintedLayerData* aLayerData)
+                        PaintedLayerData* aLayerData,
+                        const DisplayItemClip* aInactiveLayerClip)
 {
   mDisplayListBuilder = aBuilder;
   mRootPresContext = aBuilder->RootReferenceFrame()->PresContext()->GetRootPresContext();
   if (mRootPresContext) {
     mInitialDOMGeneration = mRootPresContext->GetDOMGeneration();
   }
   mContainingPaintedLayer = aLayerData;
+  mInactiveLayerClip = aInactiveLayerClip;
   aManager->SetUserData(&gLayerManagerLayerBuilder, this);
 }
 
 void
 FrameLayerBuilder::FlashPaint(gfxContext *aContext)
 {
   float r = float(rand()) / RAND_MAX;
   float g = float(rand()) / RAND_MAX;
@@ -3314,69 +3318,88 @@ void ContainerState::FinishPaintedLayerD
   }
   if (data->mDisableFlattening) {
     flags |= Layer::CONTENT_DISABLE_FLATTENING;
   }
   layer->SetContentFlags(flags);
 
   PaintedLayerData* containingPaintedLayerData =
      mLayerBuilder->GetContainingPaintedLayerData();
+  // If we're building layers for an inactive layer, the event regions are
+  // clipped to the inactive layer's clip prior to being combined into the
+  // event regions of the containing PLD.
+  // For the dispatch-to-content and maybe-hit regions, rounded corners on
+  // the clip are ignored, since these are approximate regions. For the
+  // remaining regions, rounded corners in the clip cause the region to
+  // be combined into the corresponding "imprecise" region of the
+  // containing's PLD (e.g. the maybe-hit region instead of the hit region).
+  const DisplayItemClip* inactiveLayerClip = mLayerBuilder->GetInactiveLayerClip();
   if (containingPaintedLayerData) {
     if (!data->mDispatchToContentHitRegion.GetBounds().IsEmpty()) {
       nsRect rect = nsLayoutUtils::TransformFrameRectToAncestor(
         mContainerReferenceFrame,
         data->mDispatchToContentHitRegion.GetBounds(),
         containingPaintedLayerData->mReferenceFrame);
+      if (inactiveLayerClip) {
+        rect = inactiveLayerClip->ApplyNonRoundedIntersection(rect);
+      }
       containingPaintedLayerData->mDispatchToContentHitRegion.Or(
         containingPaintedLayerData->mDispatchToContentHitRegion, rect);
     }
     if (!data->mMaybeHitRegion.GetBounds().IsEmpty()) {
       nsRect rect = nsLayoutUtils::TransformFrameRectToAncestor(
         mContainerReferenceFrame,
         data->mMaybeHitRegion.GetBounds(),
         containingPaintedLayerData->mReferenceFrame);
+      if (inactiveLayerClip) {
+        rect = inactiveLayerClip->ApplyNonRoundedIntersection(rect);
+      }
       containingPaintedLayerData->mMaybeHitRegion.Or(
         containingPaintedLayerData->mMaybeHitRegion, rect);
       containingPaintedLayerData->mMaybeHitRegion.SimplifyOutward(8);
     }
     Maybe<Matrix4x4> matrixCache;
     nsLayoutUtils::TransformToAncestorAndCombineRegions(
       data->mHitRegion,
       mContainerReferenceFrame,
       containingPaintedLayerData->mReferenceFrame,
       &containingPaintedLayerData->mHitRegion,
       &containingPaintedLayerData->mMaybeHitRegion,
-      &matrixCache);
+      &matrixCache,
+      inactiveLayerClip);
     // See the comment in nsDisplayList::AddFrame, where the touch action regions
     // are handled. The same thing applies here.
     bool alreadyHadRegions =
         !containingPaintedLayerData->mNoActionRegion.IsEmpty() ||
         !containingPaintedLayerData->mHorizontalPanRegion.IsEmpty() ||
         !containingPaintedLayerData->mVerticalPanRegion.IsEmpty();
     nsLayoutUtils::TransformToAncestorAndCombineRegions(
       data->mNoActionRegion,
       mContainerReferenceFrame,
       containingPaintedLayerData->mReferenceFrame,
       &containingPaintedLayerData->mNoActionRegion,
       &containingPaintedLayerData->mDispatchToContentHitRegion,
-      &matrixCache);
+      &matrixCache,
+      inactiveLayerClip);
     nsLayoutUtils::TransformToAncestorAndCombineRegions(
       data->mHorizontalPanRegion,
       mContainerReferenceFrame,
       containingPaintedLayerData->mReferenceFrame,
       &containingPaintedLayerData->mHorizontalPanRegion,
       &containingPaintedLayerData->mDispatchToContentHitRegion,
-      &matrixCache);
+      &matrixCache,
+      inactiveLayerClip);
     nsLayoutUtils::TransformToAncestorAndCombineRegions(
       data->mVerticalPanRegion,
       mContainerReferenceFrame,
       containingPaintedLayerData->mReferenceFrame,
       &containingPaintedLayerData->mVerticalPanRegion,
       &containingPaintedLayerData->mDispatchToContentHitRegion,
-      &matrixCache);
+      &matrixCache,
+      inactiveLayerClip);
     if (alreadyHadRegions) {
       containingPaintedLayerData->mDispatchToContentHitRegion.OrWith(
         containingPaintedLayerData->CombinedTouchActionRegion());
     }
   } else {
     EventRegions regions;
     regions.mHitRegion = ScaleRegionToOutsidePixels(data->mHitRegion);
     regions.mNoActionRegion = ScaleRegionToOutsidePixels(data->mNoActionRegion);
@@ -4659,17 +4682,17 @@ FrameLayerBuilder::AddPaintedDisplayItem
   if (entry) {
     entry->mContainerLayerFrame = aContainerState.GetContainerFrame();
     if (entry->mContainerLayerGeneration == 0) {
       entry->mContainerLayerGeneration = mContainerLayerGeneration;
     }
     if (tempManager) {
       FLB_LOG_PAINTED_LAYER_DECISION(aLayerData, "Creating nested FLB for item %p\n", aItem);
       FrameLayerBuilder* layerBuilder = new FrameLayerBuilder();
-      layerBuilder->Init(mDisplayListBuilder, tempManager, aLayerData);
+      layerBuilder->Init(mDisplayListBuilder, tempManager, aLayerData, &aClip);
 
       tempManager->BeginTransaction();
       if (mRetainingManager) {
         layerBuilder->DidBeginRetainedLayerTransaction(tempManager);
       }
 
       UniquePtr<LayerProperties> props(LayerProperties::CloneFrom(tempManager->GetRoot()));
       RefPtr<Layer> tmpLayer =
--- a/layout/painting/FrameLayerBuilder.h
+++ b/layout/painting/FrameLayerBuilder.h
@@ -194,17 +194,18 @@ public:
   typedef layers::EventRegions EventRegions;
 
   FrameLayerBuilder();
   ~FrameLayerBuilder();
 
   static void Shutdown();
 
   void Init(nsDisplayListBuilder* aBuilder, LayerManager* aManager,
-            PaintedLayerData* aLayerData = nullptr);
+            PaintedLayerData* aLayerData = nullptr,
+            const DisplayItemClip* aInactiveLayerClip = nullptr);
 
   /**
    * Call this to notify that we have just started a transaction on the
    * retained layer manager aManager.
    */
   void DidBeginRetainedLayerTransaction(LayerManager* aManager);
 
   /**
@@ -688,16 +689,21 @@ public:
     return mPaintedLayerItems.GetEntry(aLayer);
   }
 
   PaintedLayerData* GetContainingPaintedLayerData()
   {
     return mContainingPaintedLayer;
   }
 
+  const DisplayItemClip* GetInactiveLayerClip() const
+  {
+    return mInactiveLayerClip;
+  }
+
   bool IsBuildingRetainedLayers()
   {
     return !mContainingPaintedLayer && mRetainingManager;
   }
 
   /**
    * Attempt to build the most compressed layer tree possible, even if it means
    * throwing away existing retained buffers.
@@ -737,16 +743,22 @@ protected:
 
   /**
    * When building layers for an inactive layer, this is where the
    * inactive layer will be placed.
    */
   PaintedLayerData*                   mContainingPaintedLayer;
 
   /**
+   * When building layers for an inactive layer, this stores the clip
+   * of the display item that built the inactive layer.
+   */
+  const DisplayItemClip*              mInactiveLayerClip;
+
+  /**
    * Saved generation counter so we can detect DOM changes.
    */
   uint32_t                            mInitialDOMGeneration;
   /**
    * Set to true if we have detected and reported DOM modification during
    * the current paint.
    */
   bool                                mDetectedDOMModification;
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -910,17 +910,17 @@ fuzzy-if(Android,8,50) fails-if(stylo) =
 == 405517-1.xhtml 405517-1-ref.xhtml
 == 405577-1.html 405577-1-ref.html
 == 405584-1.html 405584-1-ref.html
 # == 405952-1.html 405952-1-ref.html
 == 406484-1.html 406484-1-ref.html
 == 406568-1.html 406568-1-ref.html
 fails-if(stylo) == 407016-1-a.html 407016-1-ref.html
 fails-if(stylo) == 407016-1-b.html 407016-1-ref.html
-fails-if(stylo) == 407078-1.html 407078-1-ref.html
+== 407078-1.html 407078-1-ref.html
 == 407095-1.html 407095-1-ref.html
 fuzzy-if(Android,13,9) == 407111-1.html 407111-1-ref.html  # Bug 1128229
 == 407227-1.html 407227-1-ref.html
 == 407243-1.html 407243-1-ref.html
 == 407419-1.html 407419-1-ref.html
 == 407937-1.html 407937-1-ref.html
 == 408493-1.html about:blank
 == 408493-2.html 408493-2-ref.html
@@ -1499,17 +1499,17 @@ fuzzy-if(Android,12,300) == 551463-1.htm
 # behavior further in the future, which could make this become relevant again.
 # Marked "random" rather than "fails" because it may (spuriously) appear to pass
 # on Android devices that completely lack any Sinhala font support.
 random != 553571-1.html 553571-1-notref.html # expect dotted circle in test, not in ref: "fails" under harfbuzz, which doesn't consider the sequence invalid
 fuzzy-if(!contentSameGfxBackendAsCanvas,128,91) random-if(d2d) skip-if(azureSkiaGL) fuzzy-if(skiaContent,32,150) == 555388-1.html 555388-1-ref.html
 == 556661-1.html 556661-1-ref.html
 fuzzy-if(skiaContent,4,5) fails-if(stylo) == 557087-1.html 557087-ref.html
 fails-if(Android) fuzzy-if(skiaContent&&!Android,2,5) fails-if(stylo) == 557087-2.html 557087-ref.html
-fails-if(stylo) == 557736-1.html 557736-1-ref.html
+== 557736-1.html 557736-1-ref.html
 != 558011-1.xul 558011-1-ref.xul
 == 559284-1.html 559284-1-ref.html
 fails-if(Android) == 560455-1.xul 560455-1-ref.xul
 fuzzy-if(skiaContent,2,5) == 561981-1.html 561981-1-ref.html
 == 561981-2.html 561981-2-ref.html
 fuzzy-if(skiaContent,1,5) == 561981-3.html 561981-3-ref.html
 == 561981-4.html 561981-4-ref.html
 fuzzy-if(skiaContent,1,5) == 561981-5.html 561981-5-ref.html
--- a/layout/reftests/font-face/reftest.list
+++ b/layout/reftests/font-face/reftest.list
@@ -8,18 +8,18 @@ HTTP(..) != download-2.html about:blank
 random-if(winWidget||gtkWidget) HTTP(..) == download-2-big.html download-2-big-otf.html # bug 470713
 HTTP(..) != download-2-big-otf.html about:blank
 asserts-if(Android&&!asyncPan,1-8) fails-if(stylo) HTTP(..) != download-3-notref.html download-3.html # bug 1019192
 asserts-if(Android,0-8) fails-if(stylo) HTTP(..) == download-3-ref.html download-3.html # same bugs as above
 asserts-if(Android,0-8) HTTP(..) == fallback-to-system-1.html fallback-to-system-1-ref.html # just delayed assertions from above tests
 HTTP(..) == name-override-simple-1.html name-override-simple-1-ref.html
 HTTP(..) != name-override-simple-1.html download-1-notref.html
 fails-if(!stylo) HTTP(..) == name-override-1.html name-override-1-ref.html
-fails-if(stylo) HTTP(..) == multiple-descriptor-1.html multiple-descriptor-1-ref.html
-fails-if(stylo) HTTP(..) != multiple-descriptor-1.html multiple-descriptor-1-notref.html
+HTTP(..) == multiple-descriptor-1.html multiple-descriptor-1-ref.html
+HTTP(..) != multiple-descriptor-1.html multiple-descriptor-1-notref.html
 HTTP(..) == src-list-1.html src-list-1-ref.html
 HTTP(..) == src-list-2.html src-list-2-ref.html
 random-if(winWidget||gtkWidget) HTTP(..) == src-list-2-big-otf.html src-list-2-big-ref.html # bug 470713
 HTTP(..) == src-list-format-1.html src-list-format-1-ref.html
 HTTP(..) == src-list-format-2.html src-list-format-2-ref.html
 HTTP(..) == src-list-format-3.html src-list-format-3-ref.html
 HTTP(..) == src-list-format-4.html src-list-format-1-ref.html
 HTTP(..) == src-list-format-5.html src-list-format-2-ref.html
@@ -53,17 +53,17 @@ HTTP(..) == order-1.html order-1-ref.htm
 HTTP(..) == order-2.html order-2-ref.html
 HTTP(..) == order-3.html order-3-ref.html
 HTTP(..) == multiple-in-family-1.html multiple-in-family-1-ref.html
 HTTP(..) == multiple-in-family-1b.html multiple-in-family-1-ref.html
 HTTP(..) != multiple-in-family-1.html multiple-in-family-1-notref.html
 HTTP(..) == prop-order-over-rule-order-1a.html prop-order-over-rule-order-2a.html
 HTTP(..) == prop-order-over-rule-order-1b.html prop-order-over-rule-order-2b.html
 HTTP(..) != prop-order-over-rule-order-1a.html prop-order-over-rule-order-1b.html
-fails-if(stylo) HTTP(..) == cross-iframe-1.html cross-iframe-1-ref.html
+HTTP(..) == cross-iframe-1.html cross-iframe-1-ref.html
 
 # unicode-range
 HTTP(..) == unicoderange-1.html unicoderange-1-ref.html
 HTTP(..) == unicoderange-2.html unicoderange-2-ref.html
 HTTP(..) == unicoderange-3.html unicoderange-3-ref.html
 HTTP(..) == unicoderange-4.html unicoderange-4-ref.html
 
 # Dynamic changes
--- a/layout/reftests/font-features/reftest.list
+++ b/layout/reftests/font-features/reftest.list
@@ -3,63 +3,63 @@
 # These rely on the Linux Libertine font (loaded via @font-face)
 # to ensure that features are present.
 
 # check that Turkish language causes a change in rendering (no fi ligature)
 # (also works via Pango)
 HTTP(..) != font-features-turkish.html font-features-ref.html
 
 # check that disabling ligatures causes a change
-fails-if(stylo) HTTP(..) != font-features-noliga.html font-features-ref.html
+HTTP(..) != font-features-noliga.html font-features-ref.html
 
 # check that enabling optional ligatures causes a change
-fails-if(stylo) HTTP(..) != font-features-hlig.html font-features-ref.html
+HTTP(..) != font-features-hlig.html font-features-ref.html
 
 # compare Turkish rendering with reference using ZWNJ to break the ligature
 HTTP(..) == font-features-turkish.html font-features-turkish-ref.html
 
 # compare Turkish rendering with explicitly disabled ligatures
 HTTP(..) == font-features-turkish.html font-features-noliga.html
 
 # The following should pass even if feature support isn't available,
 # because both testcase and reference will have the default rendering,
 # though they're not really meaningful unless the tests above passed already.
 
 # compare feature specified within @font-face to same feature in style rule
 fails-if(stylo) HTTP(..) == font-features-hlig-2.html font-features-hlig.html
-fails-if(stylo) HTTP(..) == font-features-hlig-4.html font-features-hlig.html
+HTTP(..) == font-features-hlig-4.html font-features-hlig.html
 HTTP(..) != font-features-hlig-5.html font-features-hlig.html
 HTTP(..) == font-features-ligatures-none.html font-features-noliga.html
 
 # check that feature in style rule overrides @font-face
-fails-if(stylo) HTTP(..) == font-features-hlig-3.html font-features-noliga.html
+HTTP(..) == font-features-hlig-3.html font-features-noliga.html
 
 # compare font-language-override rendering to lang-tagged rendering
 HTTP(..) == font-features-turkish-override-1.html font-features-turkish.html
 fails-if(stylo) HTTP(..) == font-features-turkish-override-2.html font-features-turkish.html
 
 # check use of font-language-override to override explicit lang tag
 HTTP(..) == font-features-turkish-override-3.html font-features-ref.html
 fails-if(stylo) HTTP(..) == font-features-turkish-override-4.html font-features-ref.html
 HTTP(..) == font-features-turkish-override-5.html font-features-turkish.html
 
 # check that last value wins if a feature is repeated
 HTTP(..) == font-features-order-1.html font-features-ref.html
-fails-if(stylo) HTTP(..) == font-features-order-2.html font-features-noliga.html
+HTTP(..) == font-features-order-2.html font-features-noliga.html
 
 # check priority of feature settings vs. font-variant subproperty
-fails-if(stylo) HTTP(..) == font-features-order-3.html font-features-noliga.html
+HTTP(..) == font-features-order-3.html font-features-noliga.html
 HTTP(..) == font-features-order-4.html font-features-noliga.html
-fails-if(stylo) HTTP(..) == font-features-order-5.html font-features-hlig.html
+HTTP(..) == font-features-order-5.html font-features-hlig.html
 
 # check priority involving feature settings and font-variant-alternates
 fails-if(stylo) HTTP(..) == alternates-order.html alternates-order-ref.html
 
 # check that font-specific values line up with @font-face feature settings
-fails-if(stylo) HTTP(..) == annotations.html annotations-ref.html
+HTTP(..) == annotations.html annotations-ref.html
 
 # font-variant subproperties
 # test for specific features being on and others off, based on prop values
 # (debug problems with font-variant-debug.html which displays all props)
 fails-if(stylo) HTTP(..) == font-variant-alternates.html font-variant-alternates-ref.html
 fails-if(stylo) HTTP(..) == font-variant-caps.html font-variant-caps-ref.html
 HTTP(..) == font-variant-east-asian.html font-variant-east-asian-ref.html
 HTTP(..) == font-variant-ligatures.html font-variant-ligatures-ref.html
@@ -67,37 +67,37 @@ HTTP(..) == font-variant-numeric.html fo
 HTTP(..) == font-variant-position.html font-variant-position-ref.html
 
 # font-kerning
 HTTP(..) != font-kerning-normal.html font-kerning-none.html
 HTTP(..) != font-kerning-auto.html font-kerning-none.html
 HTTP(..) == font-kerning-auto.html font-kerning-normal.html
 HTTP(..) == font-kerning-normal.html font-kerning-kern.html
 HTTP(..) == font-kerning-none.html font-kerning-nokern.html
-fails-if(stylo) HTTP(..) == font-kerning-1.html font-kerning-none.html
-fails-if(stylo) HTTP(..) == font-kerning-2.html font-kerning-normal.html
+HTTP(..) == font-kerning-1.html font-kerning-none.html
+HTTP(..) == font-kerning-2.html font-kerning-normal.html
 fails-if(stylo) HTTP(..) == font-kerning-3.html font-kerning-none.html
 HTTP(..) != font-kerning-table-none.html font-kerning-table-normal.html
 
 # sanity check for kerning - with no spaces, kerning should occur
 HTTP(..) == kerning-sanity-check-kern.html kerning-sanity-check-default.html
-fails-if(stylo) HTTP(..) != kerning-sanity-check-nokern.html kerning-sanity-check-default.html
+HTTP(..) != kerning-sanity-check-nokern.html kerning-sanity-check-default.html
 
 # OpenType features should work across inter-word spaces
 HTTP(..) == font-features-across-space-1.html font-features-across-space-1-ref.html
-fails-if(stylo) HTTP(..) == spacelookups.html spacelookups-ref.html
+HTTP(..) == spacelookups.html spacelookups-ref.html
 # tests whether word cache is in use by testing for ignored space kerns
 HTTP(..) == spacelookups-wordcache.html spacelookups-wordcache-ref.html
 # requires Japanese font with feature support, WinXP lacks one
 random-if(!winWidget&&!cocoaWidget) HTTP(..) == fwid-spaces.html fwid-spaces-ref.html
 # Arial/Times New Roman on Win7+/OSX 10.6+ have kerning pairs that include spaces
 random-if(!winWidget&&!cocoaWidget) fails-if(winWidget||cocoaWidget) HTTP(..) != kerning-spaces-arial-nokern.html kerning-spaces-arial-default.html
-random-if(!winWidget&&!cocoaWidget) fails-if(winWidget||cocoaWidget) fails-if(stylo) HTTP(..) == kerning-spaces-arial-kern.html kerning-spaces-arial-default.html
+random-if(!winWidget&&!cocoaWidget) fails-if(winWidget||cocoaWidget) HTTP(..) == kerning-spaces-arial-kern.html kerning-spaces-arial-default.html
 random-if(!winWidget&&!cocoaWidget) fails-if(winWidget||cocoaWidget) HTTP(..) != kerning-spaces-tnr-nokern.html kerning-spaces-tnr-default.html
-random-if(!winWidget&&!cocoaWidget) fails-if(winWidget||cocoaWidget) fails-if(stylo) HTTP(..) == kerning-spaces-tnr-kern.html kerning-spaces-tnr-default.html
+random-if(!winWidget&&!cocoaWidget) fails-if(winWidget||cocoaWidget) HTTP(..) == kerning-spaces-tnr-kern.html kerning-spaces-tnr-default.html
 
 # font-variant-caps fallback
 # -- sanity check - none of these should look like the default rendering
 HTTP(..) != caps-fallback-smallcaps1.html caps-fallback-default.html
 HTTP(..) != caps-fallback-smallcaps2.html caps-fallback-default.html
 HTTP(..) != caps-fallback-petitecaps.html caps-fallback-default.html
 fails-if(stylo) HTTP(..) != caps-fallback-allsmallcaps.html caps-fallback-default.html
 fails-if(stylo) HTTP(..) != caps-fallback-allpetitecaps.html caps-fallback-default.html
--- a/layout/reftests/font-inflation/reftest.list
+++ b/layout/reftests/font-inflation/reftest.list
@@ -64,17 +64,17 @@ test-pref(font.size.inflation.emPerLine,
 test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.forceEnabled,true) test-pref(font.size.inflation.lineThreshold,0) != disable-fontinfl-on-mobile-5.html disable-fontinfl-on-mobile-ref.html
 test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.forceEnabled,true) test-pref(font.size.inflation.lineThreshold,0) == preformatted-text.html preformatted-text-ref.html
 test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.forceEnabled,true) test-pref(font.size.inflation.lineThreshold,0) == fixed-height-body.html fixed-height-body-ref.html
 test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.forceEnabled,true) test-pref(font.size.inflation.lineThreshold,0) == fixed-height-body-child.html fixed-height-body-child-ref.html
 test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.forceEnabled,true) test-pref(font.size.inflation.lineThreshold,0) == consecutive-inline.html consecutive-inline-ref.html
 
 # The tests below use nonzero values of the lineThreshold preference.
 test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.forceEnabled,true) test-pref(font.size.inflation.lineThreshold,100) == text-1.html text-1.html
-test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.forceEnabled,true) test-pref(font.size.inflation.lineThreshold,100) fails-if(stylo) HTTP(..) == list-1.html list-1-ref.html
+test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.forceEnabled,true) test-pref(font.size.inflation.lineThreshold,100) HTTP(..) == list-1.html list-1-ref.html
 test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.forceEnabled,true) test-pref(font.size.inflation.lineThreshold,100) == threshold-1a.html threshold-1a.html
 test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.forceEnabled,true) test-pref(font.size.inflation.lineThreshold,100) == threshold-1b.html threshold-1b-ref.html
 test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.forceEnabled,true) test-pref(font.size.inflation.lineThreshold,100) == threshold-1c.html threshold-1c-ref.html
 test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.forceEnabled,true) test-pref(font.size.inflation.lineThreshold,100) == threshold-2.html threshold-2-ref.html
 test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.forceEnabled,true) test-pref(font.size.inflation.lineThreshold,100) == threshold-3.html threshold-3-ref.html
 test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.forceEnabled,true) test-pref(font.size.inflation.lineThreshold,100) == threshold-scope-float-1.html threshold-scope-float-1-ref.html
 test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.forceEnabled,true) test-pref(font.size.inflation.lineThreshold,100) == threshold-scope-float-2.html threshold-scope-float-2-ref.html
 test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.forceEnabled,true) test-pref(font.size.inflation.lineThreshold,100) == threshold-scope-float-overflow-1.html threshold-scope-float-overflow-1-ref.html
--- a/layout/reftests/font-matching/reftest.list
+++ b/layout/reftests/font-matching/reftest.list
@@ -1,9 +1,9 @@
-fails-if(stylo) == CSS21-t1502-no-inherited-font-family.xhtml CSS21-t1502-no-inherited-font-family-ref.xhtml
+== CSS21-t1502-no-inherited-font-family.xhtml CSS21-t1502-no-inherited-font-family-ref.xhtml
 
 # basic tests for bug 538730
 != synthetic-bold-1.html synthetic-bold-1-ref.html
 != synthetic-bold-2.html synthetic-bold-2-ref.html
 
 # synthetic bold/italic tests
 != defaultfont-bold.html defaultfont.html
 != defaultfont-italic.html defaultfont.html 
@@ -39,20 +39,20 @@ random-if(cocoaWidget) != impact-bold.ht
 == arial-variations-1.html arial-variations-1-ref.html
 == arial-variations-2.html arial-variations-2-ref.html
 == arial-variations-3.html arial-variations-3-ref.html
 == arial-variations-4.html arial-variations-4-ref.html
 == arial-variations-5.html arial-variations-5-ref.html
 == arial-variations-6.html arial-variations-6-ref.html
 
 # localized font family names should always match just as English names do
-fails-if(stylo) == localized-family-names-001.html localized-family-names-001-ref.html
-fails-if(stylo) == localized-family-names-002.html localized-family-names-002-ref.html
-fails-if(stylo) == localized-family-names-003.html localized-family-names-003-ref.html
-fails-if(stylo) == localized-family-names-004.html localized-family-names-004-ref.html
+== localized-family-names-001.html localized-family-names-001-ref.html
+== localized-family-names-002.html localized-family-names-002-ref.html
+== localized-family-names-003.html localized-family-names-003-ref.html
+== localized-family-names-004.html localized-family-names-004-ref.html
 
 # family names with escaped spaces shouldn't match the names without the spaces
 fails-if(gtkWidget&&!stylo) == familyname-escapedidents.html familyname-escapedidents-ref.html # bug 1309425, bug 1328771
 
 # weight mapping tests
 HTTP(..) == normalmedium.html normalmedium-ref.html
 HTTP(..) != normalmedium.html normalmedium-notref.html
 
--- a/layout/reftests/mathml/reftest.list
+++ b/layout/reftests/mathml/reftest.list
@@ -288,25 +288,25 @@ fails-if(winWidget) == subscript-italic-
 fails-if(Android) == mathvariant-1a.html mathvariant-1a-ref.html # Bug 1010679
 fails-if(Android) == mathvariant-1b.html mathvariant-1b-ref.html # Bug 1010679
 fails-if(Android) == mathvariant-1c.html mathvariant-1c-ref.html # Bug 1010679
 == mathvariant-1d.html mathvariant-1d-ref.html
 fails-if(Android||OSX) == mathvariant-2.html mathvariant-2-ref.html # Bugs 1010678, 1010679
 fails-if(stylo) == mathvariant-3.html mathvariant-3-ref.html
 == mathvariant-4.html mathvariant-4-ref.html
 == mathvariant-5.html mathvariant-5-ref.html
-fails-if(stylo) == dtls-1.html dtls-1-ref.html
-fails-if(stylo) == dtls-2.html dtls-2-ref.html
-fails-if(stylo) == dtls-3.html dtls-3-ref.html
-fails-if(stylo) == ssty-1.html ssty-1-ref.html
-fails-if(stylo) == ssty-2.html ssty-2-ref.html
-fails-if(stylo) == ssty-3.html ssty-3-ref.html
-fails-if(stylo) == ssty-4.html ssty-4-ref.html
-fails-if(stylo) == mathscript-1.html mathscript-1-ref.html
-fails-if(stylo) == mathscript-2.html mathscript-2-ref.html
+== dtls-1.html dtls-1-ref.html
+== dtls-2.html dtls-2-ref.html
+== dtls-3.html dtls-3-ref.html
+== ssty-1.html ssty-1-ref.html
+== ssty-2.html ssty-2-ref.html
+== ssty-3.html ssty-3-ref.html
+== ssty-4.html ssty-4-ref.html
+== mathscript-1.html mathscript-1-ref.html
+== mathscript-2.html mathscript-2-ref.html
 == mo-accent-dynamic.html mo-accent-dynamic-ref.html
 == mo-movablelimits-dynamic.html mo-movablelimits-dynamic-ref.html
 == munderover-accent-dynamic.html munderover-accent-dynamic-ref.html
 == munderover-accentunder-dynamic.html munderover-accentunder-dynamic-ref.html
 == columnlines-1a.html columnlines-1-ref.html
 != columnlines-1b.html columnlines-1-ref.html
 != columnlines-1c.html columnlines-1-ref.html
 == columnlines-2a.html columnlines-2-ref.html
--- a/layout/reftests/text/reftest.list
+++ b/layout/reftests/text/reftest.list
@@ -54,17 +54,17 @@ fails-if(Android) skip-if(d2d||cocoaWidg
 # inconsistencies.  On those platforms we just test that glyph positions are
 # subpixel.
 # D2D/DirectWrite results depend on the rendering mode chosen, so considering this as random for now.
 skip-if(!(d2d||cocoaWidget)) random-if(d2d) != subpixel-glyphs-x-2a.html subpixel-glyphs-x-2b.html
 HTTP(..) == subpixel-glyphs-x-3a.html subpixel-glyphs-x-3b.html
 # No platforms do subpixel positioning vertically
 fuzzy-if(Android,19,2) == subpixel-glyphs-y-1a.html subpixel-glyphs-y-1b.html
 fuzzy-if(Android,231,653) == subpixel-lineheight-1a.html subpixel-lineheight-1b.html
-fails-if(stylo) == swash-1.html swash-1-ref.html
+== swash-1.html swash-1-ref.html
 HTTP(..) != synthetic-bold-metrics-01.html synthetic-bold-metrics-01-notref.html
 HTTP(..) == synthetic-bold-papyrus-01.html synthetic-bold-papyrus-01-ref.html
 # Tests for text-align-last
 == text-align-last-start.html text-align-last-start-ref.html
 == text-align-last-end.html text-align-last-end-ref.html
 == text-align-last-center.html text-align-last-center-ref.html
 == text-align-last-justify.html text-align-last-justify-ref.html
 == text-align-last-justify-rtl.html text-align-last-justify-rtl-ref.html
@@ -150,17 +150,17 @@ HTTP(..) == zwnj-02.xhtml zwnj-02-ref.xh
 != zwnj-01.html zwnj-01-notref.html
 == initial-zwj-1.html initial-zwj-1-ref.html
 == cgj-01.html cgj-01-ref.html
 fails-if(stylo) == 444656.html 444656-ref.html
 fails-if(stylo) == 449555-1.html 449555-1-ref.html
 == 467722.html 467722-ref.html
 fuzzy-if(skiaContent,1,600) HTTP(..) == 475092-sub.html 475092-ref.html
 fails-if(Android) fuzzy-if(skiaContent&&!Android,90,3100) HTTP(..) == 475092-pos.html 475092-sub.html # bug 482596
-fails-if(stylo) == 476378-soft-hyphen-fallback.html 476378-soft-hyphen-fallback-ref.html
+== 476378-soft-hyphen-fallback.html 476378-soft-hyphen-fallback-ref.html
 # Test for bug 484954
 == rgba-text.html rgba-text-ref.html
 # Test for bug 575695, 'kern' table support
 HTTP(..) != kerning-01.html kerning-01-notref.html
 # Test for bug 577380, support for AAT layout (on OS X only)
 random-if(!cocoaWidget) == 577380.html 577380-ref.html
 # Test for OpenType Arabic shaping support
 HTTP(..) == arabic-shaping-1.html arabic-shaping-1-ref.html
@@ -168,17 +168,17 @@ HTTP(..) == arabic-shaping-1.html arabic
 random-if(!winWidget) == arial-bold-lam-alef-1.html arial-bold-lam-alef-1-ref.html
 # Fallback (presentation-forms) shaping with a font that lacks GSUB/GPOS
 # These tests are not valid with Mac or FT2 font backends because our masking of complex-script ranges
 # in the 'cmap' will prevent the test font (without GSUB) being used.
 fails-if(cocoaWidget||Android) HTTP(..) == arabic-fallback-1.html arabic-fallback-1-ref.html
 fails-if(cocoaWidget||Android) HTTP(..) == arabic-fallback-2.html arabic-fallback-2-ref.html
 fails-if(cocoaWidget||Android) HTTP(..) == arabic-fallback-3.html arabic-fallback-3-ref.html
 fails-if(!cocoaWidget&&!Android&&!stylo) HTTP(..) != arabic-fallback-4.html arabic-fallback-4-notref.html
-fails-if(stylo) == arabic-marks-1.html arabic-marks-1-ref.html
+== arabic-marks-1.html arabic-marks-1-ref.html
 == arabic-final-ligature-spacing.html arabic-final-ligature-spacing-ref.html
 # harfbuzz fallback mark stacking in the absence of GPOS:
 HTTP(..) != fallback-mark-stacking-1.html fallback-mark-stacking-1-notref.html
 
 == 726392-1.html 726392-1-ref.html
 == 726392-2.html 726392-2-ref.html
 == 726392-3.html 726392-3-ref.html
 == 745555-1.html 745555-1-ref.html
@@ -204,39 +204,39 @@ fails-if(Android) == emoji-04.html emoji
 != emoji-05.html emoji-05-notref.html
 
 # check that Graphite shaping (bug 631479) is working
 pref(gfx.font_rendering.graphite.enabled,true) HTTP(..) == graphite-01.html graphite-01-ref.html
 # Test 02 (using Pig Latin) is fuzzy on Win7 because glyph positioning is not guaranteed to match exactly
 # between a sequence of simple glyphs rendered individually, and the same sequence treated as a single cluster.
 fuzzy-if(winWidget,49,220) pref(gfx.font_rendering.graphite.enabled,true) HTTP(..) == graphite-02.html graphite-02-ref.html
 pref(gfx.font_rendering.graphite.enabled,true) HTTP(..) != graphite-03a.html graphite-03-notref.html
-pref(gfx.font_rendering.graphite.enabled,true) fails-if(stylo) HTTP(..) != graphite-03b.html graphite-03-notref.html
+pref(gfx.font_rendering.graphite.enabled,true) HTTP(..) != graphite-03b.html graphite-03-notref.html
 pref(gfx.font_rendering.graphite.enabled,false) HTTP(..) != graphite-01.html graphite-01-ref.html
 pref(gfx.font_rendering.graphite.enabled,false) HTTP(..) != graphite-02.html graphite-02-ref.html
 # test 03a (lang setting in Padauk font) now works in opentype/harfbuzz as well
 pref(gfx.font_rendering.graphite.enabled,false) HTTP(..) != graphite-03a.html graphite-03-notref.html
 pref(gfx.font_rendering.graphite.enabled,false) HTTP(..) == graphite-03b.html graphite-03-notref.html
 
 # tests for graphite rendering with valid and invalid lang tags
 pref(gfx.font_rendering.graphite.enabled,true) HTTP(..) == graphite-04-fa.html graphite-04-ref.html
 pref(gfx.font_rendering.graphite.enabled,true) HTTP(..) != graphite-04-sd.html graphite-04-ref.html
 pref(gfx.font_rendering.graphite.enabled,true) HTTP(..) == graphite-04-snd.html graphite-04-ref.html
 pref(gfx.font_rendering.graphite.enabled,true) HTTP(..) != graphite-04-ur.html graphite-04-ref.html
 pref(gfx.font_rendering.graphite.enabled,true) HTTP(..) == graphite-04-urd.html graphite-04-ref.html
 pref(gfx.font_rendering.graphite.enabled,true) HTTP(..) != graphite-04-sd.html graphite-04-ur.html
 
 # tests to compare graphite to opentype (will trivially pass when graphite not enabled)
-fails-if(stylo) HTTP(..) == graphite-05-ot-only.html graphite-05-ref.html
-fails-if(stylo) HTTP(..) != graphite-05-ot-only.html graphite-05-fail.html
+HTTP(..) == graphite-05-ot-only.html graphite-05-ref.html
+HTTP(..) != graphite-05-ot-only.html graphite-05-fail.html
 HTTP(..) == graphite-05-simple.html graphite-05-ref.html
 HTTP(..) == graphite-05-multipass.html graphite-05-ref.html
 HTTP(..) == graphite-05-lang.html graphite-05-ref.html
 HTTP(..) == graphite-05-badlang.html graphite-05-ref.html
-fails-if(stylo) HTTP(..) == graphite-05-feat.html graphite-05-ref.html
+HTTP(..) == graphite-05-feat.html graphite-05-ref.html
 
 # comparing composed and decomposed characters that should render identically
 # under both OpenType and Graphite shaping
 pref(gfx.font_rendering.graphite.enabled,false) HTTP(..) == glyph-decomposition-opentype.html glyph-decomposition-opentype-ref.html
 pref(gfx.font_rendering.graphite.enabled,true) HTTP(..) == glyph-decomposition-graphite.html glyph-decomposition-graphite-ref.html
 
 # test for bidi bug in graphite 1.3.2, fixed in 1.3.3 (bug 1207061)
 HTTP(..) == graphite-bidi-1.html graphite-bidi-1-ref.html
@@ -299,17 +299,17 @@ HTTP(..) == graphite-surrogate-selection
 == auto-hyphenation-it-1.html auto-hyphenation-it-1-ref.html
 == auto-hyphenation-kmr-1.html auto-hyphenation-kmr-1-ref.html
 == auto-hyphenation-la-1.html auto-hyphenation-la-1-ref.html
 == auto-hyphenation-lt-1.html auto-hyphenation-lt-1-ref.html
 == auto-hyphenation-mn-1.html auto-hyphenation-mn-1-ref.html
 == auto-hyphenation-nb-1.html auto-hyphenation-nb-1-ref.html
 == auto-hyphenation-nl-1.html auto-hyphenation-nl-1-ref.html
 == auto-hyphenation-nn-1.html auto-hyphenation-nn-1-ref.html
-fails-if(stylo) == auto-hyphenation-pl-1.html auto-hyphenation-pl-1-ref.html
+== auto-hyphenation-pl-1.html auto-hyphenation-pl-1-ref.html
 == auto-hyphenation-pt-1.html auto-hyphenation-pt-1-ref.html
 == auto-hyphenation-ru-1.html auto-hyphenation-ru-1-ref.html
 == auto-hyphenation-sh-1.html auto-hyphenation-sh-1-ref.html
 == auto-hyphenation-sl-1.html auto-hyphenation-sl-1-ref.html
 == auto-hyphenation-sr-1.html auto-hyphenation-sr-1-ref.html
 == auto-hyphenation-sv-1.html auto-hyphenation-sv-1-ref.html # test swedish patterns
 != auto-hyphenation-sv-1.html auto-hyphenation-sv-1-notref.html # verify swedish != english
 == auto-hyphenation-tr-1.html auto-hyphenation-tr-1-ref.html
--- a/layout/style/CounterStyleManager.cpp
+++ b/layout/style/CounterStyleManager.cpp
@@ -1015,29 +1015,29 @@ DependentBuiltinCounterStyle::GetFallbac
     case NS_STYLE_LIST_STYLE_SIMP_CHINESE_INFORMAL:
     case NS_STYLE_LIST_STYLE_SIMP_CHINESE_FORMAL:
     case NS_STYLE_LIST_STYLE_TRAD_CHINESE_INFORMAL:
     case NS_STYLE_LIST_STYLE_TRAD_CHINESE_FORMAL:
       // These styles all have a larger range than cjk-decimal, so the
       // only case fallback is accessed is that they are extended.
       // Since extending styles will cache the data themselves, we need
       // not cache it here.
-      return mManager->BuildCounterStyle(NS_LITERAL_STRING("cjk-decimal"));
+      return mManager->BuildCounterStyle(nsGkAtoms::cjkDecimal);
     default:
       NS_NOTREACHED("Not a valid dependent builtin style");
       return BuiltinCounterStyle::GetFallback();
   }
 }
 
 class CustomCounterStyle final : public CounterStyle
 {
 private:
   ~CustomCounterStyle() {}
 public:
-  CustomCounterStyle(const nsAString& aName,
+  CustomCounterStyle(nsIAtom* aName,
                      CounterStyleManager* aManager,
                      nsCSSCounterStyleRule* aRule)
     : CounterStyle(NS_STYLE_LIST_STYLE_CUSTOM),
       mName(aName),
       mManager(aManager),
       mRule(aRule),
       mRuleGeneration(aRule->GetGeneration()),
       mSystem(aRule->GetSystem()),
@@ -1129,17 +1129,17 @@ private:
   void ComputeRawSpeakAs(uint8_t& aSpeakAs,
                          CounterStyle*& aSpeakAsCounter);
   CounterStyle* ComputeSpeakAs();
 
   CounterStyle* ComputeExtends();
   CounterStyle* GetExtends();
   CounterStyle* GetExtendsRoot();
 
-  nsString mName;
+  nsCOMPtr<nsIAtom> mName;
 
   // CounterStyleManager should always overlive any CounterStyle as it
   // is owned by nsPresContext, and will be released after all nodes and
   // frames are released.
   CounterStyleManager* mManager;
 
   RefPtr<nsCSSCounterStyleRule> mRule;
   uint32_t mRuleGeneration;
@@ -1229,17 +1229,18 @@ CustomCounterStyle::ResetDependentData()
                 FLAG_SUFFIX_INITED |
                 FLAG_PAD_INITED);
   }
 }
 
 /* virtual */ void
 CustomCounterStyle::GetStyleName(nsSubstring& aResult)
 {
-  aResult.Assign(mName);
+  nsDependentAtomString name(mName);
+  aResult.Assign(name);
 }
 
 /* virtual */ void
 CustomCounterStyle::GetPrefix(nsSubstring& aResult)
 {
   if (!(mFlags & FLAG_PREFIX_INITED)) {
     mFlags |= FLAG_PREFIX_INITED;
 
@@ -1407,23 +1408,25 @@ CustomCounterStyle::GetPad(PadType& aRes
   aResult = mPad;
 }
 
 /* virtual */ CounterStyle*
 CustomCounterStyle::GetFallback()
 {
   if (!mFallback) {
     const nsCSSValue& value = mRule->GetDesc(eCSSCounterDesc_Fallback);
-    if (value.UnitHasStringValue()) {
-      mFallback = mManager->BuildCounterStyle(
-          nsDependentString(value.GetStringBufferValue()));
+    mFallback = CounterStyleManager::GetDecimalStyle();
+    if (value.GetUnit() != eCSSUnit_Null) {
+      if (value.GetUnit() == eCSSUnit_AtomIdent) {
+        mFallback = mManager->BuildCounterStyle(value.GetAtomValue());
+      } else {
+        MOZ_ASSERT_UNREACHABLE("Unknown unit!");
+      }
     } else if (IsExtendsSystem()) {
       mFallback = GetExtends()->GetFallback();
-    } else {
-      mFallback = CounterStyleManager::GetDecimalStyle();
     }
   }
   return mFallback;
 }
 
 /* virtual */ uint8_t
 CustomCounterStyle::GetSpeakAs()
 {
@@ -1549,20 +1552,19 @@ CustomCounterStyle::ComputeRawSpeakAs(ui
   const nsCSSValue& value = mRule->GetDesc(eCSSCounterDesc_SpeakAs);
   switch (value.GetUnit()) {
     case eCSSUnit_Auto:
       aSpeakAs = GetSpeakAsAutoValue();
       break;
     case eCSSUnit_Enumerated:
       aSpeakAs = value.GetIntValue();
       break;
-    case eCSSUnit_Ident:
+    case eCSSUnit_AtomIdent:
       aSpeakAs = NS_STYLE_COUNTER_SPEAKAS_OTHER;
-      aSpeakAsCounter = mManager->BuildCounterStyle(
-          nsDependentString(value.GetStringBufferValue()));
+      aSpeakAsCounter = mManager->BuildCounterStyle(value.GetAtomValue());
       break;
     case eCSSUnit_Null: {
       if (!IsExtendsSystem()) {
         aSpeakAs = GetSpeakAsAutoValue();
       } else {
         CounterStyle* extended = GetExtends();
         if (!extended->IsCustomStyle()) {
           // It is safe to call GetSpeakAs on non-custom style.
@@ -1659,18 +1661,17 @@ CustomCounterStyle::ComputeExtends()
   }
   if (mFlags & FLAG_EXTENDS_VISITED) {
     // loop detected
     mFlags |= FLAG_EXTENDS_LOOP;
     return nullptr;
   }
 
   const nsCSSValue& value = mRule->GetSystemArgument();
-  CounterStyle* nextCounter = mManager->BuildCounterStyle(
-      nsDependentString(value.GetStringBufferValue()));
+  CounterStyle* nextCounter = mManager->BuildCounterStyle(value.GetAtomValue());
   CounterStyle* target = nextCounter;
   if (nextCounter->IsCustomStyle()) {
     mFlags |= FLAG_EXTENDS_VISITED;
     target = static_cast<CustomCounterStyle*>(nextCounter)->ComputeExtends();
     mFlags &= ~FLAG_EXTENDS_VISITED;
   }
 
   if (target) {
@@ -1981,18 +1982,18 @@ CounterStyle::CallFallbackStyle(CounterV
 }
 
 static BuiltinCounterStyle gBuiltinStyleTable[NS_STYLE_LIST_STYLE__MAX];
 
 CounterStyleManager::CounterStyleManager(nsPresContext* aPresContext)
   : mPresContext(aPresContext)
 {
   // Insert the static styles into cache table
-  mCacheTable.Put(NS_LITERAL_STRING("none"), GetNoneStyle());
-  mCacheTable.Put(NS_LITERAL_STRING("decimal"), GetDecimalStyle());
+  mCacheTable.Put(nsGkAtoms::none, GetNoneStyle());
+  mCacheTable.Put(nsGkAtoms::decimal, GetDecimalStyle());
 }
 
 CounterStyleManager::~CounterStyleManager()
 {
   MOZ_ASSERT(!mPresContext, "Disconnect should have been called");
 }
 
 /* static */ void
@@ -2015,17 +2016,17 @@ CounterStyleManager::Disconnect()
                  "Counter style is still referenced by other objects.");
   }
 #endif
   mCacheTable.Clear();
   mPresContext = nullptr;
 }
 
 CounterStyle*
-CounterStyleManager::BuildCounterStyle(const nsSubstring& aName)
+CounterStyleManager::BuildCounterStyle(nsIAtom* aName)
 {
   CounterStyle* data = mCacheTable.GetWeak(aName);
   if (data) {
     return data;
   }
 
   // It is intentional that the predefined names are case-insensitive
   // but the user-defined names case-sensitive.
@@ -2035,20 +2036,22 @@ CounterStyleManager::BuildCounterStyle(c
   // When this assertion is removed, please remove the hack to avoid it in
   // nsStyleList::nsStyleList.
   NS_ASSERTION(styleSet->IsGecko(),
                "stylo: ServoStyleSets do not support custom counter "
                "styles yet");
   nsCSSCounterStyleRule* rule = styleSet->IsGecko() ?
     styleSet->AsGecko()->CounterStyleRuleForName(aName) : nullptr;
   if (rule) {
+    MOZ_ASSERT(rule->Name() == aName);
     data = new (mPresContext) CustomCounterStyle(aName, this, rule);
   } else {
     int32_t type;
-    nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(aName);
+    nsDependentAtomString name(aName);
+    nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(name);
     if (nsCSSProps::FindKeyword(keyword, nsCSSProps::kListStyleKTable, type)) {
       if (gBuiltinStyleTable[type].IsDependentStyle()) {
         data = new (mPresContext) DependentBuiltinCounterStyle(type, this);
       } else {
         data = GetBuiltinStyle(type);
       }
     }
   }
--- a/layout/style/CounterStyleManager.h
+++ b/layout/style/CounterStyleManager.h
@@ -154,17 +154,17 @@ public:
   void Disconnect();
 
   bool IsInitial() const
   {
     // only 'none' and 'decimal'
     return mCacheTable.Count() == 2;
   }
 
-  CounterStyle* BuildCounterStyle(const nsSubstring& aName);
+  CounterStyle* BuildCounterStyle(nsIAtom* aName);
 
   static CounterStyle* GetBuiltinStyle(int32_t aStyle);
   static CounterStyle* GetNoneStyle()
   {
     return GetBuiltinStyle(NS_STYLE_LIST_STYLE_NONE);
   }
   static CounterStyle* GetDecimalStyle()
   {
@@ -178,14 +178,14 @@ public:
   bool NotifyRuleChanged();
 
   nsPresContext* PresContext() const { return mPresContext; }
 
   NS_INLINE_DECL_REFCOUNTING(CounterStyleManager)
 
 private:
   nsPresContext* mPresContext;
-  nsRefPtrHashtable<nsStringHashKey, CounterStyle> mCacheTable;
+  nsRefPtrHashtable<nsRefPtrHashKey<nsIAtom>, CounterStyle> mCacheTable;
 };
 
 } // namespace mozilla
 
 #endif /* !defined(mozilla_CounterStyleManager_h_) */
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -1724,16 +1724,22 @@ Gecko_CSSValue_SetString(nsCSSValueBorro
 void
 Gecko_CSSValue_SetStringFromAtom(nsCSSValueBorrowedMut aCSSValue,
                                  nsIAtom* aAtom, nsCSSUnit aUnit)
 {
   aCSSValue->SetStringValue(nsDependentAtomString(aAtom), aUnit);
 }
 
 void
+Gecko_CSSValue_SetAtomIdent(nsCSSValueBorrowedMut aCSSValue, nsIAtom* aAtom)
+{
+  aCSSValue->SetAtomIdentValue(already_AddRefed<nsIAtom>(aAtom));
+}
+
+void
 Gecko_CSSValue_SetArray(nsCSSValueBorrowedMut aCSSValue, int32_t aLength)
 {
   MOZ_ASSERT(aCSSValue->GetUnit() == eCSSUnit_Null);
   RefPtr<nsCSSValue::Array> array
     = nsCSSValue::Array::Create(aLength);
   aCSSValue->SetArrayValue(array, eCSSUnit_Array);
 }
 
@@ -1788,16 +1794,27 @@ Gecko_nsStyleFont_SetLang(nsStyleFont* a
 
 void
 Gecko_nsStyleFont_CopyLangFrom(nsStyleFont* aFont, const nsStyleFont* aSource)
 {
   aFont->mLanguage = aSource->mLanguage;
 }
 
 void
+Gecko_nsStyleFont_FixupNoneGeneric(nsStyleFont* aFont,
+                                   RawGeckoPresContextBorrowed aPresContext)
+{
+  const nsFont* defaultVariableFont =
+    aPresContext->GetDefaultFont(kPresContext_DefaultVariableFont_ID,
+                                 aFont->mLanguage);
+  nsRuleNode::FixupNoneGeneric(&aFont->mFont, aPresContext,
+                               aFont->mGenericID, defaultVariableFont);
+}
+
+void
 FontSizePrefs::CopyFrom(const LangGroupFontPrefs& prefs)
 {
   mDefaultVariableSize = prefs.mDefaultVariableFont.size;
   mDefaultFixedSize = prefs.mDefaultFixedFont.size;
   mDefaultSerifSize = prefs.mDefaultSerifFont.size;
   mDefaultSansSerifSize = prefs.mDefaultSansSerifFont.size;
   mDefaultMonospaceSize = prefs.mDefaultMonospaceFont.size;
   mDefaultCursiveSize = prefs.mDefaultCursiveFont.size;
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -420,25 +420,29 @@ void Gecko_CSSValue_SetNumber(nsCSSValue
 void Gecko_CSSValue_SetKeyword(nsCSSValueBorrowedMut css_value, nsCSSKeyword keyword);
 void Gecko_CSSValue_SetPercentage(nsCSSValueBorrowedMut css_value, float percent);
 void Gecko_CSSValue_SetCalc(nsCSSValueBorrowedMut css_value, nsStyleCoord::CalcValue calc);
 void Gecko_CSSValue_SetFunction(nsCSSValueBorrowedMut css_value, int32_t len);
 void Gecko_CSSValue_SetString(nsCSSValueBorrowedMut css_value,
                               const uint8_t* string, uint32_t len, nsCSSUnit unit);
 void Gecko_CSSValue_SetStringFromAtom(nsCSSValueBorrowedMut css_value,
                                       nsIAtom* atom, nsCSSUnit unit);
+// Take an addrefed nsIAtom and set it to the nsCSSValue
+void Gecko_CSSValue_SetAtomIdent(nsCSSValueBorrowedMut css_value, nsIAtom* atom);
 void Gecko_CSSValue_SetArray(nsCSSValueBorrowedMut css_value, int32_t len);
 void Gecko_CSSValue_SetURL(nsCSSValueBorrowedMut css_value, ServoBundledURI uri);
 void Gecko_CSSValue_SetInt(nsCSSValueBorrowedMut css_value, int32_t integer, nsCSSUnit unit);
 void Gecko_CSSValue_Drop(nsCSSValueBorrowedMut css_value);
 NS_DECL_THREADSAFE_FFI_REFCOUNTING(nsCSSValueSharedList, CSSValueSharedList);
 bool Gecko_PropertyId_IsPrefEnabled(nsCSSPropertyID id);
 
 void Gecko_nsStyleFont_SetLang(nsStyleFont* font, nsIAtom* atom);
 void Gecko_nsStyleFont_CopyLangFrom(nsStyleFont* aFont, const nsStyleFont* aSource);
+void Gecko_nsStyleFont_FixupNoneGeneric(nsStyleFont* font,
+                                        RawGeckoPresContextBorrowed pres_context);
 FontSizePrefs Gecko_GetBaseSize(nsIAtom* lang);
 
 struct GeckoFontMetrics
 {
   nscoord mChSize;
   nscoord mXSize;
 };
 
--- a/layout/style/ServoElementSnapshot.cpp
+++ b/layout/style/ServoElementSnapshot.cpp
@@ -27,17 +27,17 @@ ServoElementSnapshot::~ServoElementSnaps
   MOZ_COUNT_DTOR(ServoElementSnapshot);
 }
 
 void
 ServoElementSnapshot::AddAttrs(Element* aElement)
 {
   MOZ_ASSERT(aElement);
 
-  if (HasAny(Flags::Attributes)) {
+  if (HasAttrs()) {
     return;
   }
 
   uint32_t attrCount = aElement->GetAttrCount();
   const nsAttrName* attrName;
   for (uint32_t i = 0; i < attrCount; ++i) {
     attrName = aElement->GetAttrNameAt(i);
     const nsAttrValue* attrValue =
--- a/layout/style/ServoElementSnapshot.h
+++ b/layout/style/ServoElementSnapshot.h
@@ -65,19 +65,19 @@ class ServoElementSnapshot
   typedef EventStates::ServoType ServoStateType;
 
 public:
   typedef ServoElementSnapshotFlags Flags;
 
   explicit ServoElementSnapshot(const Element* aElement);
   ~ServoElementSnapshot();
 
-  bool HasAttrs() { return HasAny(Flags::Attributes); }
+  bool HasAttrs() const { return HasAny(Flags::Attributes); }
 
-  bool HasState() { return HasAny(Flags::State); }
+  bool HasState() const { return HasAny(Flags::State); }
 
   /**
    * Captures the given state (if not previously captured).
    */
   void AddState(EventStates aState)
   {
     if (!HasAny(Flags::State)) {
       mState = aState.ServoValue();
@@ -90,30 +90,32 @@ public:
    */
   void AddAttrs(Element* aElement);
 
   /**
    * Needed methods for attribute matching.
    */
   BorrowedAttrInfo GetAttrInfoAt(uint32_t aIndex) const
   {
+    MOZ_ASSERT(HasAttrs());
     if (aIndex >= mAttrs.Length()) {
       return BorrowedAttrInfo(nullptr, nullptr);
     }
     return BorrowedAttrInfo(&mAttrs[aIndex].mName, &mAttrs[aIndex].mValue);
   }
 
   const nsAttrValue* GetParsedAttr(nsIAtom* aLocalName) const
   {
     return GetParsedAttr(aLocalName, kNameSpaceID_None);
   }
 
   const nsAttrValue* GetParsedAttr(nsIAtom* aLocalName,
                                    int32_t aNamespaceID) const
   {
+    MOZ_ASSERT(HasAttrs());
     uint32_t i, len = mAttrs.Length();
     if (aNamespaceID == kNameSpaceID_None) {
       // This should be the common case so lets make an optimized loop
       for (i = 0; i < len; ++i) {
         if (mAttrs[i].mName.Equals(aLocalName)) {
           return &mAttrs[i].mValue;
         }
       }
@@ -130,17 +132,17 @@ public:
     return nullptr;
   }
 
   bool IsInChromeDocument() const
   {
     return mIsInChromeDocument;
   }
 
-  bool HasAny(Flags aFlags) { return bool(mContains & aFlags); }
+  bool HasAny(Flags aFlags) const { return bool(mContains & aFlags); }
 
 private:
   // TODO: Profile, a 1 or 2 element AutoTArray could be worth it, given we know
   // we're dealing with attribute changes when we take snapshots of attributes,
   // though it can be wasted space if we deal with a lot of state-only
   // snapshots.
   Flags mContains;
   nsTArray<ServoAttrSnapshot> mAttrs;
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -258,19 +258,18 @@ public:
                                    nsIURI* aBaseURL,
                                    nsIPrincipal* aDocPrincipal);
 
   bool EvaluateSupportsCondition(const nsAString& aCondition,
                                  nsIURI* aDocURL,
                                  nsIURI* aBaseURL,
                                  nsIPrincipal* aDocPrincipal);
 
-  bool ParseCounterStyleName(const nsAString& aBuffer,
-                             nsIURI* aURL,
-                             nsAString& aName);
+  already_AddRefed<nsIAtom> ParseCounterStyleName(const nsAString& aBuffer,
+                                                  nsIURI* aURL);
 
   bool ParseCounterDescriptor(nsCSSCounterDesc aDescID,
                               const nsAString& aBuffer,
                               nsIURI* aSheetURL,
                               nsIURI* aBaseURL,
                               nsIPrincipal* aSheetPrincipal,
                               nsCSSValue& aValue);
 
@@ -702,17 +701,17 @@ protected:
   bool ParseSupportsConditionInParensInsideParens(bool& aConditionMet);
   bool ParseSupportsConditionTerms(bool& aConditionMet);
   enum SupportsConditionTermOperator { eAnd, eOr };
   bool ParseSupportsConditionTermsAfterOperator(
                                        bool& aConditionMet,
                                        SupportsConditionTermOperator aOperator);
 
   bool ParseCounterStyleRule(RuleAppendFunc aAppendFunc, void* aProcessData);
-  bool ParseCounterStyleName(nsAString& aName, bool aForDefinition);
+  already_AddRefed<nsIAtom> ParseCounterStyleName(bool aForDefinition);
   bool ParseCounterStyleNameValue(nsCSSValue& aValue);
   bool ParseCounterDescriptor(nsCSSCounterStyleRule *aRule);
   bool ParseCounterDescriptorValue(nsCSSCounterDesc aDescID,
                                    nsCSSValue& aValue);
   bool ParseCounterRange(nsCSSValuePair& aPair);
 
   /**
    * Parses the current input stream for a CSS token stream value and resolves
@@ -3038,31 +3037,30 @@ CSSParserImpl::ParsePropertyWithVariable
 
   // Copy the property value into the rule data.
   mTempData.MapRuleInfoInto(aPropertyID, aRuleData);
 
   mTempData.ClearProperty(propertyToParse);
   mTempData.AssertInitialState();
 }
 
-bool
-CSSParserImpl::ParseCounterStyleName(const nsAString& aBuffer,
-                                     nsIURI* aURL,
-                                     nsAString& aName)
+already_AddRefed<nsIAtom>
+CSSParserImpl::ParseCounterStyleName(const nsAString& aBuffer, nsIURI* aURL)
 {
   nsCSSScanner scanner(aBuffer, 0);
   css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURL);
   InitScanner(scanner, reporter, aURL, aURL, nullptr);
 
-  bool success = ParseCounterStyleName(aName, true) && !GetToken(true);
+  nsCOMPtr<nsIAtom> name = ParseCounterStyleName(true);
+  bool success = name && !GetToken(true);
 
   OUTPUT_ERROR();
   ReleaseScanner();
 
-  return success;
+  return success ? name.forget() : nullptr;
 }
 
 bool
 CSSParserImpl::ParseCounterDescriptor(nsCSSCounterDesc aDescID,
                                       const nsAString& aBuffer,
                                       nsIURI* aSheetURL,
                                       nsIURI* aBaseURL,
                                       nsIPrincipal* aSheetPrincipal,
@@ -4824,20 +4822,20 @@ CSSParserImpl::ParseSupportsConditionTer
       return true;
     }
   }
 }
 
 bool
 CSSParserImpl::ParseCounterStyleRule(RuleAppendFunc aAppendFunc, void* aData)
 {
-  nsAutoString name;
+  nsCOMPtr<nsIAtom> name;
   uint32_t linenum, colnum;
   if (!GetNextTokenLocation(true, &linenum, &colnum) ||
-      !ParseCounterStyleName(name, true)) {
+      !(name = ParseCounterStyleName(true))) {
     REPORT_UNEXPECTED_TOKEN(PECounterStyleNotIdent);
     return false;
   }
 
   if (!ExpectSymbol('{', true)) {
     REPORT_UNEXPECTED_TOKEN(PECounterStyleBadBlockStart);
     return false;
   }
@@ -4909,55 +4907,54 @@ CSSParserImpl::ParseCounterStyleRule(Rul
   }
 
   if (isCorrect) {
     (*aAppendFunc)(rule, aData);
   }
   return true;
 }
 
-bool
-CSSParserImpl::ParseCounterStyleName(nsAString& aName, bool aForDefinition)
+already_AddRefed<nsIAtom>
+CSSParserImpl::ParseCounterStyleName(bool aForDefinition)
 {
   if (!GetToken(true)) {
-    return false;
+    return nullptr;
   }
 
   if (mToken.mType != eCSSToken_Ident) {
     UngetToken();
-    return false;
+    return nullptr;
   }
 
   static const nsCSSKeyword kReservedNames[] = {
     eCSSKeyword_none,
     eCSSKeyword_decimal,
     eCSSKeyword_UNKNOWN
   };
 
   nsCSSValue value; // we don't actually care about the value
   if (!ParseCustomIdent(value, mToken.mIdent,
                         aForDefinition ? kReservedNames : nullptr)) {
     REPORT_UNEXPECTED_TOKEN(PECounterStyleBadName);
     UngetToken();
-    return false;
-  }
-
-  aName = mToken.mIdent;
-  if (nsCSSProps::IsPredefinedCounterStyle(aName)) {
-    ToLowerCase(aName);
-  }
-  return true;
+    return nullptr;
+  }
+
+  nsString name = mToken.mIdent;
+  if (nsCSSProps::IsPredefinedCounterStyle(name)) {
+    ToLowerCase(name);
+  }
+  return NS_Atomize(name);
 }
 
 bool
 CSSParserImpl::ParseCounterStyleNameValue(nsCSSValue& aValue)
 {
-  nsString name;
-  if (ParseCounterStyleName(name, false)) {
-    aValue.SetStringValue(name, eCSSUnit_Ident);
+  if (nsCOMPtr<nsIAtom> name = ParseCounterStyleName(false)) {
+    aValue.SetAtomIdentValue(name.forget());
     return true;
   }
   return false;
 }
 
 bool
 CSSParserImpl::ParseCounterDescriptor(nsCSSCounterStyleRule* aRule)
 {
@@ -8041,17 +8038,17 @@ CSSParserImpl::ParseCounter(nsCSSValue& 
     // get optional type
     int32_t typeItem = eCSSUnit_Counters == unit ? 2 : 1;
     nsCSSValue& type = val->Item(typeItem);
     if (ExpectSymbol(',', true)) {
       if (!ParseCounterStyleNameValue(type) && !ParseSymbols(type)) {
         break;
       }
     } else {
-      type.SetStringValue(NS_LITERAL_STRING("decimal"), eCSSUnit_Ident);
+      type.SetAtomIdentValue(do_AddRef(nsGkAtoms::decimal));
     }
 
     if (!ExpectSymbol(')', true)) {
       break;
     }
 
     aValue.SetArrayValue(val, unit);
     return true;
@@ -15276,19 +15273,18 @@ CSSParserImpl::ParseListStyle()
   }
 
   if ((found & 2) == 0) {
     values[1].SetIntValue(NS_STYLE_LIST_STYLE_POSITION_OUTSIDE,
                           eCSSUnit_Enumerated);
   }
   if ((found & 4) == 0) {
     // Provide default values
-    nsString type = (found & 1) ?
-      NS_LITERAL_STRING("none") : NS_LITERAL_STRING("disc");
-    values[2].SetStringValue(type, eCSSUnit_Ident);
+    nsIAtom* type = (found & 1) ? nsGkAtoms::none : nsGkAtoms::disc;
+    values[2].SetAtomIdentValue(do_AddRef(type));
   }
   if ((found & 8) == 0) {
     values[3].SetNoneValue();
   }
 
   // Start at 1 to avoid appending fake value.
   for (uint32_t index = 1; index < ArrayLength(listStyleIDs); ++index) {
     AppendValue(listStyleIDs[index], values[index]);
@@ -18217,23 +18213,21 @@ nsCSSParser::ParsePropertyWithVariableRe
 {
   static_cast<CSSParserImpl*>(mImpl)->
     ParsePropertyWithVariableReferences(aPropertyID, aShorthandPropertyID,
                                         aValue, aVariables, aRuleData, aDocURL,
                                         aBaseURL, aDocPrincipal, aSheet,
                                         aLineNumber, aLineOffset);
 }
 
-bool
-nsCSSParser::ParseCounterStyleName(const nsAString& aBuffer,
-                                   nsIURI* aURL,
-                                   nsAString& aName)
+already_AddRefed<nsIAtom>
+nsCSSParser::ParseCounterStyleName(const nsAString& aBuffer, nsIURI* aURL)
 {
   return static_cast<CSSParserImpl*>(mImpl)->
-    ParseCounterStyleName(aBuffer, aURL, aName);
+    ParseCounterStyleName(aBuffer, aURL);
 }
 
 bool
 nsCSSParser::ParseCounterDescriptor(nsCSSCounterDesc aDescID,
                                     const nsAString& aBuffer,
                                     nsIURI* aSheetURL,
                                     nsIURI* aBaseURL,
                                     nsIPrincipal* aSheetPrincipal,
--- a/layout/style/nsCSSParser.h
+++ b/layout/style/nsCSSParser.h
@@ -299,19 +299,21 @@ public:
                                    nsRuleData* aRuleData,
                                    nsIURI* aDocURL,
                                    nsIURI* aBaseURL,
                                    nsIPrincipal* aDocPrincipal,
                                    mozilla::CSSStyleSheet* aSheet,
                                    uint32_t aLineNumber,
                                    uint32_t aLineOffset);
 
-  bool ParseCounterStyleName(const nsAString& aBuffer,
-                             nsIURI* aURL,
-                             nsAString& aName);
+  /**
+   * Parses a string as a counter-style name. Returns nullptr if fails.
+   */
+  already_AddRefed<nsIAtom> ParseCounterStyleName(const nsAString& aBuffer,
+                                                  nsIURI* aURL);
 
   bool ParseCounterDescriptor(nsCSSCounterDesc aDescID,
                               const nsAString& aBuffer,
                               nsIURI* aSheetURL,
                               nsIURI* aBaseURL,
                               nsIPrincipal* aSheetPrincipal,
                               nsCSSValue& aValue);
 
--- a/layout/style/nsCSSRuleProcessor.cpp
+++ b/layout/style/nsCSSRuleProcessor.cpp
@@ -888,17 +888,21 @@ struct RuleCascadeData {
 
   nsTArray<nsFontFaceRuleContainer> mFontFaceRules;
   nsTArray<nsCSSKeyframesRule*> mKeyframesRules;
   nsTArray<nsCSSFontFeatureValuesRule*> mFontFeatureValuesRules;
   nsTArray<nsCSSPageRule*> mPageRules;
   nsTArray<nsCSSCounterStyleRule*> mCounterStyleRules;
 
   nsDataHashtable<nsStringHashKey, nsCSSKeyframesRule*> mKeyframesRuleTable;
-  nsDataHashtable<nsStringHashKey, nsCSSCounterStyleRule*> mCounterStyleRuleTable;
+  // The hashtable doesn't need to hold a strong reference to the name
+  // atom, because nsCSSCounterStyleRule always does. If the name changes
+  // we need to discard this table and rebuild it anyway.
+  nsDataHashtable<nsPtrHashKey<nsIAtom>,
+                  nsCSSCounterStyleRule*> mCounterStyleRuleTable;
 
   // Looks up or creates the appropriate list in |mAttributeSelectors|.
   // Returns null only on allocation failure.
   nsTArray<SelectorPair>* AttributeListFor(nsIAtom* aAttribute);
 
   nsMediaQueryResultCacheKey mCacheKey;
   RuleCascadeData*  mNext; // for a different medium
 
@@ -3113,17 +3117,17 @@ nsCSSRuleProcessor::KeyframesRuleForName
     return cascade->mKeyframesRuleTable.Get(aName);
   }
 
   return nullptr;
 }
 
 nsCSSCounterStyleRule*
 nsCSSRuleProcessor::CounterStyleRuleForName(nsPresContext* aPresContext,
-                                            const nsAString& aName)
+                                            nsIAtom* aName)
 {
   RuleCascadeData* cascade = GetRuleCascade(aPresContext);
 
   if (cascade) {
     return cascade->mCounterStyleRuleTable.Get(aName);
   }
 
   return nullptr;
@@ -3832,17 +3836,17 @@ nsCSSRuleProcessor::RefreshRuleCascade(n
         nsCSSKeyframesRule* rule = newCascade->mKeyframesRules[i];
         newCascade->mKeyframesRuleTable.Put(rule->GetName(), rule);
       }
 
       // Build mCounterStyleRuleTable
       for (nsTArray<nsCSSCounterStyleRule*>::size_type i = 0,
            iEnd = newCascade->mCounterStyleRules.Length(); i < iEnd; ++i) {
         nsCSSCounterStyleRule* rule = newCascade->mCounterStyleRules[i];
-        newCascade->mCounterStyleRuleTable.Put(rule->GetName(), rule);
+        newCascade->mCounterStyleRuleTable.Put(rule->Name(), rule);
       }
 
       // mMustGatherDocumentRules controls whether we build mDocumentRules
       // and mDocumentCacheKey so that they can be used as keys by the
       // RuleProcessorCache, as obtained by TakeDocumentRulesAndCacheKey
       // later.  We set it to false just below so that we only do this
       // the first time we build a RuleProcessorCache for a shared rule
       // processor.
--- a/layout/style/nsCSSRuleProcessor.h
+++ b/layout/style/nsCSSRuleProcessor.h
@@ -205,17 +205,17 @@ public:
   // true for success and false for failure.
   bool AppendFontFaceRules(nsPresContext* aPresContext,
                            nsTArray<nsFontFaceRuleContainer>& aArray);
 
   nsCSSKeyframesRule* KeyframesRuleForName(nsPresContext* aPresContext,
                                            const nsString& aName);
 
   nsCSSCounterStyleRule* CounterStyleRuleForName(nsPresContext* aPresContext,
-                                                 const nsAString& aName);
+                                                 nsIAtom* aName);
 
   bool AppendPageRules(nsPresContext* aPresContext,
                        nsTArray<nsCSSPageRule*>& aArray);
 
   bool AppendFontFeatureValuesRules(nsPresContext* aPresContext,
                               nsTArray<nsCSSFontFeatureValuesRule*>& aArray);
 
   /**
--- a/layout/style/nsCSSRules.cpp
+++ b/layout/style/nsCSSRules.cpp
@@ -2420,18 +2420,19 @@ nsCSSCounterStyleRule::List(FILE* out, i
 {
   nsCString baseInd, descInd;
   for (int32_t indent = aIndent; --indent >= 0; ) {
     baseInd.AppendLiteral("  ");
   }
   descInd = baseInd;
   descInd.AppendLiteral("  ");
 
+  nsDependentAtomString name(mName);
   fprintf_stderr(out, "%s@counter-style %s (rev.%u) {\n",
-                 baseInd.get(), NS_ConvertUTF16toUTF8(mName).get(),
+                 baseInd.get(), NS_ConvertUTF16toUTF8(name).get(),
                  mGeneration);
   // TODO
   fprintf_stderr(out, "%s}\n", baseInd.get());
 }
 #endif
 
 /* virtual */ int32_t
 nsCSSCounterStyleRule::GetType() const
@@ -2444,17 +2445,18 @@ nsCSSCounterStyleRule::Type() const
 {
   return nsIDOMCSSRule::COUNTER_STYLE_RULE;
 }
 
 void
 nsCSSCounterStyleRule::GetCssTextImpl(nsAString& aCssText) const
 {
   aCssText.AssignLiteral(u"@counter-style ");
-  nsStyleUtil::AppendEscapedCSSIdent(mName, aCssText);
+  nsDependentAtomString name(mName);
+  nsStyleUtil::AppendEscapedCSSIdent(name, aCssText);
   aCssText.AppendLiteral(u" {\n");
   for (nsCSSCounterDesc id = nsCSSCounterDesc(0);
        id < eCSSCounterDesc_COUNT;
        id = nsCSSCounterDesc(id + 1)) {
     if (mValues[id].GetUnit() != eCSSUnit_Null) {
       nsAutoString tmp;
       // This is annoying.  We want to be a const method, but kGetters stores
       // XPCOM method pointers, which aren't const methods.  The thing is,
@@ -2470,26 +2472,26 @@ nsCSSCounterStyleRule::GetCssTextImpl(ns
   aCssText.AppendLiteral(u"}");
 }
 
 // nsIDOMCSSCounterStyleRule methods
 NS_IMETHODIMP
 nsCSSCounterStyleRule::GetName(nsAString& aName)
 {
   aName.Truncate();
-  nsStyleUtil::AppendEscapedCSSIdent(mName, aName);
+  nsDependentAtomString name(mName);
+  nsStyleUtil::AppendEscapedCSSIdent(name, aName);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsCSSCounterStyleRule::SetName(const nsAString& aName)
 {
   nsCSSParser parser;
-  nsAutoString name;
-  if (parser.ParseCounterStyleName(aName, nullptr, name)) {
+  if (nsCOMPtr<nsIAtom> name = parser.ParseCounterStyleName(aName, nullptr)) {
     nsIDocument* doc = GetDocument();
     MOZ_AUTO_DOC_UPDATE(doc, UPDATE_STYLE, true);
 
     mName = name;
 
     if (StyleSheet* sheet = GetStyleSheet()) {
       sheet->AsGecko()->SetModifiedByChildRule();
       if (doc) {
@@ -2670,23 +2672,28 @@ nsCSSCounterStyleRule::GetSpeakAs(nsAStr
           aSpeakAs.AssignLiteral(u"spell-out");
           break;
         default:
           NS_NOTREACHED("Unknown speech synthesis");
       }
       break;
 
     case eCSSUnit_Auto:
-    case eCSSUnit_Ident:
+    case eCSSUnit_AtomIdent:
       aSpeakAs.Truncate();
       value.AppendToString(eCSSProperty_UNKNOWN,
                            aSpeakAs, nsCSSValue::eNormalized);
       break;
 
+    case eCSSUnit_Null:
+      aSpeakAs.Truncate();
+      break;
+
     default:
+      NS_NOTREACHED("Unknown speech synthesis");
       aSpeakAs.Truncate();
   }
   return NS_OK;
 }
 
 nsresult
 nsCSSCounterStyleRule::GetDescriptor(nsCSSCounterDesc aDescID,
                                      nsAString& aValue)
--- a/layout/style/nsCSSRules.h
+++ b/layout/style/nsCSSRules.h
@@ -481,22 +481,23 @@ protected:
 };
 
 } // namespace mozilla
 
 class nsCSSCounterStyleRule final : public mozilla::css::Rule,
                                     public nsIDOMCSSCounterStyleRule
 {
 public:
-  explicit nsCSSCounterStyleRule(const nsAString& aName,
+  explicit nsCSSCounterStyleRule(nsIAtom* aName,
                                  uint32_t aLineNumber, uint32_t aColumnNumber)
     : mozilla::css::Rule(aLineNumber, aColumnNumber)
     , mName(aName)
     , mGeneration(0)
   {
+    MOZ_ASSERT(aName, "Must have non-null name");
   }
 
 private:
   nsCSSCounterStyleRule(const nsCSSCounterStyleRule& aCopy);
   ~nsCSSCounterStyleRule();
 
 public:
   NS_DECL_ISUPPORTS_INHERITED
@@ -539,17 +540,17 @@ public:
   // The XPCOM SetFallback is OK
 
   // This function is only used to check whether a non-empty value, which has
   // been accepted by parser, is valid for the given system and descriptor.
   static bool CheckDescValue(int32_t aSystem,
                              nsCSSCounterDesc aDescID,
                              const nsCSSValue& aValue);
 
-  const nsString& GetName() const { return mName; }
+  nsIAtom* Name() const { return mName; }
 
   uint32_t GetGeneration() const { return mGeneration; }
 
   int32_t GetSystem() const;
   const nsCSSValue& GetSystemArgument() const;
 
   const nsCSSValue& GetDesc(nsCSSCounterDesc aDescID) const
   {
@@ -568,14 +569,14 @@ public:
 private:
   typedef NS_STDCALL_FUNCPROTO(nsresult, Getter, nsCSSCounterStyleRule,
                                GetSymbols, (nsAString&));
   static const Getter kGetters[];
 
   nsresult GetDescriptor(nsCSSCounterDesc aDescID, nsAString& aValue);
   nsresult SetDescriptor(nsCSSCounterDesc aDescID, const nsAString& aValue);
 
-  nsString   mName;
+  nsCOMPtr<nsIAtom> mName;
   nsCSSValue mValues[eCSSCounterDesc_COUNT];
   uint32_t   mGeneration;
 };
 
 #endif /* !defined(nsCSSRules_h_) */
--- a/layout/style/nsCSSValue.cpp
+++ b/layout/style/nsCSSValue.cpp
@@ -502,16 +502,24 @@ void nsCSSValue::SetStringValue(const ns
   mUnit = aUnit;
   MOZ_ASSERT(UnitHasStringValue(), "not a string unit");
   if (UnitHasStringValue()) {
     mValue.mString = BufferFromString(aValue).take();
   } else
     mUnit = eCSSUnit_Null;
 }
 
+void
+nsCSSValue::SetAtomIdentValue(already_AddRefed<nsIAtom> aValue)
+{
+  Reset();
+  mUnit = eCSSUnit_AtomIdent;
+  mValue.mAtom = aValue.take();
+}
+
 void nsCSSValue::SetColorValue(nscolor aValue)
 {
   SetIntegerColorValue(aValue, eCSSUnit_RGBAColor);
 }
 
 
 
 void nsCSSValue::SetIntegerColorValue(nscolor aValue, nsCSSUnit aUnit)
--- a/layout/style/nsCSSValue.h
+++ b/layout/style/nsCSSValue.h
@@ -900,16 +900,17 @@ public:
   {
     static_assert(mozilla::EnumTypeFitsWithin<T, int32_t>::value,
                   "aValue must be an enum that fits within mValue.mInt");
     SetIntValue(static_cast<int32_t>(aValue), eCSSUnit_Enumerated);
   }
   void SetPercentValue(float aValue);
   void SetFloatValue(float aValue, nsCSSUnit aUnit);
   void SetStringValue(const nsString& aValue, nsCSSUnit aUnit);
+  void SetAtomIdentValue(already_AddRefed<nsIAtom> aValue);
   void SetColorValue(nscolor aValue);
   void SetIntegerColorValue(nscolor aValue, nsCSSUnit aUnit);
   // converts the nscoord to pixels
   void SetIntegerCoordValue(nscoord aCoord);
   void SetFloatColorValue(float aComponent1,
                           float aComponent2,
                           float aComponent3,
                           float aAlpha, nsCSSUnit aUnit);
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -405,16 +405,48 @@ nsRuleNode::GetMetricsFor(nsPresContext*
     if (wm.IsVertical() && !wm.IsSideways()) {
       isVertical = true;
     }
   }
   return nsRuleNode::GetMetricsFor(aPresContext, isVertical, aStyleFont,
                                    aFontSize, aUseUserFontSet);
 }
 
+/* static */
+void
+nsRuleNode::FixupNoneGeneric(nsFont* aFont,
+                             const nsPresContext* aPresContext,
+                             uint8_t aGenericFontID,
+                             const nsFont* aDefaultVariableFont)
+{
+  bool useDocumentFonts =
+    aPresContext->GetCachedBoolPref(kPresContext_UseDocumentFonts);
+  if (aGenericFontID == kGenericFont_NONE ||
+      (!useDocumentFonts && (aGenericFontID == kGenericFont_cursive ||
+                             aGenericFontID == kGenericFont_fantasy))) {
+    FontFamilyType defaultGeneric =
+      aDefaultVariableFont->fontlist.FirstGeneric();
+    MOZ_ASSERT(aDefaultVariableFont->fontlist.Length() == 1 &&
+               (defaultGeneric == eFamily_serif ||
+                defaultGeneric == eFamily_sans_serif));
+    if (defaultGeneric != eFamily_none) {
+      if (useDocumentFonts) {
+        aFont->fontlist.SetDefaultFontType(defaultGeneric);
+      } else {
+        // Either prioritize the first generic in the list,
+        // or (if there isn't one) prepend the default variable font.
+        if (!aFont->fontlist.PrioritizeFirstGeneric()) {
+          aFont->fontlist.PrependGeneric(defaultGeneric);
+        }
+      }
+    }
+  } else {
+    aFont->fontlist.SetDefaultFontType(eFamily_none);
+  }
+}
 
 static nsSize CalcViewportUnitsScale(nsPresContext* aPresContext)
 {
   // The caller is making use of viewport units, so notify the pres context
   // that it will need to rebuild the rule tree if the size of the viewport
   // changes.
   aPresContext->SetUsesViewportUnits(true);
 
@@ -3551,40 +3583,19 @@ nsRuleNode::SetFont(nsPresContext* aPres
 
   // font-family: font family list, enum, inherit
   const nsCSSValue* familyValue = aRuleData->ValueForFontFamily();
   NS_ASSERTION(eCSSUnit_Enumerated != familyValue->GetUnit(),
                "system fonts should not be in mFamily anymore");
   if (eCSSUnit_FontFamilyList == familyValue->GetUnit()) {
     // set the correct font if we are using DocumentFonts OR we are overriding for XUL
     // MJA: bug 31816
-    bool useDocumentFonts =
-      aPresContext->GetCachedBoolPref(kPresContext_UseDocumentFonts);
-    if (aGenericFontID == kGenericFont_NONE ||
-        (!useDocumentFonts && (aGenericFontID == kGenericFont_cursive ||
-                               aGenericFontID == kGenericFont_fantasy))) {
-      FontFamilyType defaultGeneric =
-        defaultVariableFont->fontlist.FirstGeneric();
-      MOZ_ASSERT(defaultVariableFont->fontlist.Length() == 1 &&
-                 (defaultGeneric == eFamily_serif ||
-                  defaultGeneric == eFamily_sans_serif));
-      if (defaultGeneric != eFamily_none) {
-        if (useDocumentFonts) {
-          aFont->mFont.fontlist.SetDefaultFontType(defaultGeneric);
-        } else {
-          // Either prioritize the first generic in the list,
-          // or (if there isn't one) prepend the default variable font.
-          if (!aFont->mFont.fontlist.PrioritizeFirstGeneric()) {
-            aFont->mFont.fontlist.PrependGeneric(defaultGeneric);
-          }
-        }
-      }
-    } else {
-      aFont->mFont.fontlist.SetDefaultFontType(eFamily_none);
-    }
+    nsRuleNode::FixupNoneGeneric(&aFont->mFont, aPresContext,
+                                 aGenericFontID, defaultVariableFont);
+
     aFont->mFont.systemFont = false;
     // Technically this is redundant with the code below, but it's good
     // to have since we'll still want it once we get rid of
     // SetGenericFont (bug 380915).
     aFont->mGenericID = aGenericFontID;
   }
   else if (eCSSUnit_System_Font == familyValue->GetUnit()) {
     aFont->mFont.fontlist = systemFont.fontlist;
@@ -7946,51 +7957,49 @@ nsRuleNode::ComputeListData(void* aStart
   switch (typeValue->GetUnit()) {
     case eCSSUnit_Unset:
     case eCSSUnit_Inherit: {
       conditions.SetUncacheable();
       list->SetCounterStyle(parentList->GetCounterStyle());
       break;
     }
     case eCSSUnit_Initial:
-      list->SetListStyleType(NS_LITERAL_STRING("disc"), mPresContext);
-      break;
-    case eCSSUnit_Ident: {
-      nsString typeIdent;
-      typeValue->GetStringValue(typeIdent);
-      list->SetListStyleType(typeIdent, mPresContext);
+      list->SetListStyleType(nsGkAtoms::disc, mPresContext);
+      break;
+    case eCSSUnit_AtomIdent: {
+      list->SetListStyleType(typeValue->GetAtomValue(), mPresContext);
       break;
     }
     case eCSSUnit_String: {
       nsString str;
       typeValue->GetStringValue(str);
       list->SetCounterStyle(new AnonymousCounterStyle(str));
       break;
     }
     case eCSSUnit_Enumerated: {
       // For compatibility with html attribute map.
       // This branch should never be called for value from CSS.
       int32_t intValue = typeValue->GetIntValue();
-      nsAutoString name;
+      nsCOMPtr<nsIAtom> name;
       switch (intValue) {
         case NS_STYLE_LIST_STYLE_LOWER_ROMAN:
-          name.AssignLiteral(u"lower-roman");
+          name = nsGkAtoms::lowerRoman;
           break;
         case NS_STYLE_LIST_STYLE_UPPER_ROMAN:
-          name.AssignLiteral(u"upper-roman");
+          name = nsGkAtoms::upperRoman;
           break;
         case NS_STYLE_LIST_STYLE_LOWER_ALPHA:
-          name.AssignLiteral(u"lower-alpha");
+          name = nsGkAtoms::lowerAlpha;
           break;
         case NS_STYLE_LIST_STYLE_UPPER_ALPHA:
-          name.AssignLiteral(u"upper-alpha");
+          name = nsGkAtoms::upperAlpha;
           break;
         default:
-          CopyASCIItoUTF16(nsCSSProps::ValueToKeyword(
-                  intValue, nsCSSProps::kListStyleKTable), name);
+          name = NS_Atomize(nsCSSProps::ValueToKeyword(
+                  intValue, nsCSSProps::kListStyleKTable));
           break;
       }
       list->SetListStyleType(name, mPresContext);
       break;
     }
     case eCSSUnit_Symbols:
       list->SetCounterStyle(new AnonymousCounterStyle(typeValue->GetArrayValue()));
       break;
--- a/layout/style/nsRuleNode.h
+++ b/layout/style/nsRuleNode.h
@@ -806,16 +806,25 @@ public:
                                                        bool aUseUserFontSet);
 
   static already_AddRefed<nsFontMetrics> GetMetricsFor(nsPresContext* aPresContext,
                                                        nsStyleContext* aStyleContext,
                                                        const nsStyleFont* aStyleFont,
                                                        nscoord aFontSize,
                                                        bool aUseUserFontSet);
 
+  /**
+   * Appropriately add the correct font if we are using DocumentFonts or
+   * overriding for XUL
+   */
+  static void FixupNoneGeneric(nsFont* aFont,
+                               const nsPresContext* aPresContext,
+                               uint8_t aGenericFontID,
+                               const nsFont* aDefaultVariableFont);
+
   // Transition never returns null; on out of memory it'll just return |this|.
   nsRuleNode* Transition(nsIStyleRule* aRule, mozilla::SheetType aLevel,
                          bool aIsImportantRule);
   nsRuleNode* GetParent() const { return mParent; }
   bool IsRoot() const { return mParent == nullptr; }
 
   // Return the root of the rule tree that this rule node is in.
   nsRuleNode* RuleTree();
--- a/layout/style/nsStyleSet.cpp
+++ b/layout/style/nsStyleSet.cpp
@@ -2250,17 +2250,17 @@ nsStyleSet::KeyframesRuleForName(const n
       ruleProc->KeyframesRuleForName(presContext, aName);
     if (result)
       return result;
   }
   return nullptr;
 }
 
 nsCSSCounterStyleRule*
-nsStyleSet::CounterStyleRuleForName(const nsAString& aName)
+nsStyleSet::CounterStyleRuleForName(nsIAtom* aName)
 {
   NS_ENSURE_FALSE(mInShutdown, nullptr);
   NS_ASSERTION(mBatching == 0, "rule processors out of date");
 
   nsPresContext* presContext = PresContext();
   for (uint32_t i = ArrayLength(gCSSSheetTypes); i-- != 0; ) {
     if (gCSSSheetTypes[i] == SheetType::ScopedDoc)
       continue;
--- a/layout/style/nsStyleSet.h
+++ b/layout/style/nsStyleSet.h
@@ -303,17 +303,17 @@ class nsStyleSet final
   // Append all the currently-active font face rules to aArray.  Return
   // true for success and false for failure.
   bool AppendFontFaceRules(nsTArray<nsFontFaceRuleContainer>& aArray);
 
   // Return the winning (in the cascade) @keyframes rule for the given name.
   nsCSSKeyframesRule* KeyframesRuleForName(const nsString& aName);
 
   // Return the winning (in the cascade) @counter-style rule for the given name.
-  nsCSSCounterStyleRule* CounterStyleRuleForName(const nsAString& aName);
+  nsCSSCounterStyleRule* CounterStyleRuleForName(nsIAtom* aName);
 
   // Fetch object for looking up font feature values
   already_AddRefed<gfxFontFeatureValueSet> GetFontFeatureValuesLookup();
 
   // Append all the currently-active font feature values rules to aArray.
   // Return true for success and false for failure.
   bool AppendFontFeatureValuesRules(
                               nsTArray<nsCSSFontFeatureValuesRule*>& aArray);
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -600,18 +600,18 @@ nsStyleList::nsStyleList(const nsPresCon
   : mListStylePosition(NS_STYLE_LIST_STYLE_POSITION_OUTSIDE)
 {
   MOZ_COUNT_CTOR(nsStyleList);
   if (aContext->StyleSet()->IsServo()) {
     // FIXME: bug 1328319.
     mCounterStyle =
       CounterStyleManager::GetBuiltinStyle(NS_STYLE_LIST_STYLE_DISC);
   } else {
-    mCounterStyle = aContext->CounterStyleManager()->
-      BuildCounterStyle(NS_LITERAL_STRING("disc"));
+    mCounterStyle = aContext->
+      CounterStyleManager()->BuildCounterStyle(nsGkAtoms::disc);
   }
   SetQuotesInitial();
 }
 
 nsStyleList::~nsStyleList()
 {
   MOZ_COUNT_DTOR(nsStyleList);
 }
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -1477,18 +1477,17 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
   }
   void SetCounterStyle(mozilla::CounterStyle* aStyle)
   {
     // NB: This function is called off-main-thread during parallel restyle, but
     // only with builtin styles that use dummy refcounting.
     MOZ_ASSERT(NS_IsMainThread() || !aStyle->IsDependentStyle());
     mCounterStyle = aStyle;
   }
-  void SetListStyleType(const nsSubstring& aType,
-                        nsPresContext* aPresContext)
+  void SetListStyleType(nsIAtom* aType, nsPresContext* aPresContext)
   {
     SetCounterStyle(aPresContext->CounterStyleManager()->BuildCounterStyle(aType));
   }
 
   const nsStyleQuoteValues::QuotePairArray& GetQuotePairs() const;
 
   void SetQuotesInherit(const nsStyleList* aOther);
   void SetQuotesInitial();
--- a/layout/style/test/stylo-failures.md
+++ b/layout/style/test/stylo-failures.md
@@ -129,17 +129,17 @@ to mochitest command.
   * font-variant-{alternates,east-asian,ligatures,numeric} properties servo/servo#15957
     * test_property_syntax_errors.html `font-variant-alternates` [2]
     * test_value_storage.html `font-variant` [176]
     * test_specified_value_serialization.html `bug-721136` [1]
 * Properties implemented but not in geckolib:
   * font-feature-settings property servo/servo#15975
     * test_inherit_storage.html `font-feature-settings` [2]
     * test_initial_storage.html `font-feature-settings` [1]
-    * test_value_storage.html `font-feature-settings` [118]
+    * test_value_storage.html `font-feature-settings` [40]
   * image-orientation property bug 1341758
     * test_value_storage.html `image-orientation` [40]
 * Stylesheet cloning is somehow busted bug 1348481
   * test_selectors.html `matched clone` [3]
 * Unsupported prefixed values
   * moz-prefixed gradient functions bug 1337655
     * test_value_storage.html `-moz-linear-gradient` [322]
     * ... `-moz-radial-gradient` [309]
@@ -154,18 +154,16 @@ to mochitest command.
     * test_flexbox_flex_shorthand.html `-moz-fit-content` [4]
     * test_value_storage.html `-moz-max-content` [46]
     * ... `-moz-min-content` [6]
     * ... `-moz-fit-content` [6]
     * ... `-moz-available` [4]
   * -webkit-{flex,inline-flex} for display servo/servo#15400
     * test_webkit_flex_display.html [4]
 * Unsupported values
-  * SVG-only values of pointer-events not recognized
-    * test_value_storage.html `pointer-events` [1]
   * new syntax of rgba?() and hsla?() functions servo/rust-cssparser#113
     * test_computed_style.html `css-color-4` [2]
   * SVG-in-OpenType values not supported servo/servo#15211 bug 1355412
     * test_value_storage.html `context-` [7]
     * test_bug798843_pref.html [7]
 * Incorrect parsing
   * Incorrect bounds
     * test_bug664955.html `font size is larger than max font size` [2]
@@ -202,17 +200,16 @@ to mochitest command.
   * test_value_storage.html `'font'` [128]
   * test_shorthand_property_getters.html `font shorthand` [1]
   * test_system_font_serialization.html [5]
 * clamp negative value from calc() servo/servo#15296
   * test_value_storage.html `font-size: calc(` [3]
   * ... `font-size: var(--a)` [3]
 * Negative value should be rejected
   * test_property_syntax_errors.html `transition-duration`: servo/servo#15343 [20]
-  * ... `'text-shadow'`: third length of text-shadow servo/servo#15999 [2]
 * Quirks mode support
   * hashless color servo/servo#15341
     * test_property_syntax_errors.html `color: 000000` [22]
     * ... `color: 96ed2a` [22]
     * ... `color: fff` [4]
   * unitless length servo/servo#15342
     * test_property_syntax_errors.html ` 20 ` [6]
     * ... `: 10 ` [6]
new file mode 100644
--- /dev/null
+++ b/media/libaom/README_MOZILLA
@@ -0,0 +1,21 @@
+This directory contains build files for the aom video
+codec reference implementation. The actual library
+source is in $TOPSRCDIR/third_party/aom/
+
+Any patches or additional configuration to be applied to the
+upstream source should be kept here in the media/libaom
+directory.
+
+To update the library source and build config files, execute
+
+  ./mach vendor aom
+
+To update to a specific upstream git tag or commit, use
+
+  ./mach vendor aom -r <commit>
+
+The upstream aom git repository is:
+
+    https://aomedia.googlesource.com/aom
+
+The git commit ID used was 4d668d7feb1f8abd809d1bca0418570a7f142a36 (Wed May 03 17:10:13 2017 +0000).
new file mode 100644
--- /dev/null
+++ b/media/libaom/config/aom_version.h
@@ -0,0 +1,7 @@
+#define VERSION_MAJOR  0
+#define VERSION_MINOR  0
+#define VERSION_PATCH  0
+#define VERSION_EXTRA  ""
+#define VERSION_PACKED ((VERSION_MAJOR<<16)|(VERSION_MINOR<<8)|(VERSION_PATCH))
+#define VERSION_STRING_NOSP "Release"
+#define VERSION_STRING      " Release"
new file mode 100644
--- /dev/null
+++ b/media/libaom/config/generic/aom_config.asm
@@ -0,0 +1,155 @@
+@ This file was created from a .asm file
+@  using the ads2gas.pl script.
+	.equ DO1STROUNDING, 0
+.equ ARCH_ARM ,  0
+.equ ARCH_MIPS ,  0
+.equ ARCH_X86 ,  0
+.equ ARCH_X86_64 ,  0
+.equ HAVE_EDSP ,  0
+.equ HAVE_MEDIA ,  0
+.equ HAVE_NEON ,  0
+.equ HAVE_NEON_ASM ,  0
+.equ HAVE_MIPS32 ,  0
+.equ HAVE_DSPR2 ,  0
+.equ HAVE_MSA ,  0
+.equ HAVE_MIPS64 ,  0
+.equ HAVE_MMX ,  0
+.equ HAVE_SSE ,  0
+.equ HAVE_SSE2 ,  0
+.equ HAVE_SSE3 ,  0
+.equ HAVE_SSSE3 ,  0
+.equ HAVE_SSE4_1 ,  0
+.equ HAVE_AVX ,  0
+.equ HAVE_AVX2 ,  0
+.equ HAVE_AOM_PORTS ,  1
+.equ HAVE_FEXCEPT ,  1
+.equ HAVE_PTHREAD_H ,  1
+.equ HAVE_WXWIDGETS ,  0
+.equ CONFIG_DEPENDENCY_TRACKING ,  1
+.equ CONFIG_EXTERNAL_BUILD ,  1
+.equ CONFIG_INSTALL_DOCS ,  1
+.equ CONFIG_INSTALL_BINS ,  1
+.equ CONFIG_INSTALL_LIBS ,  1
+.equ CONFIG_INSTALL_SRCS ,  0
+.equ CONFIG_DEBUG ,  0
+.equ CONFIG_GPROF ,  0
+.equ CONFIG_GCOV ,  0
+.equ CONFIG_RVCT ,  0
+.equ CONFIG_GCC ,  1
+.equ CONFIG_MSVS ,  0
+.equ CONFIG_PIC ,  1
+.equ CONFIG_BIG_ENDIAN ,  0
+.equ CONFIG_CODEC_SRCS ,  0
+.equ CONFIG_DEBUG_LIBS ,  0
+.equ CONFIG_RUNTIME_CPU_DETECT ,  0
+.equ CONFIG_POSTPROC ,  0
+.equ CONFIG_MULTITHREAD ,  1
+.equ CONFIG_INTERNAL_STATS ,  0
+.equ CONFIG_AV1_ENCODER ,  1
+.equ CONFIG_AV1_DECODER ,  1
+.equ CONFIG_AV1 ,  1
+.equ CONFIG_ENCODERS ,  1
+.equ CONFIG_DECODERS ,  1
+.equ CONFIG_STATIC_MSVCRT ,  0
+.equ CONFIG_SPATIAL_RESAMPLING ,  1
+.equ CONFIG_REALTIME_ONLY ,  0
+.equ CONFIG_ONTHEFLY_BITPACKING ,  0
+.equ CONFIG_ERROR_CONCEALMENT ,  0
+.equ CONFIG_SHARED ,  0
+.equ CONFIG_STATIC ,  1
+.equ CONFIG_SMALL ,  0
+.equ CONFIG_POSTPROC_VISUALIZER ,  0
+.equ CONFIG_OS_SUPPORT ,  1
+.equ CONFIG_UNIT_TESTS ,  0
+.equ CONFIG_WEBM_IO ,  1
+.equ CONFIG_LIBYUV ,  1
+.equ CONFIG_ACCOUNTING ,  0
+.equ CONFIG_INSPECTION ,  0
+.equ CONFIG_DECODE_PERF_TESTS ,  0
+.equ CONFIG_ENCODE_PERF_TESTS ,  0
+.equ CONFIG_COEFFICIENT_RANGE_CHECKING ,  0
+.equ CONFIG_LOWBITDEPTH ,  1
+.equ CONFIG_HIGHBITDEPTH ,  0
+.equ CONFIG_EXPERIMENTAL ,  0
+.equ CONFIG_SIZE_LIMIT ,  1
+.equ CONFIG_FP_MB_STATS ,  0
+.equ CONFIG_CDEF ,  1
+.equ CONFIG_VAR_TX ,  0
+.equ CONFIG_RECT_TX ,  1
+.equ CONFIG_REF_MV ,  1
+.equ CONFIG_TPL_MV ,  0
+.equ CONFIG_DUAL_FILTER ,  1
+.equ CONFIG_CONVOLVE_ROUND ,  0
+.equ CONFIG_COMPOUND_ROUND ,  0
+.equ CONFIG_EXT_TX ,  0
+.equ CONFIG_TX64X64 ,  0
+.equ CONFIG_SUB8X8_MC ,  0
+.equ CONFIG_EXT_INTRA ,  1
+.equ CONFIG_INTRA_INTERP ,  0
+.equ CONFIG_FILTER_INTRA ,  0
+.equ CONFIG_INTRABC ,  0
+.equ CONFIG_EXT_INTER ,  0