Merge m-c to inbound. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Thu, 01 Sep 2016 12:15:35 -0400
changeset 353582 292a3b62983fd33329bca5a27534db2299dcbc4c
parent 353581 28b4b2e50fb01c2a4ac6f9edf74b13c86c064b00 (current diff)
parent 353548 4dd14de3fa420bf5d750ca7c969ace2a9a9931ac (diff)
child 353583 32a569f4df061afba57235815ee860dae64166d3
push id6570
push userraliiev@mozilla.com
push dateMon, 14 Nov 2016 12:26:13 +0000
treeherdermozilla-beta@f455459b2ae5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone51.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 m-c to inbound. a=merge
dom/media/test/external/external_media_tests/urls/youtube/long2-720.ini
dom/media/test/external/external_media_tests/urls/youtube/long3-crashes-720.ini
dom/media/test/external/external_media_tests/urls/youtube/long4-crashes-900.ini
dom/media/test/external/external_media_tests/urls/youtube/massive-6000.ini
dom/media/test/external/external_media_tests/urls/youtube/medium2-60.ini
dom/media/test/external/external_media_tests/urls/youtube/medium3-120.ini
dom/media/test/external/external_media_tests/urls/youtube/short0-10.ini
dom/media/test/external/external_media_tests/urls/youtube/short1-15.ini
dom/media/test/external/external_media_tests/urls/youtube/short2-15.ini
dom/media/test/external/external_media_tests/urls/youtube/short3-crashes-15.ini
mobile/android/base/moz.build
mobile/android/components/AndroidActivitiesGlue.js
mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/WebActivityMapper.java
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -59,20 +59,18 @@ if (isGonk) {
     return libcutils;
   });
 }
 
 XPCOMUtils.defineLazyServiceGetter(Services, 'captivePortalDetector',
                                   '@mozilla.org/toolkit/captive-detector;1',
                                   'nsICaptivePortalDetector');
 
-if (AppConstants.MOZ_SAFE_BROWSING) {
-  XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing",
-                "resource://gre/modules/SafeBrowsing.jsm");
-}
+XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing",
+              "resource://gre/modules/SafeBrowsing.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "SafeMode",
                                   "resource://gre/modules/SafeMode.jsm");
 
 window.performance.measure('gecko-shell-jsm-loaded', 'gecko-shell-loadstart');
 
 function debug(str) {
   dump(' -*- Shell.js: ' + str + '\n');
@@ -429,21 +427,19 @@ var shell = {
     window.performance.mark('gecko-shell-system-frame-set');
 
     ppmm.addMessageListener("content-handler", this);
     ppmm.addMessageListener("dial-handler", this);
     ppmm.addMessageListener("sms-handler", this);
     ppmm.addMessageListener("mail-handler", this);
     ppmm.addMessageListener("file-picker", this);
 
-    if (AppConstants.MOZ_SAFE_BROWSING) {
-      setTimeout(function() {
-        SafeBrowsing.init();
-      }, 5000);
-    }
+    setTimeout(function() {
+      SafeBrowsing.init();
+    }, 5000);
   },
 
   stop: function shell_stop() {
     window.removeEventListener('unload', this);
     window.removeEventListener('keydown', this, true);
     window.removeEventListener('keyup', this, true);
     window.removeEventListener('MozApplicationManifest', this);
     window.removeEventListener('sizemodechange', this);
--- a/b2g/confvars.sh
+++ b/b2g/confvars.sh
@@ -12,18 +12,16 @@ MOZ_UA_OS_AGNOSTIC=1
 
 MOZ_B2G_VERSION=2.6.0.0-prerelease
 MOZ_B2G_OS_NAME=Boot2Gecko
 
 MOZ_BRANDING_DIRECTORY=b2g/branding/unofficial
 MOZ_OFFICIAL_BRANDING_DIRECTORY=b2g/branding/official
 # MOZ_APP_DISPLAYNAME is set by branding/configure.sh
 
-MOZ_SAFE_BROWSING=1
-
 MOZ_NO_SMART_CARDS=1
 MOZ_APP_STATIC_INI=1
 MOZ_NO_EV_CERTS=1
 
 if test "$OS_TARGET" = "Android"; then
 MOZ_CAPTURE=1
 MOZ_RAW=1
 MOZ_AUDIO_CHANNEL_MANAGER=1
--- a/b2g/graphene/confvars.sh
+++ b/b2g/graphene/confvars.sh
@@ -19,17 +19,16 @@ MOZ_APP_UA_NAME=Firefox
 
 MOZ_B2G_VERSION=2.6.0.0-prerelease
 MOZ_B2G_OS_NAME=Boot2Gecko
 
 MOZ_BRANDING_DIRECTORY=b2g/branding/unofficial
 MOZ_OFFICIAL_BRANDING_DIRECTORY=b2g/branding/official
 # MOZ_APP_DISPLAYNAME is set by branding/configure.sh
 
-MOZ_SAFE_BROWSING=1
 MOZ_CAPTIVEDETECT=1
 
 MOZ_NO_SMART_CARDS=1
 MOZ_APP_STATIC_INI=1
 NSS_NO_LIBPKIX=1
 
 if test "$OS_TARGET" = "Android"; then
 MOZ_CAPTURE=1
--- a/browser/base/content/browser-doctype.inc
+++ b/browser/base/content/browser-doctype.inc
@@ -8,18 +8,16 @@
 <!ENTITY % charsetDTD SYSTEM "chrome://global/locale/charsetMenu.dtd" >
 %charsetDTD;
 <!ENTITY % textcontextDTD SYSTEM "chrome://global/locale/textcontext.dtd" >
 %textcontextDTD;
 <!ENTITY % customizeToolbarDTD SYSTEM "chrome://global/locale/customizeToolbar.dtd">
   %customizeToolbarDTD;
 <!ENTITY % placesDTD SYSTEM "chrome://browser/locale/places/places.dtd">
 %placesDTD;
-#ifdef MOZ_SAFE_BROWSING
 <!ENTITY % safebrowsingDTD SYSTEM "chrome://browser/locale/safebrowsing/phishing-afterload-warning-message.dtd">
 %safebrowsingDTD;
-#endif
 <!ENTITY % aboutHomeDTD SYSTEM "chrome://browser/locale/aboutHome.dtd">
 %aboutHomeDTD;
 <!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
 %syncBrandDTD;
 ]>
 
--- a/browser/base/content/browser-safebrowsing.js
+++ b/browser/base/content/browser-safebrowsing.js
@@ -1,15 +1,12 @@
 /* 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/. */
 
-// Note: this file is not shipped (through jar.mn)
-// if MOZ_SAFE_BROWSING is not defined.
-
 var gSafeBrowsing = {
 
   setReportPhishingMenu: function() {
     // In order to detect whether or not we're at the phishing warning
     // page, we have to check the documentURI instead of the currentURI.
     // This is because when the DocShell loads an error page, the
     // currentURI stays at the original target, while the documentURI
     // will point to the internal error page we loaded instead.
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -758,17 +758,17 @@ html|*#fullscreen-exit-button {
 
 
 /* notification anchors should only be visible when their associated
    notifications are */
 .notification-anchor-icon {
   -moz-user-focus: normal;
 }
 
-.blocked-permission-icon:not([showing]),
+#blocked-permissions-container > .blocked-permission-icon:not([showing]),
 .notification-anchor-icon:not([showing]) {
   display: none;
 }
 
 #invalid-form-popup > description {
   max-width: 280px;
 }
 
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -52,20 +52,18 @@ Cu.import("resource://gre/modules/Notifi
   ["UpdateUtils", "resource://gre/modules/UpdateUtils.jsm"],
   ["Weave", "resource://services-sync/main.js"],
   ["fxAccounts", "resource://gre/modules/FxAccounts.jsm"],
   ["gDevTools", "resource://devtools/client/framework/gDevTools.jsm"],
   ["gDevToolsBrowser", "resource://devtools/client/framework/gDevTools.jsm"],
   ["webrtcUI", "resource:///modules/webrtcUI.jsm", ]
 ].forEach(([name, resource]) => XPCOMUtils.defineLazyModuleGetter(this, name, resource));
 
-if (AppConstants.MOZ_SAFE_BROWSING) {
-  XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing",
-    "resource://gre/modules/SafeBrowsing.jsm");
-}
+XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing",
+  "resource://gre/modules/SafeBrowsing.jsm");
 
 if (AppConstants.MOZ_CRASHREPORTER) {
   XPCOMUtils.defineLazyModuleGetter(this, "PluginCrashReporter",
     "resource:///modules/ContentCrashHandlers.jsm");
 }
 
 // lazy service getters
 [
@@ -1141,20 +1139,18 @@ var gBrowserInit = {
       }
       // Note: loadOneOrMoreURIs *must not* be called if window.arguments.length >= 3.
       // Such callers expect that window.arguments[0] is handled as a single URI.
       else {
         loadOneOrMoreURIs(uriToLoad);
       }
     }
 
-    if (AppConstants.MOZ_SAFE_BROWSING) {
-      // Bug 778855 - Perf regression if we do this here. To be addressed in bug 779008.
-      setTimeout(function() { SafeBrowsing.init(); }, 2000);
-    }
+    // Bug 778855 - Perf regression if we do this here. To be addressed in bug 779008.
+    setTimeout(function() { SafeBrowsing.init(); }, 2000);
 
     Services.obs.addObserver(gIdentityHandler, "perm-changed", false);
     Services.obs.addObserver(gSessionHistoryObserver, "browser:purge-session-history", false);
     Services.obs.addObserver(gXPInstallObserver, "addon-install-disabled", false);
     Services.obs.addObserver(gXPInstallObserver, "addon-install-started", false);
     Services.obs.addObserver(gXPInstallObserver, "addon-install-blocked", false);
     Services.obs.addObserver(gXPInstallObserver, "addon-install-origin-blocked", false);
     Services.obs.addObserver(gXPInstallObserver, "addon-install-failed", false);
@@ -7372,17 +7368,17 @@ var gIdentityHandler = {
   _createPermissionItem: function (aPermission) {
     let container = document.createElement("hbox");
     container.setAttribute("class", "identity-popup-permission-item");
     container.setAttribute("align", "center");
 
     let img = document.createElement("image");
     let classes = "identity-popup-permission-icon " + aPermission.id + "-icon";
     if (aPermission.state == SitePermissions.BLOCK)
-      classes += " blocked";
+      classes += " blocked-permission-icon";
     if (aPermission.inUse)
       classes += " in-use";
     img.setAttribute("class", classes);
 
     let nameLabel = document.createElement("label");
     nameLabel.setAttribute("flex", "1");
     nameLabel.setAttribute("class", "identity-popup-permission-label");
     nameLabel.textContent = SitePermissions.getPermissionLabel(aPermission.id);
--- a/browser/base/content/global-scripts.inc
+++ b/browser/base/content/global-scripts.inc
@@ -18,19 +18,17 @@
 <script type="application/javascript" src="chrome://browser/content/browser-feeds.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-fullScreenAndPointerLock.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-fullZoom.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-gestureSupport.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-media.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-places.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-plugins.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-refreshblocker.js"/>
-#ifdef MOZ_SAFE_BROWSING
 <script type="application/javascript" src="chrome://browser/content/browser-safebrowsing.js"/>
-#endif
 <script type="application/javascript" src="chrome://browser/content/browser-sidebar.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-social.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-syncui.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-tabsintitlebar.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-thumbnails.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-trackingprotection.js"/>
 
 #ifdef MOZ_DATA_REPORTING
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -690,18 +690,21 @@ nsContextMenu.prototype = {
       if (this.target instanceof Ci.nsIImageLoadingContent &&
           this.target.currentURI) {
         this.onImage = true;
 
         var request =
           this.target.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
         if (request && (request.imageStatus & request.STATUS_SIZE_AVAILABLE))
           this.onLoadedImage = true;
-        if (request && (request.imageStatus & request.STATUS_LOAD_COMPLETE))
+        if (request &&
+            (request.imageStatus & request.STATUS_LOAD_COMPLETE) &&
+            !(request.imageStatus & request.STATUS_ERROR)) {
           this.onCompletedImage = true;
+        }
 
         this.mediaURL = this.target.currentURI.spec;
 
         var descURL = this.target.getAttribute("longdesc");
         if (descURL) {
           this.imageDescURL = makeURLAbsolute(ownerDoc.body.baseURI, descURL);
         }
       }
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -737,18 +737,19 @@ function openTourPage()
   let scope = {}
   Components.utils.import("resource:///modules/UITour.jsm", scope);
   openUILinkIn(scope.UITour.url, "tab");
 }
 
 function buildHelpMenu()
 {
   // Enable/disable the "Report Web Forgery" menu item.
-  if (typeof gSafeBrowsing != "undefined" && AppConstants.MOZ_SAFE_BROWSING)
+  if (typeof gSafeBrowsing != "undefined") {
     gSafeBrowsing.setReportPhishingMenu();
+  }
 }
 
 function isElementVisible(aElement)
 {
   if (!aElement)
     return false;
 
   // If aElement or a direct or indirect parent is hidden or collapsed,
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -82,19 +82,17 @@ browser.jar:
         content/browser/browser-fullScreenAndPointerLock.js  (content/browser-fullScreenAndPointerLock.js)
         content/browser/browser-fullZoom.js           (content/browser-fullZoom.js)
         content/browser/browser-fxaccounts.js         (content/browser-fxaccounts.js)
         content/browser/browser-gestureSupport.js     (content/browser-gestureSupport.js)
         content/browser/browser-media.js              (content/browser-media.js)
         content/browser/browser-places.js             (content/browser-places.js)
         content/browser/browser-plugins.js            (content/browser-plugins.js)
         content/browser/browser-refreshblocker.js     (content/browser-refreshblocker.js)
-#ifdef MOZ_SAFE_BROWSING
         content/browser/browser-safebrowsing.js       (content/browser-safebrowsing.js)
-#endif
         content/browser/browser-sidebar.js            (content/browser-sidebar.js)
         content/browser/browser-social.js             (content/browser-social.js)
         content/browser/browser-syncui.js             (content/browser-syncui.js)
 *       content/browser/browser-tabPreviews.xml       (content/browser-tabPreviews.xml)
 #ifdef CAN_DRAW_IN_TITLEBAR
         content/browser/browser-tabsintitlebar.js       (content/browser-tabsintitlebar.js)
 #else
         content/browser/browser-tabsintitlebar.js       (content/browser-tabsintitlebar-stub.js)
@@ -186,15 +184,13 @@ browser.jar:
         content/browser/webrtcIndicator.js            (content/webrtcIndicator.js)
 #endif
 #ifdef XP_WIN
         content/browser/win6BrowserOverlay.xul        (content/win6BrowserOverlay.xul)
 #endif
 # the following files are browser-specific overrides
 *       content/browser/license.html                  (/toolkit/content/license.html)
 % override chrome://global/content/license.html chrome://browser/content/license.html
-#ifdef MOZ_SAFE_BROWSING
         content/browser/report-phishing-overlay.xul     (content/report-phishing-overlay.xul)
         content/browser/blockedSite.xhtml               (content/blockedSite.xhtml)
 % overlay chrome://browser/content/browser.xul chrome://browser/content/report-phishing-overlay.xul
-#endif
 
 % override chrome://global/content/netError.xhtml chrome://browser/content/aboutNetError.xhtml
--- a/browser/components/about/AboutRedirector.cpp
+++ b/browser/components/about/AboutRedirector.cpp
@@ -30,23 +30,21 @@ struct RedirEntry {
   Entries which do not have URI_SAFE_FOR_UNTRUSTED_CONTENT will run with chrome
   privileges. This is potentially dangerous. Please use
   URI_SAFE_FOR_UNTRUSTED_CONTENT in the third argument to each map item below
   unless your about: page really needs chrome privileges. Security review is
   required before adding new map entries without
   URI_SAFE_FOR_UNTRUSTED_CONTENT.
 */
 static RedirEntry kRedirMap[] = {
-#ifdef MOZ_SAFE_BROWSING
   { "blocked", "chrome://browser/content/blockedSite.xhtml",
     nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
     nsIAboutModule::URI_CAN_LOAD_IN_CHILD |
     nsIAboutModule::ALLOW_SCRIPT |
     nsIAboutModule::HIDE_FROM_ABOUTABOUT },
-#endif
   { "certerror", "chrome://browser/content/aboutNetError.xhtml",
     nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
     nsIAboutModule::URI_CAN_LOAD_IN_CHILD |
     nsIAboutModule::ALLOW_SCRIPT |
     nsIAboutModule::HIDE_FROM_ABOUTABOUT },
   { "socialerror", "chrome://browser/content/aboutSocialError.xhtml",
     nsIAboutModule::ALLOW_SCRIPT |
     nsIAboutModule::HIDE_FROM_ABOUTABOUT },
--- a/browser/components/build/nsModule.cpp
+++ b/browser/components/build/nsModule.cpp
@@ -80,19 +80,17 @@ static const mozilla::Module::CIDEntry k
 static const mozilla::Module::ContractIDEntry kBrowserContracts[] = {
     { NS_BROWSERDIRECTORYPROVIDER_CONTRACTID, &kNS_BROWSERDIRECTORYPROVIDER_CID },
 #if defined(XP_WIN)
     { NS_SHELLSERVICE_CONTRACTID, &kNS_SHELLSERVICE_CID },
 #elif defined(MOZ_WIDGET_GTK)
     { NS_SHELLSERVICE_CONTRACTID, &kNS_SHELLSERVICE_CID },
 #endif
     { NS_FEEDSNIFFER_CONTRACTID, &kNS_FEEDSNIFFER_CID },
-#ifdef MOZ_SAFE_BROWSING
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "blocked", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
-#endif
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "certerror", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "socialerror", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "providerdirectory", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "tabcrashed", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "feeds", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "privatebrowsing", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "rights", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "robots", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
--- a/browser/components/moz.build
+++ b/browser/components/moz.build
@@ -45,23 +45,21 @@ EXTRA_COMPONENTS += [
     'nsBrowserGlue.js',
 ]
 
 EXTRA_JS_MODULES += [
     'distribution.js',
 ]
 
 BROWSER_CHROME_MANIFESTS += [
+    'safebrowsing/content/test/browser.ini',
     'tests/browser/browser.ini'
 ]
 
 XPCSHELL_TESTS_MANIFESTS += [
     'tests/unit/xpcshell.ini'
 ]
 
-if CONFIG['MOZ_SAFE_BROWSING']:
-    BROWSER_CHROME_MANIFESTS += ['safebrowsing/content/test/browser.ini']
-
 with Files('safebrowsing/*'):
     BUG_COMPONENT = ('Toolkit', 'Phishing Protection')
 
 with Files('controlcenter/**'):
     BUG_COMPONENT = ('Firefox', 'General')
--- a/browser/confvars.sh
+++ b/browser/confvars.sh
@@ -27,17 +27,16 @@ if test "$OS_ARCH" = "WINNT"; then
       fi
     fi
   fi
 fi
 
 # Enable building ./signmar and running libmar signature tests
 MOZ_ENABLE_SIGNMAR=1
 
-MOZ_SAFE_BROWSING=1
 MOZ_APP_VERSION=$FIREFOX_VERSION
 MOZ_APP_VERSION_DISPLAY=$FIREFOX_VERSION_DISPLAY
 MOZ_EXTENSIONS_DEFAULT=" gio"
 # MOZ_APP_DISPLAYNAME will be set by branding/configure.sh
 # MOZ_BRANDING_DIRECTORY is the default branding directory used when none is
 # specified. It should never point to the "official" branding directory.
 # For mozilla-beta, mozilla-release, or mozilla-central repositories, use
 # "unofficial" branding.
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -591,23 +591,21 @@
 @RESPATH@/components/extensions-toolkit.manifest
 @RESPATH@/browser/components/extensions-browser.manifest
 
 ; Modules
 @RESPATH@/browser/modules/*
 @RESPATH@/modules/*
 
 ; Safe Browsing
-#ifdef MOZ_URL_CLASSIFIER
 @RESPATH@/components/nsURLClassifier.manifest
 @RESPATH@/components/nsUrlClassifierHashCompleter.js
 @RESPATH@/components/nsUrlClassifierListManager.js
 @RESPATH@/components/nsUrlClassifierLib.js
 @RESPATH@/components/url-classifier.xpt
-#endif
 
 ; Private Browsing
 @RESPATH@/components/privatebrowsing.xpt
 @RESPATH@/components/PrivateBrowsing.manifest
 @RESPATH@/components/PrivateBrowsingTrackingProtectionWhitelist.js
 
 ; Security Reports
 @RESPATH@/components/SecurityReporter.manifest
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -48,20 +48,18 @@
     locale/browser/downloads/downloads.dtd         (%chrome/browser/downloads/downloads.dtd)
     locale/browser/downloads/downloads.properties  (%chrome/browser/downloads/downloads.properties)
     locale/browser/places/places.dtd               (%chrome/browser/places/places.dtd)
     locale/browser/places/places.properties        (%chrome/browser/places/places.properties)
     locale/browser/places/editBookmarkOverlay.dtd  (%chrome/browser/places/editBookmarkOverlay.dtd)
     locale/browser/places/bookmarkProperties.properties (%chrome/browser/places/bookmarkProperties.properties)
     locale/browser/preferences/selectBookmark.dtd  (%chrome/browser/preferences/selectBookmark.dtd)
     locale/browser/places/moveBookmarks.dtd        (%chrome/browser/places/moveBookmarks.dtd)
-#ifdef MOZ_SAFE_BROWSING
     locale/browser/safebrowsing/phishing-afterload-warning-message.dtd (%chrome/browser/safebrowsing/phishing-afterload-warning-message.dtd)
     locale/browser/safebrowsing/report-phishing.dtd                    (%chrome/browser/safebrowsing/report-phishing.dtd)
-#endif
     locale/browser/feeds/subscribe.dtd              (%chrome/browser/feeds/subscribe.dtd)
     locale/browser/feeds/subscribe.properties       (%chrome/browser/feeds/subscribe.properties)
     locale/browser/migration/migration.dtd         (%chrome/browser/migration/migration.dtd)
     locale/browser/migration/migration.properties  (%chrome/browser/migration/migration.properties)
     locale/browser/preferences/advanced.dtd           (%chrome/browser/preferences/advanced.dtd)
     locale/browser/preferences/applicationManager.dtd        (%chrome/browser/preferences/applicationManager.dtd)
     locale/browser/preferences/applicationManager.properties (%chrome/browser/preferences/applicationManager.properties)
     locale/browser/preferences/blocklists.dtd         (%chrome/browser/preferences/blocklists.dtd)
--- a/build/autoconf/android.m4
+++ b/build/autoconf/android.m4
@@ -216,18 +216,18 @@ if test -n "$MOZ_INSTALL_TRACKING"; then
     AC_SUBST(MOZ_INSTALL_TRACKING)
     MOZ_ANDROID_AAR(play-services-ads, $ANDROID_GOOGLE_PLAY_SERVICES_VERSION, google, com/google/android/gms)
     MOZ_ANDROID_AAR(play-services-basement, $ANDROID_GOOGLE_PLAY_SERVICES_VERSION, google, com/google/android/gms)
 fi
 
 ])
 
 dnl Configure an Android SDK.
-dnl Arg 1: target SDK version, like 22.
-dnl Arg 2: build tools version, like 22.0.1.
+dnl Arg 1: target SDK version, like 23.
+dnl Arg 2: list of build-tools versions, like "23.0.3 23.0.1".
 AC_DEFUN([MOZ_ANDROID_SDK],
 [
 
 MOZ_ARG_WITH_STRING(android-sdk,
 [  --with-android-sdk=DIR
                           location where the Android SDK can be found (like ~/.mozbuild/android-sdk-linux)],
     android_sdk_root=$withval)
 
@@ -249,22 +249,31 @@ case "$target" in
     android_target_sdk=$1
     AC_MSG_CHECKING([for Android SDK platform version $android_target_sdk])
     android_sdk=$android_sdk_root/platforms/android-$android_target_sdk
     if ! test -e "$android_sdk/source.properties" ; then
         AC_MSG_ERROR([You must download Android SDK platform version $android_target_sdk.  Try |mach bootstrap|.  (Looked for $android_sdk)])
     fi
     AC_MSG_RESULT([$android_sdk])
 
-    android_build_tools="$android_sdk_root"/build-tools/$2
-    AC_MSG_CHECKING([for Android build-tools version $2])
-    if test -d "$android_build_tools" -a -f "$android_build_tools/aapt"; then
-        AC_MSG_RESULT([$android_build_tools])
-    else
-        AC_MSG_ERROR([You must install the Android build-tools version $2.  Try |mach bootstrap|.  (Looked for $android_build_tools)])
+    AC_MSG_CHECKING([for Android build-tools])
+    android_build_tools_base="$android_sdk_root"/build-tools
+    android_build_tools_version=""
+    versions=($2)
+    for version in $versions; do
+        android_build_tools="$android_build_tools_base"/$version
+        if test -d "$android_build_tools" -a -f "$android_build_tools/aapt"; then
+            android_build_tools_version=$version
+            AC_MSG_RESULT([$android_build_tools])
+            break
+        fi
+    done
+    if test "$android_build_tools_version" == ""; then
+        version=$(echo $versions | cut -d" " -f1)
+        AC_MSG_ERROR([You must install the Android build-tools version $version.  Try |mach bootstrap|.  (Looked for "$android_build_tools_base"/$version)])
     fi
 
     MOZ_PATH_PROG(ZIPALIGN, zipalign, :, [$android_build_tools])
     MOZ_PATH_PROG(DX, dx, :, [$android_build_tools])
     MOZ_PATH_PROG(AAPT, aapt, :, [$android_build_tools])
     MOZ_PATH_PROG(AIDL, aidl, :, [$android_build_tools])
     if test -z "$ZIPALIGN" -o "$ZIPALIGN" = ":"; then
       AC_MSG_ERROR([The program zipalign was not found.  Try |mach bootstrap|.])
@@ -304,17 +313,17 @@ case "$target" in
     if test -z "$EMULATOR" -o "$EMULATOR" = ":"; then
       AC_MSG_ERROR([The program emulator was not found.  Try |mach bootstrap|.])
     fi
 
     ANDROID_TARGET_SDK="${android_target_sdk}"
     ANDROID_SDK="${android_sdk}"
     ANDROID_SDK_ROOT="${android_sdk_root}"
     ANDROID_TOOLS="${android_tools}"
-    ANDROID_BUILD_TOOLS_VERSION="$2"
+    ANDROID_BUILD_TOOLS_VERSION="$android_build_tools_version"
     AC_DEFINE_UNQUOTED(ANDROID_TARGET_SDK,$ANDROID_TARGET_SDK)
     AC_SUBST(ANDROID_TARGET_SDK)
     AC_SUBST(ANDROID_SDK_ROOT)
     AC_SUBST(ANDROID_SDK)
     AC_SUBST(ANDROID_TOOLS)
     AC_SUBST(ANDROID_BUILD_TOOLS_VERSION)
 
     MOZ_ANDROID_AAR(customtabs, $ANDROID_SUPPORT_LIBRARY_VERSION, android, com/android/support)
--- a/devtools/client/debugger/moz.build
+++ b/devtools/client/debugger/moz.build
@@ -8,9 +8,12 @@ DIRS += [
     'new'
 ]
 
 DevToolsModules(
     'debugger-commands.js',
     'panel.js'
 )
 
-BROWSER_CHROME_MANIFESTS += ['test/mochitest/browser.ini']
+BROWSER_CHROME_MANIFESTS += [
+  'new/test/mochitest/browser.ini',
+  'test/mochitest/browser.ini'
+]
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+
+[browser_dbg_stub.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg_stub.js
@@ -0,0 +1,7 @@
+
+add_task(function*() {
+  ok(true,
+     "This is a stub so that we can run the new debugger tests " +
+     "by copying them in here. This will go away once we land " +
+     "the initial suite of new tests");
+});
--- a/devtools/client/definitions.js
+++ b/devtools/client/definitions.js
@@ -177,24 +177,38 @@ Tools.jsdebugger = {
     return true;
   },
 
   build: function (iframeWindow, toolbox) {
     return new DebuggerPanel(iframeWindow, toolbox);
   }
 };
 
-if (Services.prefs.getBoolPref("devtools.debugger.new-debugger-frontend")) {
-  const NewDebuggerPanel = require("devtools/client/debugger/new/panel").DebuggerPanel;
+function switchDebugger() {
+  if (Services.prefs.getBoolPref("devtools.debugger.new-debugger-frontend")) {
+    const NewDebuggerPanel = require("devtools/client/debugger/new/panel").DebuggerPanel;
 
-  Tools.jsdebugger.url = "chrome://devtools/content/debugger/new/index.html";
-  Tools.jsdebugger.build = function (iframeWindow, toolbox) {
-    return new NewDebuggerPanel(iframeWindow, toolbox);
-  };
+    Tools.jsdebugger.url = "chrome://devtools/content/debugger/new/index.html";
+    Tools.jsdebugger.build = function (iframeWindow, toolbox) {
+      return new NewDebuggerPanel(iframeWindow, toolbox);
+    };
+  } else {
+    Tools.jsdebugger.url = "chrome://devtools/content/debugger/debugger.xul";
+    Tools.jsdebugger.build = function (iframeWindow, toolbox) {
+      return new DebuggerPanel(iframeWindow, toolbox);
+    };
+  }
 }
+switchDebugger();
+
+Services.prefs.addObserver(
+  "devtools.debugger.new-debugger-frontend",
+  { observe: switchDebugger },
+  false
+);
 
 Tools.styleEditor = {
   id: "styleeditor",
   key: l10n("open.commandkey", styleEditorStrings),
   ordinal: 4,
   visibilityswitch: "devtools.styleeditor.enabled",
   accesskey: l10n("open.accesskey", styleEditorStrings),
   modifiers: "shift",
--- a/devtools/client/shared/components/frame.js
+++ b/devtools/client/shared/components/frame.js
@@ -171,17 +171,18 @@ module.exports = createClass({
       let functionDisplayName = frame.functionDisplayName;
       if (!functionDisplayName && showAnonymousFunctionName) {
         functionDisplayName = webl10n.getStr("stacktrace.anonymousFunction");
       }
 
       if (functionDisplayName) {
         elements.push(
           dom.span({ className: "frame-link-function-display-name" },
-            functionDisplayName)
+            functionDisplayName),
+          " "
         );
       }
     }
 
     let displaySource = showFullSourceUrl ? long : short;
     if (isSourceMapped) {
       displaySource = getSourceMappedFile(displaySource);
     } else if (showEmptyPathAsHost && (displaySource === "" || displaySource === "/")) {
@@ -231,14 +232,14 @@ module.exports = createClass({
     } else {
       sourceEl = dom.span({
         className: "frame-link-source",
       }, sourceInnerEl);
     }
     elements.push(sourceEl);
 
     if (showHost && host) {
-      elements.push(dom.span({ className: "frame-link-host" }, host));
+      elements.push(" ", dom.span({ className: "frame-link-host" }, host));
     }
 
     return dom.span(attributes, ...elements);
   }
 });
--- a/devtools/client/shared/components/stack-trace.js
+++ b/devtools/client/shared/components/stack-trace.js
@@ -37,32 +37,32 @@ const StackTrace = createClass({
   },
 
   render() {
     let { stacktrace, onViewSourceInDebugger } = this.props;
 
     let frames = [];
     stacktrace.forEach(s => {
       if (s.asyncCause) {
-        frames.push(AsyncFrame({
+        frames.push("\t", AsyncFrame({
           asyncCause: s.asyncCause
-        }));
+        }), "\n");
       }
 
-      frames.push(Frame({
+      frames.push("\t", Frame({
         frame: {
           functionDisplayName: s.functionName,
           source: s.filename.split(" -> ").pop(),
           line: s.lineNumber,
           column: s.columnNumber,
         },
         showFunctionName: true,
         showAnonymousFunctionName: true,
         showFullSourceUrl: true,
         onClick: onViewSourceInDebugger
-      }));
+      }), "\n");
     });
 
     return dom.div({ className: "stack-trace" }, frames);
   }
 });
 
 module.exports = StackTrace;
--- a/devtools/client/shared/components/test/mochitest/test_stack-trace.html
+++ b/devtools/client/shared/components/test/mochitest/test_stack-trace.html
@@ -40,17 +40,19 @@ window.onload = function() {
     };
 
     let trace = ReactDOM.render(StackTrace(props), window.document.body);
     yield forceRender(trace);
 
     let traceEl = trace.getDOMNode();
     ok(traceEl, "Rendered StackTrace has an element");
 
-    let frameEls = traceEl.childNodes;
+    // Get the child nodes and filter out the text-only whitespace ones
+    let frameEls = Array.from(traceEl.childNodes)
+      .filter(n => n.className.includes("frame"));
     ok(frameEls, "Rendered StackTrace has frames");
     is(frameEls.length, 3, "StackTrace has 3 frames");
 
     // Check the top frame, function name should be anonymous
     checkFrameString({
       el: frameEls[0],
       functionName: "<anonymous>",
       source: "http://myfile.com/mahscripts.js",
@@ -71,13 +73,21 @@ window.onload = function() {
       functionName: "loadFunc",
       source: "http://myfile.com/loadee.js",
       file: "http://myfile.com/loadee.js",
       line: 10,
       column: null,
       shouldLink: true,
       tooltip: "View source in Debugger → http://myfile.com/loadee.js:10",
     });
+
+    // Check the tabs and newlines in the stack trace textContent
+    let traceText = traceEl.textContent;
+    let traceLines = traceText.split("\n");
+    ok(traceLines.length > 0, "There are newlines in the stack trace text");
+    is(traceLines.pop(), "", "There is a newline at the end of the stack trace text");
+    is(traceLines.length, 3, "The stack trace text has 3 lines");
+    ok(traceLines.every(l => l[0] == "\t"), "Every stack trace line starts with tab");
   });
 }
 </script>
 </body>
 </html>
--- a/devtools/client/webconsole/console-output.js
+++ b/devtools/client/webconsole/console-output.js
@@ -930,18 +930,16 @@ Messages.Simple.prototype = extend(Messa
       twisty.addEventListener("click", this._onClickCollapsible);
       this.element.appendChild(twisty);
       this.collapsible = true;
       this.element.setAttribute("collapsible", true);
     }
 
     this.element.appendChild(body);
 
-    this.element.appendChild(this.document.createTextNode("\n"));
-
     this.element.clipboardText = this.element.textContent;
 
     if (this.private) {
       this.element.setAttribute("private", true);
     }
 
     // TODO: handle object releasing in a more elegant way once all console
     // messages use the new API - bug 778766.
@@ -988,22 +986,26 @@ Messages.Simple.prototype = extend(Messa
 
     // do this before repeatNode is rendered - it has no effect afterwards
     this._repeatID.textContent += "|" + container.textContent;
 
     let repeatNode = this._renderRepeatNode();
     let location = this._renderLocation();
 
     if (repeatNode) {
+      bodyFlex.appendChild(this.document.createTextNode(" "));
       bodyFlex.appendChild(repeatNode);
     }
     if (location) {
+      bodyFlex.appendChild(this.document.createTextNode(" "));
       bodyFlex.appendChild(location);
     }
 
+    bodyFlex.appendChild(this.document.createTextNode("\n"));
+
     if (this.stack) {
       this._attachment = new Widgets.Stacktrace(this, this.stack).render().element;
     }
 
     if (this._attachment) {
       bodyWrapper.appendChild(this._attachment);
     }
 
--- a/devtools/client/webconsole/test/browser_console_copy_entire_message_context_menu.js
+++ b/devtools/client/webconsole/test/browser_console_copy_entire_message_context_menu.js
@@ -1,69 +1,97 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
+/* globals goDoCommand */
+
 "use strict";
 
 // Test copying of the entire console message when right-clicked
 // with no other text selected. See Bug 1100562.
 
-function test() {
+add_task(function* () {
   let hud;
   let outputNode;
   let contextMenu;
 
-  const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
-                   "test/test-console.html";
-
-  Task.spawn(runner).then(finishTest);
+  const TEST_URI = "http://example.com/browser/devtools/client/webconsole/test/test-console.html";
 
-  function* runner() {
-    const {tab} = yield loadTab(TEST_URI);
-    hud = yield openConsole(tab);
-    outputNode = hud.outputNode;
-    contextMenu = hud.iframeWindow.document.getElementById("output-contextmenu");
+  const { tab, browser } = yield loadTab(TEST_URI);
+  hud = yield openConsole(tab);
+  outputNode = hud.outputNode;
+  contextMenu = hud.iframeWindow.document.getElementById("output-contextmenu");
+
+  registerCleanupFunction(() => {
+    hud = outputNode = contextMenu = null;
+  });
 
-    registerCleanupFunction(() => {
-      hud = outputNode = contextMenu = null;
-    });
+  hud.jsterm.clearOutput();
 
-    hud.jsterm.clearOutput();
-    content.console.log("bug 1100562");
+  yield ContentTask.spawn(browser, {}, function* () {
+    let button = content.document.getElementById("testTrace");
+    button.click();
+  });
 
-    let [results] = yield waitForMessages({
-      webconsole: hud,
-      messages: [{
+  let results = yield waitForMessages({
+    webconsole: hud,
+    messages: [
+      {
         text: "bug 1100562",
         category: CATEGORY_WEBDEV,
         severity: SEVERITY_LOG,
-      }]
-    });
+        lines: 1,
+      },
+      {
+        name: "console.trace output",
+        consoleTrace: true,
+        lines: 3,
+      },
+    ]
+  });
 
-    outputNode.focus();
-    let message = [...results.matched][0];
+  outputNode.focus();
 
-    yield waitForContextMenu(contextMenu, message, copyFromPopup,
-                             testContextMenuCopy);
+  for (let result of results) {
+    let message = [...result.matched][0];
 
-    function copyFromPopup() {
+    yield waitForContextMenu(contextMenu, message, () => {
       let copyItem = contextMenu.querySelector("#cMenu_copy");
       copyItem.doCommand();
 
       let controller = top.document.commandDispatcher
                                    .getControllerForCommand("cmd_copy");
       is(controller.isCommandEnabled("cmd_copy"), true, "cmd_copy is enabled");
-    }
+    });
+
+    let clipboardText;
+
+    yield waitForClipboardPromise(
+      () => goDoCommand("cmd_copy"),
+      (str) => {
+        clipboardText = str;
+        return message.textContent == clipboardText;
+      }
+    );
+
+    ok(clipboardText, "Clipboard text was found and saved");
 
-    function testContextMenuCopy() {
-      waitForClipboard((str) => {
-        return message.textContent.trim() == str.trim();
-      }, () => {
-        goDoCommand("cmd_copy");
-      }, () => {}, () => {}
-      );
+    let lines = clipboardText.split("\n");
+    ok(lines.length > 0, "There is at least one newline in the message");
+    is(lines.pop(), "", "There is a newline at the end");
+    is(lines.length, result.lines, `There are ${result.lines} lines in the message`);
+
+    // Test the first line for "timestamp message repeat file:line"
+    let firstLine = lines.shift();
+    ok(/^[\d:.]+ .+ \d+ .+:\d+$/.test(firstLine),
+      "The message's first line has the right format");
+
+    // Test the remaining lines (stack trace) for "TABfunctionName sourceURL:line:col"
+    for (let line of lines) {
+      ok(/^\t.+ .+:\d+:\d+$/.test(line), "The stack trace line has the right format");
     }
+  }
 
-    yield closeConsole(tab);
-  }
-}
+  yield closeConsole(tab);
+  yield finishTest();
+});
--- a/devtools/client/webconsole/test/test-console.html
+++ b/devtools/client/webconsole/test/test-console.html
@@ -8,20 +8,27 @@
       };
 
       function test() {
         var str = "Dolske Digs Bacon, Now and Forevermore."
         for (var i=0; i < 5; i++) {
           console.log(str);
         }
       }
+
+      function testTrace() {
+        console.log("bug 1100562");
+        console.trace();
+      }
+
       console.info("INLINE SCRIPT:");
       test();
       console.warn("I'm warning you, he will eat up all yr bacon.");
       console.error("Error Message");
     </script>
   </head>
   <body>
     <h1 id="header">Heads Up Display Demo</h1>
     <button onclick="test();">Log stuff about Dolske</button>
+    <button id="testTrace" onclick="testTrace();">Log stuff with stacktrace</button>
     <div id="myDiv"></div>
   </body>
 </html>
--- a/devtools/shared/l10n.js
+++ b/devtools/shared/l10n.js
@@ -1,44 +1,55 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const parsePropertiesFile = require("devtools/shared/node-properties/node-properties");
 const { sprintf } = require("devtools/shared/sprintfjs/sprintf");
 
+const propertiesMap = {};
+
+/**
+ * Memoized getter for properties files that ensures a given url is only required and
+ * parsed once.
+ *
+ * @param {String} url
+ *        The URL of the properties file to parse.
+ * @return {Object} parsed properties mapped in an object.
+ */
+function getProperties(url) {
+  if (!propertiesMap[url]) {
+    propertiesMap[url] = parsePropertiesFile(require(`raw!${url}`));
+  }
+
+  return propertiesMap[url];
+}
+
 /**
  * Localization convenience methods.
  *
  * @param string stringBundleName
  *        The desired string bundle's name.
  */
 function LocalizationHelper(stringBundleName) {
   this.stringBundleName = stringBundleName;
 }
 
 LocalizationHelper.prototype = {
-  get properties() {
-    if (!this._properties) {
-      this._properties = parsePropertiesFile(require(`raw!${this.stringBundleName}`));
-    }
-
-    return this._properties;
-  },
-
   /**
    * L10N shortcut function.
    *
    * @param string name
    * @return string
    */
   getStr: function (name) {
-    if (name in this.properties) {
-      return this.properties[name];
+    let properties = getProperties(this.stringBundleName);
+    if (name in properties) {
+      return properties[name];
     }
 
     throw new Error("No localization found for [" + name + "]");
   },
 
   /**
    * L10N shortcut function.
    *
@@ -98,16 +109,73 @@ LocalizationHelper.prototype = {
 
     return number.toLocaleString(undefined, {
       maximumFractionDigits: decimals,
       minimumFractionDigits: decimals
     });
   }
 };
 
+function getPropertiesForNode(node) {
+  let bundleEl = node.closest("[data-localization-bundle]");
+  if (!bundleEl) {
+    return null;
+  }
+
+  let propertiesUrl = bundleEl.getAttribute("data-localization-bundle");
+  return getProperties(propertiesUrl);
+}
+
+/**
+ * Translate existing markup annotated with data-localization attributes.
+ *
+ * How to use data-localization in markup:
+ *
+ *   <div data-localization="content=myContent;title=myTitle"/>
+ *
+ * The data-localization attribute identifies an element as being localizable.
+ * The content of the attribute is semi-colon separated list of descriptors.
+ * - "title=myTitle" means the "title" attribute should be replaced with the localized
+ *   string corresponding to the key "myTitle".
+ * - "content=myContent" means the text content of the node should be replaced by the
+ *   string corresponding to "myContent"
+ *
+ * How to define the localization bundle in markup:
+ *
+ *   <div data-localization-bundle="url/to/my.properties">
+ *     [...]
+ *       <div data-localization="content=myContent;title=myTitle"/>
+ *
+ * Set the data-localization-bundle on an ancestor of the nodes that should be localized.
+ *
+ * @param {Element} root
+ *        The root node to use for the localization
+ */
+function localizeMarkup(root) {
+  let elements = root.querySelectorAll("[data-localization]");
+  for (let element of elements) {
+    let properties = getPropertiesForNode(element);
+    if (!properties) {
+      continue;
+    }
+
+    let attributes = element.getAttribute("data-localization").split(";");
+    for (let attribute of attributes) {
+      let [name, value] = attribute.trim().split("=");
+      if (name === "content") {
+        element.textContent = properties[value];
+      } else {
+        element.setAttribute(name, properties[value]);
+      }
+    }
+
+    element.removeAttribute("data-localization");
+  }
+}
+
 const sharedL10N = new LocalizationHelper("devtools-shared/locale/shared.properties");
 const ELLIPSIS = sharedL10N.getStr("ellipsis");
 
 /**
  * A helper for having the same interface as LocalizationHelper, but for more
  * than one file. Useful for abstracting l10n string locations.
  */
 function MultiLocalizationHelper(...stringBundleNames) {
@@ -135,10 +203,11 @@ function MultiLocalizationHelper(...stri
           }
         }
         return null;
       };
     });
 }
 
 exports.LocalizationHelper = LocalizationHelper;
+exports.localizeMarkup = localizeMarkup;
 exports.MultiLocalizationHelper = MultiLocalizationHelper;
 exports.ELLIPSIS = ELLIPSIS;
--- a/devtools/shared/tests/browser/browser.ini
+++ b/devtools/shared/tests/browser/browser.ini
@@ -1,7 +1,8 @@
 [DEFAULT]
 tags = devtools
 subsuite = devtools
 support-files =
   ../../../server/tests/browser/head.js
 
 [browser_async_storage.js]
+[browser_l10n_localizeMarkup.js]
new file mode 100644
--- /dev/null
+++ b/devtools/shared/tests/browser/browser_l10n_localizeMarkup.js
@@ -0,0 +1,54 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the markup localization works properly.
+
+const { localizeMarkup, LocalizationHelper } = require("devtools/shared/l10n");
+
+add_task(function* () {
+  info("Check that the strings used for this test are still valid");
+  let INSPECTOR_L10N = new LocalizationHelper("devtools/locale/inspector.properties");
+  let TOOLBOX_L10N = new LocalizationHelper("devtools/locale/toolbox.properties");
+  let str1 = INSPECTOR_L10N.getStr("inspector.label");
+  let str2 = INSPECTOR_L10N.getStr("inspector.commandkey");
+  let str3 = TOOLBOX_L10N.getStr("toolbox.defaultTitle");
+  ok(str1 && str2 && str3, "If this failed, strings should be updated in the test");
+
+  info("Create the test markup");
+  let div = document.createElement("div");
+  div.innerHTML =
+  `<div data-localization-bundle="devtools/locale/inspector.properties">
+     <div id="d0" data-localization="content=inspector.someInvalidKey"></div>
+     <div id="d1" data-localization="content=inspector.label">Text will disappear</div>
+     <div id="d2" data-localization="content=inspector.label;title=inspector.commandkey">
+     </div>
+     <!-- keep the following data-localization on two separate lines -->
+     <div id="d3" data-localization="content=inspector.label;
+                                     title=inspector.commandkey"></div>
+     <div id="d4" data-localization="aria-label=inspector.label">Some content</div>
+     <div data-localization-bundle="devtools/locale/toolbox.properties">
+       <div id="d5" data-localization="content=toolbox.defaultTitle"></div>
+     </div>
+   </div>
+  `;
+
+  info("Use localization helper to localize the test markup");
+  localizeMarkup(div);
+
+  let div1 = div.querySelector("#d1");
+  let div2 = div.querySelector("#d2");
+  let div3 = div.querySelector("#d3");
+  let div4 = div.querySelector("#d4");
+  let div5 = div.querySelector("#d5");
+
+  is(div1.innerHTML, str1, "The content of #d1 is localized");
+  is(div2.innerHTML, str1, "The content of #d2 is localized");
+  is(div2.getAttribute("title"), str2, "The title of #d2 is localized");
+  is(div3.innerHTML, str1, "The content of #d3 is localized");
+  is(div3.getAttribute("title"), str2, "The title of #d3 is localized");
+  is(div4.innerHTML, "Some content", "The content of #d4 is not replaced");
+  is(div4.getAttribute("aria-label"), str1, "The aria-label of #d4 is localized");
+  is(div5.innerHTML, str3, "The content of #d5 is localized with another bundle");
+});
--- a/dom/animation/AnimationEffectReadOnly.cpp
+++ b/dom/animation/AnimationEffectReadOnly.cpp
@@ -138,39 +138,45 @@ AnimationEffectReadOnly::GetComputedTimi
   }
   const TimeDuration& localTime = aLocalTime.Value();
 
   // Calculate the time within the active interval.
   // https://w3c.github.io/web-animations/#active-time
   StickyTimeDuration activeTime;
 
   StickyTimeDuration beforeActiveBoundary =
-    std::min(StickyTimeDuration(aTiming.mDelay), result.mEndTime);
+    std::max(std::min(StickyTimeDuration(aTiming.mDelay), result.mEndTime),
+             zeroDuration);
+
   StickyTimeDuration activeAfterBoundary =
-    std::min(StickyTimeDuration(aTiming.mDelay + result.mActiveDuration),
-             result.mEndTime);
+    std::max(std::min(StickyTimeDuration(aTiming.mDelay +
+                                         result.mActiveDuration),
+                      result.mEndTime),
+             zeroDuration);
 
   if (localTime > activeAfterBoundary ||
       (aPlaybackRate >= 0 && localTime == activeAfterBoundary)) {
     result.mPhase = ComputedTiming::AnimationPhase::After;
     if (!result.FillsForwards()) {
       // The animation isn't active or filling at this time.
       return result;
     }
-    activeTime = std::max(std::min(result.mActiveDuration,
-                                   result.mActiveDuration + aTiming.mEndDelay),
-                          zeroDuration);
+    activeTime =
+      std::max(std::min(StickyTimeDuration(localTime - aTiming.mDelay),
+                        result.mActiveDuration),
+               zeroDuration);
   } else if (localTime < beforeActiveBoundary ||
              (aPlaybackRate < 0 && localTime == beforeActiveBoundary)) {
     result.mPhase = ComputedTiming::AnimationPhase::Before;
     if (!result.FillsBackwards()) {
       // The animation isn't active or filling at this time.
       return result;
     }
-    // activeTime is zero
+    activeTime = std::max(StickyTimeDuration(localTime - aTiming.mDelay),
+                          zeroDuration);
   } else {
     MOZ_ASSERT(result.mActiveDuration != zeroDuration,
                "How can we be in the middle of a zero-duration interval?");
     result.mPhase = ComputedTiming::AnimationPhase::Active;
     activeTime = localTime - aTiming.mDelay;
   }
 
   // Convert active time to a multiple of iterations.
--- a/dom/animation/TimingParams.h
+++ b/dom/animation/TimingParams.h
@@ -109,17 +109,18 @@ struct TimingParams
       return zeroDuration;
     }
 
     return mDuration->MultDouble(mIterations);
   }
 
   StickyTimeDuration EndTime() const
   {
-    return mDelay + ActiveDuration() + mEndDelay;
+    return std::max(mDelay + ActiveDuration() + mEndDelay,
+                    StickyTimeDuration());
   }
 
   bool operator==(const TimingParams& aOther) const;
   bool operator!=(const TimingParams& aOther) const
   {
     return !(*this == aOther);
   }
 };
--- a/dom/animation/test/css-animations/file_animation-computed-timing.html
+++ b/dom/animation/test/css-animations/file_animation-computed-timing.html
@@ -187,17 +187,17 @@ test(function(t) {
                 'Initial value of endTime');
 }, 'endTime of an infinitely repeating zero-duration animation');
 
 test(function(t) {
   // Fill forwards so div.getAnimations()[0] won't return an
   // undefined value.
   var div = addDiv(t, {style: 'animation: moveAnimation 10s -100s forwards'});
   var effect = div.getAnimations()[0].effect;
-  assert_equals(effect.getComputedTiming().endTime, -90 * MS_PER_SEC,
+  assert_equals(effect.getComputedTiming().endTime, 0,
                 'Initial value of endTime');
 }, 'endTime of an animation that finishes before its startTime');
 
 
 // --------------------
 // activeDuration
 // = iteration duration * iteration count
 // --------------------
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -274,61 +274,76 @@ public:
                                                 NS_LITERAL_STRING("error"),
                                                 false,
                                                 false);
   }
 };
 
 /**
  * This listener observes the first video frame to arrive with a non-empty size,
- * and calls HTMLMediaElement::ReceivedMediaStreamInitialSize() with that size.
+ * and calls HTMLMediaElement::UpdateInitialMediaSize() with that size.
  */
 class HTMLMediaElement::StreamSizeListener : public DirectMediaStreamTrackListener {
 public:
   explicit StreamSizeListener(HTMLMediaElement* aElement) :
     mElement(aElement),
     mInitialSizeFound(false)
   {}
+
   void Forget() { mElement = nullptr; }
 
   void ReceivedSize(gfx::IntSize aSize)
   {
+    MOZ_ASSERT(NS_IsMainThread());
+
     if (!mElement) {
       return;
     }
+
     RefPtr<HTMLMediaElement> deathGrip = mElement;
     mElement->UpdateInitialMediaSize(aSize);
   }
 
   void NotifyRealtimeTrackData(MediaStreamGraph* aGraph,
                                StreamTime aTrackOffset,
                                const MediaSegment& aMedia) override
   {
-    if (mInitialSizeFound || aMedia.GetType() != MediaSegment::VIDEO) {
+    if (mInitialSizeFound) {
       return;
     }
+
+    if (aMedia.GetType() != MediaSegment::VIDEO) {
+      MOZ_ASSERT(false, "Should only lock on to a video track");
+      return;
+    }
+
     const VideoSegment& video = static_cast<const VideoSegment&>(aMedia);
     for (VideoSegment::ConstChunkIterator c(video); !c.IsEnded(); c.Next()) {
       if (c->mFrame.GetIntrinsicSize() != gfx::IntSize(0,0)) {
         mInitialSizeFound = true;
         nsCOMPtr<nsIRunnable> event =
-          NewRunnableMethod<gfx::IntSize>(
-              this, &StreamSizeListener::ReceivedSize,
-              c->mFrame.GetIntrinsicSize());
-        aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
+          NewRunnableMethod<gfx::IntSize>(this, &StreamSizeListener::ReceivedSize,
+                                          c->mFrame.GetIntrinsicSize());
+        // This is fine to dispatch straight to main thread (instead of via
+        // ...AfterStreamUpdate()) since it reflects state of the element,
+        // not the stream. Events reflecting stream or track state should be
+        // dispatched so their order is preserved.
+        NS_DispatchToMainThread(event.forget());
         return;
       }
     }
   }
 
 private:
   // These fields may only be accessed on the main thread
   HTMLMediaElement* mElement;
 
-  // These fields may only be accessed on the MSG thread
+  // These fields may only be accessed on the MSG's appending thread.
+  // (this is a direct listener so we get called by whoever is producing
+  // this track's data)
   bool mInitialSizeFound;
 };
 
 /**
  * There is a reference cycle involving this class: MediaLoadListener
  * holds a reference to the HTMLMediaElement, which holds a reference
  * to an nsIChannel, which holds a reference to this listener.
  * We break the reference cycle in OnStartRequest by clearing mElement.
@@ -2570,26 +2585,31 @@ HTMLMediaElement::CaptureStreamInternal(
     }
 
     // mAudioCaptured tells the user that the audio played by this media element
     // is being routed to the captureStreams *instead* of being played to
     // speakers.
     mAudioCaptured = true;
   }
 
+  if (mDecoder) {
+    out->mCapturingDecoder = true;
+    mDecoder->AddOutputStream(out->mStream->GetInputStream()->AsProcessedStream(),
+                              aFinishWhenEnded);
+  } else if (mSrcStream) {
+    out->mCapturingMediaStream = true;
+  }
+
   if (mReadyState == HAVE_NOTHING) {
-    // Do not expose the tracks directly before we have metadata.
+    // Do not expose the tracks until we have metadata.
     RefPtr<DOMMediaStream> result = out->mStream;
     return result.forget();
   }
 
   if (mDecoder) {
-    out->mCapturingDecoder = true;
-    mDecoder->AddOutputStream(out->mStream->GetInputStream()->AsProcessedStream(),
-                              aFinishWhenEnded);
     if (HasAudio()) {
       TrackID audioTrackId = mMediaInfo.mAudio.mTrackId;
       RefPtr<MediaStreamTrackSource> trackSource =
         getter->GetMediaStreamTrackSource(audioTrackId);
       RefPtr<MediaStreamTrack> track =
         out->mStream->CreateDOMTrack(audioTrackId, MediaSegment::AUDIO,
                                      trackSource);
       out->mStream->AddTrackInternal(track);
@@ -2605,32 +2625,16 @@ HTMLMediaElement::CaptureStreamInternal(
                                      trackSource);
       out->mStream->AddTrackInternal(track);
       LOG(LogLevel::Debug,
           ("Created video track %d for captured decoder", videoTrackId));
     }
   }
 
   if (mSrcStream) {
-    out->mCapturingMediaStream = true;
-    MediaStream* inputStream = out->mStream->GetInputStream();
-    if (!inputStream) {
-      NS_ERROR("No input stream");
-      RefPtr<DOMMediaStream> result = out->mStream;
-      return result.forget();
-    }
-
-    ProcessedMediaStream* processedInputStream =
-      inputStream->AsProcessedStream();
-    if (!processedInputStream) {
-      NS_ERROR("Input stream not a ProcessedMediaStream");
-      RefPtr<DOMMediaStream> result = out->mStream;
-      return result.forget();
-    }
-
     for (size_t i = 0; i < AudioTracks()->Length(); ++i) {
       AudioTrack* t = (*AudioTracks())[i];
       if (t->Enabled()) {
         AddCaptureMediaTrackToOutputStream(t, *out, false);
       }
     }
     if (IsVideo() && !out->mCapturingAudioOnly) {
       // Only add video tracks if we're a video element and the output stream
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -2549,17 +2549,17 @@ TabParent::GetAuthPrompt(uint32_t aPromp
   // of the dialogs works as it should when using tabs.
   nsCOMPtr<nsISupports> prompt;
   rv = wwatch->GetPrompt(window, iid, getter_AddRefs(prompt));
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsILoginManagerPrompter> prompter = do_QueryInterface(prompt);
   if (prompter) {
     nsCOMPtr<nsIDOMElement> browser = do_QueryInterface(mFrameElement);
-    prompter->SetE10sData(browser, nullptr);
+    prompter->SetBrowser(browser);
   }
 
   *aResult = prompt.forget().take();
   return NS_OK;
 }
 
 PColorPickerParent*
 TabParent::AllocPColorPickerParent(const nsString& aTitle,
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -237,17 +237,16 @@ MediaDecoderStateMachine::MediaDecoderSt
   mIsVideoPrerolling(false),
   mAudioCaptured(false),
   INIT_WATCHABLE(mAudioCompleted, false),
   INIT_WATCHABLE(mVideoCompleted, false),
   mNotifyMetadataBeforeFirstFrame(false),
   mQuickBuffering(false),
   mMinimizePreroll(false),
   mDecodeThreadWaiting(false),
-  mDecodingFirstFrame(true),
   mSentLoadedMetadataEvent(false),
   mSentFirstFrameLoadedEvent(false),
   mSentPlaybackEndedEvent(false),
   mVideoDecodeSuspended(false),
   mVideoDecodeSuspendTimer(mTaskQueue),
   mOutputStreamManager(new OutputStreamManager()),
   mResource(aDecoder->GetResource()),
   mAudioOffloading(false),
@@ -483,25 +482,26 @@ bool MediaDecoderStateMachine::HaveEnoug
 bool
 MediaDecoderStateMachine::NeedToDecodeVideo()
 {
   MOZ_ASSERT(OnTaskQueue());
   SAMPLE_LOG("NeedToDecodeVideo() isDec=%d minPrl=%d enufVid=%d",
              IsVideoDecoding(), mMinimizePreroll, HaveEnoughDecodedVideo());
   return IsVideoDecoding() &&
          mState != DECODER_STATE_SEEKING &&
-         ((IsDecodingFirstFrame() && VideoQueue().GetSize() == 0) ||
+         ((!mSentFirstFrameLoadedEvent && VideoQueue().GetSize() == 0) ||
           (!mMinimizePreroll && !HaveEnoughDecodedVideo()));
 }
 
 bool
 MediaDecoderStateMachine::NeedToSkipToNextKeyframe()
 {
   MOZ_ASSERT(OnTaskQueue());
-  if (IsDecodingFirstFrame()) {
+  // Don't skip when we're still decoding first frames.
+  if (!mSentFirstFrameLoadedEvent) {
     return false;
   }
   MOZ_ASSERT(mState == DECODER_STATE_DECODING ||
              mState == DECODER_STATE_BUFFERING ||
              mState == DECODER_STATE_SEEKING);
 
   // Since GetClock() can only be called after starting MediaSink, we return
   // false quickly if it is not started because we won't fall behind playback
@@ -552,17 +552,17 @@ bool
 MediaDecoderStateMachine::NeedToDecodeAudio()
 {
   MOZ_ASSERT(OnTaskQueue());
   SAMPLE_LOG("NeedToDecodeAudio() isDec=%d minPrl=%d enufAud=%d",
              IsAudioDecoding(), mMinimizePreroll, HaveEnoughDecodedAudio());
 
   return IsAudioDecoding() &&
          mState != DECODER_STATE_SEEKING &&
-         ((IsDecodingFirstFrame() && AudioQueue().GetSize() == 0) ||
+         ((!mSentFirstFrameLoadedEvent && AudioQueue().GetSize() == 0) ||
           (!mMinimizePreroll && !HaveEnoughDecodedAudio()));
 }
 
 void
 MediaDecoderStateMachine::OnAudioDecoded(MediaData* aAudioSample)
 {
   MOZ_ASSERT(OnTaskQueue());
   MOZ_ASSERT(mState != DECODER_STATE_SEEKING);
@@ -728,17 +728,17 @@ MediaDecoderStateMachine::OnNotDecoded(M
     }
   }
 }
 
 bool
 MediaDecoderStateMachine::MaybeFinishDecodeFirstFrame()
 {
   MOZ_ASSERT(OnTaskQueue());
-  if (!IsDecodingFirstFrame() ||
+  if (mSentFirstFrameLoadedEvent ||
       (IsAudioDecoding() && AudioQueue().GetSize() == 0) ||
       (IsVideoDecoding() && VideoQueue().GetSize() == 0)) {
     return false;
   }
   FinishDecodeFirstFrame();
   if (!mQueuedSeek.Exists()) {
     return false;
   }
@@ -785,17 +785,17 @@ MediaDecoderStateMachine::OnVideoDecoded
       // arrive, increase the amount of audio we buffer to ensure that we
       // don't run out of audio. This is unnecessary for async readers,
       // since they decode audio and video on different threads so they
       // are unlikely to run out of decoded audio.
       if (mReader->IsAsync()) {
         return;
       }
       TimeDuration decodeTime = TimeStamp::Now() - aDecodeStartTime;
-      if (!IsDecodingFirstFrame() &&
+      if (mSentFirstFrameLoadedEvent &&
           THRESHOLD_FACTOR * DurationToUsecs(decodeTime) > mLowAudioThresholdUsecs &&
           !HasLowUndecodedData())
       {
         mLowAudioThresholdUsecs =
           std::min(THRESHOLD_FACTOR * DurationToUsecs(decodeTime), mAmpleAudioThresholdUsecs);
         mAmpleAudioThresholdUsecs = std::max(THRESHOLD_FACTOR * mLowAudioThresholdUsecs,
                                               mAmpleAudioThresholdUsecs);
         DECODER_LOG("Slow video decode, set mLowAudioThresholdUsecs=%lld mAmpleAudioThresholdUsecs=%lld",
@@ -1072,17 +1072,16 @@ MediaDecoderStateMachine::ExitState(Stat
 }
 
 void
 MediaDecoderStateMachine::EnterState(State aState)
 {
   MOZ_ASSERT(OnTaskQueue());
   switch (aState) {
     case DECODER_STATE_DECODING_METADATA:
-      mDecodingFirstFrame = true;
       ReadMetadata();
       break;
     case DECODER_STATE_DORMANT:
       DiscardSeekTaskIfExist();
       if (IsPlaying()) {
         StopPlayback();
       }
       Reset();
@@ -1255,29 +1254,21 @@ MediaDecoderStateMachine::Shutdown()
 }
 
 void
 MediaDecoderStateMachine::StartDecoding()
 {
   MOZ_ASSERT(OnTaskQueue());
   MOZ_ASSERT(mState == DECODER_STATE_DECODING);
 
-  if (mDecodingFirstFrame && mSentFirstFrameLoadedEvent) {
-    // We're resuming from dormant state, so we don't need to request
-    // the first samples in order to determine the media start time,
-    // we have the start time from last time we loaded.
-    // FinishDecodeFirstFrame will be launched upon completion of the seek when
-    // we have data ready to play.
-    MOZ_ASSERT(mQueuedSeek.Exists() && mSentFirstFrameLoadedEvent,
-               "Return from dormant must have queued seek");
-
-    if (mQueuedSeek.Exists()) {
-      InitiateSeek(Move(mQueuedSeek));
-      return;
-    }
+  // Handle the pending seek now if we've decoded first frames. Otherwise it
+  // will be handled after decoding first frames.
+  if (mSentFirstFrameLoadedEvent && mQueuedSeek.Exists()) {
+    InitiateSeek(Move(mQueuedSeek));
+    return;
   }
 
   if (CheckIfDecodeComplete()) {
     SetState(DECODER_STATE_COMPLETED);
     return;
   }
 
   mDecodeStartTime = TimeStamp::Now();
@@ -1506,18 +1497,22 @@ MediaDecoderStateMachine::Seek(SeekTarge
   if (aTarget.IsNextFrame() && !HasVideo()) {
     DECODER_WARN("Ignore a NextFrameSeekTask on a media file without video track.");
     return MediaDecoder::SeekPromise::CreateAndReject(/* aIgnored = */ true, __func__);
   }
 
   MOZ_ASSERT(mState > DECODER_STATE_DECODING_METADATA,
                "We should have got duration already");
 
-  if (mState < DECODER_STATE_DECODING ||
-      (IsDecodingFirstFrame() && !mReader->ForceZeroStartTime())) {
+  // Can't seek until the start time is known.
+  bool hasStartTime = mSentFirstFrameLoadedEvent || mReader->ForceZeroStartTime();
+  // Can't seek when state is WAIT_FOR_CDM or DORMANT.
+  bool stateAllowed = mState >= DECODER_STATE_DECODING;
+
+  if (!stateAllowed || !hasStartTime) {
     DECODER_LOG("Seek() Not Enough Data to continue at this stage, queuing seek");
     mQueuedSeek.RejectIfExists(__func__);
     mQueuedSeek.mTarget = aTarget;
     return mQueuedSeek.mPromise.Ensure(__func__);
   }
   mQueuedSeek.RejectIfExists(__func__);
 
   DECODER_LOG("Changed state to SEEKING (to %lld)", aTarget.GetTime().ToMicroseconds());
@@ -1901,18 +1896,18 @@ bool MediaDecoderStateMachine::HasLowUnd
 {
   MOZ_ASSERT(OnTaskQueue());
   return HasLowUndecodedData(mLowDataThresholdUsecs);
 }
 
 bool MediaDecoderStateMachine::HasLowUndecodedData(int64_t aUsecs)
 {
   MOZ_ASSERT(OnTaskQueue());
-  NS_ASSERTION(mState >= DECODER_STATE_DECODING && !IsDecodingFirstFrame(),
-               "Must have loaded first frame for mBuffered to be valid");
+  MOZ_ASSERT(mState >= DECODER_STATE_DECODING && mSentFirstFrameLoadedEvent,
+             "Must have loaded first frame for mBuffered to be valid");
 
   // If we don't have a duration, mBuffered is probably not going to have
   // a useful buffered range. Return false here so that we don't get stuck in
   // buffering mode for live streams.
   if (Duration().IsInfinite()) {
     return false;
   }
 
@@ -2069,22 +2064,16 @@ MediaDecoderStateMachine::EnqueueFirstFr
                              : MediaDecoderEventVisibility::Observable;
       self->mFirstFrameLoadedEvent.Notify(
         nsAutoPtr<MediaInfo>(new MediaInfo(self->mInfo)), visibility);
     },
     // Reject
     []() { MOZ_CRASH("Should not reach"); }));
 }
 
-bool
-MediaDecoderStateMachine::IsDecodingFirstFrame()
-{
-  return mState == DECODER_STATE_DECODING && mDecodingFirstFrame;
-}
-
 void
 MediaDecoderStateMachine::FinishDecodeFirstFrame()
 {
   MOZ_ASSERT(OnTaskQueue());
   DECODER_LOG("FinishDecodeFirstFrame");
 
   if (!mSentFirstFrameLoadedEvent) {
     mMediaSink->Redraw(mInfo.mVideo);
@@ -2101,19 +2090,18 @@ MediaDecoderStateMachine::FinishDecodeFi
 
   // Get potentially updated metadata
   mReader->ReadUpdatedMetadata(&mInfo);
 
   if (!mNotifyMetadataBeforeFirstFrame) {
     // If we didn't have duration and/or start time before, we should now.
     EnqueueLoadedMetadataEvent();
   }
+
   EnqueueFirstFrameLoadedEvent();
-
-  mDecodingFirstFrame = false;
 }
 
 void
 MediaDecoderStateMachine::SeekCompleted()
 {
   MOZ_ASSERT(OnTaskQueue());
   MOZ_ASSERT(mState == DECODER_STATE_SEEKING);
 
@@ -2168,19 +2156,21 @@ MediaDecoderStateMachine::SeekCompleted(
   // Reset the MediaDecoderReaderWrapper's callbask.
   DiscardSeekTaskIfExist();
 
   // NOTE: Discarding the mSeekTask must be done before here. The following code
   // might ask the MediaDecoderReaderWrapper to request media data, however, the
   // SeekTask::Discard() will ask MediaDecoderReaderWrapper to discard media
   // data requests.
 
-  if (mDecodingFirstFrame) {
-    // We were resuming from dormant, or initiated a seek early.
-    // We can fire loadeddata now.
+  // Notify FirstFrameLoaded now if we haven't since we've decoded some data
+  // for readyState to transition to HAVE_CURRENT_DATA and fire 'loadeddata'.
+  if (!mSentFirstFrameLoadedEvent) {
+    // Only MSE can start seeking before finishing decoding first frames.
+    MOZ_ASSERT(mReader->ForceZeroStartTime());
     FinishDecodeFirstFrame();
   }
 
   // Ensure timestamps are up to date.
   UpdatePlaybackPositionInternal(newCurrentTime);
 
   // Try to decode another frame to detect if we're at the end...
   DECODER_LOG("Seek completed, mCurrentPosition=%lld", mCurrentPosition.Ref());
@@ -2268,19 +2258,18 @@ nsresult MediaDecoderStateMachine::RunSt
   switch (mState) {
     case DECODER_STATE_SHUTDOWN:
     case DECODER_STATE_DORMANT:
     case DECODER_STATE_WAIT_FOR_CDM:
     case DECODER_STATE_DECODING_METADATA:
       return NS_OK;
 
     case DECODER_STATE_DECODING: {
-      if (IsDecodingFirstFrame()) {
-        // We haven't completed decoding our first frames, we can't start
-        // playback yet.
+      // Can't start playback until having decoded first frames.
+      if (!mSentFirstFrameLoadedEvent) {
         return NS_OK;
       }
       if (mPlayState != MediaDecoder::PLAY_STATE_PLAYING && IsPlaying())
       {
         // We're playing, but the element/decoder is in paused state. Stop
         // playing!
         StopPlayback();
       }
@@ -2487,17 +2476,17 @@ MediaDecoderStateMachine::UpdatePlayback
 }
 
 void MediaDecoderStateMachine::UpdateNextFrameStatus()
 {
   MOZ_ASSERT(OnTaskQueue());
 
   MediaDecoderOwner::NextFrameStatus status;
   const char* statusString;
-  if (mState <= DECODER_STATE_WAIT_FOR_CDM || IsDecodingFirstFrame()) {
+  if (mState < DECODER_STATE_DECODING || !mSentFirstFrameLoadedEvent) {
     status = MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE;
     statusString = "NEXT_FRAME_UNAVAILABLE";
   } else if (IsBuffering()) {
     status = MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_BUFFERING;
     statusString = "NEXT_FRAME_UNAVAILABLE_BUFFERING";
   } else if (IsSeeking()) {
     status = MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_SEEKING;
     statusString = "NEXT_FRAME_UNAVAILABLE_SEEKING";
@@ -2825,22 +2814,22 @@ MediaDecoderStateMachine::DumpDebugInfo(
   MOZ_ASSERT(NS_IsMainThread());
 
   // It is fine to capture a raw pointer here because MediaDecoder only call
   // this function before shutdown begins.
   nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([this] () {
     mMediaSink->DumpDebugInfo();
     DUMP_LOG(
       "GetMediaTime=%lld GetClock=%lld mMediaSink=%p "
-      "mState=%s mPlayState=%d mDecodingFirstFrame=%d IsPlaying=%d "
+      "mState=%s mPlayState=%d mSentFirstFrameLoadedEvent=%d IsPlaying=%d "
       "mAudioStatus=%s mVideoStatus=%s mDecodedAudioEndTime=%lld mDecodedVideoEndTime=%lld "
       "mIsAudioPrerolling=%d mIsVideoPrerolling=%d "
       "mAudioCompleted=%d mVideoCompleted=%d",
       GetMediaTime(), mMediaSink->IsStarted() ? GetClock() : -1, mMediaSink.get(),
-      ToStateStr(), mPlayState.Ref(), mDecodingFirstFrame, IsPlaying(),
+      ToStateStr(), mPlayState.Ref(), mSentFirstFrameLoadedEvent, IsPlaying(),
       AudioRequestStatus(), VideoRequestStatus(), mDecodedAudioEndTime, mDecodedVideoEndTime,
       mIsAudioPrerolling, mIsVideoPrerolling, mAudioCompleted.Ref(), mVideoCompleted.Ref());
   });
 
   OwnerThread()->DispatchStateChange(r.forget());
 }
 
 void MediaDecoderStateMachine::AddOutputStream(ProcessedMediaStream* aStream,
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -562,18 +562,17 @@ protected:
   void OnMetadataRead(MetadataHolder* aMetadata);
   void OnMetadataNotRead(ReadMetadataFailureReason aReason);
 
   // Checks whether we're finished decoding first audio and/or video packets.
   // If so will trigger firing loadeddata event.
   // If there are any queued seek, will change state to DECODER_STATE_SEEKING
   // and return true.
   bool MaybeFinishDecodeFirstFrame();
-  // Return true if we are currently decoding the first frames.
-  bool IsDecodingFirstFrame();
+
   void FinishDecodeFirstFrame();
 
   // Completes the seek operation, moves onto the next appropriate state.
   void SeekCompleted();
 
   // Queries our state to see whether the decode has finished for all streams.
   bool CheckIfDecodeComplete();
 
@@ -880,28 +879,24 @@ private:
 
   nsAutoPtr<MetadataTags> mMetadataTags;
 
   mozilla::MediaMetadataManager mMetadataManager;
 
   // Track our request to update the buffered ranges
   MozPromiseRequestHolder<MediaDecoderReader::BufferedUpdatePromise> mBufferedUpdateRequest;
 
-  // True if we need to call FinishDecodeFirstFrame() upon frame decoding
-  // succeeding.
-  bool mDecodingFirstFrame;
-
   // True if we are back from DECODER_STATE_DORMANT state and
   // LoadedMetadataEvent was already sent.
   bool mSentLoadedMetadataEvent;
-  // True if we are back from DECODER_STATE_DORMANT state and
-  // FirstFrameLoadedEvent was already sent, then we can skip
-  // SetStartTime because the mStartTime already set before. Also we don't need
-  // to decode any audio/video since the MediaDecoder will trigger a seek
-  // operation soon.
+
+  // True if we've decoded first frames (thus having the start time) and
+  // notified the FirstFrameLoaded event. Note we can't initiate seek until the
+  // start time is known which happens when the first frames are decoded or we
+  // are playing an MSE stream (the start time is always assumed 0).
   bool mSentFirstFrameLoadedEvent;
 
   bool mSentPlaybackEndedEvent;
 
   // True if video decoding is suspended.
   bool mVideoDecodeSuspended;
 
   // Track enabling video decode suspension via timer
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -57,19 +57,19 @@ TrackTypeToStr(TrackInfo::TrackType aTra
   }
 }
 
 MediaFormatReader::MediaFormatReader(AbstractMediaDecoder* aDecoder,
                                      MediaDataDemuxer* aDemuxer,
                                      VideoFrameContainer* aVideoFrameContainer,
                                      layers::LayersBackend aLayersBackend)
   : MediaDecoderReader(aDecoder)
-  , mAudio(this, MediaData::AUDIO_DATA, Preferences::GetUint("media.audio-decode-ahead", 2),
+  , mAudio(this, MediaData::AUDIO_DATA,
            Preferences::GetUint("media.audio-max-decode-error", 3))
-  , mVideo(this, MediaData::VIDEO_DATA, Preferences::GetUint("media.video-decode-ahead", 2),
+  , mVideo(this, MediaData::VIDEO_DATA,
            Preferences::GetUint("media.video-max-decode-error", 2))
   , mDemuxer(aDemuxer)
   , mDemuxerInitDone(false)
   , mLastReportedNumDecodedFrames(0)
   , mPreviousDecodedKeyframeTime_us(sNoPreviousDecodedKeyframe)
   , mLayersBackendType(aLayersBackend)
   , mInitDone(false)
   , mIsEncrypted(false)
@@ -557,17 +557,17 @@ MediaFormatReader::RequestVideoData(bool
   if (!mVideo.HasInternalSeekPending() &&
       ShouldSkip(aSkipToNextKeyframe, timeThreshold)) {
     RefPtr<MediaDataPromise> p = mVideo.EnsurePromise(__func__);
     SkipVideoDemuxToNextKeyFrame(timeThreshold);
     return p;
   }
 
   RefPtr<MediaDataPromise> p = mVideo.EnsurePromise(__func__);
-  NotifyDecodingRequested(TrackInfo::kVideoTrack);
+  ScheduleUpdate(TrackInfo::kVideoTrack);
 
   return p;
 }
 
 void
 MediaFormatReader::OnDemuxFailed(TrackType aTrack, DemuxerFailureReason aFailure)
 {
   MOZ_ASSERT(OnTaskQueue());
@@ -601,17 +601,16 @@ MediaFormatReader::OnDemuxFailed(TrackTy
       MOZ_ASSERT(false);
       break;
   }
 }
 
 void
 MediaFormatReader::DoDemuxVideo()
 {
-  // TODO Use DecodeAhead value rather than 1.
   mVideo.mDemuxRequest.Begin(mVideo.mTrackDemuxer->GetSamples(1)
                       ->Then(OwnerThread(), __func__, this,
                              &MediaFormatReader::OnVideoDemuxCompleted,
                              &MediaFormatReader::OnVideoDemuxFailed));
 }
 
 void
 MediaFormatReader::OnVideoDemuxCompleted(RefPtr<MediaTrackDemuxer::SamplesHolder> aSamples)
@@ -652,25 +651,24 @@ MediaFormatReader::RequestAudioData()
   }
 
   if (mShutdown) {
     NS_WARNING("RequestAudioData on shutdown MediaFormatReader!");
     return MediaDataPromise::CreateAndReject(CANCELED, __func__);
   }
 
   RefPtr<MediaDataPromise> p = mAudio.EnsurePromise(__func__);
-  NotifyDecodingRequested(TrackInfo::kAudioTrack);
+  ScheduleUpdate(TrackInfo::kAudioTrack);
 
   return p;
 }
 
 void
 MediaFormatReader::DoDemuxAudio()
 {
-  // TODO Use DecodeAhead value rather than 1.
   mAudio.mDemuxRequest.Begin(mAudio.mTrackDemuxer->GetSamples(1)
                       ->Then(OwnerThread(), __func__, this,
                              &MediaFormatReader::OnAudioDemuxCompleted,
                              &MediaFormatReader::OnAudioDemuxFailed));
 }
 
 void
 MediaFormatReader::OnAudioDemuxCompleted(RefPtr<MediaTrackDemuxer::SamplesHolder> aSamples)
@@ -701,17 +699,17 @@ MediaFormatReader::NotifyNewOutput(Track
 }
 
 void
 MediaFormatReader::NotifyInputExhausted(TrackType aTrack)
 {
   MOZ_ASSERT(OnTaskQueue());
   LOGV("Decoder has requested more %s data", TrackTypeToStr(aTrack));
   auto& decoder = GetDecoderData(aTrack);
-  decoder.mInputExhausted = true;
+  decoder.mDecodePending = false;
   ScheduleUpdate(aTrack);
 }
 
 void
 MediaFormatReader::NotifyDrainComplete(TrackType aTrack)
 {
   MOZ_ASSERT(OnTaskQueue());
   auto& decoder = GetDecoderData(aTrack);
@@ -750,43 +748,31 @@ void
 MediaFormatReader::NotifyEndOfStream(TrackType aTrack)
 {
   MOZ_ASSERT(OnTaskQueue());
   auto& decoder = GetDecoderData(aTrack);
   decoder.mDemuxEOS = true;
   ScheduleUpdate(aTrack);
 }
 
-void
-MediaFormatReader::NotifyDecodingRequested(TrackType aTrack)
-{
-  MOZ_ASSERT(OnTaskQueue());
-  auto& decoder = GetDecoderData(aTrack);
-  decoder.mDecodingRequested = true;
-  ScheduleUpdate(aTrack);
-}
-
 bool
 MediaFormatReader::NeedInput(DecoderData& aDecoder)
 {
-  // We try to keep a few more compressed samples input than decoded samples
-  // have been output, provided the state machine has requested we send it a
-  // decoded sample. To account for H.264 streams which may require a longer
-  // run of input than we input, decoders fire an "input exhausted" callback,
-  // which overrides our "few more samples" threshold.
+  // To account for H.264 streams which may require a longer
+  // run of input than we input, decoders fire an "input exhausted" callback.
+  // The decoder will not be fed a new raw sample until InputExhausted
+  // has been called.
   return
+    (aDecoder.HasPromise() || aDecoder.mTimeThreshold.isSome()) &&
     !aDecoder.HasPendingDrain() &&
     !aDecoder.HasFatalError() &&
-    aDecoder.mDecodingRequested &&
     !aDecoder.mDemuxRequest.Exists() &&
+    !aDecoder.mOutput.Length() &&
     !aDecoder.HasInternalSeekPending() &&
-    aDecoder.mOutput.Length() <= aDecoder.mDecodeAhead &&
-    (aDecoder.mInputExhausted || !aDecoder.mQueuedSamples.IsEmpty() ||
-     aDecoder.mTimeThreshold.isSome() ||
-     aDecoder.mNumSamplesInput - aDecoder.mNumSamplesOutput <= aDecoder.mDecodeAhead);
+    !aDecoder.mDecodePending;
 }
 
 void
 MediaFormatReader::ScheduleUpdate(TrackType aTrack)
 {
   MOZ_ASSERT(OnTaskQueue());
   if (mShutdown) {
     return;
@@ -927,16 +913,17 @@ MediaFormatReader::DecodeDemuxedSamples(
                                         MediaRawData* aSample)
 {
   MOZ_ASSERT(OnTaskQueue());
   auto& decoder = GetDecoderData(aTrack);
   if (NS_FAILED(decoder.mDecoder->Input(aSample))) {
       LOG("Unable to pass frame to decoder");
       return false;
   }
+  decoder.mDecodePending = true;
   return true;
 }
 
 void
 MediaFormatReader::HandleDemuxedSamples(TrackType aTrack,
                                         AbstractMediaDecoder::AutoNotifyDecoded& aA)
 {
   MOZ_ASSERT(OnTaskQueue());
@@ -1002,17 +989,17 @@ MediaFormatReader::HandleDemuxedSamples(
       decoder.mLastStreamSourceID = info->GetID();
       decoder.mNextStreamSourceID.reset();
       // Reset will clear our array of queued samples. So make a copy now.
       nsTArray<RefPtr<MediaRawData>> samples{decoder.mQueuedSamples};
       Reset(aTrack);
       decoder.ShutdownDecoder();
       if (sample->mKeyframe) {
         decoder.mQueuedSamples.AppendElements(Move(samples));
-        NotifyDecodingRequested(aTrack);
+        ScheduleUpdate(aTrack);
       } else {
         TimeInterval time =
           TimeInterval(TimeUnit::FromMicroseconds(sample->mTime),
                        TimeUnit::FromMicroseconds(sample->GetEndTime()));
         InternalSeekTarget seekTarget =
           decoder.mTimeThreshold.refOr(InternalSeekTarget(time, false));
         LOG("Stream change occurred on a non-keyframe. Seeking to:%lld",
             sample->mTime);
@@ -1040,19 +1027,16 @@ MediaFormatReader::HandleDemuxedSamples(
     decoder.mQueuedSamples.RemoveElementAt(0);
     if (mDemuxOnly) {
       // If demuxed-only case, ReturnOutput will resolve with one demuxed data.
       // Then we should stop doing the iteration.
       return;
     }
     samplesPending = true;
   }
-
-  // We have serviced the decoder's request for more data.
-  decoder.mInputExhausted = false;
 }
 
 void
 MediaFormatReader::InternalSeek(TrackType aTrack, const InternalSeekTarget& aTarget)
 {
   MOZ_ASSERT(OnTaskQueue());
   LOG("%s internal seek to %f",
       TrackTypeToStr(aTrack), aTarget.Time().ToSeconds());
@@ -1066,17 +1050,17 @@ MediaFormatReader::InternalSeek(TrackTyp
              ->Then(OwnerThread(), __func__,
                     [self, aTrack] (media::TimeUnit aTime) {
                       auto& decoder = self->GetDecoderData(aTrack);
                       decoder.mSeekRequest.Complete();
                       MOZ_ASSERT(decoder.mTimeThreshold,
                                  "Seek promise must be disconnected when timethreshold is reset");
                       decoder.mTimeThreshold.ref().mHasSeeked = true;
                       self->SetVideoDecodeThreshold();
-                      self->NotifyDecodingRequested(aTrack);
+                      self->ScheduleUpdate(aTrack);
                     },
                     [self, aTrack] (DemuxerFailureReason aResult) {
                       auto& decoder = self->GetDecoderData(aTrack);
                       decoder.mSeekRequest.Complete();
                       switch (aResult) {
                         case DemuxerFailureReason::WAITING_FOR_DATA:
                           self->NotifyWaitingForData(aTrack);
                           break;
@@ -1270,16 +1254,17 @@ MediaFormatReader::Update(TrackType aTra
 
   if (decoder.mNeedDraining) {
     DrainDecoder(aTrack);
     return;
   }
 
   if (decoder.mError &&
       decoder.mError.ref() == MediaDataDecoderError::DECODE_ERROR) {
+    decoder.mDecodePending = false;
     decoder.mError.reset();
     if (++decoder.mNumOfConsecutiveError > decoder.mMaxConsecutiveError) {
       NotifyError(aTrack);
       return;
     }
     LOG("%s decoded error count %d", TrackTypeToStr(aTrack),
                                      decoder.mNumOfConsecutiveError);
     media::TimeUnit nextKeyframe;
@@ -1287,21 +1272,21 @@ MediaFormatReader::Update(TrackType aTra
         NS_SUCCEEDED(decoder.mTrackDemuxer->GetNextRandomAccessPoint(&nextKeyframe))) {
       SkipVideoDemuxToNextKeyFrame(decoder.mLastSampleTime.refOr(TimeInterval()).Length());
       return;
     }
   }
 
   bool needInput = NeedInput(decoder);
 
-  LOGV("Update(%s) ni=%d no=%d ie=%d, in:%llu out:%llu qs=%u pending:%u waiting:%d ahead:%d sid:%u",
-       TrackTypeToStr(aTrack), needInput, needOutput, decoder.mInputExhausted,
+  LOGV("Update(%s) ni=%d no=%d ie=%d, in:%llu out:%llu qs=%u pending:%u waiting:%d promise:%d sid:%u",
+       TrackTypeToStr(aTrack), needInput, needOutput, decoder.mDecodePending,
        decoder.mNumSamplesInput, decoder.mNumSamplesOutput,
        uint32_t(size_t(decoder.mSizeOfQueue)), uint32_t(decoder.mOutput.Length()),
-       decoder.mWaitingForData, !decoder.HasPromise(), decoder.mLastStreamSourceID);
+       decoder.mWaitingForData, decoder.HasPromise(), decoder.mLastStreamSourceID);
 
   if (decoder.mWaitingForData &&
       (!decoder.mTimeThreshold || decoder.mTimeThreshold.ref().mWaiting)) {
     // Nothing more we can do at present.
     LOGV("Still waiting for data.");
     return;
   }
 
@@ -1572,17 +1557,17 @@ void
 MediaFormatReader::OnVideoSkipCompleted(uint32_t aSkipped)
 {
   MOZ_ASSERT(OnTaskQueue());
   LOG("Skipping succeeded, skipped %u frames", aSkipped);
   mSkipRequest.Complete();
 
   VideoSkipReset(aSkipped);
 
-  NotifyDecodingRequested(TrackInfo::kVideoTrack);
+  ScheduleUpdate(TrackInfo::kVideoTrack);
 }
 
 void
 MediaFormatReader::OnVideoSkipFailed(MediaTrackDemuxer::SkipFailureHolder aFailure)
 {
   MOZ_ASSERT(OnTaskQueue());
   LOG("Skipping failed, skipped %u frames", aFailure.mSkipped);
   mSkipRequest.Complete();
@@ -1590,17 +1575,17 @@ MediaFormatReader::OnVideoSkipFailed(Med
   switch (aFailure.mFailure) {
     case DemuxerFailureReason::END_OF_STREAM: MOZ_FALLTHROUGH;
     case DemuxerFailureReason::WAITING_FOR_DATA:
       // Some frames may have been output by the decoder since we initiated the
       // videoskip process and we know they would be late.
       DropDecodedSamples(TrackInfo::kVideoTrack);
       // We can't complete the skip operation, will just service a video frame
       // normally.
-      NotifyDecodingRequested(TrackInfo::kVideoTrack);
+      ScheduleUpdate(TrackInfo::kVideoTrack);
       break;
     case DemuxerFailureReason::CANCELED: MOZ_FALLTHROUGH;
     case DemuxerFailureReason::SHUTDOWN:
       if (mVideo.HasPromise()) {
         mVideo.RejectPromise(CANCELED, __func__);
       }
       break;
     default:
@@ -2008,22 +1993,21 @@ MediaFormatReader::GetMozDebugReaderData
     MonitorAutoLock mon(mVideo.mMonitor);
     videoName = mVideo.mDescription;
   }
 
   result += nsPrintfCString("audio decoder: %s\n", audioName);
   result += nsPrintfCString("audio frames decoded: %lld\n",
                             mAudio.mNumSamplesOutputTotal);
   if (HasAudio()) {
-    result += nsPrintfCString("audio state: ni=%d no=%d ie=%d demuxr:%d demuxq:%d decoder:%d tt:%f tths:%d in:%llu out:%llu qs=%u pending:%u waiting:%d sid:%u\n",
+    result += nsPrintfCString("audio state: ni=%d no=%d ie=%d demuxr:%d demuxq:%d tt:%f tths:%d in:%llu out:%llu qs=%u pending:%u waiting:%d sid:%u\n",
                               NeedInput(mAudio), mAudio.HasPromise(),
-                              mAudio.mInputExhausted,
+                              mAudio.mDecodePending,
                               mAudio.mDemuxRequest.Exists(),
                               int(mAudio.mQueuedSamples.Length()),
-                              mAudio.mDecodingRequested,
                               mAudio.mTimeThreshold
                               ? mAudio.mTimeThreshold.ref().Time().ToSeconds()
                               : -1.0,
                               mAudio.mTimeThreshold
                               ? mAudio.mTimeThreshold.ref().mHasSeeked
                               : -1,
                               mAudio.mNumSamplesInput, mAudio.mNumSamplesOutput,
                               unsigned(size_t(mAudio.mSizeOfQueue)),
@@ -2032,22 +2016,21 @@ MediaFormatReader::GetMozDebugReaderData
   }
   result += nsPrintfCString("video decoder: %s\n", videoName);
   result += nsPrintfCString("hardware video decoding: %s\n",
                             VideoIsHardwareAccelerated() ? "enabled" : "disabled");
   result += nsPrintfCString("video frames decoded: %lld (skipped:%lld)\n",
                             mVideo.mNumSamplesOutputTotal,
                             mVideo.mNumSamplesSkippedTotal);
   if (HasVideo()) {
-    result += nsPrintfCString("video state: ni=%d no=%d ie=%d demuxr:%d demuxq:%d decoder:%d tt:%f tths:%d in:%llu out:%llu qs=%u pending:%u waiting:%d sid:%u\n",
+    result += nsPrintfCString("video state: ni=%d no=%d ie=%d demuxr:%d demuxq:%d tt:%f tths:%d in:%llu out:%llu qs=%u pending:%u waiting:%d sid:%u\n",
                               NeedInput(mVideo), mVideo.HasPromise(),
-                              mVideo.mInputExhausted,
+                              mVideo.mDecodePending,
                               mVideo.mDemuxRequest.Exists(),
                               int(mVideo.mQueuedSamples.Length()),
-                              mVideo.mDecodingRequested,
                               mVideo.mTimeThreshold
                               ? mVideo.mTimeThreshold.ref().Time().ToSeconds()
                               : -1.0,
                               mVideo.mTimeThreshold
                               ? mVideo.mTimeThreshold.ref().mHasSeeked
                               : -1,
                               mVideo.mNumSamplesInput, mVideo.mNumSamplesOutput,
                               unsigned(size_t(mVideo.mSizeOfQueue)),
@@ -2075,14 +2058,14 @@ MediaFormatReader::SetBlankDecode(TrackT
 
   if (decoder.mIsBlankDecode == aIsBlankDecode) {
     return;
   }
 
   decoder.mIsBlankDecode = aIsBlankDecode;
   decoder.Flush();
   decoder.ShutdownDecoder();
-  NotifyDecodingRequested(TrackInfo::kVideoTrack); // Calls ScheduleUpdate().
+  ScheduleUpdate(TrackInfo::kVideoTrack);
 
   return;
 }
 
 } // namespace mozilla
--- a/dom/media/MediaFormatReader.h
+++ b/dom/media/MediaFormatReader.h
@@ -164,17 +164,16 @@ private:
   // Drain the current decoder.
   void DrainDecoder(TrackType aTrack);
   void NotifyNewOutput(TrackType aTrack, MediaData* aSample);
   void NotifyInputExhausted(TrackType aTrack);
   void NotifyDrainComplete(TrackType aTrack);
   void NotifyError(TrackType aTrack, MediaDataDecoderError aError = MediaDataDecoderError::FATAL_ERROR);
   void NotifyWaitingForData(TrackType aTrack);
   void NotifyEndOfStream(TrackType aTrack);
-  void NotifyDecodingRequested(TrackType aTrack);
 
   void ExtractCryptoInitData(nsTArray<uint8_t>& aInitData);
 
   // Initializes mLayersBackendType if possible.
   void InitLayersBackendType();
 
   // DecoderCallback proxies the MediaDataDecoderCallback calls to these
   // functions.
@@ -226,31 +225,28 @@ private:
   private:
     MediaFormatReader* mReader;
     TrackType mType;
   };
 
   struct DecoderData {
     DecoderData(MediaFormatReader* aOwner,
                 MediaData::Type aType,
-                uint32_t aDecodeAhead,
                 uint32_t aNumOfMaxError)
       : mOwner(aOwner)
       , mType(aType)
       , mMonitor("DecoderData")
       , mDescription("shutdown")
-      , mDecodeAhead(aDecodeAhead)
       , mUpdateScheduled(false)
       , mDemuxEOS(false)
       , mWaitingForData(false)
       , mReceivedNewData(false)
       , mDecoderInitialized(false)
-      , mDecodingRequested(false)
       , mOutputRequested(false)
-      , mInputExhausted(false)
+      , mDecodePending(false)
       , mNeedDraining(false)
       , mDraining(false)
       , mDrainComplete(false)
       , mNumOfConsecutiveError(0)
       , mMaxConsecutiveError(aNumOfMaxError)
       , mNumSamplesInput(0)
       , mNumSamplesOutput(0)
       , mNumSamplesOutputTotal(0)
@@ -283,17 +279,16 @@ private:
       if (mDecoder) {
         mDecoder->Shutdown();
       }
       mDescription = "shutdown";
       mDecoder = nullptr;
     }
 
     // Only accessed from reader's task queue.
-    uint32_t mDecodeAhead;
     bool mUpdateScheduled;
     bool mDemuxEOS;
     bool mWaitingForData;
     bool mReceivedNewData;
 
     // Pending seek.
     MozPromiseRequestHolder<MediaTrackDemuxer::SeekPromise> mSeekRequest;
 
@@ -307,21 +302,24 @@ private:
       return !mWaitingPromise.IsEmpty();
     }
 
     // MediaDataDecoder handler's variables.
     // Decoder initialization promise holder.
     MozPromiseRequestHolder<MediaDataDecoder::InitPromise> mInitPromise;
     // False when decoder is created. True when decoder Init() promise is resolved.
     bool mDecoderInitialized;
-    // Set when decoding can proceed. It is reset when a decoding promise is
-    // rejected or prior a seek operation.
-    bool mDecodingRequested;
     bool mOutputRequested;
-    bool mInputExhausted;
+    // Set to true once the MediaDataDecoder has been fed a compressed sample.
+    // No more sample will be passed to the decoder while true.
+    // mDecodePending is reset when:
+    // 1- The decoder returns a sample
+    // 2- The decoder calls InputExhausted
+    // 3- The decoder is Flushed or Reset.
+    bool mDecodePending;
     bool mNeedDraining;
     bool mDraining;
     bool mDrainComplete;
 
     bool HasPendingDrain() const
     {
       return mDraining || mDrainComplete;
     }
@@ -371,19 +369,18 @@ private:
     // Flush the decoder if present and reset decoding related data.
     // Decoding will be suspended until mInputRequested is set again.
     // Following a flush, the decoder is ready to accept any new data.
     void Flush()
     {
       if (mDecoder) {
         mDecoder->Flush();
       }
-      mDecodingRequested = false;
       mOutputRequested = false;
-      mInputExhausted = false;
+      mDecodePending = false;
       mOutput.Clear();
       mNumSamplesInput = 0;
       mNumSamplesOutput = 0;
       mSizeOfQueue = 0;
       mDraining = false;
       mDrainComplete = false;
     }
 
@@ -392,20 +389,19 @@ private:
     // Decoding will be suspended until mInputRequested is set again.
     // The track demuxer is *not* reset.
     void ResetState()
     {
       MOZ_ASSERT(mOwner->OnTaskQueue());
       mDemuxEOS = false;
       mWaitingForData = false;
       mQueuedSamples.Clear();
-      mDecodingRequested = false;
       mOutputRequested = false;
-      mInputExhausted = false;
       mNeedDraining = false;
+      mDecodePending = false;
       mDraining = false;
       mDrainComplete = false;
       mTimeThreshold.reset();
       mLastSampleTime.reset();
       mOutput.Clear();
       mNumSamplesInput = 0;
       mNumSamplesOutput = 0;
       mSizeOfQueue = 0;
@@ -436,19 +432,18 @@ private:
     bool mIsBlankDecode;
 
   };
 
   class DecoderDataWithPromise : public DecoderData {
   public:
     DecoderDataWithPromise(MediaFormatReader* aOwner,
                            MediaData::Type aType,
-                           uint32_t aDecodeAhead,
                            uint32_t aNumOfMaxError)
-      : DecoderData(aOwner, aType, aDecodeAhead, aNumOfMaxError)
+      : DecoderData(aOwner, aType, aNumOfMaxError)
       , mHasPromise(false)
 
     {}
 
     bool HasPromise() const override
     {
       return mHasPromise;
     }
@@ -467,17 +462,16 @@ private:
       mHasPromise = false;
     }
 
     void RejectPromise(MediaDecoderReader::NotDecodedReason aReason,
                        const char* aMethodName) override
     {
       MOZ_ASSERT(mOwner->OnTaskQueue());
       mPromise.Reject(aReason, aMethodName);
-      mDecodingRequested = false;
       mHasPromise = false;
     }
 
   private:
     MozPromiseHolder<MediaDataPromise> mPromise;
     Atomic<bool> mHasPromise;
   };
 
--- a/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -1072,16 +1072,35 @@ void
 MediaStreamGraph::NotifyOutputData(AudioDataValue* aBuffer, size_t aFrames,
                                    TrackRate aRate, uint32_t aChannels)
 {
   for (auto& listener : mAudioInputs) {
     listener->NotifyOutputData(this, aBuffer, aFrames, aRate, aChannels);
   }
 }
 
+void
+MediaStreamGraph::AssertOnGraphThreadOrNotRunning() const
+{
+  // either we're on the right thread (and calling CurrentDriver() is safe),
+  // or we're going to assert anyways, so don't cross-check CurrentDriver
+#ifdef DEBUG
+  MediaStreamGraphImpl const * graph =
+    static_cast<MediaStreamGraphImpl const *>(this);
+  // if all the safety checks fail, assert we own the monitor
+  if (!graph->mDriver->OnThread()) {
+    if (!(graph->mDetectedNotRunning &&
+          graph->mLifecycleState > MediaStreamGraphImpl::LIFECYCLE_RUNNING &&
+          NS_IsMainThread())) {
+      graph->mMonitor.AssertCurrentThreadOwns();
+    }
+  }
+#endif
+}
+
 bool
 MediaStreamGraphImpl::ShouldUpdateMainThread()
 {
   if (mRealtime) {
     return true;
   }
 
   TimeStamp now = TimeStamp::Now();
--- a/dom/media/MediaStreamGraph.h
+++ b/dom/media/MediaStreamGraph.h
@@ -1348,16 +1348,17 @@ public:
    * Media graph thread only.
    * Dispatches a runnable that will run on the main thread after all
    * main-thread stream state has been next updated.
    * Should only be called during MediaStreamListener callbacks or during
    * ProcessedMediaStream::ProcessInput().
    */
   virtual void DispatchToMainThreadAfterStreamStateUpdate(already_AddRefed<nsIRunnable> aRunnable)
   {
+    AssertOnGraphThreadOrNotRunning();
     *mPendingUpdateRunnables.AppendElement() = aRunnable;
   }
 
   /**
    * Returns graph sample rate in Hz.
    */
   TrackRate GraphRate() const { return mSampleRate; }
 
@@ -1369,16 +1370,18 @@ public:
 
   /**
    * Data going to the speakers from the GraphDriver's DataCallback
    * to notify any listeners (for echo cancellation).
    */
   void NotifyOutputData(AudioDataValue* aBuffer, size_t aFrames,
                         TrackRate aRate, uint32_t aChannels);
 
+  void AssertOnGraphThreadOrNotRunning() const;
+
 protected:
   explicit MediaStreamGraph(TrackRate aSampleRate)
     : mSampleRate(aSampleRate)
   {
     MOZ_COUNT_CTOR(MediaStreamGraph);
   }
   virtual ~MediaStreamGraph()
   {
--- a/dom/media/MediaStreamGraphImpl.h
+++ b/dom/media/MediaStreamGraphImpl.h
@@ -197,34 +197,18 @@ public:
   /**
    * Respond to CollectReports with sizes collected on the graph thread.
    */
   static void
   FinishCollectReports(nsIHandleReportCallback* aHandleReport,
                        nsISupports* aData,
                        const nsTArray<AudioNodeSizes>& aAudioStreamSizes);
 
-  // The following methods run on the graph thread (or possibly the main thread if
-  // mLifecycleState > LIFECYCLE_RUNNING)
-  void AssertOnGraphThreadOrNotRunning() const
-  {
-    // either we're on the right thread (and calling CurrentDriver() is safe),
-    // or we're going to assert anyways, so don't cross-check CurrentDriver
-#ifdef DEBUG
-    // if all the safety checks fail, assert we own the monitor
-    if (!mDriver->OnThread()) {
-      if (!(mDetectedNotRunning &&
-            mLifecycleState > LIFECYCLE_RUNNING &&
-            NS_IsMainThread())) {
-        mMonitor.AssertCurrentThreadOwns();
-      }
-    }
-#endif
-  }
-
+  // The following methods run on the graph thread (or possibly the main thread
+  // if mLifecycleState > LIFECYCLE_RUNNING)
   void CollectSizesForMemoryReport(
          already_AddRefed<nsIHandleReportCallback> aHandleReport,
          already_AddRefed<nsISupports> aHandlerData);
 
   /**
    * Returns true if this MediaStreamGraph should keep running
    */
   bool UpdateMainThreadState();
--- a/dom/media/flac/FlacFrameParser.cpp
+++ b/dom/media/flac/FlacFrameParser.cpp
@@ -127,17 +127,17 @@ FlacFrameParser::DecodeHeaderBlock(const
       uint32_t sampleRate = (blob >> 44) & BITMASK(20);
       if (!sampleRate) {
         return false;
       }
       uint32_t numChannels = ((blob >> 41) & BITMASK(3)) + 1;
       if (numChannels > FLAC_MAX_CHANNELS) {
         return false;
       }
-      uint32_t bps = ((blob >> 38) & BITMASK(5)) + 1;
+      uint32_t bps = ((blob >> 36) & BITMASK(5)) + 1;
       if (bps > 24) {
         return false;
       }
       mNumFrames = blob & BITMASK(36);
 
       mInfo.mMimeType = "audio/flac";
       mInfo.mRate = sampleRate;
       mInfo.mChannels = numChannels;
--- a/dom/media/platforms/PlatformDecoderModule.h
+++ b/dom/media/platforms/PlatformDecoderModule.h
@@ -170,16 +170,19 @@ public:
   virtual void Output(MediaData* aData) = 0;
 
   // Denotes an error in the decoding process. The reader will stop calling
   // the decoder.
   virtual void Error(MediaDataDecoderError aError) = 0;
 
   // Denotes that the last input sample has been inserted into the decoder,
   // and no more output can be produced unless more input is sent.
+  // A frame decoding session is completed once InputExhausted has been called.
+  // MediaDataDecoder::Input will not be called again until InputExhausted has
+  // been called.
   virtual void InputExhausted() = 0;
 
   virtual void DrainComplete() = 0;
 
   virtual void ReleaseMediaResources() {}
 
   virtual bool OnReaderTaskQueue() = 0;
 
--- a/dom/media/platforms/agnostic/BlankDecoderModule.cpp
+++ b/dom/media/platforms/agnostic/BlankDecoderModule.cpp
@@ -91,21 +91,17 @@ private:
     }
 
     // Frames come out in DTS order but we need to output them in PTS order.
     mReorderQueue.Push(aData);
 
     while (mReorderQueue.Length() > mMaxRefFrames) {
       mCallback->Output(mReorderQueue.Pop().get());
     }
-
-    if (mReorderQueue.Length() <= mMaxRefFrames) {
-      mCallback->InputExhausted();
-    }
-
+    mCallback->InputExhausted();
   }
 
 private:
   nsAutoPtr<BlankMediaDataCreator> mCreator;
   MediaDataDecoderCallback* mCallback;
   const uint32_t mMaxRefFrames;
   ReorderQueue mReorderQueue;
   TrackInfo::TrackType mType;
--- a/dom/media/platforms/agnostic/OpusDecoder.cpp
+++ b/dom/media/platforms/agnostic/OpusDecoder.cpp
@@ -158,22 +158,19 @@ OpusDataDecoder::ProcessDecode(MediaRawD
   switch (err) {
     case DecodeError::FATAL_ERROR:
       mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
       return;
     case DecodeError::DECODE_ERROR:
       mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
       break;
     case DecodeError::DECODE_SUCCESS:
+      mCallback->InputExhausted();
       break;
   }
-
-  if (mTaskQueue->IsEmpty()) {
-    mCallback->InputExhausted();
-  }
 }
 
 OpusDataDecoder::DecodeError
 OpusDataDecoder::DoDecode(MediaRawData* aSample)
 {
   int64_t aDiscardPadding = 0;
   if (aSample->mExtraData) {
     aDiscardPadding = BigEndian::readInt64(aSample->mExtraData->Elements());
--- a/dom/media/platforms/agnostic/TheoraDecoder.cpp
+++ b/dom/media/platforms/agnostic/TheoraDecoder.cpp
@@ -197,17 +197,17 @@ void
 TheoraDecoder::ProcessDecode(MediaRawData* aSample)
 {
   MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
   if (mIsFlushing) {
     return;
   }
   if (DoDecode(aSample) == -1) {
     mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
-  } else if (mTaskQueue->IsEmpty()) {
+  } else {
     mCallback->InputExhausted();
   }
 }
 
 nsresult
 TheoraDecoder::Input(MediaRawData* aSample)
 {
   MOZ_ASSERT(mCallback->OnReaderTaskQueue());
--- a/dom/media/platforms/agnostic/VPXDecoder.cpp
+++ b/dom/media/platforms/agnostic/VPXDecoder.cpp
@@ -187,17 +187,17 @@ void
 VPXDecoder::ProcessDecode(MediaRawData* aSample)
 {
   MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
   if (mIsFlushing) {
     return;
   }
   if (DoDecode(aSample) == -1) {
     mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
-  } else if (mTaskQueue->IsEmpty()) {
+  } else {
     mCallback->InputExhausted();
   }
 }
 
 nsresult
 VPXDecoder::Input(MediaRawData* aSample)
 {
   MOZ_ASSERT(mCallback->OnReaderTaskQueue());
--- a/dom/media/platforms/agnostic/VorbisDecoder.cpp
+++ b/dom/media/platforms/agnostic/VorbisDecoder.cpp
@@ -138,17 +138,17 @@ void
 VorbisDataDecoder::ProcessDecode(MediaRawData* aSample)
 {
   MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
   if (mIsFlushing) {
     return;
   }
   if (DoDecode(aSample) == -1) {
     mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
-  } else if (mTaskQueue->IsEmpty()) {
+  } else {
     mCallback->InputExhausted();
   }
 }
 
 int
 VorbisDataDecoder::DoDecode(MediaRawData* aSample)
 {
   MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
--- a/dom/media/platforms/agnostic/WAVDecoder.cpp
+++ b/dom/media/platforms/agnostic/WAVDecoder.cpp
@@ -63,16 +63,18 @@ WaveDataDecoder::Init()
   return InitPromise::CreateAndResolve(TrackInfo::kAudioTrack, __func__);
 }
 
 nsresult
 WaveDataDecoder::Input(MediaRawData* aSample)
 {
   if (!DoDecode(aSample)) {
     mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
+  } else {
+    mCallback->InputExhausted();
   }
   return NS_OK;
 }
 
 bool
 WaveDataDecoder::DoDecode(MediaRawData* aSample)
 {
   size_t aLength = aSample->Size();
--- a/dom/media/platforms/apple/AppleATDecoder.cpp
+++ b/dom/media/platforms/apple/AppleATDecoder.cpp
@@ -210,20 +210,17 @@ AppleATDecoder::SubmitSample(MediaRawDat
       if (NS_FAILED(DecodeSample(mQueuedSamples[i]))) {
         mQueuedSamples.Clear();
         mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
         return;
       }
     }
     mQueuedSamples.Clear();
   }
-
-  if (mTaskQueue->IsEmpty()) {
-    mCallback->InputExhausted();
-  }
+  mCallback->InputExhausted();
 }
 
 nsresult
 AppleATDecoder::DecodeSample(MediaRawData* aSample)
 {
   MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
 
   // Array containing the queued decoded audio frames, about to be output.
--- a/dom/media/platforms/apple/AppleVTDecoder.cpp
+++ b/dom/media/platforms/apple/AppleVTDecoder.cpp
@@ -29,21 +29,19 @@ AppleVTDecoder::AppleVTDecoder(const Vid
                                MediaDataDecoderCallback* aCallback,
                                layers::ImageContainer* aImageContainer)
   : mExtraData(aConfig.mExtraData)
   , mCallback(aCallback)
   , mPictureWidth(aConfig.mImage.width)
   , mPictureHeight(aConfig.mImage.height)
   , mDisplayWidth(aConfig.mDisplay.width)
   , mDisplayHeight(aConfig.mDisplay.height)
-  , mQueuedSamples(0)
   , mTaskQueue(aTaskQueue)
   , mMaxRefFrames(mp4_demuxer::H264::ComputeMaxRefFrames(aConfig.mExtraData))
   , mImageContainer(aImageContainer)
-  , mInputIncoming(0)
   , mIsShutDown(false)
 #ifdef MOZ_WIDGET_UIKIT
   , mUseSoftwareImages(true)
 #else
   , mUseSoftwareImages(false)
 #endif
   , mIsFlushing(false)
   , mMonitor("AppleVideoDecoder")
@@ -83,34 +81,30 @@ AppleVTDecoder::Input(MediaRawData* aSam
 
   LOG("mp4 input sample %p pts %lld duration %lld us%s %d bytes",
       aSample,
       aSample->mTime,
       aSample->mDuration,
       aSample->mKeyframe ? " keyframe" : "",
       aSample->Size());
 
-  mInputIncoming++;
-
   mTaskQueue->Dispatch(NewRunnableMethod<RefPtr<MediaRawData>>(
     this, &AppleVTDecoder::ProcessDecode, aSample));
   return NS_OK;
 }
 
 nsresult
 AppleVTDecoder::Flush()
 {
   MOZ_ASSERT(mCallback->OnReaderTaskQueue());
   mIsFlushing = true;
   nsCOMPtr<nsIRunnable> runnable =
     NewRunnableMethod(this, &AppleVTDecoder::ProcessFlush);
   SyncRunnable::DispatchToThread(mTaskQueue, runnable);
   mIsFlushing = false;
-  // All ProcessDecode() tasks should be done.
-  MOZ_ASSERT(mInputIncoming == 0);
 
   mSeekTargetThreshold.reset();
 
   return NS_OK;
 }
 
 nsresult
 AppleVTDecoder::Drain()
@@ -137,28 +131,21 @@ AppleVTDecoder::Shutdown()
   return NS_OK;
 }
 
 nsresult
 AppleVTDecoder::ProcessDecode(MediaRawData* aSample)
 {
   AssertOnTaskQueueThread();
 
-  mInputIncoming--;
-
   if (mIsFlushing) {
     return NS_OK;
   }
 
   auto rv = DoDecode(aSample);
-  // Ask for more data.
-  if (NS_SUCCEEDED(rv) && !mInputIncoming && mQueuedSamples <= mMaxRefFrames) {
-    LOG("%s task queue empty; requesting more data", GetDescriptionName());
-    mCallback->InputExhausted();
-  }
 
   return rv;
 }
 
 void
 AppleVTDecoder::ProcessShutdown()
 {
   if (mSession) {
@@ -208,27 +195,25 @@ AppleVTDecoder::CreateAppleFrameRef(cons
 
 void
 AppleVTDecoder::DrainReorderedFrames()
 {
   MonitorAutoLock mon(mMonitor);
   while (!mReorderQueue.IsEmpty()) {
     mCallback->Output(mReorderQueue.Pop().get());
   }
-  mQueuedSamples = 0;
 }
 
 void
 AppleVTDecoder::ClearReorderedFrames()
 {
   MonitorAutoLock mon(mMonitor);
   while (!mReorderQueue.IsEmpty()) {
     mReorderQueue.Pop();
   }
-  mQueuedSamples = 0;
 }
 
 void
 AppleVTDecoder::SetSeekThreshold(const media::TimeUnit& aTime)
 {
   LOG("SetSeekThreshold %lld", aTime.ToMicroseconds());
   mSeekTargetThreshold = Some(aTime);
 }
@@ -283,26 +268,20 @@ AppleVTDecoder::OutputFrame(CVPixelBuffe
   LOG("mp4 output frame %lld dts %lld pts %lld duration %lld us%s",
       aFrameRef.byte_offset,
       aFrameRef.decode_timestamp.ToMicroseconds(),
       aFrameRef.composition_timestamp.ToMicroseconds(),
       aFrameRef.duration.ToMicroseconds(),
       aFrameRef.is_sync_point ? " keyframe" : ""
   );
 
-  if (mQueuedSamples > mMaxRefFrames) {
-    // We had stopped requesting more input because we had received too much at
-    // the time. We can ask for more once again.
+  if (!aImage) {
+    // Image was dropped by decoder or none return yet.
+    // We need more input to continue.
     mCallback->InputExhausted();
-  }
-  MOZ_ASSERT(mQueuedSamples);
-  mQueuedSamples--;
-
-  if (!aImage) {
-    // Image was dropped by decoder.
     return NS_OK;
   }
 
   bool useNullSample = false;
   if (mSeekTargetThreshold.isSome()) {
     if ((aFrameRef.composition_timestamp + aFrameRef.duration) < mSeekTargetThreshold.ref()) {
       useNullSample = true;
     } else {
@@ -405,19 +384,20 @@ AppleVTDecoder::OutputFrame(CVPixelBuffe
     mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
     return NS_ERROR_FAILURE;
   }
 
   // Frames come out in DTS order but we need to output them
   // in composition order.
   MonitorAutoLock mon(mMonitor);
   mReorderQueue.Push(data);
-  while (mReorderQueue.Length() > mMaxRefFrames) {
+  if (mReorderQueue.Length() > mMaxRefFrames) {
     mCallback->Output(mReorderQueue.Pop().get());
   }
+  mCallback->InputExhausted();
   LOG("%llu decoded frames queued",
       static_cast<unsigned long long>(mReorderQueue.Length()));
 
   return NS_OK;
 }
 
 nsresult
 AppleVTDecoder::WaitForAsynchronousFrames()
@@ -475,18 +455,16 @@ AppleVTDecoder::DoDecode(MediaRawData* a
   }
   CMSampleTimingInfo timestamp = TimingInfoFromSample(aSample);
   rv = CMSampleBufferCreate(kCFAllocatorDefault, block, true, 0, 0, mFormat, 1, 1, &timestamp, 0, NULL, sample.receive());
   if (rv != noErr) {
     NS_ERROR("Couldn't create CMSampleBuffer");
     return NS_ERROR_FAILURE;
   }
 
-  mQueuedSamples++;
-
   VTDecodeFrameFlags decodeFlags =
     kVTDecodeFrame_EnableAsynchronousDecompression;
   rv = VTDecompressionSessionDecodeFrame(mSession,
                                          sample,
                                          decodeFlags,
                                          CreateAppleFrameRef(aSample),
                                          &infoFlags);
   if (rv != noErr && !(infoFlags & kVTDecodeInfo_FrameDropped)) {
--- a/dom/media/platforms/apple/AppleVTDecoder.h
+++ b/dom/media/platforms/apple/AppleVTDecoder.h
@@ -85,35 +85,27 @@ private:
 
   const RefPtr<MediaByteBuffer> mExtraData;
   MediaDataDecoderCallback* mCallback;
   const uint32_t mPictureWidth;
   const uint32_t mPictureHeight;
   const uint32_t mDisplayWidth;
   const uint32_t mDisplayHeight;
 
-  // Number of times a sample was queued via Input(). Will be decreased upon
-  // the decoder's callback being invoked.
-  // This is used to calculate how many frames has been buffered by the decoder.
-  Atomic<uint32_t> mQueuedSamples;
-
   // Method to set up the decompression session.
   nsresult InitializeSession();
   nsresult WaitForAsynchronousFrames();
   CFDictionaryRef CreateDecoderSpecification();
   CFDictionaryRef CreateDecoderExtensions();
   // Method to pass a frame to VideoToolbox for decoding.
   nsresult DoDecode(MediaRawData* aSample);
 
   const RefPtr<TaskQueue> mTaskQueue;
   const uint32_t mMaxRefFrames;
   const RefPtr<layers::ImageContainer> mImageContainer;
-  // Increased when Input is called, and decreased when ProcessFrame runs.
-  // Reaching 0 indicates that there's no pending Input.
-  Atomic<uint32_t> mInputIncoming;
   Atomic<bool> mIsShutDown;
   const bool mUseSoftwareImages;
 
   // Set on reader/decode thread calling Flush() to indicate that output is
   // not required and so input samples on mTaskQueue need not be processed.
   // Cleared on mTaskQueue in ProcessDrain().
   Atomic<bool> mIsFlushing;
   // Protects mReorderQueue.
--- a/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.cpp
@@ -128,16 +128,17 @@ FFmpegAudioDecoder<LIBAV_VER>::DoDecode(
 
   if (!PrepareFrame()) {
     NS_WARNING("FFmpeg audio decoder failed to allocate frame.");
     return DecodeResult::FATAL_ERROR;
   }
 
   int64_t samplePosition = aSample->mOffset;
   media::TimeUnit pts = media::TimeUnit::FromMicroseconds(aSample->mTime);
+  bool didOutput = false;
 
   while (packet.size > 0) {
     int decoded;
     int bytesConsumed =
       mLib->avcodec_decode_audio4(mCodecContext, mFrame, &decoded, &packet);
 
     if (bytesConsumed < 0) {
       NS_WARNING("FFmpeg audio decoder error.");
@@ -176,28 +177,29 @@ FFmpegAudioDecoder<LIBAV_VER>::DoDecode(
       RefPtr<AudioData> data = new AudioData(samplePosition,
                                              pts.ToMicroseconds(),
                                              duration.ToMicroseconds(),
                                              mFrame->nb_samples,
                                              Move(audio),
                                              numChannels,
                                              samplingRate);
       mCallback->Output(data);
+      didOutput = true;
       pts += duration;
       if (!pts.IsValid()) {
         NS_WARNING("Invalid count of accumulated audio samples");
         return DecodeResult::DECODE_ERROR;
       }
     }
     packet.data += bytesConsumed;
     packet.size -= bytesConsumed;
     samplePosition += bytesConsumed;
   }
 
-  return DecodeResult::DECODE_FRAME;
+  return didOutput ? DecodeResult::DECODE_FRAME : DecodeResult::DECODE_NO_FRAME;
 }
 
 void
 FFmpegAudioDecoder<LIBAV_VER>::ProcessDrain()
 {
   ProcessFlush();
   mCallback->DrainComplete();
 }
--- a/dom/media/platforms/ffmpeg/FFmpegDataDecoder.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegDataDecoder.cpp
@@ -112,20 +112,22 @@ FFmpegDataDecoder<LIBAV_VER>::ProcessDec
   }
   switch (DoDecode(aSample)) {
     case DecodeResult::DECODE_ERROR:
       mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
       break;
     case DecodeResult::FATAL_ERROR:
       mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
       break;
+    case DecodeResult::DECODE_NO_FRAME:
+    case DecodeResult::DECODE_FRAME:
+      mCallback->InputExhausted();
+      break;
     default:
-      if (mTaskQueue->IsEmpty()) {
-        mCallback->InputExhausted();
-      }
+      break;
   }
 }
 
 nsresult
 FFmpegDataDecoder<LIBAV_VER>::Input(MediaRawData* aSample)
 {
   mTaskQueue->Dispatch(NewRunnableMethod<RefPtr<MediaRawData>>(
     this, &FFmpegDataDecoder::ProcessDecode, aSample));
--- a/dom/media/platforms/wmf/WMFMediaDataDecoder.cpp
+++ b/dom/media/platforms/wmf/WMFMediaDataDecoder.cpp
@@ -144,19 +144,17 @@ WMFMediaDataDecoder::ProcessOutput()
   RefPtr<MediaData> output;
   HRESULT hr = S_OK;
   while (SUCCEEDED(hr = mMFTManager->Output(mLastStreamOffset, output)) &&
          output) {
     mHasSuccessfulOutput = true;
     mCallback->Output(output);
   }
   if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
-    if (mTaskQueue->IsEmpty()) {
-      mCallback->InputExhausted();
-    }
+    mCallback->InputExhausted();
   } else if (FAILED(hr)) {
     NS_WARNING("WMFMediaDataDecoder failed to output data");
     mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
     if (!mRecordedError) {
       SendTelemetry(hr);
       mRecordedError = true;
     }
   }
--- a/dom/media/test/external/external_media_tests/media_utils/youtube_puppeteer.py
+++ b/dom/media/test/external/external_media_tests/media_utils/youtube_puppeteer.py
@@ -10,19 +10,26 @@ from marionette import Marionette
 from marionette_driver import By, expected, Wait
 from marionette_driver.errors import TimeoutException, NoSuchElementException
 from video_puppeteer import VideoPuppeteer, VideoException
 from external_media_tests.utils import verbose_until
 
 
 class YouTubePuppeteer(VideoPuppeteer):
     """
-    Wrapper around a YouTube #movie_player element.
+    Wrapper around a YouTube .html5-video-player element.
 
-    Partial reference: https://developers.google.com/youtube/js_api_reference.
+    Can be used with youtube videos or youtube videos at embedded URLS. E.g.
+    both https://www.youtube.com/watch?v=AbAACm1IQE0 and
+    https://www.youtube.com/embed/AbAACm1IQE0 should work.
+
+    Using an embedded video has the advantage of not auto-playing more videos
+    while a test is running.
+
+    Partial reference: https://developers.google.com/youtube/iframe_api_reference.
     This reference is useful for site-specific features such as interacting
     with ads, or accessing YouTube's debug data.
     """
 
     _yt_player_state = {
         'UNSTARTED': -1,
         'ENDED': 0,
         'PLAYING': 1,
@@ -32,24 +39,26 @@ class YouTubePuppeteer(VideoPuppeteer):
     }
     _yt_player_state_name = {v: k for k, v in _yt_player_state.items()}
     _time_pattern = re.compile('(?P<minute>\d+):(?P<second>\d+)')
 
     def __init__(self, marionette, url, **kwargs):
         self.player = None
         super(YouTubePuppeteer,
               self).__init__(marionette, url,
-                             video_selector='#movie_player video',
+                             video_selector='.html5-video-player video',
                              **kwargs)
         wait = Wait(self.marionette, timeout=30)
         with self.marionette.using_context(Marionette.CONTEXT_CONTENT):
             verbose_until(wait, self,
-                          expected.element_present(By.ID, 'movie_player'))
-            self.player = self.marionette.find_element(By.ID, 'movie_player')
-            self.marionette.execute_script("log('#movie_player "
+                          expected.element_present(By.CLASS_NAME,
+                                                   'html5-video-player'))
+            self.player = self.marionette.find_element(By.CLASS_NAME,
+                                                       'html5-video-player')
+            self.marionette.execute_script("log('.html5-video-player "
                                            "element obtained');")
         # When an ad is playing, self.player_duration indicates the duration
         # of the spliced-in ad stream, not the duration of the main video, so
         # we attempt to skip the ad first.
         for attempt in range(5):
             sleep(1)
             self.process_ad()
             if (self.ad_inactive and self.duration and not
@@ -112,31 +121,31 @@ class YouTubePuppeteer(VideoPuppeteer):
                 return loads(text)
             except ValueError:
                 self.marionette.log('Error loading json: DebugText',
                                     level='DEBUG')
 
     def execute_yt_script(self, script):
         """
         Execute JS script in content context with access to video element and
-        YouTube #movie_player element.
+        YouTube .html5-video-player element.
 
         :param script: script to be executed.
 
         :return: value returned by script
         """
         with self.marionette.using_context(Marionette.CONTEXT_CONTENT):
             return self.marionette.execute_script(script,
                                                   script_args=[self.video,
                                                                self.player])
 
     @property
     def playback_quality(self):
         """
-        Please see https://developers.google.com/youtube/js_api_reference#Playback_quality
+        Please see https://developers.google.com/youtube/iframe_api_reference#Playback_quality
         for valid values.
 
         :return: A string with a valid value returned via YouTube.
         """
         return self.execute_yt_script('return arguments[1].'
                                       'wrappedJSObject.getPlaybackQuality();')
 
     @property
@@ -171,29 +180,29 @@ class YouTubePuppeteer(VideoPuppeteer):
         return self.execute_yt_script('return arguments[1].'
                                       'wrappedJSObject.getVideoUrl();')
 
     @property
     def player_state(self):
         """
 
         :return: The YouTube state of the video. See
-         https://developers.google.com/youtube/js_api_reference#getPlayerState
+         https://developers.google.com/youtube/iframe_api_reference#getPlayerState
          for valid values.
         """
         state = self.execute_yt_script('return arguments[1].'
                                        'wrappedJSObject.getPlayerState();')
         return state
 
     @property
     def player_unstarted(self):
         """
         This and the following properties are based on the
         player.getPlayerState() call
-        (https://developers.google.com/youtube/js_api_reference#Playback_status)
+        (https://developers.google.com/youtube/iframe_api_reference#Playback_status)
 
         :return: True if the video has not yet started.
         """
         return self.player_state == self._yt_player_state['UNSTARTED']
 
     @property
     def player_ended(self):
         """
@@ -235,17 +244,17 @@ class YouTubePuppeteer(VideoPuppeteer):
         return self.player_state == self._yt_player_state['CUED']
 
     @property
     def ad_state(self):
         """
         Get state of current ad.
 
         :return: Returns one of the constants listed in
-         https://developers.google.com/youtube/js_api_reference#Playback_status
+         https://developers.google.com/youtube/iframe_api_reference#Playback_status
          for an ad.
 
         """
         # Note: ad_state is sometimes not accurate, due to some sort of lag?
         return self.execute_yt_script('return arguments[1].'
                                       'wrappedJSObject.getAdState();')
 
     @property
@@ -345,17 +354,17 @@ class YouTubePuppeteer(VideoPuppeteer):
         """
         if self.ad_playing:
             self.marionette.log('Waiting while ad plays')
             sleep(10)
         else:
             # no ad playing
             return False
         if self.ad_skippable:
-            selector = '#movie_player .videoAdUiSkipContainer'
+            selector = '.html5-video-player .videoAdUiSkipContainer'
             wait = Wait(self.marionette, timeout=30)
             try:
                 with self.marionette.using_context(Marionette.CONTEXT_CONTENT):
                     wait.until(expected.element_displayed(By.CSS_SELECTOR,
                                                           selector))
                     ad_button = self.marionette.find_element(By.CSS_SELECTOR,
                                                              selector)
                     ad_button.click()
@@ -373,17 +382,17 @@ class YouTubePuppeteer(VideoPuppeteer):
         :return: ad duration in seconds, if currently displayed in player
         """
         if not (self.ad_playing or self.player_measure_progress() == 0):
             return None
         # If the ad is not Flash...
         if (self.ad_playing and self.video_src.startswith('mediasource') and
                 self.duration):
             return self.duration
-        selector = '#movie_player .videoAdUiAttribution'
+        selector = '.html5-media-player .videoAdUiAttribution'
         wait = Wait(self.marionette, timeout=5)
         try:
             with self.marionette.using_context(Marionette.CONTEXT_CONTENT):
                 wait.until(expected.element_present(By.CSS_SELECTOR,
                                                     selector))
                 countdown = self.marionette.find_element(By.CSS_SELECTOR,
                                                          selector)
                 ad_time = self._time_pattern.search(countdown.text)
@@ -459,28 +468,28 @@ class YouTubePuppeteer(VideoPuppeteer):
             return False
 
     def __str__(self):
         messages = [super(YouTubePuppeteer, self).__str__()]
         if self.player:
             player_state = self._yt_player_state_name[self.player_state]
             ad_state = self._yt_player_state_name[self.ad_state]
             messages += [
-                '#movie_player: {',
+                '.html5-media-player: {',
                 '\tvideo id: {0},'.format(self.movie_id),
                 '\tvideo_title: {0}'.format(self.movie_title),
                 '\tcurrent_state: {0},'.format(player_state),
                 '\tad_state: {0},'.format(ad_state),
                 '\tplayback_quality: {0},'.format(self.playback_quality),
                 '\tcurrent_time: {0},'.format(self.player_current_time),
                 '\tduration: {0},'.format(self.player_duration),
                 '}'
             ]
         else:
-            messages += ['\t#movie_player: None']
+            messages += ['\t.html5-media-player: None']
         return '\n'.join(messages)
 
 
 def playback_started(yt):
     """
     Check whether playback has started.
 
     :param yt: YouTubePuppeteer
--- a/dom/media/test/external/external_media_tests/urls/default.ini
+++ b/dom/media/test/external/external_media_tests/urls/default.ini
@@ -1,9 +1,9 @@
-# short videos; no ads; max 5 minutes
+# short videos; no ads; embedded; max 5 minutes
 # 0:12
-[https://youtu.be/AbAACm1IQE0]
+[https://youtube.com/embed/AbAACm1IQE0?autoplay=1]
 # 2:18
-[https://www.youtube.com/watch?v=yOQQCoxs8-k]
+[https://youtube.com/embed/yOQQCoxs8-k?autoplay=1]
 # 0:08
-[https://www.youtube.com/watch?v=1visYpIREUM]
+[https://youtube.com/embed/1visYpIREUM?autoplay=1]
 # 2:09
-[https://www.youtube.com/watch?v=rjmuKV9BTkE]
+[https://youtube.com/embed/rjmuKV9BTkE?autoplay=1]
--- a/dom/media/test/external/external_media_tests/urls/youtube/long1-720.ini
+++ b/dom/media/test/external/external_media_tests/urls/youtube/long1-720.ini
@@ -1,14 +1,5 @@
-# all long videos; < 12 hours total
-# 2:18:00
-[http://youtu.be/FLX64H5FYa8]
-# 1:00:00
-[https://www.youtube.com/watch?v=AYYDshv8C4g]
-# 1:10:00
-[https://www.youtube.com/watch?v=V0Vy4kYAPDk]
-# 1:47:00
-[https://www.youtube.com/watch?v=bFtGE2C7Pxs]
-
-
-# shutdownhang | WaitForSingleObjectEx | WaitForSingleObject | PR_Wait | nsThread::ProcessNextEvent(bool, bool*) | NS_ProcessNextEvent(nsIThread*, bool) | mozilla::MediaShutdownManager::Shutdown()
-# 1:43:00
-[https://www.youtube.com/watch?v=BXMtXpmpXPU]
+# a couple of very long videos, < 12 hours total
+# 6:00:00 - can't embed due to copyright
+[https://www.youtube.com/watch?v=5N8sUccRiTA]
+# 2:09:00
+[https://www.youtube.com/embed/b6q5N16dje4?autoplay=1]
deleted file mode 100644
--- a/dom/media/test/external/external_media_tests/urls/youtube/long2-720.ini
+++ /dev/null
@@ -1,9 +0,0 @@
-# a couple of very long videos, < 12 hours total
-# 6:00:00
-[https://www.youtube.com/watch?v=5N8sUccRiTA]
-# 2:27:00
-[https://www.youtube.com/watch?v=NAVrm3wjzq8]
-# 58:50
-[https://www.youtube.com/watch?v=uP1BBw3IYco]
-# 2:09:00
-[https://www.youtube.com/watch?v=b6q5N16dje4]
rename from dom/media/test/external/external_media_tests/urls/youtube/long3-crashes-720.ini
rename to dom/media/test/external/external_media_tests/urls/youtube/long2-crashes-720.ini
--- a/dom/media/test/external/external_media_tests/urls/youtube/long3-crashes-720.ini
+++ b/dom/media/test/external/external_media_tests/urls/youtube/long2-crashes-720.ini
@@ -1,8 +1,11 @@
+# It appears these are not currently used by tests. They are left here as they
+# reference failure scenarios. If tese are fixed that can be removed.
+
 # videos from crashes, < 12 hours
 
 # hang | NtUserMessageCall | SendMessageW
 # 1:10:00
 [https://www.youtube.com/watch?v=Ztie4DqeOak]
 
 # nsPluginInstanceOwner::GetDocument(nsIDocument**)
 # 22:40
rename from dom/media/test/external/external_media_tests/urls/youtube/long4-crashes-900.ini
rename to dom/media/test/external/external_media_tests/urls/youtube/long3-crashes-900.ini
--- a/dom/media/test/external/external_media_tests/urls/youtube/long4-crashes-900.ini
+++ b/dom/media/test/external/external_media_tests/urls/youtube/long3-crashes-900.ini
@@ -1,8 +1,11 @@
+# It appears these are not currently used by tests. They are left here as they
+# reference failure scenarios. If tese are fixed that can be removed.
+
 # Total time: about 12-13 hours + unskippable ads
 #Request url:  https://crash-stats.mozilla.com/api/SuperSearchUnredacted/?product=Firefox&url=%24https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D&url=%21~list&url=%21~index&_results_number=50&platform=Windows&version=37.0&date=%3E2015-03-26
 
 #Request url:    https://crash-stats.mozilla.com/api/SuperSearchUnredacted/?product=Firefox&url=%24https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D&url=%21~list&url=%21~index&_results_number=5&platform=Windows&version=37.0&signature=%3Dhang+%7C+NtUserMessageCall+%7C+SendMessageW&date=%3E2015-03-26
 
 #Request url:    https://crash-stats.mozilla.com/api/SuperSearchUnredacted/?product=Firefox&url=%24https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D&url=%21~list&url=%21~index&_results_number=5&platform=Windows&version=37.0&signature=%3DOOM+%7C+small&date=%3E2015-03-26
 
 #Request url:    https://crash-stats.mozilla.com/api/SuperSearchUnredacted/?product=Firefox&url=%24https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D&url=%21~list&url=%21~index&_results_number=5&platform=Windows&version=37.0&signature=%3Dmozilla%3A%3Alayers%3A%3ACompositorD3D11%3A%3AHandleError%28long%2C+mozilla%3A%3Alayers%3A%3ACompositorD3D11%3A%3ASeverity%29+%7C+mozilla%3A%3Alayers%3A%3ACompositorD3D11%3A%3AFailed%28long%2C+mozilla%3A%3Alayers%3A%3ACompositorD3D11%3A%3ASeverity%29+%7C+mozilla%3A%3Alayers%3A%3ACompositorD3D11%3A%3AUpdateRenderTarget%28%29&date=%3E2015-03-26
deleted file mode 100644
--- a/dom/media/test/external/external_media_tests/urls/youtube/massive-6000.ini
+++ /dev/null
@@ -1,15 +0,0 @@
-# very long test; 96-100 hours?
-# 00:3:26
-[https://www.youtube.com/watch?v=7RMQksXpQSk]
-# nyan cat 10 hours
-[http://youtu.be/9bZkp7q19f0]
-# 4:54:00
-[https://www.youtube.com/watch?v=jWlKjw3LBDk]
-# 3:00:01
-[https://www.youtube.com/watch?v=ub9JUDS_6i8]
-# 10 hours rick roll
-[https://www.youtube.com/watch?v=BROWqjuTM0g]
-# 24 hours
-[https://www.youtube.com/watch?v=FvHiLLkPhQE]
-# 2 hours
-[https://www.youtube.com/watch?v=VmOuW5zTt9w
--- a/dom/media/test/external/external_media_tests/urls/youtube/medium1-60.ini
+++ b/dom/media/test/external/external_media_tests/urls/youtube/medium1-60.ini
@@ -1,18 +1,18 @@
 # mix of shorter/longer videos with/without ads, < 60 min
-# 4:59
-[http://youtu.be/pWI8RB2dmfU]
+# 4:59 - can't embed
+[https://www.youtube.com/watch?v=pWI8RB2dmfU]
 # 0:46 ad at start
-[http://youtu.be/6SFp1z7uA6g]
+[https://www.youtube.com/embed/6SFp1z7uA6g?autoplay=1]
 # 0:58 ad at start
-[http://youtu.be/Aebs62bX0dA]
+[https://www.youtube.com/embed/Aebs62bX0dA?autoplay=1]
 # 1:43 ad
-[https://www.youtube.com/watch?v=l5ODwR6FPRQ]
-# 8:00 ad
+[https://www.youtube.com/embed/l5ODwR6FPRQ?autoplay=1]
+# 8:00 ad - can't embed
 [https://www.youtube.com/watch?v=KlyXNRrsk4A]
 # video with ad in beginning and in the middle 20:00
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1176815
-[https://www.youtube.com/watch?v=cht9Xq9suGg]
+[https://www.youtube.com/embed/cht9Xq9suGg?autoplay=1]
 # 1:35 ad
-[https://www.youtube.com/watch?v=orybDrUj4vA]
-# 3:02 - ad
-[https://youtu.be/tDDVAErOI5U]
+[https://www.youtube.com/embed/orybDrUj4vA?autoplay=1]
+# 3:02 ad
+[https://www.youtube.com/embed/tDDVAErOI5U?autoplay=1]
deleted file mode 100644
--- a/dom/media/test/external/external_media_tests/urls/youtube/medium2-60.ini
+++ /dev/null
@@ -1,6 +0,0 @@
-# a few longer videos, < 60 min total
-# 0:30:00 no ad
-[https://www.youtube.com/watch?v=-qXxNPvqHtQ]
-# 0:20:00
-[http://youtu.be/Fu2DcHzokew]
-
deleted file mode 100644
--- a/dom/media/test/external/external_media_tests/urls/youtube/medium3-120.ini
+++ /dev/null
@@ -1,11 +0,0 @@
-# a few longer videos, < 120 min total
-# video with ad in the middle
-# 21:00
-[https://www.youtube.com/watch?v=cht9Xq9suGg]
-# 16:00
-[https://www.youtube.com/watch?v=6Lm9EHhbJAY]
-# 20:00
-[https://www.youtube.com/watch?v=8XQ1onjXJK0]
-# 59:06
-[https://www.youtube.com/watch?v=kmpiY5kssU4]
-
rename from dom/media/test/external/external_media_tests/urls/youtube/short0-10.ini
rename to dom/media/test/external/external_media_tests/urls/youtube/short1-10.ini
deleted file mode 100644
--- a/dom/media/test/external/external_media_tests/urls/youtube/short1-15.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-# 00:12
-[https://youtu.be/AbAACm1IQE0]
-# longer video with ads; < 15 min total
-# 13:40
-[https://www.youtube.com/watch?v=87uo2TPrsl8]
deleted file mode 100644
--- a/dom/media/test/external/external_media_tests/urls/youtube/short2-15.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-# 1-2 longer videos with ads; < 15 minutes total
-[https://www.youtube.com/watch?v=v678Em6qyzk]
-[https://www.youtube.com/watch?v=l8XOZJkozfI]
-
-
rename from dom/media/test/external/external_media_tests/urls/youtube/short3-crashes-15.ini
rename to dom/media/test/external/external_media_tests/urls/youtube/short2-crashes-15.ini
--- a/dom/media/test/external/external_media_tests/urls/youtube/short3-crashes-15.ini
+++ b/dom/media/test/external/external_media_tests/urls/youtube/short2-crashes-15.ini
@@ -1,8 +1,11 @@
+# It appears these are not currently used by tests. They are left here as they
+# reference failure scenarios. If tese are fixed that can be removed.
+
 # crash-data videos, < 15 minutes total
 
 # hang | NtUserMessageCall | SendMessageW
 # 5:40
 [https://www.youtube.com/watch?v=UIobdRNLNek]
 
 # F1398665248_____________________________
 # 3:59
--- a/dom/plugins/base/npapi.h
+++ b/dom/plugins/base/npapi.h
@@ -41,16 +41,17 @@
 #endif
 #endif
 
 #if defined(XP_UNIX)
 #include <stdio.h>
 #if defined(MOZ_X11)
 #include <X11/Xlib.h>
 #include <X11/Xutil.h>
+#include "X11UndefineNone.h"
 #endif
 #endif
 
 #if defined(XP_SYMBIAN)
 #include <QEvent>
 #include <QRegion>
 #endif
 
--- a/dom/plugins/base/nsPluginInstanceOwner.cpp
+++ b/dom/plugins/base/nsPluginInstanceOwner.cpp
@@ -2605,17 +2605,17 @@ nsEventStatus nsPluginInstanceOwner::Pro
         // Get reference point relative to screen:
         LayoutDeviceIntPoint rootPoint(-1, -1);
         if (widget) {
           rootPoint = anEvent.mRefPoint + widget->WidgetToScreenOffset();
         }
 #ifdef MOZ_WIDGET_GTK
         Window root = GDK_ROOT_WINDOW();
 #else
-        Window root = None; // Could XQueryTree, but this is not important.
+        Window root = X11None; // Could XQueryTree, but this is not important.
 #endif
 
         switch (anEvent.mMessage) {
           case eMouseOver:
           case eMouseOut:
             {
               XCrossingEvent& event = pluginEvent.xcrossing;
               event.type = anEvent.mMessage == eMouseOver ?
@@ -2623,17 +2623,17 @@ nsEventStatus nsPluginInstanceOwner::Pro
               event.root = root;
               event.time = anEvent.mTime;
               event.x = pluginPoint.x;
               event.y = pluginPoint.y;
               event.x_root = rootPoint.x;
               event.y_root = rootPoint.y;
               event.state = XInputEventState(mouseEvent);
               // information lost
-              event.subwindow = None;
+              event.subwindow = X11None;
               event.mode = -1;
               event.detail = NotifyDetailNone;
               event.same_screen = True;
               event.focus = mContentFocused;
             }
             break;
           case eMouseMove:
             {
@@ -2642,17 +2642,17 @@ nsEventStatus nsPluginInstanceOwner::Pro
               event.root = root;
               event.time = anEvent.mTime;
               event.x = pluginPoint.x;
               event.y = pluginPoint.y;
               event.x_root = rootPoint.x;
               event.y_root = rootPoint.y;
               event.state = XInputEventState(mouseEvent);
               // information lost
-              event.subwindow = None;
+              event.subwindow = X11None;
               event.is_hint = NotifyNormal;
               event.same_screen = True;
             }
             break;
           case eMouseDown:
           case eMouseUp:
             {
               XButtonEvent& event = pluginEvent.xbutton;
@@ -2673,17 +2673,17 @@ nsEventStatus nsPluginInstanceOwner::Pro
                 case WidgetMouseEvent::eRightButton:
                   event.button = 3;
                   break;
                 default: // WidgetMouseEvent::eLeftButton;
                   event.button = 1;
                   break;
                 }
               // information lost:
-              event.subwindow = None;
+              event.subwindow = X11None;
               event.same_screen = True;
             }
             break;
           default:
             break;
           }
       }
       break;
@@ -2717,17 +2717,17 @@ nsEventStatus nsPluginInstanceOwner::Pro
               break;
             default:
               break;
             }
 #endif
 
           // Information that could be obtained from pluginEvent but we may not
           // want to promise to provide:
-          event.subwindow = None;
+          event.subwindow = X11None;
           event.x = 0;
           event.y = 0;
           event.x_root = -1;
           event.y_root = -1;
           event.same_screen = False;
         }
       else
         {
@@ -2759,17 +2759,17 @@ nsEventStatus nsPluginInstanceOwner::Pro
   if (!pluginEvent.type) {
     return rv;
   }
 
   // Fill in (useless) generic event information.
   XAnyEvent& event = pluginEvent.xany;
   event.display = widget ?
     static_cast<Display*>(widget->GetNativeData(NS_NATIVE_DISPLAY)) : nullptr;
-  event.window = None; // not a real window
+  event.window = X11None; // not a real window
   // information lost:
   event.serial = 0;
   event.send_event = False;
 
   int16_t response = kNPEventNotHandled;
   mInstance->HandleEvent(&pluginEvent, &response, NS_PLUGIN_CALL_SAFE_TO_REENTER_GECKO);
   if (response == kNPEventHandled)
     rv = nsEventStatus_eConsumeNoDefault;
--- a/dom/plugins/base/nsPluginNativeWindowGtk.cpp
+++ b/dom/plugins/base/nsPluginNativeWindowGtk.cpp
@@ -212,17 +212,17 @@ nsresult nsPluginNativeWindowGtk::Create
     return NS_ERROR_FAILURE;
 
   mWsInfo.display = GDK_WINDOW_XDISPLAY(gdkWindow);
 #if (MOZ_WIDGET_GTK == 2)
   mWsInfo.colormap = GDK_COLORMAP_XCOLORMAP(gdk_drawable_get_colormap(gdkWindow));
   GdkVisual* gdkVisual = gdk_drawable_get_visual(gdkWindow);
   mWsInfo.depth = gdkVisual->depth;
 #else
-  mWsInfo.colormap = None;
+  mWsInfo.colormap = X11None;
   GdkVisual* gdkVisual = gdk_window_get_visual(gdkWindow);
   mWsInfo.depth = gdk_visual_get_depth(gdkVisual);
 #endif
   mWsInfo.visual = GDK_VISUAL_XVISUAL(gdkVisual);
     
   return NS_OK;
 }
 
--- a/dom/plugins/ipc/PluginInstanceChild.cpp
+++ b/dom/plugins/ipc/PluginInstanceChild.cpp
@@ -1277,17 +1277,17 @@ PluginInstanceChild::AnswerNPP_SetWindow
                 // workaround https://bugzilla.gnome.org/show_bug.cgi?id=607061
                 // See wrap_gtk_plug_embedded in PluginModuleChild.cpp.
                 g_object_set_data(G_OBJECT(socket_window),
                                   "moz-existed-before-set-window",
                                   GUINT_TO_POINTER(1));
             }
         }
 
-        if (aWindow.visualID != None
+        if (aWindow.visualID != X11None
             && gtk_check_version(2, 12, 10) != nullptr) { // older
             // Workaround for a bug in Gtk+ (prior to 2.12.10) where deleting
             // a foreign GdkColormap will also free the XColormap.
             // http://git.gnome.org/browse/gtk+/log/gdk/x11/gdkcolor-x11.c?id=GTK_2_12_10
             GdkVisual *gdkvisual = gdkx_visual_get(aWindow.visualID);
             GdkColormap *gdkcolor =
                 gdk_x11_colormap_foreign_new(gdkvisual, aWindow.colormap);
 
--- a/dom/plugins/test/testplugin/nptest_gtk2.cpp
+++ b/dom/plugins/test/testplugin/nptest_gtk2.cpp
@@ -76,33 +76,33 @@ pluginInstanceInit(InstanceData* instanc
 #ifdef MOZ_X11
   instanceData->platformData = static_cast<PlatformData*>
     (NPN_MemAlloc(sizeof(PlatformData)));
   if (!instanceData->platformData)
     return NPERR_OUT_OF_MEMORY_ERROR;
 
   instanceData->platformData->display = nullptr;
   instanceData->platformData->visual = nullptr;
-  instanceData->platformData->colormap = None;  
+  instanceData->platformData->colormap = X11None;
   instanceData->platformData->plug = nullptr;
 
   return NPERR_NO_ERROR;
 #else
   // we only support X11 here, since thats what the plugin system uses
   return NPERR_INCOMPATIBLE_VERSION_ERROR;
 #endif
 }
 
 void
 pluginInstanceShutdown(InstanceData* instanceData)
 {
   if (instanceData->hasWidget) {
     Window window = reinterpret_cast<XID>(instanceData->window.window);
 
-    if (window != None) {
+    if (window != X11None) {
       // This window XID should still be valid.
       // See bug 429604 and bug 454756.
       XWindowAttributes attributes;
       if (!XGetWindowAttributes(instanceData->platformData->display, window,
                                 &attributes))
         g_error("XGetWindowAttributes failed at plugin instance shutdown");
     }
   }
--- a/gfx/2d/BorrowedContext.h
+++ b/gfx/2d/BorrowedContext.h
@@ -6,16 +6,17 @@
 #ifndef _MOZILLA_GFX_BORROWED_CONTEXT_H
 #define _MOZILLA_GFX_BORROWED_CONTEXT_H
 
 #include "2D.h"
 
 #ifdef MOZ_X11
 #include <X11/extensions/Xrender.h>
 #include <X11/Xlib.h>
+#include "X11UndefineNone.h"
 #endif
 
 struct _cairo;
 typedef struct _cairo cairo_t;
 
 namespace mozilla {
 
 namespace gfx {
@@ -82,26 +83,26 @@ private:
  * to see if it succeeded. The DrawTarget should not be used while
  * the drawable is borrowed. */
 class BorrowedXlibDrawable
 {
 public:
   BorrowedXlibDrawable()
     : mDT(nullptr),
       mDisplay(nullptr),
-      mDrawable(None),
+      mDrawable(X11None),
       mScreen(nullptr),
       mVisual(nullptr),
       mXRenderFormat(nullptr)
   {}
 
   explicit BorrowedXlibDrawable(DrawTarget *aDT)
     : mDT(nullptr),
       mDisplay(nullptr),
-      mDrawable(None),
+      mDrawable(X11None),
       mScreen(nullptr),
       mVisual(nullptr),
       mXRenderFormat(nullptr)
   {
     Init(aDT);
   }
 
   // We can optionally Init after construction in
--- a/gfx/2d/DrawTargetCairo.cpp
+++ b/gfx/2d/DrawTargetCairo.cpp
@@ -2158,17 +2158,17 @@ DrawTargetCairo::Draw3DTransformedSurfac
   XRenderSetPictureTransform(display, srcPict, (XTransform*)&xform);
 
   Picture dstPict = XRenderCreatePicture(display,
                                          cairo_xlib_surface_get_drawable(xformSurf),
                                          cairo_xlib_surface_get_xrender_format(xformSurf),
                                          0, nullptr);
 
   XRenderComposite(display, PictOpSrc,
-                   srcPict, None, dstPict,
+                   srcPict, X11None, dstPict,
                    0, 0, 0, 0, 0, 0,
                    xformBounds.width, xformBounds.height);
 
   XRenderFreePicture(display, srcPict);
   XRenderFreePicture(display, dstPict);
 
   cairo_device_release(device);
   cairo_surface_mark_dirty(xformSurf);
@@ -2308,17 +2308,17 @@ BorrowedCairoContext::ReturnCairoContext
 
 #ifdef MOZ_X11
 bool
 BorrowedXlibDrawable::Init(DrawTarget* aDT)
 {
   MOZ_ASSERT(aDT, "Caller should check for nullptr");
   MOZ_ASSERT(!mDT, "Can't initialize twice!");
   mDT = aDT;
-  mDrawable = None;
+  mDrawable = X11None;
 
 #ifdef CAIRO_HAS_XLIB_SURFACE
   if (aDT->GetBackendType() != BackendType::CAIRO ||
       aDT->IsDualDrawTarget() ||
       aDT->IsTiledDrawTarget()) {
     return false;
   }
 
@@ -2351,15 +2351,15 @@ BorrowedXlibDrawable::Init(DrawTarget* a
 
 void
 BorrowedXlibDrawable::Finish()
 {
   DrawTargetCairo* cairoDT = static_cast<DrawTargetCairo*>(mDT);
   cairo_surface_t* surf = cairo_get_group_target(cairoDT->mContext);
   cairo_surface_mark_dirty(surf);
   if (mDrawable) {
-    mDrawable = None;
+    mDrawable = X11None;
   }
 }
 #endif
 
 } // namespace gfx
 } // namespace mozilla
--- a/gfx/gl/GLContextProviderGLX.cpp
+++ b/gfx/gl/GLContextProviderGLX.cpp
@@ -6,16 +6,17 @@
 #ifdef MOZ_WIDGET_GTK
 #include <gdk/gdk.h>
 #include <gdk/gdkx.h>
 #define GET_NATIVE_WINDOW(aWidget) GDK_WINDOW_XID((GdkWindow*) aWidget->GetNativeData(NS_NATIVE_WINDOW))
 #endif
 
 #include <X11/Xlib.h>
 #include <X11/Xutil.h>
+#include "X11UndefineNone.h"
 
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/widget/CompositorWidget.h"
 #include "mozilla/Unused.h"
 
 #include "prenv.h"
 #include "GLContextProvider.h"
@@ -303,36 +304,36 @@ GLXLibrary::SupportsVideoSync()
 
     return mHasVideoSync;
 }
 
 GLXPixmap
 GLXLibrary::CreatePixmap(gfxASurface* aSurface)
 {
     if (!SupportsTextureFromPixmap(aSurface)) {
-        return None;
+        return X11None;
     }
 
     gfxXlibSurface* xs = static_cast<gfxXlibSurface*>(aSurface);
     const XRenderPictFormat* format = xs->XRenderFormat();
     if (!format || format->type != PictTypeDirect) {
-        return None;
+        return X11None;
     }
     const XRenderDirectFormat& direct = format->direct;
     int alphaSize = FloorLog2(direct.alphaMask + 1);
     NS_ASSERTION((1 << alphaSize) - 1 == direct.alphaMask,
                  "Unexpected render format with non-adjacent alpha bits");
 
     int attribs[] = { LOCAL_GLX_DOUBLEBUFFER, False,
                       LOCAL_GLX_DRAWABLE_TYPE, LOCAL_GLX_PIXMAP_BIT,
                       LOCAL_GLX_ALPHA_SIZE, alphaSize,
                       (alphaSize ? LOCAL_GLX_BIND_TO_TEXTURE_RGBA_EXT
                        : LOCAL_GLX_BIND_TO_TEXTURE_RGB_EXT), True,
                       LOCAL_GLX_RENDER_TYPE, LOCAL_GLX_RGBA_BIT,
-                      None };
+                      X11None };
 
     int numConfigs = 0;
     Display* display = xs->XDisplay();
     int xscreen = DefaultScreen(display);
 
     ScopedXFree<GLXFBConfig> cfgs(xChooseFBConfig(display,
                                                   xscreen,
                                                   attribs,
@@ -346,17 +347,17 @@ GLXLibrary::CreatePixmap(gfxASurface* aS
         static_cast<unsigned long>(direct.greenMask) << direct.green;
     unsigned long blueMask =
         static_cast<unsigned long>(direct.blueMask) << direct.blue;
     // This is true if the Pixmap has bits for alpha or unused bits.
     bool haveNonColorBits =
         ~(redMask | greenMask | blueMask) != -1UL << format->depth;
 
     for (int i = 0; i < numConfigs; i++) {
-        int id = None;
+        int id = X11None;
         sGLXLibrary.xGetFBConfigAttrib(display, cfgs[i], LOCAL_GLX_VISUAL_ID, &id);
         Visual* visual;
         int depth;
         FindVisualAndDepth(display, id, &visual, &depth);
         if (!visual ||
             visual->c_class != TrueColor ||
             visual->red_mask != redMask ||
             visual->green_mask != greenMask ||
@@ -419,24 +420,24 @@ GLXLibrary::CreatePixmap(gfxASurface* aS
         matchIndex = i;
         break;
     }
     if (matchIndex == -1) {
         // GLX can't handle A8 surfaces, so this is not really unexpected. The
         // caller should deal with this situation.
         NS_WARN_IF_FALSE(format->depth == 8,
                          "[GLX] Couldn't find a FBConfig matching Pixmap format");
-        return None;
+        return X11None;
     }
 
     int pixmapAttribs[] = { LOCAL_GLX_TEXTURE_TARGET_EXT, LOCAL_GLX_TEXTURE_2D_EXT,
                             LOCAL_GLX_TEXTURE_FORMAT_EXT,
                             (alphaSize ? LOCAL_GLX_TEXTURE_FORMAT_RGBA_EXT
                              : LOCAL_GLX_TEXTURE_FORMAT_RGB_EXT),
-                            None};
+                            X11None};
 
     GLXPixmap glxpixmap = xCreatePixmap(display,
                                         cfgs[matchIndex],
                                         xs->XDrawable(),
                                         pixmapAttribs);
 
     return glxpixmap;
 }
@@ -895,17 +896,17 @@ GLContextGLX::~GLContextGLX()
     if (!mOwnsContext) {
         return;
     }
 
     // see bug 659842 comment 76
 #ifdef DEBUG
     bool success =
 #endif
-    mGLX->xMakeCurrent(mDisplay, None, nullptr);
+    mGLX->xMakeCurrent(mDisplay, X11None, nullptr);
     MOZ_ASSERT(success,
                "glXMakeCurrent failed to release GL context before we call "
                "glXDestroyContext!");
 
     mGLX->xDestroyContext(mDisplay, mContext);
 
     if (mDeleteDrawable) {
         mGLX->xDestroyPixmap(mDisplay, mDrawable);
@@ -1237,17 +1238,17 @@ GLContextGLX::FindFBConfigForWindow(Disp
         return false;
     }
     const VisualID windowVisualID = XVisualIDFromVisual(windowAttrs.visual);
 #ifdef DEBUG
     printf("[GLX] window %lx has VisualID 0x%lx\n", window, windowVisualID);
 #endif
 
     for (int i = 0; i < numConfigs; i++) {
-        int visid = None;
+        int visid = X11None;
         sGLXLibrary.xGetFBConfigAttrib(display, cfgs[i], LOCAL_GLX_VISUAL_ID, &visid);
         if (!visid) {
             continue;
         }
         if (sGLXLibrary.IsATI()) {
             int depth;
             Visual* visual;
             FindVisualAndDepth(display, visid, &visual, &depth);
--- a/gfx/layers/ipc/ShadowLayerUtilsX11.cpp
+++ b/gfx/layers/ipc/ShadowLayerUtilsX11.cpp
@@ -6,16 +6,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ShadowLayerUtilsX11.h"
 #include <X11/X.h>                      // for Drawable, XID
 #include <X11/Xlib.h>                   // for Display, Visual, etc
 #include <X11/extensions/Xrender.h>     // for XRenderPictFormat, etc
 #include <X11/extensions/render.h>      // for PictFormat
 #include "cairo-xlib.h"
+#include "X11UndefineNone.h"
 #include <stdint.h>                     // for uint32_t
 #include "GLDefs.h"                     // for GLenum
 #include "gfxPlatform.h"                // for gfxPlatform
 #include "gfxXlibSurface.h"             // for gfxXlibSurface
 #include "gfx2DGlue.h"                  // for Moz2D transistion helpers
 #include "mozilla/X11Util.h"            // for DefaultXDisplay, FinishX, etc
 #include "mozilla/gfx/Point.h"          // for IntSize
 #include "mozilla/layers/CompositableForwarder.h"
@@ -60,17 +61,17 @@ GetXRenderPictFormatFromId(Display* aDis
   tmplate.id = aFormatId;
   return XRenderFindFormat(aDisplay, PictFormatID, &tmplate, 0);
 }
 
 SurfaceDescriptorX11::SurfaceDescriptorX11(gfxXlibSurface* aSurf,
                                            bool aForwardGLX)
   : mId(aSurf->XDrawable())
   , mSize(aSurf->GetSize())
-  , mGLXPixmap(None)
+  , mGLXPixmap(X11None)
 {
   const XRenderPictFormat *pictFormat = aSurf->XRenderFormat();
   if (pictFormat) {
     mFormat = pictFormat->id;
   } else {
     mFormat = cairo_xlib_surface_get_visual(aSurf->CairoSurface())->visualid;
   }
 
@@ -81,17 +82,17 @@ SurfaceDescriptorX11::SurfaceDescriptorX
 #endif
 }
 
 SurfaceDescriptorX11::SurfaceDescriptorX11(Drawable aDrawable, XID aFormatID,
                                            const gfx::IntSize& aSize)
   : mId(aDrawable)
   , mFormat(aFormatID)
   , mSize(aSize)
-  , mGLXPixmap(None)
+  , mGLXPixmap(X11None)
 { }
 
 already_AddRefed<gfxXlibSurface>
 SurfaceDescriptorX11::OpenForeign() const
 {
   Display* display = DefaultXDisplay();
   Screen* screen = DefaultScreenOfDisplay(display);
 
new file mode 100644
--- /dev/null
+++ b/gfx/src/X11UndefineNone.h
@@ -0,0 +1,27 @@
+/* -*- 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 MOZILLA_GFX_X11UNDEFINENONE_H_
+#define MOZILLA_GFX_X11UNDEFINENONE_H_
+
+// The header <X11/X.h> defines "None" as a macro that expands to "0L".
+// This is terrible because many enumerations have an enumerator named "None".
+// To work around this, we undefine the macro "None", and define a replacement
+// macro named "X11None".
+// Include this header after including X11 headers, where necessary.
+#ifdef None
+#  undef None
+#  define X11None 0L
+// <X11/X.h> also defines "RevertToNone" as a macro that expands to "(int)None".
+// Since we are undefining "None", that stops working. To keep it working,
+// we undefine "RevertToNone" and redefine it in terms of "X11None".
+#  ifdef RevertToNone
+#    undef RevertToNone
+#    define RevertToNone (int)X11None
+#  endif
+#endif
+
+#endif /* MOZILLA_GFX_X11UNDEFINENONE_H_ */
--- a/gfx/src/X11Util.cpp
+++ b/gfx/src/X11Util.cpp
@@ -24,17 +24,17 @@ FindVisualAndDepth(Display* aDisplay, Vi
             if (visual->visualid == aVisualID) {
                 *aVisual = visual;
                 *aDepth = d_info->depth;
                 return;
             }
         }
     }
 
-    NS_ASSERTION(aVisualID == None, "VisualID not on Screen.");
+    NS_ASSERTION(aVisualID == X11None, "VisualID not on Screen.");
     *aVisual = nullptr;
     *aDepth = 0;
     return;
 }
 
 void
 FinishX(Display* aDisplay)
 {
--- a/gfx/src/X11Util.h
+++ b/gfx/src/X11Util.h
@@ -8,16 +8,17 @@
 #ifndef mozilla_X11Util_h
 #define mozilla_X11Util_h
 
 // Utilities common to all X clients, regardless of UI toolkit.
 
 #if defined(MOZ_WIDGET_GTK)
 #  include <gdk/gdk.h>
 #  include <gdk/gdkx.h>
+#  include "X11UndefineNone.h"
 #else
 #  error Unknown toolkit
 #endif
 
 #include <string.h>                     // for memset
 #include "mozilla/Scoped.h"             // for SCOPED_TEMPLATE
 
 namespace mozilla {
--- a/gfx/src/moz.build
+++ b/gfx/src/moz.build
@@ -34,16 +34,17 @@ EXPORTS += [
     'nsRegion.h',
     'nsRegionFwd.h',
     'nsRenderingContext.h',
     'nsSize.h',
     'nsThemeConstants.h',
     'nsTransform2D.h',
     'PingPongRegion.h',
     'RegionBuilder.h',
+    'X11UndefineNone.h'
 ]
 
 EXPORTS.mozilla += [
     'AppUnits.h',
     'ArrayView.h',
 ]
 
 EXPORTS.mozilla.gfx += [
--- a/gfx/thebes/gfxXlibSurface.cpp
+++ b/gfx/thebes/gfxXlibSurface.cpp
@@ -20,59 +20,59 @@
 #include "mozilla/CheckedInt.h"
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 
 gfxXlibSurface::gfxXlibSurface(Display *dpy, Drawable drawable, Visual *visual)
     : mPixmapTaken(false), mDisplay(dpy), mDrawable(drawable)
 #if defined(GL_PROVIDER_GLX)
-    , mGLXPixmap(None)
+    , mGLXPixmap(X11None)
 #endif
 {
     const gfx::IntSize size = DoSizeQuery();
     cairo_surface_t *surf = cairo_xlib_surface_create(dpy, drawable, visual, size.width, size.height);
     Init(surf);
 }
 
 gfxXlibSurface::gfxXlibSurface(Display *dpy, Drawable drawable, Visual *visual, const gfx::IntSize& size)
     : mPixmapTaken(false), mDisplay(dpy), mDrawable(drawable)
 #if defined(GL_PROVIDER_GLX)
-    , mGLXPixmap(None)
+    , mGLXPixmap(X11None)
 #endif
 {
     NS_ASSERTION(Factory::CheckSurfaceSize(size, XLIB_IMAGE_SIDE_SIZE_LIMIT),
                  "Bad size");
 
     cairo_surface_t *surf = cairo_xlib_surface_create(dpy, drawable, visual, size.width, size.height);
     Init(surf);
 }
 
 gfxXlibSurface::gfxXlibSurface(Screen *screen, Drawable drawable, XRenderPictFormat *format,
                                const gfx::IntSize& size)
     : mPixmapTaken(false), mDisplay(DisplayOfScreen(screen)),
       mDrawable(drawable)
 #if defined(GL_PROVIDER_GLX)
-      , mGLXPixmap(None)
+      , mGLXPixmap(X11None)
 #endif
 {
     NS_ASSERTION(Factory::CheckSurfaceSize(size, XLIB_IMAGE_SIDE_SIZE_LIMIT),
                  "Bad Size");
 
     cairo_surface_t *surf =
         cairo_xlib_surface_create_with_xrender_format(mDisplay, drawable,
                                                       screen, format,
                                                       size.width, size.height);
     Init(surf);
 }
 
 gfxXlibSurface::gfxXlibSurface(cairo_surface_t *csurf)
     : mPixmapTaken(false)
 #if defined(GL_PROVIDER_GLX)
-      , mGLXPixmap(None)
+      , mGLXPixmap(X11None)
 #endif
 {
     NS_PRECONDITION(cairo_surface_status(csurf) == 0,
                     "Not expecting an error surface");
 
     mDrawable = cairo_xlib_surface_get_drawable(csurf);
     mDisplay = cairo_xlib_surface_get_display(csurf);
 
@@ -92,19 +92,19 @@ gfxXlibSurface::~gfxXlibSurface()
     }
 }
 
 static Drawable
 CreatePixmap(Screen *screen, const gfx::IntSize& size, unsigned int depth,
              Drawable relatedDrawable)
 {
     if (!Factory::CheckSurfaceSize(size, XLIB_IMAGE_SIDE_SIZE_LIMIT))
-        return None;
+        return X11None;
 
-    if (relatedDrawable == None) {
+    if (relatedDrawable == X11None) {
         relatedDrawable = RootWindowOfScreen(screen);
     }
     Display *dpy = DisplayOfScreen(screen);
     // X gives us a fatal error if we try to create a pixmap of width
     // or height 0
     return XCreatePixmap(dpy, relatedDrawable,
                          std::max(1, size.width), std::max(1, size.height),
                          depth);
@@ -269,17 +269,17 @@ gfxXlibSurface::CreateSimilarSurface(gfx
 }
 
 void
 gfxXlibSurface::Finish()
 {
 #if defined(GL_PROVIDER_GLX)
     if (mPixmapTaken && mGLXPixmap) {
         gl::sGLXLibrary.DestroyPixmap(mDisplay, mGLXPixmap);
-        mGLXPixmap = None;
+        mGLXPixmap = X11None;
     }
 #endif
     gfxASurface::Finish();
 }
 
 const gfx::IntSize
 gfxXlibSurface::GetSize() const
 {
--- a/gfx/thebes/gfxXlibSurface.h
+++ b/gfx/thebes/gfxXlibSurface.h
@@ -5,16 +5,17 @@
 
 #ifndef GFX_XLIBSURFACE_H
 #define GFX_XLIBSURFACE_H
 
 #include "gfxASurface.h"
 
 #include <X11/extensions/Xrender.h>
 #include <X11/Xlib.h>
+#include "X11UndefineNone.h"
 
 #if defined(GL_PROVIDER_GLX)
 #include "GLXLibrary.h"
 #endif
 
 #include "nsSize.h"
 
 // Although the dimension parameters in the xCreatePixmapReq wire protocol are
@@ -41,23 +42,23 @@ public:
     explicit gfxXlibSurface(cairo_surface_t *csurf);
 
     // create a new Pixmap and wrapper surface.
     // |relatedDrawable| provides a hint to the server for determining whether
     // the pixmap should be in video or system memory.  It must be on
     // |screen| (if specified).
     static already_AddRefed<gfxXlibSurface>
     Create(Screen *screen, Visual *visual, const mozilla::gfx::IntSize& size,
-           Drawable relatedDrawable = None);
+           Drawable relatedDrawable = X11None);
     static cairo_surface_t *
     CreateCairoSurface(Screen *screen, Visual *visual, const mozilla::gfx::IntSize& size,
-                       Drawable relatedDrawable = None);
+                       Drawable relatedDrawable = X11None);
     static already_AddRefed<gfxXlibSurface>
     Create(Screen* screen, XRenderPictFormat *format, const mozilla::gfx::IntSize& size,
-           Drawable relatedDrawable = None);
+           Drawable relatedDrawable = X11None);
 
     virtual ~gfxXlibSurface();
 
     virtual already_AddRefed<gfxASurface>
     CreateSimilarSurface(gfxContentType aType,
                          const mozilla::gfx::IntSize& aSize) override;
     virtual void Finish() override;
 
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -477,17 +477,19 @@ ServoStyleSet::StyleDocument(bool aLeave
     doc->UnsetHasDirtyDescendantsForServo();
   }
 }
 
 void
 ServoStyleSet::StyleNewSubtree(nsIContent* aContent)
 {
   MOZ_ASSERT(aContent->IsDirtyForServo());
-  Servo_RestyleSubtree(aContent, mRawSet.get());
+  if (aContent->IsElement() || aContent->IsNodeOfType(nsINode::eTEXT)) {
+    Servo_RestyleSubtree(aContent, mRawSet.get());
+  }
   ClearDirtyBits(aContent);
 }
 
 void
 ServoStyleSet::StyleNewChildren(nsIContent* aParent)
 {
   MOZ_ASSERT(aParent->HasDirtyDescendantsForServo());
   Servo_RestyleSubtree(aParent, mRawSet.get());
--- a/media/mtransport/test/ice_unittest.cpp
+++ b/media/mtransport/test/ice_unittest.cpp
@@ -758,17 +758,17 @@ class IceTestPeer : public sigslot::has_
         RefPtr<NrIceMediaStream> aStream = ice_ctx_->ctx()->GetStream(i);
         if (!aStream || aStream->HasParsedAttributes()) {
           continue;
         }
         std::vector<std::string> candidates =
             remote->GetCandidates(i);
 
         for (size_t j=0; j<candidates.size(); ++j) {
-          std::cerr << name_ << " Candidate: " + candidates[j] << std::endl;
+          std::cerr << name_ << " Adding remote candidate: " + candidates[j] << std::endl;
         }
         res = aStream->ParseAttributes(candidates);
         ASSERT_TRUE(NS_SUCCEEDED(res));
       }
     } else {
       // Parse empty attributes and then trickle them out later
       for (size_t i=0; i<ice_ctx_->ctx()->GetStreamCount(); ++i) {
         RefPtr<NrIceMediaStream> aStream = ice_ctx_->ctx()->GetStream(i);
--- a/media/mtransport/third_party/nICEr/src/ice/ice_candidate_pair.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_candidate_pair.c
@@ -176,17 +176,18 @@ int nr_ice_candidate_pair_unfreeze(nr_ic
     nr_ice_candidate_pair_set_state(pctx,pair,NR_ICE_PAIR_STATE_WAITING);
 
     return(0);
   }
 
 static void nr_ice_candidate_pair_stun_cb(NR_SOCKET s, int how, void *cb_arg)
   {
     int r,_status;
-    nr_ice_cand_pair *pair=cb_arg,*orig_pair;
+    nr_ice_cand_pair *pair=cb_arg;
+    nr_ice_cand_pair *actual_pair=0;
     nr_ice_candidate *cand=0;
     nr_stun_message *sres;
     nr_transport_addr *request_src;
     nr_transport_addr *request_dst;
     nr_transport_addr *response_src;
     nr_transport_addr response_dst;
     nr_stun_message_attribute *attr;
 
@@ -251,58 +252,65 @@ static void nr_ice_candidate_pair_stun_c
           nr_ice_candidate_pair_set_state(pair->pctx,pair,NR_ICE_PAIR_STATE_SUCCEEDED);
         }
         else if(pair->stun_client->state == NR_STUN_CLIENT_STATE_DONE) {
           /* OK, this didn't correspond to a pair on the check list, but
              it probably matches one of our candidates */
 
           cand=TAILQ_FIRST(&pair->local->component->candidates);
           while(cand){
-            if(!nr_transport_addr_cmp(&cand->addr,&pair->stun_client->results.ice_binding_response.mapped_addr,NR_TRANSPORT_ADDR_CMP_MODE_ALL))
+            if(!nr_transport_addr_cmp(&cand->addr,&pair->stun_client->results.ice_binding_response.mapped_addr,NR_TRANSPORT_ADDR_CMP_MODE_ALL)) {
+              r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s): found pre-existing local candidate of type %d for mapped address %s", pair->pctx->label,cand->type,cand->addr.as_string);
+              assert(cand->type != HOST);
               break;
+            }
 
             cand=TAILQ_NEXT(cand,entry_comp);
           }
 
-          /* OK, nothing found, must be peer reflexive */
           if(!cand) {
+            /* OK, nothing found, must be a new peer reflexive */
             if (pair->pctx->ctx->flags & NR_ICE_CTX_FLAGS_RELAY_ONLY) {
               /* Any STUN response with a reflexive address in it is unwanted
                  when we'll send on relay only. Bail since cand is used below. */
               goto done;
             }
             if(r=nr_ice_candidate_create(pair->pctx->ctx,
               pair->local->component,pair->local->isock,pair->local->osock,
               PEER_REFLEXIVE,pair->local->tcp_type,0,pair->local->component->component_id,&cand))
               ABORT(r);
             if(r=nr_transport_addr_copy(&cand->addr,&pair->stun_client->results.ice_binding_response.mapped_addr))
               ABORT(r);
             cand->state=NR_ICE_CAND_STATE_INITIALIZED;
             TAILQ_INSERT_TAIL(&pair->local->component->candidates,cand,entry_comp);
+          } else {
+            /* Check if we have a pair for this candidate already. */
+            if(r=nr_ice_media_stream_find_pair(pair->remote->stream, cand, pair->remote, &actual_pair)) {
+              r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s): no pair exists for %s and %s", pair->pctx->label,cand->addr.as_string, pair->remote->addr.as_string);
+            }
           }
 
-          /* Note: we stomp the existing pair! */
-          orig_pair=pair;
-          if(r=nr_ice_candidate_pair_create(pair->pctx,cand,pair->remote,
-            &pair))
-            ABORT(r);
+          if(!actual_pair) {
+            if(r=nr_ice_candidate_pair_create(pair->pctx,cand,pair->remote, &actual_pair))
+              ABORT(r);
 
-          nr_ice_candidate_pair_set_state(pair->pctx,pair,NR_ICE_PAIR_STATE_SUCCEEDED);
+            if(r=nr_ice_component_insert_pair(actual_pair->remote->component,actual_pair))
+              ABORT(r);
 
-          if(r=nr_ice_component_insert_pair(pair->remote->component,pair))
-            ABORT(r);
+            /* If the original pair was nominated, make us nominated too. */
+            if(pair->peer_nominated)
+              actual_pair->peer_nominated=1;
 
-          /* If the original pair was nominated, make us nominated,
-             since we replace him*/
-          if(orig_pair->peer_nominated)
-            pair->peer_nominated=1;
+            /* Now mark the orig pair failed */
+            nr_ice_candidate_pair_set_state(pair->pctx,pair,NR_ICE_PAIR_STATE_FAILED);
+          }
 
-
-          /* Now mark the orig pair failed */
-          nr_ice_candidate_pair_set_state(orig_pair->pctx,orig_pair,NR_ICE_PAIR_STATE_FAILED);
+          assert(actual_pair);
+          nr_ice_candidate_pair_set_state(actual_pair->pctx,actual_pair,NR_ICE_PAIR_STATE_SUCCEEDED);
+          pair=actual_pair;
 
         }
 
         /* Should we set nominated? */
         if(pair->pctx->controlling){
           if(pair->pctx->ctx->flags & NR_ICE_CTX_FLAGS_AGGRESSIVE_NOMINATION)
             pair->nominated=1;
         }
--- a/media/mtransport/third_party/nICEr/src/ice/ice_media_stream.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_media_stream.c
@@ -908,8 +908,26 @@ void nr_ice_media_stream_role_change(nr_
     /* Re-insert into the check list */
     TAILQ_FOREACH_SAFE(pair,&old_checklist,check_queue_entry,temp_pair) {
       TAILQ_REMOVE(&old_checklist,pair,check_queue_entry);
       nr_ice_candidate_pair_role_change(pair);
       nr_ice_candidate_pair_insert(&stream->check_list,pair);
     }
   }
 
+int nr_ice_media_stream_find_pair(nr_ice_media_stream *str, nr_ice_candidate *lcand, nr_ice_candidate *rcand, nr_ice_cand_pair **pair)
+  {
+    nr_ice_cand_pair_head *head = &str->check_list;
+    nr_ice_cand_pair *c1;
+
+    c1=TAILQ_FIRST(head);
+    while(c1){
+      if(c1->local == lcand &&
+         c1->remote == rcand) {
+        *pair=c1;
+        return(0);
+      }
+
+      c1=TAILQ_NEXT(c1,check_queue_entry);
+    }
+
+    return(R_NOT_FOUND);
+  }
--- a/media/mtransport/third_party/nICEr/src/ice/ice_media_stream.h
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_media_stream.h
@@ -87,16 +87,17 @@ int nr_ice_media_stream_unfreeze_pairs_f
 int nr_ice_media_stream_dump_state(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream,FILE *out);
 int nr_ice_media_stream_component_nominated(nr_ice_media_stream *stream,nr_ice_component *component);
 int nr_ice_media_stream_component_failed(nr_ice_media_stream *stream,nr_ice_component *component);
 int nr_ice_media_stream_set_state(nr_ice_media_stream *str, int state);
 int nr_ice_media_stream_get_best_candidate(nr_ice_media_stream *str, int component, nr_ice_candidate **candp);
 int nr_ice_media_stream_send(nr_ice_peer_ctx *pctx, nr_ice_media_stream *str, int component, UCHAR *data, int len);
 int nr_ice_media_stream_get_active(nr_ice_peer_ctx *pctx, nr_ice_media_stream *str, int component, nr_ice_candidate **local, nr_ice_candidate **remote);
 int nr_ice_media_stream_find_component(nr_ice_media_stream *str, int comp_id, nr_ice_component **compp);
+int nr_ice_media_stream_find_pair(nr_ice_media_stream *str, nr_ice_candidate *local, nr_ice_candidate *remote, nr_ice_cand_pair **pair);
 int nr_ice_media_stream_addrs(nr_ice_peer_ctx *pctx, nr_ice_media_stream *str, int component, nr_transport_addr *local, nr_transport_addr *remote);
 int
 nr_ice_peer_ctx_parse_media_stream_attribute(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream, char *attr);
 int nr_ice_media_stream_get_consent_status(nr_ice_media_stream *stream, int component_id, int *can_send, struct timeval *ts);
 int nr_ice_media_stream_disable_component(nr_ice_media_stream *stream, int component_id);
 int nr_ice_media_stream_pair_new_trickle_candidate(nr_ice_peer_ctx *pctx, nr_ice_media_stream *pstream, nr_ice_candidate *cand);
 void nr_ice_media_stream_role_change(nr_ice_media_stream *stream);
 
--- a/mobile/android/base/java/org/mozilla/gecko/IntentHelper.java
+++ b/mobile/android/base/java/org/mozilla/gecko/IntentHelper.java
@@ -7,17 +7,16 @@ package org.mozilla.gecko;
 
 import org.mozilla.gecko.overlays.ui.ShareDialog;
 import org.mozilla.gecko.util.ActivityResultHandler;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.JSONUtils;
 import org.mozilla.gecko.util.NativeEventListener;
 import org.mozilla.gecko.util.NativeJSObject;
-import org.mozilla.gecko.util.WebActivityMapper;
 import org.mozilla.gecko.widget.ExternalIntentDuringPrivateBrowsingPromptFragment;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.annotation.TargetApi;
 import android.content.Context;
@@ -43,17 +42,16 @@ import java.util.Locale;
 public final class IntentHelper implements GeckoEventListener,
                                            NativeEventListener {
 
     private static final String LOGTAG = "GeckoIntentHelper";
     private static final String[] EVENTS = {
         "Intent:GetHandlers",
         "Intent:Open",
         "Intent:OpenForResult",
-        "WebActivity:Open"
     };
 
     private static final String[] NATIVE_EVENTS = {
         "Intent:OpenNoHandler",
     };
 
     // via http://developer.android.com/distribute/tools/promote/linking.html
     private static String MARKET_INTENT_URI_PACKAGE_PREFIX = "market://details?id=";
@@ -411,18 +409,16 @@ public final class IntentHelper implemen
     public void handleMessage(String event, JSONObject message) {
         try {
             if (event.equals("Intent:GetHandlers")) {
                 getHandlers(message);
             } else if (event.equals("Intent:Open")) {
                 open(message);
             } else if (event.equals("Intent:OpenForResult")) {
                 openForResult(message);
-            } else if (event.equals("WebActivity:Open")) {
-                openWebActivity(message);
             }
         } catch (JSONException e) {
             Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
         }
     }
 
     private void getHandlers(JSONObject message) throws JSONException {
         final Intent intent = getOpenURIIntent(activity,
@@ -567,21 +563,16 @@ public final class IntentHelper implemen
      * @param encodedUri The encoded uri. While the page does not open correctly without specifying
      *                   a uri parameter, it happily accepts the empty String so this argument may
      *                   be the empty String.
      */
     private String getUnknownProtocolErrorPageUri(final String encodedUri) {
         return UNKNOWN_PROTOCOL_URI_PREFIX + encodedUri;
     }
 
-    private void openWebActivity(JSONObject message) throws JSONException {
-        final Intent intent = WebActivityMapper.getIntentForWebActivity(message.getJSONObject("activity"));
-        ActivityHandlerHelper.startIntentForActivity(activity, intent, new ResultHandler(message));
-    }
-
     private static class ResultHandler implements ActivityResultHandler {
         private final JSONObject message;
 
         public ResultHandler(JSONObject message) {
             this.message = message;
         }
 
         @Override
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -134,17 +134,16 @@ gujar.sources += [geckoview_source_dir +
     'util/PrefUtils.java',
     'util/ProxySelector.java',
     'util/RawResource.java',
     'util/StringUtils.java',
     'util/ThreadUtils.java',
     'util/UIAsyncTask.java',
     'util/UUIDUtil.java',
     'util/WeakReferenceHandler.java',
-    'util/WebActivityMapper.java',
     'util/WindowUtils.java',
 ]]
 gujar.extra_jars = [
     CONFIG['ANDROID_SUPPORT_ANNOTATIONS_JAR_LIB'],
     CONFIG['ANDROID_SUPPORT_V4_AAR_LIB'],
     CONFIG['ANDROID_SUPPORT_V4_AAR_INTERNAL_LIB'],
     'constants.jar',
     'gecko-mozglue.jar',
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -52,20 +52,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/LoginManagerContent.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerParent",
                                   "resource://gre/modules/LoginManagerParent.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
 
-if (AppConstants.MOZ_SAFE_BROWSING) {
-  XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing",
-                                    "resource://gre/modules/SafeBrowsing.jsm");
-}
+XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing",
+                                  "resource://gre/modules/SafeBrowsing.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
                                   "resource://gre/modules/BrowserUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Sanitizer",
@@ -544,20 +542,18 @@ var BrowserApp = {
       });
 
       InitLater(() => LightWeightThemeWebInstaller.init());
       InitLater(() => SpatialNavigation.init(BrowserApp.deck, null), window, "SpatialNavigation");
       InitLater(() => CastingApps.init(), window, "CastingApps");
       InitLater(() => Services.search.init(), Services, "search");
       InitLater(() => DownloadNotifications.init(), window, "DownloadNotifications");
 
-      if (AppConstants.MOZ_SAFE_BROWSING) {
-        // Bug 778855 - Perf regression if we do this here. To be addressed in bug 779008.
-        InitLater(() => SafeBrowsing.init(), window, "SafeBrowsing");
-      }
+      // Bug 778855 - Perf regression if we do this here. To be addressed in bug 779008.
+      InitLater(() => SafeBrowsing.init(), window, "SafeBrowsing");
 
       InitLater(() => Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager));
       InitLater(() => LoginManagerParent.init(), window, "LoginManagerParent");
 
     }, false);
 
     // Pass caret StateChanged events to ActionBarHandler.
     window.addEventListener("mozcaretstatechanged", e => {
deleted file mode 100644
--- a/mobile/android/components/AndroidActivitiesGlue.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict"
-
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cu = Components.utils;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Messaging.jsm");
-
-function ActivitiesGlue() { }
-
-ActivitiesGlue.prototype = {
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIActivityUIGlue]),
-  classID: Components.ID("{e4deb5f6-d5e3-4fce-bc53-901dd9951c48}"),
-
-  // Ignore aActivities results on Android, go straight to Android intents.
-  chooseActivity: function ap_chooseActivity(aOptions, aActivities, aCallback) {
-    Messaging.sendRequestForResult({
-      type: "WebActivity:Open",
-      activity: { name: aOptions.name, data: aOptions.data }
-    }).then((result) => {
-      aCallback.handleEvent(Ci.nsIActivityUIGlueCallback.NATIVE_ACTIVITY, result);
-    });
-  }
-};
-
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ActivitiesGlue]);
--- a/mobile/android/components/LoginManagerPrompter.js
+++ b/mobile/android/components/LoginManagerPrompter.js
@@ -100,28 +100,33 @@ LoginManagerPrompter.prototype = {
 
   /* ---------- nsILoginManagerPrompter prompts ---------- */
 
   /*
    * init
    *
    */
   init : function (aWindow, aFactory) {
-    this._window = aWindow;
+    this._chromeWindow = this._getChromeWindow(aWindow).wrappedJSObject;
     this._factory = aFactory || null;
+    this._browser = null;
 
     var prefBranch = Services.prefs.getBranch("signon.");
     this._debug = prefBranch.getBoolPref("debug");
     this.log("===== initialized =====");
   },
 
-  setE10sData : function (aBrowser, aOpener) {
-    throw new Error("This should be filled in when Android is multiprocess");
+  set browser(aBrowser) {
+    this._browser = aBrowser;
   },
 
+  // setting this attribute is ignored because Android does not consider
+  // opener windows when displaying login notifications
+  set opener(aOpener) { },
+
   /*
    * promptToSavePassword
    *
    */
   promptToSavePassword : function (aLogin) {
     this._showSaveLoginNotification(aLogin);
       Services.telemetry.getHistogramById("PWMGR_PROMPT_REMEMBER_ACTION").add(PROMPT_DISPLAYED);
   },
@@ -135,20 +140,17 @@ LoginManagerPrompter.prototype = {
    * @param aButtons
    *        Buttons to display with the doorhanger
    * @param aUsername
    *        Username string used in creating a doorhanger action
    * @param aPassword
    *        Password string used in creating a doorhanger action
    */
   _showLoginNotification : function (aBody, aButtons, aUsername, aPassword) {
-    let notifyWin = this._window.top;
-    let chromeWin = this._getChromeWindow(notifyWin).wrappedJSObject;
-    let browser = chromeWin.BrowserApp.getBrowserForWindow(notifyWin);
-    let tabID = chromeWin.BrowserApp.getTabForBrowser(browser).id;
+    let tabID = this._chromeWindow.BrowserApp.getTabForBrowser(this._browser).id;
 
     let actionText = {
       text: aUsername,
       type: "EDIT",
       bundle: { username: aUsername,
       password: aPassword }
     };
 
@@ -329,34 +331,35 @@ LoginManagerPrompter.prototype = {
   },
 
   /*
    * _getChromeWindow
    *
    * Given a content DOM window, returns the chrome window it's in.
    */
   _getChromeWindow: function (aWindow) {
+    if (aWindow instanceof Ci.nsIDOMChromeWindow)
+      return aWindow;
     var chromeWin = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
       .getInterface(Ci.nsIWebNavigation)
       .QueryInterface(Ci.nsIDocShell)
       .chromeEventHandler.ownerDocument.defaultView;
     return chromeWin;
   },
 
   /*
    * _getNativeWindow
    *
    * Returns the NativeWindow to this prompter, or null if there isn't
    * a NativeWindow available (w/ error sent to logcat).
    */
   _getNativeWindow : function () {
     let nativeWindow = null;
     try {
-      let notifyWin = this._window.top;
-      let chromeWin = this._getChromeWindow(notifyWin).wrappedJSObject;
+      let chromeWin = this._chromeWindow;
       if (chromeWin.NativeWindow) {
         nativeWindow = chromeWin.NativeWindow;
       } else {
         Cu.reportError("NativeWindow not available on window");
       }
 
     } catch (e) {
       // If any errors happen, just assume no native window helper.
--- a/mobile/android/components/MobileComponents.manifest
+++ b/mobile/android/components/MobileComponents.manifest
@@ -9,19 +9,17 @@ contract @mozilla.org/network/protocol/a
 contract @mozilla.org/network/protocol/about;1?what=home {322ba47e-7047-4f71-aebf-cb7d69325cd9}
 contract @mozilla.org/network/protocol/about;1?what=downloads {322ba47e-7047-4f71-aebf-cb7d69325cd9}
 contract @mozilla.org/network/protocol/about;1?what=reader {322ba47e-7047-4f71-aebf-cb7d69325cd9}
 contract @mozilla.org/network/protocol/about;1?what=feedback {322ba47e-7047-4f71-aebf-cb7d69325cd9}
 contract @mozilla.org/network/protocol/about;1?what=privatebrowsing {322ba47e-7047-4f71-aebf-cb7d69325cd9}
 #ifdef MOZ_SERVICES_HEALTHREPORT
 contract @mozilla.org/network/protocol/about;1?what=healthreport {322ba47e-7047-4f71-aebf-cb7d69325cd9}
 #endif
-#ifdef MOZ_SAFE_BROWSING
 contract @mozilla.org/network/protocol/about;1?what=blocked {322ba47e-7047-4f71-aebf-cb7d69325cd9}
-#endif
 contract @mozilla.org/network/protocol/about;1?what=accounts {322ba47e-7047-4f71-aebf-cb7d69325cd9}
 contract @mozilla.org/network/protocol/about;1?what=logins {322ba47e-7047-4f71-aebf-cb7d69325cd9}
 
 # DirectoryProvider.js
 component {ef0f7a87-c1ee-45a8-8d67-26f586e46a4b} DirectoryProvider.js
 contract @mozilla.org/browser/directory-provider;1 {ef0f7a87-c1ee-45a8-8d67-26f586e46a4b}
 category xpcom-directory-providers browser-directory-provider @mozilla.org/browser/directory-provider;1
 
@@ -112,17 +110,13 @@ component {a78d7e59-b558-4321-a3d6-dffe2
 contract @mozilla.org/snippets;1 {a78d7e59-b558-4321-a3d6-dffe2f1e76dd}
 category browser-delayed-startup-finished Snippets @mozilla.org/snippets;1
 category update-timer Snippets @mozilla.org/snippets;1,getService,snippets-update-timer,browser.snippets.updateInterval,86400
 
 # ColorPicker.js
 component {430b987f-bb9f-46a3-99a5-241749220b29} ColorPicker.js
 contract @mozilla.org/colorpicker;1 {430b987f-bb9f-46a3-99a5-241749220b29}
 
-# AndroidActivitiesGlue.js
-component {e4deb5f6-d5e3-4fce-bc53-901dd9951c48} AndroidActivitiesGlue.js
-contract @mozilla.org/dom/activities/ui-glue;1 {e4deb5f6-d5e3-4fce-bc53-901dd9951c48}
-
 # PersistentNotificationHandler.js
 component {75390fe7-f8a3-423a-b3b1-258d7eabed40} PersistentNotificationHandler.js
 contract @mozilla.org/persistent-notification-handler;1 {75390fe7-f8a3-423a-b3b1-258d7eabed40}
 category persistent-notification-click PersistentNotificationHandler @mozilla.org/persistent-notification-handler;1
 category persistent-notification-close PersistentNotificationHandler @mozilla.org/persistent-notification-handler;1
--- a/mobile/android/components/extensions/test/mochitest/test_ext_pageAction.html
+++ b/mobile/android/components/extensions/test/mochitest/test_ext_pageAction.html
@@ -13,17 +13,17 @@
 <script type="text/javascript">
 "use strict";
 
 let dataURI = "iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAC4klEQVRYhdWXLWzbQBSADQtDAwsHC1tUhUxqfL67lk2tdn+OJg0ODU0rLByqgqINBY6tmlbn7LMTJ5FaFVVBk1G0oUGjG2jT2Y7jxmmcbU/6iJ+f36fz+e5sGP9riCGm9hB37RG+scd4Yo/wsDXCZyIE2xuXsce4bY+wXkAsQtzYmExrfFgvkJkRbkzo1ehoxx5iXcgI/9iYUGt8WH9MqDXEcmNChmEYrRCf2SHWeYgQx3x0tLNRIeKQLTtEFyJEep4NTuhk8BC+yMrwEE3+iozo42d8gK7FAOkMsRiiN8QhW2ttSK5QTfRRV4QoymVeJMvPvDp7gCZigD613MN6yRFA3SWarow9QB9LCfG+NeF9qCtjAKOSQjCqVKhfVsiHEQ+grgx/lRGqUihAc1uL8EFD+KCRO+GrF4J61phcoRoPoEzkYhZYpykh5sMb7kOdIeY+jHKur4QI4Feh4AFX1nVeLxrAvQchGsBz5ls6wa2QdwcvIcE2863bTH79KOvsz/uUYJsp+J0pSzNlDckVqqVGUAF+n6uS7txcOl6wot4JVy70ufDLy4pWLUQVPE81pRI0mGe9oxLMHSeohHvMs/STUNaUK6vDPCvOyxMFDx4achehRDJmHnydnkPww5OFfLxrGIZBFDyYl4LpMzlTQFIP6AQx86w2UeYBccFpJrcKv5L9eGDtUAU6RIELqsB74uynjy/UBRF1gS5BTFxwQT1wTiXoUg9MH7m/3NZRRoi5IJytUbMgzv4Wc832+oQkiKgEehmyMkkpKsFkQV11QsRJL5rJYBLItQgRaUZEmnoZXsomz3vGiWw+I9KMF9SVFOqZEemZekli1jN3U/UOqhHHvC6oWWGElhfSpGdOk6+O9prdwvtLj5BjRsQxdRnot+Zeifpy/2/0stktKTRNLmbk0mwXyl8253fyojj+8rxOHNAhjjm5n0/5OOCGOKBzkrMO0Z75lvSAzKlrF32Z/3z8BqLAn+yMV7VhAAAAAElFTkSuQmCC";
 
 let image = atob(dataURI);
 const IMAGE_ARRAYBUFFER = Uint8Array.from(image, byte => byte.charCodeAt(0)).buffer;
 
-function backgroundScript() {
+function background() {
   browser.test.assertTrue("pageAction" in browser, "Namespace 'pageAction' exists in browser");
   browser.test.assertTrue("show" in browser.pageAction, "API method 'show' exists in browser.pageAction");
 
   // TODO: Use the Tabs API to obtain the tab ids for showing pageActions.
   let tabId = 1;
   browser.test.onMessage.addListener(msg => {
     if (msg === "pageAction-show") {
       browser.pageAction.show(tabId).then(() => {
@@ -41,17 +41,17 @@ function backgroundScript() {
     browser.test.sendMessage("page-action-clicked");
   });
 
   browser.test.sendMessage("ready");
 }
 
 add_task(function* test_contentscript() {
   let extension = ExtensionTestUtils.loadExtension({
-    background: "(" + backgroundScript.toString() + ")()",
+    background,
     manifest: {
       "name": "PageAction Extension",
       "page_action": {
         "default_title": "Page Action",
         "default_icon": {
           "18": "extension.png",
         },
       },
--- a/mobile/android/components/extensions/test/mochitest/test_ext_pageAction_popup.html
+++ b/mobile/android/components/extensions/test/mochitest/test_ext_pageAction_popup.html
@@ -16,17 +16,17 @@
 Cu.import("resource://gre/modules/Services.jsm");
 
 let dataURI = "iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAC4klEQVRYhdWXLWzbQBSADQtDAwsHC1tUhUxqfL67lk2tdn+OJg0ODU0rLByqgqINBY6tmlbn7LMTJ5FaFVVBk1G0oUGjG2jT2Y7jxmmcbU/6iJ+f36fz+e5sGP9riCGm9hB37RG+scd4Yo/wsDXCZyIE2xuXsce4bY+wXkAsQtzYmExrfFgvkJkRbkzo1ehoxx5iXcgI/9iYUGt8WH9MqDXEcmNChmEYrRCf2SHWeYgQx3x0tLNRIeKQLTtEFyJEep4NTuhk8BC+yMrwEE3+iozo42d8gK7FAOkMsRiiN8QhW2ttSK5QTfRRV4QoymVeJMvPvDp7gCZigD613MN6yRFA3SWarow9QB9LCfG+NeF9qCtjAKOSQjCqVKhfVsiHEQ+grgx/lRGqUihAc1uL8EFD+KCRO+GrF4J61phcoRoPoEzkYhZYpykh5sMb7kOdIeY+jHKur4QI4Feh4AFX1nVeLxrAvQchGsBz5ls6wa2QdwcvIcE2863bTH79KOvsz/uUYJsp+J0pSzNlDckVqqVGUAF+n6uS7txcOl6wot4JVy70ufDLy4pWLUQVPE81pRI0mGe9oxLMHSeohHvMs/STUNaUK6vDPCvOyxMFDx4achehRDJmHnydnkPww5OFfLxrGIZBFDyYl4LpMzlTQFIP6AQx86w2UeYBccFpJrcKv5L9eGDtUAU6RIELqsB74uynjy/UBRF1gS5BTFxwQT1wTiXoUg9MH7m/3NZRRoi5IJytUbMgzv4Wc832+oQkiKgEehmyMkkpKsFkQV11QsRJL5rJYBLItQgRaUZEmnoZXsomz3vGiWw+I9KMF9SVFOqZEemZekli1jN3U/UOqhHHvC6oWWGElhfSpGdOk6+O9prdwvtLj5BjRsQxdRnot+Zeifpy/2/0stktKTRNLmbk0mwXyl8253fyojj+8rxOHNAhjjm5n0/5OOCGOKBzkrMO0Z75lvSAzKlrF32Z/3z8BqLAn+yMV7VhAAAAAElFTkSuQmCC";
 
 let image = atob(dataURI);
 const IMAGE_ARRAYBUFFER = Uint8Array.from(image, byte => byte.charCodeAt(0)).buffer;
 
 add_task(function* test_contentscript() {
-  function backgroundScript() {
+  function background() {
     // TODO: Use the Tabs API to obtain the tab ids for showing pageActions.
     let tabId = 1;
     let onClickedListenerEnabled = false;
 
     browser.test.onMessage.addListener((msg, details) => {
       if (msg === "page-action-show") {
         // TODO: switch to using .show(tabId).then(...) once bug 1270742 lands.
         browser.pageAction.show(tabId).then(() => {
@@ -66,33 +66,33 @@ add_task(function* test_contentscript() 
         if (details.location == location.href) {
           window.close();
         }
       }
     });
   }
 
   let extension = ExtensionTestUtils.loadExtension({
-    background: `(${backgroundScript}())`,
+    background,
     manifest: {
       "name": "PageAction Extension",
       "page_action": {
         "default_title": "Page Action",
         "default_popup": "default.html",
         "default_icon": {
           "18": "extension.png",
         },
       },
     },
     files: {
-      "default.html": `<html><head><meta charset="utf-8"><script src="popup.js"></${"script"}></head></html>`,
+      "default.html": `<html><head><meta charset="utf-8"><script src="popup.js"><\/script></head></html>`,
       "extension.png": IMAGE_ARRAYBUFFER,
-      "a.html": `<html><head><meta charset="utf-8"><script src="popup.js"></${"script"}></head></html>`,
-      "b.html": `<html><head><meta charset="utf-8"><script src="popup.js"></${"script"}></head></html>`,
-      "popup.js": `(${popupScript})()`,
+      "a.html": `<html><head><meta charset="utf-8"><script src="popup.js"><\/script></head></html>`,
+      "b.html": `<html><head><meta charset="utf-8"><script src="popup.js"><\/script></head></html>`,
+      "popup.js": popupScript,
     },
   });
 
   let tabClosedPromise = () => {
     return new Promise(resolve => {
       let chromeWin = Services.wm.getMostRecentWindow("navigator:browser");
       let BrowserApp = chromeWin.BrowserApp;
 
--- a/mobile/android/components/moz.build
+++ b/mobile/android/components/moz.build
@@ -8,17 +8,16 @@ XPIDL_SOURCES += [
     'SessionStore.idl',
 ]
 
 XPIDL_MODULE = 'MobileComponents'
 
 EXTRA_COMPONENTS += [
     'AboutRedirector.js',
     'AddonUpdateService.js',
-    'AndroidActivitiesGlue.js',
     'BlocklistPrompt.js',
     'BrowserCLH.js',
     'ColorPicker.js',
     'ContentDispatchChooser.js',
     'ContentPermissionPrompt.js',
     'DirectoryProvider.js',
     'FilePicker.js',
     'FxAccountsPush.js',
--- a/mobile/android/confvars.sh
+++ b/mobile/android/confvars.sh
@@ -18,18 +18,16 @@ MOZ_OFFICIAL_BRANDING_DIRECTORY=mobile/a
 MOZ_ANDROID_MIN_SDK_VERSION=15
 
 # There are several entry points into the Firefox application.  These are the names of some of the classes that are
 # listed in the Android manifest.  They are specified in here to avoid hard-coding them in source code files.
 MOZ_ANDROID_APPLICATION_CLASS=org.mozilla.gecko.GeckoApplication
 MOZ_ANDROID_BROWSER_INTENT_CLASS=org.mozilla.gecko.BrowserApp
 MOZ_ANDROID_SEARCH_INTENT_CLASS=org.mozilla.search.SearchActivity
 
-MOZ_SAFE_BROWSING=1
-
 MOZ_NO_SMART_CARDS=1
 
 MOZ_XULRUNNER=
 
 MOZ_CAPTURE=1
 MOZ_RAW=1
 
 # use custom widget for html:select
deleted file mode 100644
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/WebActivityMapper.java
+++ /dev/null
@@ -1,221 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * 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/. */
-
-package org.mozilla.gecko.util;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Environment;
-import android.provider.MediaStore;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.io.File;
-import java.util.HashMap;
-import java.util.Map;
-
-public final class WebActivityMapper {
-    private static final String LOGTAG = "Gecko";
-
-    private static final Map<String, WebActivityMapping> activityMap = new HashMap<String, WebActivityMapping>();
-    static {
-        activityMap.put("dial", new DialMapping());
-        activityMap.put("open", new OpenMapping());
-        activityMap.put("pick", new PickMapping());
-        activityMap.put("send", new SendMapping());
-        activityMap.put("view", new ViewMapping());
-        activityMap.put("record", new RecordMapping());
-    };
-
-    private static abstract class WebActivityMapping {
-        protected JSONObject mData;
-
-        public void setData(JSONObject data) {
-            mData = data;
-        }
-
-        // Cannot return null
-        public abstract String getAction();
-
-        public String getMime() throws JSONException {
-            return null;
-        }
-
-        public String getUri() throws JSONException {
-            return null;
-        }
-
-        public void putExtras(Intent intent) throws JSONException {}
-    }
-
-    /**
-     * Provides useful defaults for mime type and uri.
-     */
-    private static abstract class BaseMapping extends WebActivityMapping {
-        /**
-         * If 'type' is present in data object, uses the value as the MIME type.
-         */
-        @Override
-        public String getMime() throws JSONException {
-            return mData.optString("type", null);
-        }
-
-        /**
-         * If 'uri' or 'url' is present in data object, uses the respective value as the Uri.
-         */
-        @Override
-        public String getUri() throws JSONException {
-            // Will return uri or url if present.
-            String uri = mData.optString("uri", null);
-            return uri != null ? uri : mData.optString("url", null);
-        }
-    }
-
-    public static Intent getIntentForWebActivity(JSONObject message) throws JSONException {
-        final String name = message.getString("name").toLowerCase();
-        final JSONObject data = message.getJSONObject("data");
-
-        Log.w(LOGTAG, "Activity is: " + name);
-        final WebActivityMapping mapping = activityMap.get(name);
-        if (mapping == null) {
-            Log.w(LOGTAG, "No mapping found!");
-            return null;
-        }
-
-        mapping.setData(data);
-
-        final Intent intent = new Intent(mapping.getAction());
-
-        final String mime = mapping.getMime();
-        if (!TextUtils.isEmpty(mime)) {
-            intent.setType(mime);
-        }
-
-        final String uri = mapping.getUri();
-        if (!TextUtils.isEmpty(uri)) {
-            intent.setData(Uri.parse(uri));
-        }
-
-        mapping.putExtras(intent);
-
-        return intent;
-    }
-
-    private static class DialMapping extends WebActivityMapping {
-        @Override
-        public String getAction() {
-            return Intent.ACTION_DIAL;
-        }
-
-        @Override
-        public String getUri() throws JSONException {
-            return "tel:" + mData.getString("number");
-        }
-    }
-
-    private static class OpenMapping extends BaseMapping {
-        @Override
-        public String getAction() {
-            return Intent.ACTION_VIEW;
-        }
-    }
-
-    private static class PickMapping extends BaseMapping {
-        @Override
-        public String getAction() {
-            return Intent.ACTION_GET_CONTENT;
-        }
-
-        @Override
-        public String getMime() throws JSONException {
-            // bug 1007112 - pick action needs a mimetype to work
-            String mime = mData.optString("type", null);
-            return !TextUtils.isEmpty(mime) ? mime : "*/*";
-        }
-    }
-
-    private static class SendMapping extends BaseMapping {
-        @Override
-        public String getAction() {
-            return Intent.ACTION_SEND;
-        }
-
-        @Override
-        public void putExtras(Intent intent) throws JSONException {
-            optPutExtra("text", Intent.EXTRA_TEXT, intent);
-            optPutExtra("html_text", Intent.EXTRA_HTML_TEXT, intent);
-            optPutExtra("stream", Intent.EXTRA_STREAM, intent);
-        }
-
-        private void optPutExtra(String key, String extraName, Intent intent) {
-            final String extraValue = mData.optString(key);
-            if (!TextUtils.isEmpty(extraValue)) {
-                intent.putExtra(extraName, extraValue);
-            }
-        }
-    }
-
-    private static class ViewMapping extends BaseMapping {
-        @Override
-        public String getAction() {
-            return Intent.ACTION_VIEW;
-        }
-
-        @Override
-        public String getMime() {
-            // MozActivity adds a type 'url' here, we don't want to set the MIME to 'url'.
-            String type = mData.optString("type", null);
-            if ("url".equals(type) || "uri".equals(type)) {
-                return null;
-            } else {
-                return type;
-            }
-        }
-    }
-
-    private static class RecordMapping extends WebActivityMapping {
-        @Override
-        public String getAction() {
-            String type = mData.optString("type", null);
-            if ("photos".equals(type)) {
-                return "android.media.action.IMAGE_CAPTURE";
-            } else if ("videos".equals(type)) {
-                return "android.media.action.VIDEO_CAPTURE";
-            }
-            return null;
-        }
-
-        // Add an extra to specify where to save the picture/video.
-        @Override
-        public void putExtras(Intent intent) {
-            final String action = getAction();
-
-            final String dirType = action == "android.media.action.IMAGE_CAPTURE"
-                ? Environment.DIRECTORY_PICTURES
-                : Environment.DIRECTORY_MOVIES;
-
-            final String ext = action == "android.media.action.IMAGE_CAPTURE"
-                ? ".jpg"
-                : ".mp4";
-
-            File destDir = Environment.getExternalStoragePublicDirectory(dirType);
-
-            try {
-                File dest = File.createTempFile(
-                    "capture", /* prefix */
-                    ext,       /* suffix */
-                    destDir    /* directory */
-                );
-                intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(dest));
-            } catch (Exception e) {
-                Log.w(LOGTAG, "Failed to add extra for " + action + " : " + e);
-            }
-        }
-    }
-}
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -109,27 +109,25 @@
 @BINPATH@/components/content_geckomediaplugins.xpt
 @BINPATH@/components/content_html.xpt
 @BINPATH@/components/content_webrtc.xpt
 @BINPATH@/components/content_xslt.xpt
 @BINPATH@/components/cookie.xpt
 @BINPATH@/components/directory.xpt
 @BINPATH@/components/docshell.xpt
 @BINPATH@/components/dom.xpt
-@BINPATH@/components/dom_activities.xpt
 @BINPATH@/components/dom_apps.xpt
 @BINPATH@/components/dom_newapps.xpt
 @BINPATH@/components/dom_base.xpt
 @BINPATH@/components/dom_canvas.xpt
 @BINPATH@/components/dom_core.xpt
 @BINPATH@/components/dom_css.xpt
 @BINPATH@/components/dom_events.xpt
 @BINPATH@/components/dom_geolocation.xpt
 @BINPATH@/components/dom_media.xpt
-@BINPATH@/components/dom_messages.xpt
 @BINPATH@/components/dom_network.xpt
 @BINPATH@/components/dom_notification.xpt
 @BINPATH@/components/dom_html.xpt
 @BINPATH@/components/dom_offline.xpt
 @BINPATH@/components/dom_json.xpt
 @BINPATH@/components/dom_power.xpt
 #ifdef MOZ_ANDROID_GCM
 @BINPATH@/components/dom_push.xpt
@@ -367,23 +365,16 @@
 @BINPATH@/components/XULStore.manifest
 @BINPATH@/components/Webapps.js
 @BINPATH@/components/Webapps.manifest
 @BINPATH@/components/AppsService.js
 @BINPATH@/components/AppsService.manifest
 @BINPATH@/components/htmlMenuBuilder.js
 @BINPATH@/components/htmlMenuBuilder.manifest
 
-@BINPATH@/components/Activities.manifest
-@BINPATH@/components/AndroidActivitiesGlue.js
-@BINPATH@/components/ActivityProxy.js
-@BINPATH@/components/ActivityRequestHandler.js
-@BINPATH@/components/ActivityWrapper.js
-@BINPATH@/components/ActivityMessageConfigurator.js
-
 @BINPATH@/components/SystemMessageInternal.js
 @BINPATH@/components/SystemMessageManager.js
 @BINPATH@/components/SystemMessageCache.js
 @BINPATH@/components/SystemMessageManager.manifest
 
 @BINPATH@/components/InstallPackagedWebapp.manifest
 @BINPATH@/components/InstallPackagedWebapp.js
 
@@ -426,24 +417,22 @@
 @BINPATH@/components/PACGenerator.manifest
 
 @BINPATH@/components/TVSimulatorService.js
 @BINPATH@/components/TVSimulatorService.manifest
 
 ; Modules
 @BINPATH@/modules/*
 
-#ifdef MOZ_SAFE_BROWSING
 ; Safe Browsing
 @BINPATH@/components/nsURLClassifier.manifest
 @BINPATH@/components/nsUrlClassifierHashCompleter.js
 @BINPATH@/components/nsUrlClassifierListManager.js
 @BINPATH@/components/nsUrlClassifierLib.js
 @BINPATH@/components/url-classifier.xpt
-#endif
 
 ; Private Browsing
 @BINPATH@/components/privatebrowsing.xpt
 @BINPATH@/components/PrivateBrowsing.manifest
 @BINPATH@/components/PrivateBrowsingTrackingProtectionWhitelist.js
 
 ; Security Reports
 @BINPATH@/components/SecurityReporter.manifest
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountClient.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountClient.java
@@ -1,21 +1,24 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.background.fxa;
 
 import org.mozilla.gecko.background.fxa.FxAccountClient20.AccountStatusResponse;
+import org.mozilla.gecko.background.fxa.FxAccountClient20.RecoveryEmailStatusResponse;
 import org.mozilla.gecko.background.fxa.FxAccountClient20.RequestDelegate;
-import org.mozilla.gecko.background.fxa.FxAccountClient20.RecoveryEmailStatusResponse;
 import org.mozilla.gecko.background.fxa.FxAccountClient20.TwoKeys;
 import org.mozilla.gecko.fxa.FxAccountDevice;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 
+import java.util.List;
+
 public interface FxAccountClient {
   public void accountStatus(String uid, RequestDelegate<AccountStatusResponse> requestDelegate);
   public void recoveryEmailStatus(byte[] sessionToken, RequestDelegate<RecoveryEmailStatusResponse> requestDelegate);
   public void keys(byte[] keyFetchToken, RequestDelegate<TwoKeys> requestDelegate);
   public void sign(byte[] sessionToken, ExtendedJSONObject publicKey, long certificateDurationInMilliseconds, RequestDelegate<String> requestDelegate);
   public void registerOrUpdateDevice(byte[] sessionToken, FxAccountDevice device, RequestDelegate<FxAccountDevice> requestDelegate);
   public void deviceList(byte[] sessionToken, RequestDelegate<FxAccountDevice[]> requestDelegate);
+  public void notifyDevices(byte[] sessionToken, List<String> deviceIds, ExtendedJSONObject payload, Long TTL, RequestDelegate<ExtendedJSONObject> requestDelegate);
 }
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountClient20.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountClient20.java
@@ -1,14 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.background.fxa;
 
+import android.support.annotation.NonNull;
+
 import org.json.simple.JSONArray;
 import org.json.simple.JSONObject;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientMalformedResponseException;
 import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
 import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.Locales;
 import org.mozilla.gecko.fxa.FxAccountDevice;
@@ -28,16 +30,17 @@ import java.io.UnsupportedEncodingExcept
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URLEncoder;
 import java.security.GeneralSecurityException;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.concurrent.Executor;
 
 import javax.crypto.Mac;
 
 import ch.boye.httpclientandroidlib.HttpEntity;
@@ -827,17 +830,16 @@ public class FxAccountClient20 implement
     try {
       HKDF.deriveMany(sessionToken, new byte[0], FxAccountUtils.KW("sessionToken"), tokenId, reqHMACKey, requestKey);
     } catch (Exception e) {
       invokeHandleError(delegate, e);
       return;
     }
 
     final BaseResource resource;
-    final ExtendedJSONObject body;
     try {
       resource = getBaseResource("account/devices");
     } catch (URISyntaxException | UnsupportedEncodingException e) {
       invokeHandleError(delegate, e);
       return;
     }
 
     resource.delegate = new ResourceDelegate<FxAccountDevice[]>(resource, delegate, ResponseType.JSON_ARRAY, tokenId, reqHMACKey) {
@@ -853,9 +855,60 @@ public class FxAccountClient20 implement
         } catch (Exception e) {
           delegate.handleError(e);
         }
       }
     };
 
     resource.get();
   }
+
+  @Override
+  public void notifyDevices(@NonNull byte[] sessionToken, @NonNull List<String> deviceIds, ExtendedJSONObject payload, Long TTL, RequestDelegate<ExtendedJSONObject> delegate) {
+    final byte[] tokenId = new byte[32];
+    final byte[] reqHMACKey = new byte[32];
+    final byte[] requestKey = new byte[32];
+    try {
+      HKDF.deriveMany(sessionToken, new byte[0], FxAccountUtils.KW("sessionToken"), tokenId, reqHMACKey, requestKey);
+    } catch (Exception e) {
+      invokeHandleError(delegate, e);
+      return;
+    }
+
+    final BaseResource resource;
+    final ExtendedJSONObject body = createNotifyDevicesBody(deviceIds, payload, TTL);
+    try {
+      resource = getBaseResource("account/devices/notify");
+    } catch (URISyntaxException | UnsupportedEncodingException e) {
+      invokeHandleError(delegate, e);
+      return;
+    }
+
+    resource.delegate = new ResourceDelegate<ExtendedJSONObject>(resource, delegate, ResponseType.JSON_OBJECT, tokenId, reqHMACKey) {
+      @Override
+      public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
+        try {
+          delegate.handleSuccess(body);
+        } catch (Exception e) {
+          delegate.handleError(e);
+        }
+      }
+    };
+
+    post(resource, body);
+  }
+
+  @NonNull
+  @SuppressWarnings("unchecked")
+  private ExtendedJSONObject createNotifyDevicesBody(@NonNull List<String> deviceIds, ExtendedJSONObject payload, Long TTL) {
+    final ExtendedJSONObject body = new ExtendedJSONObject();
+    final JSONArray to = new JSONArray();
+    to.addAll(deviceIds);
+    body.put("to", to);
+    if (payload != null) {
+      body.put("payload", payload);
+    }
+    if (TTL != null) {
+      body.put("TTL", TTL);
+    }
+    return body;
+  }
 }
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/FxAccountDeviceRegistrator.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/FxAccountDeviceRegistrator.java
@@ -14,19 +14,18 @@ import android.util.Log;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.fxa.FxAccountClient;
 import org.mozilla.gecko.background.fxa.FxAccountClient20;
 import org.mozilla.gecko.background.fxa.FxAccountClient20.AccountStatusResponse;
 import org.mozilla.gecko.background.fxa.FxAccountClient20.RequestDelegate;
 import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
 import org.mozilla.gecko.background.fxa.FxAccountRemoteError;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
+import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount.InvalidFxAState;
 import org.mozilla.gecko.fxa.login.State;
-import org.mozilla.gecko.fxa.login.State.StateLabel;
-import org.mozilla.gecko.fxa.login.TokensAndKeysState;
 import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate;
 import org.mozilla.gecko.util.BundleEventListener;
 import org.mozilla.gecko.util.EventCallback;
 
 import java.io.UnsupportedEncodingException;
 import java.lang.ref.WeakReference;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
@@ -107,17 +106,21 @@ public class FxAccountDeviceRegistrator 
   }
 
   private static void doFxaRegistration(final Context context, final Bundle subscription, final boolean allowRecursion) throws InvalidFxAState {
     String pushCallback = subscription.getString("pushCallback");
     String pushPublicKey = subscription.getString("pushPublicKey");
     String pushAuthKey = subscription.getString("pushAuthKey");
 
     final AndroidFxAccount fxAccount = AndroidFxAccount.fromContext(context);
-    final byte[] sessionToken = getSessionToken(fxAccount);
+    if (fxAccount == null) {
+      Log.e(LOG_TAG, "AndroidFxAccount is null");
+      return;
+    }
+    final byte[] sessionToken = fxAccount.getSessionToken();
     final FxAccountDevice device;
     String deviceId = fxAccount.getDeviceId();
     String clientName = getClientName(fxAccount, context);
     if (TextUtils.isEmpty(deviceId)) {
       Log.i(LOG_TAG, "Attempting registration for a new device");
       device = FxAccountDevice.forRegister(clientName, "mobile", pushCallback, pushPublicKey, pushAuthKey);
     } else {
       Log.i(LOG_TAG, "Attempting registration for an existing device");
@@ -175,27 +178,16 @@ public class FxAccountDeviceRegistrator 
           new SharedPreferencesClientsDataDelegate(fxAccount.getSyncPrefs(), context);
       return clientsDataDelegate.getClientName();
     } catch (UnsupportedEncodingException | GeneralSecurityException e) {
       Log.e(LOG_TAG, "Unable to get client name.", e);
       return null;
     }
   }
 
-  @Nullable
-  private static byte[] getSessionToken(final AndroidFxAccount fxAccount) throws InvalidFxAState {
-    State state = fxAccount.getState();
-    StateLabel stateLabel = state.getStateLabel();
-    if (stateLabel == StateLabel.Cohabiting || stateLabel == StateLabel.Married) {
-      TokensAndKeysState tokensAndKeysState = (TokensAndKeysState) state;
-      return tokensAndKeysState.getSessionToken();
-    }
-    throw new InvalidFxAState("Cannot get sessionToken: not in a TokensAndKeysState state");
-  }
-
   private static void handleTokenError(final FxAccountClientRemoteException error,
                                        final FxAccountClient fxAccountClient,
                                        final AndroidFxAccount fxAccount) {
     Log.i(LOG_TAG, "Recovering from invalid token error: ", error);
     logErrorAndResetDeviceRegistrationVersion(error, fxAccount);
     fxAccountClient.accountStatus(fxAccount.getState().uid,
         new RequestDelegate<AccountStatusResponse>() {
       @Override
@@ -282,17 +274,9 @@ public class FxAccountDeviceRegistrator 
     // We have no choice but to use reflection here, sorry :(
     Class<?> eventDispatcher = Class.forName("org.mozilla.gecko.EventDispatcher");
     Method getInstance = eventDispatcher.getMethod("getInstance");
     Object instance = getInstance.invoke(null);
     Method registerBackgroundThreadListener = eventDispatcher.getMethod("registerBackgroundThreadListener",
             BundleEventListener.class, String[].class);
     registerBackgroundThreadListener.invoke(instance, this, new String[] { "FxAccountsPush:Subscribe:Response" });
   }
-
-  public static class InvalidFxAState extends Exception {
-    private static final long serialVersionUID = -8537626959811195978L;
-
-    public InvalidFxAState(String message) {
-      super(message);
-    }
-  }
 }
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/FxAccountPushHandler.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/FxAccountPushHandler.java
@@ -2,24 +2,28 @@ package org.mozilla.gecko.fxa;
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.content.Context;
 import android.os.Bundle;
 import android.text.TextUtils;
 import android.util.Log;
 
+import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 
 public class FxAccountPushHandler {
     private static final String LOG_TAG = "FxAccountPush";
 
     private static final String COMMAND_DEVICE_DISCONNECTED = "fxaccounts:device_disconnected";
+    private static final String COMMAND_COLLECTION_CHANGED = "sync:collection_changed";
+
+    private static final String CLIENTS_COLLECTION = "clients";
 
     // Forbid instantiation
     private FxAccountPushHandler() {}
 
     public static void handleFxAPushMessage(Context context, Bundle bundle) {
         Log.i(LOG_TAG, "Handling FxA Push Message");
         String rawMessage = bundle.getString("message");
         JSONObject message = null;
@@ -40,25 +44,45 @@ public class FxAccountPushHandler {
         }
         try {
             String command = message.getString("command");
             JSONObject data = message.getJSONObject("data");
             switch (command) {
                 case COMMAND_DEVICE_DISCONNECTED:
                     handleDeviceDisconnection(context, data);
                     break;
+                case COMMAND_COLLECTION_CHANGED:
+                    handleCollectionChanged(context, data);
+                    break;
                 default:
                     Log.d(LOG_TAG, "No handler defined for FxA Push command " + command);
                     break;
             }
         } catch (JSONException e) {
             Log.e(LOG_TAG, "Error while handling FxA push notification", e);
         }
     }
 
+    private static void handleCollectionChanged(Context context, JSONObject data) throws JSONException {
+        JSONArray collections = data.getJSONArray("collections");
+        int len = collections.length();
+        for (int i = 0; i < len; i++) {
+            if (collections.getString(i).equals(CLIENTS_COLLECTION)) {
+                final Account account = FirefoxAccounts.getFirefoxAccount(context);
+                if (account == null) {
+                    Log.e(LOG_TAG, "The account does not exist anymore");
+                    return;
+                }
+                final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
+                fxAccount.requestImmediateSync(new String[] { CLIENTS_COLLECTION }, null);
+                return;
+            }
+        }
+    }
+
     private static void handleDeviceDisconnection(Context context, JSONObject data) throws JSONException {
         final Account account = FirefoxAccounts.getFirefoxAccount(context);
         if (account == null) {
             Log.e(LOG_TAG, "The account does not exist anymore");
             return;
         }
         final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
         if (!fxAccount.getDeviceId().equals(data.getString("id"))) {
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/AndroidFxAccount.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/AndroidFxAccount.java
@@ -25,16 +25,17 @@ import org.mozilla.gecko.background.comm
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.fxa.FxAccountUtils;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.fxa.FirefoxAccounts;
 import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.fxa.login.State;
 import org.mozilla.gecko.fxa.login.State.StateLabel;
 import org.mozilla.gecko.fxa.login.StateFactory;
+import org.mozilla.gecko.fxa.login.TokensAndKeysState;
 import org.mozilla.gecko.fxa.sync.FxAccountProfileService;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.setup.Constants;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import java.io.UnsupportedEncodingException;
 import java.net.URISyntaxException;
@@ -602,16 +603,34 @@ public class AndroidFxAccount {
       StateLabel stateLabel = StateLabel.valueOf(stateLabelString);
       Logger.debug(LOG_TAG, "Account is in state " + stateLabel);
       return StateFactory.fromJSONObject(stateLabel, new ExtendedJSONObject(stateString));
     } catch (Exception e) {
       throw new IllegalStateException("could not get state", e);
     }
   }
 
+  public byte[] getSessionToken() throws InvalidFxAState {
+    State state = getState();
+    StateLabel stateLabel = state.getStateLabel();
+    if (stateLabel == StateLabel.Cohabiting || stateLabel == StateLabel.Married) {
+      TokensAndKeysState tokensAndKeysState = (TokensAndKeysState) state;
+      return tokensAndKeysState.getSessionToken();
+    }
+    throw new InvalidFxAState("Cannot get sessionToken: not in a TokensAndKeysState state");
+  }
+
+  public static class InvalidFxAState extends Exception {
+    private static final long serialVersionUID = -8537626959811195978L;
+
+    public InvalidFxAState(String message) {
+      super(message);
+    }
+  }
+
   /**
    * <b>For debugging only!</b>
    */
   public void dump() {
     if (!FxAccountUtils.LOG_PERSONAL_INFORMATION) {
       return;
     }
     ExtendedJSONObject o = toJSONObject();
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/SyncClientsEngineStage.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/SyncClientsEngineStage.java
@@ -1,29 +1,37 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.sync.stage;
 
 import android.accounts.Account;
 import android.content.Context;
+import android.support.annotation.NonNull;
 import android.text.TextUtils;
+import android.util.Log;
 
 import java.io.UnsupportedEncodingException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import org.json.simple.JSONArray;
 import org.json.simple.JSONObject;
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.background.common.log.Logger;
+import org.mozilla.gecko.background.fxa.FxAccountClient;
+import org.mozilla.gecko.background.fxa.FxAccountClient20;
+import org.mozilla.gecko.background.fxa.FxAccountClientException;
 import org.mozilla.gecko.fxa.FirefoxAccounts;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.sync.CommandProcessor;
 import org.mozilla.gecko.sync.CommandProcessor.Command;
 import org.mozilla.gecko.sync.CryptoRecord;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.HTTPFailureException;
 import org.mozilla.gecko.sync.NoCollectionKeysSetException;
@@ -49,28 +57,29 @@ import ch.boye.httpclientandroidlib.Http
 
 public class SyncClientsEngineStage extends AbstractSessionManagingSyncStage {
   private static final String LOG_TAG = "SyncClientsEngineStage";
 
   public static final String COLLECTION_NAME       = "clients";
   public static final String STAGE_NAME            = COLLECTION_NAME;
   public static final int CLIENTS_TTL_REFRESH      = 604800000;   // 7 days in milliseconds.
   public static final int MAX_UPLOAD_FAILURE_COUNT = 5;
+  public static final long NOTIFY_TAB_SENT_TTL_SECS = TimeUnit.SECONDS.convert(1L, TimeUnit.HOURS); // 1 hour
 
   protected final ClientRecordFactory factory = new ClientRecordFactory();
   protected ClientUploadDelegate clientUploadDelegate;
   protected ClientDownloadDelegate clientDownloadDelegate;
 
   // Be sure to use this safely via getClientsDatabaseAccessor/closeDataAccessor.
   protected ClientsDatabaseAccessor db;
 
   protected volatile boolean shouldWipe;
   protected volatile boolean shouldUploadLocalRecord;     // Set if, e.g., we received commands or need to refresh our version.
   protected final AtomicInteger uploadAttemptsCount = new AtomicInteger();
-  protected final List<ClientRecord> toUpload = new ArrayList<ClientRecord>();
+  protected final List<ClientRecord> modifiedClientsToUpload = new ArrayList<ClientRecord>();
 
   protected int getClientsCount() {
     return getClientsDatabaseAccessor().clientsCount();
   }
 
   protected synchronized ClientsDatabaseAccessor getClientsDatabaseAccessor() {
     if (db == null) {
       db = new ClientsDatabaseAccessor(session.getContext());
@@ -146,23 +155,90 @@ public class SyncClientsEngineStage exte
       Logger.debug(LOG_TAG, "Server response asserts " + response.weaveRecords() + " records.");
 
       // TODO: persist the response timestamp to know whether to download next time (Bug 726055).
       clientUploadDelegate = new ClientUploadDelegate();
       clientsDelegate.setClientsCount(clientsCount);
 
       // If we upload remote records, checkAndUpload() will be called upon
       // upload success in the delegate. Otherwise call checkAndUpload() now.
-      if (toUpload.size() > 0) {
+      if (modifiedClientsToUpload.size() > 0) {
+        // modifiedClientsToUpload is cleared in uploadRemoteRecords, save what we need here
+        final List<String> devicesToNotify = new ArrayList<>();
+        for (ClientRecord record : modifiedClientsToUpload) {
+          if (!TextUtils.isEmpty(record.fxaDeviceId)) {
+            devicesToNotify.add(record.fxaDeviceId);
+          }
+        }
+
+        // This method is synchronous, there's no risk of notifying the clients
+        // before we actually uploaded the records
         uploadRemoteRecords();
+
+        // Notify the clients who got their record written
+        notifyClients(devicesToNotify);
+
         return;
       }
       checkAndUpload();
     }
 
+    private void notifyClients(final List<String> devicesToNotify) {
+      final ExecutorService executor = Executors.newSingleThreadExecutor();
+      final Context context = session.getContext();
+      final Account account = FirefoxAccounts.getFirefoxAccount(context);
+      if (account == null) {
+        Log.e(LOG_TAG, "Can't notify other clients: no account");
+        return;
+      }
+      final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
+      final ExtendedJSONObject payload = createNotifyDevicesPayload();
+
+      final byte[] sessionToken;
+      try {
+        sessionToken = fxAccount.getSessionToken();
+      } catch (AndroidFxAccount.InvalidFxAState invalidFxAState) {
+        Log.e(LOG_TAG, "Could not get session token", invalidFxAState);
+        return;
+      }
+
+      // API doc : https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#post-v1accountdevicesnotify
+      final FxAccountClient fxAccountClient = new FxAccountClient20(fxAccount.getAccountServerURI(), executor);
+      fxAccountClient.notifyDevices(sessionToken, devicesToNotify, payload, NOTIFY_TAB_SENT_TTL_SECS, new FxAccountClient20.RequestDelegate<ExtendedJSONObject>() {
+        @Override
+        public void handleError(Exception e) {
+          Log.e(LOG_TAG, "Error while notifying devices", e);
+        }
+
+        @Override
+        public void handleFailure(FxAccountClientException.FxAccountClientRemoteException e) {
+          Log.e(LOG_TAG, "Error while notifying devices", e);
+        }
+
+        @Override
+        public void handleSuccess(ExtendedJSONObject result) {
+          Log.i(LOG_TAG, devicesToNotify.size() + " devices notified");
+        }
+      });
+    }
+
+    @NonNull
+    @SuppressWarnings("unchecked")
+    private ExtendedJSONObject createNotifyDevicesPayload() {
+      final ExtendedJSONObject payload = new ExtendedJSONObject();
+      payload.put("version", 1);
+      payload.put("command", "sync:collection_changed");
+      final ExtendedJSONObject data = new ExtendedJSONObject();
+      final JSONArray collections = new JSONArray();
+      collections.add("clients");
+      data.put("collections", collections);
+      payload.put("data", data);
+      return payload;
+    }
+
     @Override
     public void handleRequestFailure(SyncStorageResponse response) {
       BaseResource.consumeEntity(response); // We don't need the response at all, and any exception handling shouldn't need the response body.
       localAccountGUIDDownloaded = false;
 
       try {
         Logger.info(LOG_TAG, "Client upload failed. Aborting sync.");
         session.abort(new HTTPFailureException(response), "Client download failed.");
@@ -285,17 +361,17 @@ public class SyncClientsEngineStage exte
       // If upload failed because of `ifUnmodifiedSince` then there are new
       // commands uploaded to our record. We must download and process them first.
       if (!shouldUploadLocalRecord ||
           statusCode == HttpStatus.SC_PRECONDITION_FAILED ||
           uploadAttemptsCount.incrementAndGet() > MAX_UPLOAD_FAILURE_COUNT) {
 
         Logger.debug(LOG_TAG, "Client upload failed. Aborting sync.");
         if (!currentlyUploadingLocalRecord) {
-          toUpload.clear(); // These will be redownloaded.
+          modifiedClientsToUpload.clear(); // These will be redownloaded.
         }
         BaseResource.consumeEntity(response); // The exception thrown should need the response body.
         session.abort(new HTTPFailureException(response), "Client upload failed.");
         return;
       }
       Logger.trace(LOG_TAG, "Retrying upload…");
       // Preconditions:
       // shouldUploadLocalRecord == true &&
@@ -469,41 +545,41 @@ public class SyncClientsEngineStage exte
 
     for (Command command : commands) {
       JSONObject jsonCommand = command.asJSONObject();
       if (record.commands == null) {
         record.commands = new JSONArray();
       }
       record.commands.add(jsonCommand);
     }
-    toUpload.add(record);
+    modifiedClientsToUpload.add(record);
   }
 
   @SuppressWarnings("unchecked")
   protected void uploadRemoteRecords() {
-    Logger.trace(LOG_TAG, "In uploadRemoteRecords. Uploading " + toUpload.size() + " records" );
+    Logger.trace(LOG_TAG, "In uploadRemoteRecords. Uploading " + modifiedClientsToUpload.size() + " records" );
 
-    for (ClientRecord r : toUpload) {
+    for (ClientRecord r : modifiedClientsToUpload) {
       Logger.trace(LOG_TAG, ">> Uploading record " + r.guid + ": " + r.name);
     }
 
-    if (toUpload.size() == 1) {
-      ClientRecord record = toUpload.get(0);
+    if (modifiedClientsToUpload.size() == 1) {
+      ClientRecord record = modifiedClientsToUpload.get(0);
       Logger.debug(LOG_TAG, "Only 1 remote record to upload.");
       Logger.debug(LOG_TAG, "Record last modified: " + record.lastModified);
       CryptoRecord cryptoRecord = encryptClientRecord(record);
       if (cryptoRecord != null) {
         clientUploadDelegate.setUploadDetails(false);
         this.uploadClientRecord(cryptoRecord);
       }
       return;
     }
 
     JSONArray cryptoRecords = new JSONArray();
-    for (ClientRecord record : toUpload) {
+    for (ClientRecord record : modifiedClientsToUpload) {
       Logger.trace(LOG_TAG, "Record " + record.guid + " is being uploaded" );
 
       CryptoRecord cryptoRecord = encryptClientRecord(record);
       cryptoRecords.add(cryptoRecord.toJSONObject());
     }
     Logger.debug(LOG_TAG, "Uploading records: " + cryptoRecords.size());
     clientUploadDelegate.setUploadDetails(false);
     this.uploadClientRecords(cryptoRecords);
@@ -542,17 +618,17 @@ public class SyncClientsEngineStage exte
       session.abort(e, encryptionFailure);
     }
     return null;
   }
 
   public void clearRecordsToUpload() {
     try {
       getClientsDatabaseAccessor().wipeCommandsTable();
-      toUpload.clear();
+      modifiedClientsToUpload.clear();
     } finally {
       closeDataAccessor();
     }
   }
 
   protected void downloadClientRecords() {
     shouldWipe = true;
     clientDownloadDelegate = makeClientDownloadDelegate();
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestClientsEngineStage.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestClientsEngineStage.java
@@ -760,36 +760,36 @@ public class TestClientsEngineStage exte
   public void testAddCommandsToUnversionedClient() throws NullCursorException {
     db = new TestAddCommandsMockClientsDatabaseAccessor();
 
     final ClientRecord remoteRecord = new ClientRecord();
     remoteRecord.version = null;
     final String expectedGUID = remoteRecord.guid;
 
     this.addCommands(remoteRecord);
-    assertEquals(1, toUpload.size());
+    assertEquals(1, modifiedClientsToUpload.size());
 
-    final ClientRecord recordToUpload = toUpload.get(0);
+    final ClientRecord recordToUpload = modifiedClientsToUpload.get(0);
     assertEquals(4, recordToUpload.commands.size());
     assertEquals(expectedGUID, recordToUpload.guid);
     assertEquals(null, recordToUpload.version);
   }
 
   @Test
   public void testAddCommandsToVersionedClient() throws NullCursorException {
     db = new TestAddCommandsMockClientsDatabaseAccessor();
 
     final ClientRecord remoteRecord = new ClientRecord();
     remoteRecord.version = "12a1";
     final String expectedGUID = remoteRecord.guid;
 
     this.addCommands(remoteRecord);
-    assertEquals(1, toUpload.size());
+    assertEquals(1, modifiedClientsToUpload.size());
 
-    final ClientRecord recordToUpload = toUpload.get(0);
+    final ClientRecord recordToUpload = modifiedClientsToUpload.get(0);
     assertEquals(4, recordToUpload.commands.size());
     assertEquals(expectedGUID, recordToUpload.guid);
     assertEquals("12a1", recordToUpload.version);
   }
 
   @Test
   public void testLastModifiedTimestamp() throws NullCursorException {
     // If we uploaded a record a moment ago, we shouldn't upload another.
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/fxa/login/MockFxAccountClient.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/fxa/login/MockFxAccountClient.java
@@ -1,16 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.fxa.login;
 
 import android.text.TextUtils;
 
 import org.mozilla.gecko.background.fxa.FxAccountClient;
+import org.mozilla.gecko.background.fxa.FxAccountClient20;
 import org.mozilla.gecko.background.fxa.FxAccountClient20.AccountStatusResponse;
 import org.mozilla.gecko.background.fxa.FxAccountClient20.RequestDelegate;
 import org.mozilla.gecko.background.fxa.FxAccountClient20.RecoveryEmailStatusResponse;
 import org.mozilla.gecko.background.fxa.FxAccountClient20.TwoKeys;
 import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
 import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
 import org.mozilla.gecko.background.fxa.FxAccountRemoteError;
 import org.mozilla.gecko.background.fxa.FxAccountUtils;
@@ -18,16 +19,17 @@ import org.mozilla.gecko.fxa.FxAccountDe
 import org.mozilla.gecko.browserid.MockMyIDTokenFactory;
 import org.mozilla.gecko.browserid.RSACryptoImplementation;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.Utils;
 
 import java.io.UnsupportedEncodingException;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 
 import ch.boye.httpclientandroidlib.HttpStatus;
 import ch.boye.httpclientandroidlib.ProtocolVersion;
 import ch.boye.httpclientandroidlib.entity.StringEntity;
 import ch.boye.httpclientandroidlib.message.BasicHttpResponse;
 
@@ -211,9 +213,14 @@ public class MockFxAccountClient impleme
     if (!user.verified) {
       handleFailure(requestDelegate, HttpStatus.SC_BAD_REQUEST, FxAccountRemoteError.ATTEMPT_TO_OPERATE_ON_AN_UNVERIFIED_ACCOUNT, "user is unverified");
       return;
     }
     Collection<FxAccountDevice> devices = user.devices.values();
     FxAccountDevice[] devicesArray = devices.toArray(new FxAccountDevice[devices.size()]);
     requestDelegate.handleSuccess(devicesArray);
   }
+
+  @Override
+  public void notifyDevices(byte[] sessionToken, List<String> deviceIds, ExtendedJSONObject payload, Long TTL, RequestDelegate<ExtendedJSONObject> requestDelegate) {
+    requestDelegate.handleSuccess(new ExtendedJSONObject());
+  }
 }
--- a/old-configure.in
+++ b/old-configure.in
@@ -2334,17 +2334,16 @@ VPX_USE_YASM=
 VPX_ASFLAGS=
 VPX_AS_CONVERSION=
 VPX_X86_ASM=
 VPX_ARM_ASM=
 LIBJPEG_TURBO_AS=
 LIBJPEG_TURBO_ASFLAGS=
 MOZ_PREF_EXTENSIONS=1
 MOZ_REFLOW_PERF=
-MOZ_SAFE_BROWSING=
 MOZ_SPELLCHECK=1
 MOZ_TOOLKIT_SEARCH=1
 MOZ_UI_LOCALE=en-US
 MOZ_UNIVERSALCHARDET=1
 MOZ_URL_CLASSIFIER=
 MOZ_XUL=1
 MOZ_ZIPWRITER=1
 MOZ_NO_SMART_CARDS=
@@ -2490,17 +2489,17 @@ AC_SUBST(MOZ_B2G_VERSION)
 dnl ========================================================
 dnl Ensure Android SDK and build-tools versions depending on
 dnl mobile target.
 dnl ========================================================
 
 if test -z "$gonkdir" ; then
     case "$MOZ_BUILD_APP" in
     mobile/android)
-        MOZ_ANDROID_SDK(23, 23.0.3)
+        MOZ_ANDROID_SDK(23, "23.0.3 23.0.1")
         ;;
     esac
 fi
 
 dnl ========================================================
 dnl =
 dnl = Toolkit Options
 dnl =
@@ -4300,34 +4299,18 @@ then
 fi
 
 if test -n "$MOZ_SYSTEM_SQLITE"; then
     AC_DEFINE(MOZ_SYSTEM_SQLITE)
 fi
 AC_SUBST(MOZ_SYSTEM_SQLITE)
 
 dnl ========================================================
-dnl = Enable safe browsing (anti-phishing)
-dnl ========================================================
-MOZ_ARG_ENABLE_BOOL(safe-browsing,
-[  --enable-safe-browsing  Enable safe browsing (anti-phishing) implementation],
-    MOZ_SAFE_BROWSING=1,
-    MOZ_SAFE_BROWSING= )
-if test -n "$MOZ_SAFE_BROWSING"; then
-    AC_DEFINE(MOZ_SAFE_BROWSING)
-fi
-AC_SUBST(MOZ_SAFE_BROWSING)
-
-dnl ========================================================
 dnl = Enable url-classifier
 dnl ========================================================
-dnl Implicitly enabled by default if building with safe-browsing
-if test -n "$MOZ_SAFE_BROWSING"; then
-    MOZ_URL_CLASSIFIER=1
-fi
 MOZ_ARG_ENABLE_BOOL(url-classifier,
 [  --enable-url-classifier Enable url classifier module],
     MOZ_URL_CLASSIFIER=1,
     MOZ_URL_CLASSIFIER= )
 if test -n "$MOZ_URL_CLASSIFIER"; then
     AC_DEFINE(MOZ_URL_CLASSIFIER)
 fi
 AC_SUBST(MOZ_URL_CLASSIFIER)
--- a/python/mozboot/mozboot/bootstrap.py
+++ b/python/mozboot/mozboot/bootstrap.py
@@ -412,17 +412,17 @@ def current_firefox_checkout(check_outpu
 
     path = os.getcwd()
     while path:
         hg_dir = os.path.join(path, '.hg')
         git_dir = os.path.join(path, '.git')
         if hg and os.path.exists(hg_dir):
             # Verify the hg repo is a Firefox repo by looking at rev 0.
             try:
-                node = check_output([hg, 'log', '-r', '0', '-T', '{node}'], cwd=path)
+                node = check_output([hg, 'log', '-r', '0', '--template', '{node}'], cwd=path)
                 if node in HG_ROOT_REVISIONS:
                     return 'hg'
                 # Else the root revision is different. There could be nested
                 # repos. So keep traversing the parents.
             except subprocess.CalledProcessError:
                 pass
 
         # TODO check git remotes or `git rev-parse -q --verify $sha1^{commit}`
--- a/python/mozboot/mozboot/centosfedora.py
+++ b/python/mozboot/mozboot/centosfedora.py
@@ -68,16 +68,17 @@ class CentOSFedoraBootstrapper(BaseBoots
                 'python2-devel',
             ]
 
             self.browser_packages += [
                 'gcc-c++',
             ]
 
             self.mobile_android_packages += [
+                'java-1.8.0-openjdk-devel',
                 'ncurses-devel.i686',
                 'libstdc++.i686',
                 'zlib-devel.i686',
             ]
 
     def install_system_packages(self):
         self.dnf_groupinstall(*self.group_packages)
         self.dnf_install(*self.packages)
@@ -125,16 +126,25 @@ class CentOSFedoraBootstrapper(BaseBoots
         self.sdk_url = 'https://dl.google.com/android/android-sdk_r24.0.1-linux.tgz'
         self.ndk_url = android.android_ndk_url('linux')
 
         android.ensure_android_sdk_and_ndk(path=mozbuild_path,
                                            sdk_path=self.sdk_path, sdk_url=self.sdk_url,
                                            ndk_path=self.ndk_path, ndk_url=self.ndk_url,
                                            artifact_mode=artifact_mode)
 
+        # Most recent version of build-tools appears to be 23.0.1 on Fedora
+        packages = [p for p in android.ANDROID_PACKAGES if not p.startswith('build-tools')]
+        packages.append('build-tools-23.0.1')
+
+        # 3. We expect the |android| tool to be at
+        # ~/.mozbuild/android-sdk-linux/tools/android.
+        android_tool = os.path.join(self.sdk_path, 'tools', 'android')
+        android.ensure_android_packages(android_tool=android_tool, packages=packages)
+
     def suggest_mobile_android_mozconfig(self, artifact_mode=False):
         import android
         android.suggest_mozconfig(sdk_path=self.sdk_path,
                                   ndk_path=self.ndk_path,
                                   artifact_mode=artifact_mode)
 
     def suggest_mobile_android_artifact_mode_mozconfig(self):
         self.suggest_mobile_android_mozconfig(artifact_mode=True)
--- a/python/mozlint/mozlint/vcs.py
+++ b/python/mozlint/mozlint/vcs.py
@@ -44,17 +44,17 @@ class VCSFiles(object):
         return self.vcs == 'git'
 
     def _run(self, cmd):
         files = subprocess.check_output(cmd).split()
         return [os.path.join(self.root, f) for f in files]
 
     def by_rev(self, rev):
         if self.is_hg:
-            return self._run(['hg', 'log', '-T', '{files % "\\n{file}"}', '-r', rev])
+            return self._run(['hg', 'log', '--template', '{files % "\\n{file}"}', '-r', rev])
         elif self.is_git:
             return self._run(['git', 'diff', '--name-only', rev])
         return []
 
     def by_workdir(self):
         if self.is_hg:
             return self._run(['hg', 'status', '-amn'])
         elif self.is_git:
--- a/testing/firefox-ui/tests/puppeteer/test_appinfo.py
+++ b/testing/firefox-ui/tests/puppeteer/test_appinfo.py
@@ -11,17 +11,18 @@ class TestAppInfo(FirefoxTestCase):
     def test_valid_properties(self):
         binary = self.marionette.bin
         version_info = mozversion.get_version(binary=binary)
 
         self.assertEqual(self.appinfo.ID, version_info['application_id'])
         self.assertEqual(self.appinfo.name, version_info['application_name'])
         self.assertEqual(self.appinfo.vendor, version_info['application_vendor'])
         self.assertEqual(self.appinfo.version, version_info['application_version'])
-        self.assertEqual(self.appinfo.platformBuildID, version_info['platform_buildid'])
+        # Bug 1298328 - Platform buildid mismatch due to incremental builds
+        # self.assertEqual(self.appinfo.platformBuildID, version_info['platform_buildid'])
         self.assertEqual(self.appinfo.platformVersion, version_info['platform_version'])
         self.assertIsNotNone(self.appinfo.locale)
         self.assertIsNotNone(self.appinfo.user_agent)
         self.assertIsNotNone(self.appinfo.XPCOMABI)
 
     def test_invalid_properties(self):
         with self.assertRaises(AttributeError):
             self.appinfo.unknown
--- a/testing/mochitest/tests/SimpleTest/ExtensionTestUtils.js
+++ b/testing/mochitest/tests/SimpleTest/ExtensionTestUtils.js
@@ -78,16 +78,33 @@ ExtensionTestUtils.loadExtension = funct
       } else {
         messageQueue.add([msg, ...args]);
         checkMessages();
       }
 
     },
   };
 
+  // Mimic serialization of functions as done in `Extension.generateXPI` and
+  // `Extension.generateZipFile` because functions are dropped when `ext` object
+  // is sent to the main process via the message manager.
+  ext = Object.assign({}, ext);
+  if (ext.files) {
+    ext.files = Object.assign({}, ext.files);
+    for (let filename of Object.keys(ext.files)) {
+      let file = ext.files[filename];
+      if (typeof file == "function") {
+        ext.files[filename] = `(${file})();`
+      }
+    }
+  }
+  if (typeof ext.background == "function") {
+    ext.background = `(${ext.background})();`
+  }
+
   var extension = SpecialPowers.loadExtension(ext, handler);
 
   registerCleanup(() => {
     if (extension.state == "pending" || extension.state == "running") {
       SimpleTest.ok(false, "Extension left running at test shutdown")
       return extension.unload();
     } else if (extension.state == "unloading") {
       SimpleTest.ok(false, "Extension not fully unloaded at test shutdown")
--- a/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/getComputedStyle.html
+++ b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/getComputedStyle.html
@@ -102,22 +102,18 @@ test(function(t) {
   anim.currentTime = 1000;
   assert_equals(getComputedStyle(div).opacity, '0.9',
                 'set currentTime before endTime');
 
   anim.currentTime = 5000;
   assert_equals(getComputedStyle(div).opacity, '0.5',
                 'set currentTime same as endTime');
 
-  anim.currentTime = 9999;
-  assert_equals(getComputedStyle(div).opacity, '0.5',
-                'set currentTime during duration');
-
   anim.currentTime = 10000;
-  assert_equals(getComputedStyle(div).opacity, '0.5',
+  assert_equals(getComputedStyle(div).opacity, '0',
                 'set currentTime after endTime');
 }, 'change currentTime when fill forwards and endDelay is negative');
 
 test(function(t) {
   var div = createDiv(t);
   var anim = div.animate({ opacity: [ 0, 1 ] },
                          { duration: 10000,
                            direction: 'normal' });
--- a/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/getComputedTiming.html
+++ b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/getComputedTiming.html
@@ -184,20 +184,20 @@ var gEndTimeTests = [
     input:    { duration: Infinity, iterations: 10, delay: -1000 },
     expected: Infinity },
   { desc:     "an non-zero duration and negative delay",
     input:    { duration: 1000, iterations: 2, delay: -1000 },
     expected: 1000 },
   { desc:     "an non-zero duration and negative delay greater than active " +
               "duration",
     input:    { duration: 1000, iterations: 2, delay: -3000 },
-    expected: -1000 },
+    expected: 0 },
   { desc:     "a zero duration and negative delay",
     input:    { duration: 0, iterations: 2, delay: -1000 },
-    expected: -1000 }
+    expected: 0 }
 ];
 
 gEndTimeTests.forEach(function(stest) {
   test(function(t) {
     var effect = new KeyframeEffectReadOnly(target,
                                             { left: ["10px", "20px"] },
                                             stest.input);
 
--- a/testing/web-platform/tests/web-animations/timing-model/animation-effects/active-time.html
+++ b/testing/web-platform/tests/web-animations/timing-model/animation-effects/active-time.html
@@ -95,17 +95,17 @@ test(function(t) {
   assert_times_equal(anim.effect.getComputedTiming().progress, 0.5);
 }, 'Active time in after phase with forwards fill and negative end delay'
    + ' is the active duration + end delay');
 
 test(function(t) {
   var anim = createDiv(t).animate(null, { duration: 1000,
                                           iterations: 2.3,
                                           delay: 500,
-                                          endDelay: -3000,
+                                          endDelay: -2500,
                                           fill: 'forwards' });
   anim.finish();
   assert_equals(anim.effect.getComputedTiming().currentIteration, 0);
   assert_equals(anim.effect.getComputedTiming().progress, 0);
 }, 'Active time in after phase with forwards fill and negative end delay'
    + ' greater in magnitude than the active duration is zero');
 
 test(function(t) {
--- a/testing/web-platform/tests/web-animations/timing-model/animation-effects/phases-and-states.html
+++ b/testing/web-platform/tests/web-animations/timing-model/animation-effects/phases-and-states.html
@@ -68,17 +68,17 @@ test(function(t) {
     assert_phase_at_time(animation, test.phase, test.currentTime);
   });
 }, 'Phase calculation for an animation effect with a positive start delay');
 
 test(function(t) {
   var animation = createDiv(t).animate(null, { duration: 1, delay: -1 });
 
   [ { currentTime: -2, phase: 'before' },
-    { currentTime: -1, phase: 'active' },
+    { currentTime: -1, phase: 'before' },
     { currentTime:  0, phase: 'after'  } ]
   .forEach(function(test) {
     assert_phase_at_time(animation, test.phase, test.currentTime);
   });
 }, 'Phase calculation for an animation effect with a negative start delay');
 
 test(function(t) {
   var animation = createDiv(t).animate(null, { duration: 1, endDelay: 1 });
@@ -116,57 +116,61 @@ test(function(t) {
   });
 }, 'Phase calculation for an animation effect with a negative end delay equal'
    + ' in magnitude to the active duration');
 
 test(function(t) {
   var animation = createDiv(t).animate(null, { duration: 1, endDelay: -2 });
 
   [ { currentTime: -2, phase: 'before' },
-    { currentTime: -1, phase: 'after'  } ]
+    { currentTime: -1, phase: 'before' },
+    { currentTime:  0, phase: 'after'  } ]
   .forEach(function(test) {
     assert_phase_at_time(animation, test.phase, test.currentTime);
   });
 }, 'Phase calculation for an animation effect with a negative end delay'
    + ' greater in magnitude than the active duration');
 
 test(function(t) {
   var animation = createDiv(t).animate(null, { duration: 2,
                                                delay: 1,
                                                endDelay: -1 });
 
-  [ { currentTime: 0,   phase: 'before' },
-    { currentTime: 1,   phase: 'active' },
+  [ { currentTime: 0, phase: 'before' },
+    { currentTime: 1, phase: 'active' },
     { currentTime: 2, phase: 'after'  } ]
   .forEach(function(test) {
     assert_phase_at_time(animation, test.phase, test.currentTime);
   });
 }, 'Phase calculation for an animation effect with a positive start delay'
    + ' and a negative end delay lesser in magnitude than the active duration');
 
 test(function(t) {
   var animation = createDiv(t).animate(null, { duration: 1,
                                                delay: -1,
                                                endDelay: -1 });
 
-  [ { currentTime: -2,   phase: 'before' },
-    { currentTime: -1,   phase: 'after'  } ]
+  [ { currentTime: -2, phase: 'before' },
+    { currentTime: -1, phase: 'before' },
+    { currentTime:  0, phase: 'after'  } ]
   .forEach(function(test) {
     assert_phase_at_time(animation, test.phase, test.currentTime);
   });
 }, 'Phase calculation for an animation effect with a negative start delay'
    + ' and a negative end delay equal in magnitude to the active duration');
 
 test(function(t) {
   var animation = createDiv(t).animate(null, { duration: 1,
                                                delay: -1,
                                                endDelay: -2 });
 
-  [ { currentTime: -3,   phase: 'before' },
-    { currentTime: -2,   phase: 'after'  } ]
+  [ { currentTime: -3, phase: 'before' },
+    { currentTime: -2, phase: 'before' },
+    { currentTime: -1, phase: 'before' },
+    { currentTime:  0, phase: 'after'  } ]
   .forEach(function(test) {
     assert_phase_at_time(animation, test.phase, test.currentTime);
   });
 }, 'Phase calculation for an animation effect with a negative start delay'
    + ' and a negative end delay equal greater in magnitude than the active'
    + ' duration');
 
 test(function(t) {
--- a/toolkit/components/build/nsToolkitCompsModule.cpp
+++ b/toolkit/components/build/nsToolkitCompsModule.cpp
@@ -21,23 +21,21 @@
 
 #include "nsDownloadManager.h"
 #include "DownloadPlatform.h"
 #include "nsDownloadProxy.h"
 #include "rdf.h"
 
 #include "nsTypeAheadFind.h"
 
-#ifdef MOZ_URL_CLASSIFIER
 #include "ApplicationReputation.h"
 #include "nsUrlClassifierDBService.h"
 #include "nsUrlClassifierStreamUpdater.h"
 #include "nsUrlClassifierUtils.h"
 #include "nsUrlClassifierPrefixSet.h"
-#endif
 
 #include "nsBrowserStatusFilter.h"
 #include "mozilla/FinalizationWitnessService.h"
 #include "mozilla/NativeOSFileInternals.h"
 #include "mozilla/AddonContentPolicy.h"
 #include "mozilla/AddonPathService.h"
 
 #if defined(XP_WIN)
@@ -86,17 +84,16 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsAlertsS
 
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsDownloadManager,
                                          nsDownloadManager::GetSingleton)
 NS_GENERIC_FACTORY_CONSTRUCTOR(DownloadPlatform)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsDownloadProxy)
 
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsTypeAheadFind)
 
-#ifdef MOZ_URL_CLASSIFIER
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(ApplicationReputationService,
                                          ApplicationReputationService::GetSingleton)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsUrlClassifierPrefixSet)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsUrlClassifierStreamUpdater)
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsUrlClassifierUtils, Init)
 
 static nsresult
 nsUrlClassifierDBServiceConstructor(nsISupports *aOuter, REFNSIID aIID,
@@ -111,17 +108,16 @@ nsUrlClassifierDBServiceConstructor(nsIS
         return rv;
     }
     /* NS_ADDREF(inst); */
     rv = inst->QueryInterface(aIID, aResult);
     NS_RELEASE(inst);
 
     return rv;
 }
-#endif
 
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsBrowserStatusFilter)
 #if defined(MOZ_UPDATER) && !defined(MOZ_WIDGET_ANDROID)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsUpdateProcessor)
 #endif
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(FinalizationWitnessService, Init)
 NS_GENERIC_FACTORY_CONSTRUCTOR(NativeOSFileInternalsService)
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(NativeFileWatcherService, Init)
@@ -143,23 +139,21 @@ NS_DEFINE_NAMED_CID(NS_ALERTSSERVICE_CID
 #if !defined(MOZ_DISABLE_PARENTAL_CONTROLS)
 NS_DEFINE_NAMED_CID(NS_PARENTALCONTROLSSERVICE_CID);
 #endif
 NS_DEFINE_NAMED_CID(NS_DOWNLOADMANAGER_CID);
 NS_DEFINE_NAMED_CID(NS_DOWNLOADPLATFORM_CID);
 NS_DEFINE_NAMED_CID(NS_DOWNLOAD_CID);
 NS_DEFINE_NAMED_CID(NS_FIND_SERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_TYPEAHEADFIND_CID);
-#ifdef MOZ_URL_CLASSIFIER
 NS_DEFINE_NAMED_CID(NS_APPLICATION_REPUTATION_SERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_URLCLASSIFIERPREFIXSET_CID);
 NS_DEFINE_NAMED_CID(NS_URLCLASSIFIERDBSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_URLCLASSIFIERSTREAMUPDATER_CID);
 NS_DEFINE_NAMED_CID(NS_URLCLASSIFIERUTILS_CID);
-#endif
 NS_DEFINE_NAMED_CID(NS_BROWSERSTATUSFILTER_CID);
 #if defined(MOZ_UPDATER) && !defined(MOZ_WIDGET_ANDROID)
 NS_DEFINE_NAMED_CID(NS_UPDATEPROCESSOR_CID);
 #endif
 NS_DEFINE_NAMED_CID(FINALIZATIONWITNESSSERVICE_CID);
 NS_DEFINE_NAMED_CID(NATIVE_OSFILE_INTERNALS_SERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_ADDONCONTENTPOLICY_CID);
 NS_DEFINE_NAMED_CID(NS_ADDON_PATH_SERVICE_CID);
@@ -179,23 +173,21 @@ static const Module::CIDEntry kToolkitCI
 #if !defined(MOZ_DISABLE_PARENTAL_CONTROLS)
   { &kNS_PARENTALCONTROLSSERVICE_CID, false, nullptr, nsParentalControlsServiceConstructor },
 #endif
   { &kNS_DOWNLOADMANAGER_CID, false, nullptr, nsDownloadManagerConstructor },
   { &kNS_DOWNLOADPLATFORM_CID, false, nullptr, DownloadPlatformConstructor },
   { &kNS_DOWNLOAD_CID, false, nullptr, nsDownloadProxyConstructor },
   { &kNS_FIND_SERVICE_CID, false, nullptr, nsFindServiceConstructor },
   { &kNS_TYPEAHEADFIND_CID, false, nullptr, nsTypeAheadFindConstructor },
-#ifdef MOZ_URL_CLASSIFIER
   { &kNS_APPLICATION_REPUTATION_SERVICE_CID, false, nullptr, ApplicationReputationServiceConstructor },
   { &kNS_URLCLASSIFIERPREFIXSET_CID, false, nullptr, nsUrlClassifierPrefixSetConstructor },
   { &kNS_URLCLASSIFIERDBSERVICE_CID, false, nullptr, nsUrlClassifierDBServiceConstructor },
   { &kNS_URLCLASSIFIERSTREAMUPDATER_CID, false, nullptr, nsUrlClassifierStreamUpdaterConstructor },
   { &kNS_URLCLASSIFIERUTILS_CID, false, nullptr, nsUrlClassifierUtilsConstructor },
-#endif
   { &kNS_BROWSERSTATUSFILTER_CID, false, nullptr, nsBrowserStatusFilterConstructor },
 #if defined(MOZ_UPDATER) && !defined(MOZ_WIDGET_ANDROID)
   { &kNS_UPDATEPROCESSOR_CID, false, nullptr, nsUpdateProcessorConstructor },
 #endif
   { &kFINALIZATIONWITNESSSERVICE_CID, false, nullptr, FinalizationWitnessServiceConstructor },
   { &kNATIVE_OSFILE_INTERNALS_SERVICE_CID, false, nullptr, NativeOSFileInternalsServiceConstructor },
   { &kNS_ADDONCONTENTPOLICY_CID, false, nullptr, AddonContentPolicyConstructor },
   { &kNS_ADDON_PATH_SERVICE_CID, false, nullptr, AddonPathServiceConstructor },
@@ -216,24 +208,22 @@ static const Module::ContractIDEntry kTo
   { NS_ALERTSERVICE_CONTRACTID, &kNS_ALERTSSERVICE_CID },
 #if !defined(MOZ_DISABLE_PARENTAL_CONTROLS)
   { NS_PARENTALCONTROLSSERVICE_CONTRACTID, &kNS_PARENTALCONTROLSSERVICE_CID },
 #endif
   { NS_DOWNLOADMANAGER_CONTRACTID, &kNS_DOWNLOADMANAGER_CID },
   { NS_DOWNLOADPLATFORM_CONTRACTID, &kNS_DOWNLOADPLATFORM_CID },
   { NS_FIND_SERVICE_CONTRACTID, &kNS_FIND_SERVICE_CID },
   { NS_TYPEAHEADFIND_CONTRACTID, &kNS_TYPEAHEADFIND_CID },
-#ifdef MOZ_URL_CLASSIFIER
   { NS_APPLICATION_REPUTATION_SERVICE_CONTRACTID, &kNS_APPLICATION_REPUTATION_SERVICE_CID },
   { NS_URLCLASSIFIERPREFIXSET_CONTRACTID, &kNS_URLCLASSIFIERPREFIXSET_CID },
   { NS_URLCLASSIFIERDBSERVICE_CONTRACTID, &kNS_URLCLASSIFIERDBSERVICE_CID },
   { NS_URICLASSIFIERSERVICE_CONTRACTID, &kNS_URLCLASSIFIERDBSERVICE_CID },
   { NS_URLCLASSIFIERSTREAMUPDATER_CONTRACTID, &kNS_URLCLASSIFIERSTREAMUPDATER_CID },
   { NS_URLCLASSIFIERUTILS_CONTRACTID, &kNS_URLCLASSIFIERUTILS_CID },
-#endif
   { NS_BROWSERSTATUSFILTER_CONTRACTID, &kNS_BROWSERSTATUSFILTER_CID },
 #if defined(MOZ_UPDATER) && !defined(MOZ_WIDGET_ANDROID)
   { NS_UPDATEPROCESSOR_CONTRACTID, &kNS_UPDATEPROCESSOR_CID },
 #endif
   { FINALIZATIONWITNESSSERVICE_CONTRACTID, &kFINALIZATIONWITNESSSERVICE_CID },
   { NATIVE_OSFILE_INTERNALS_SERVICE_CONTRACTID, &kNATIVE_OSFILE_INTERNALS_SERVICE_CID },
   { NS_ADDONCONTENTPOLICY_CONTRACTID, &kNS_ADDONCONTENTPOLICY_CID },
   { NS_ADDONPATHSERVICE_CONTRACTID, &kNS_ADDON_PATH_SERVICE_CID },
--- a/toolkit/components/downloads/moz.build
+++ b/toolkit/components/downloads/moz.build
@@ -27,30 +27,26 @@ XPIDL_SOURCES += [
     'nsIDownloadManager.idl',
     'nsIDownloadManagerUI.idl',
     'nsIDownloadProgressListener.idl',
 ]
 
 XPIDL_MODULE = 'downloads'
 
 UNIFIED_SOURCES += [
-    'nsDownloadManager.cpp',
+    'ApplicationReputation.cpp',
+    'chromium/chrome/common/safe_browsing/csd.pb.cc',
+    'nsDownloadManager.cpp'
 ]
 
 # SQLFunctions.cpp cannot be built in unified mode because of Windows headers.
 SOURCES += [
     'SQLFunctions.cpp',
 ]
 
-if CONFIG['MOZ_URL_CLASSIFIER']:
-    UNIFIED_SOURCES += [
-        'ApplicationReputation.cpp',
-        'chromium/chrome/common/safe_browsing/csd.pb.cc'
-    ]
-
 if CONFIG['OS_ARCH'] == 'WINNT':
     # Can't build unified because we need CreateEvent which some IPC code
     # included in LoadContext ends up undefining.
     SOURCES += [
         'nsDownloadScanner.cpp',
     ]
 
 # XXX - Until Suite builds off XULRunner we can't guarantee our implementation
--- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_background_debug_global.html
+++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_background_debug_global.html
@@ -24,29 +24,28 @@ const {
 /**
  * This test is asserting that ext-backgroundPage.js successfully sets its
  * debug global in the AddonWrapper provided by XPIProvider.jsm
  *
  * It does _not_ test any functionality in devtools and does not guarantee
  * debugging is actually working correctly end-to-end.
  */
 
-function backgroundScript() {
+function background() {
   window.testThing = "test!";
   browser.test.notifyPass("background script ran");
 }
 
 const ID = "debug@tests.mozilla.org";
 let extensionData = {
   useAddonManager: "temporary",
-  background: "(" + backgroundScript.toString() + ")()",
+  background,
   manifest: {
     applications: {gecko: {id: ID}},
   },
-  files: {},
 };
 
 add_task(function* () {
   let extension = ExtensionTestUtils.loadExtension(extensionData);
   yield extension.startup();
 
   yield extension.awaitFinish("background script ran");
 
--- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_background_page.html
+++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_background_page.html
@@ -21,17 +21,17 @@ Cu.import("resource://testing-common/Tes
 add_task(function* testAlertNotShownInBackgroundWindow() {
   ok(!Services.wm.getEnumerator("alert:alert").hasMoreElements(),
      "Alerts should not be present at the start of the test.");
 
   let consoleOpened = TestUtils.topicObserved("web-console-created");
 
 
   let extension = ExtensionTestUtils.loadExtension({
-    background: "new " + function() {
+    background: function() {
       browser.test.log("background script executed");
 
       alert("I am an alert in the background.");
 
       browser.test.notifyPass("alertCalled");
     },
   });
 
--- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_contentscript_unrecognizedprop_warning.html
+++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_contentscript_unrecognizedprop_warning.html
@@ -12,17 +12,17 @@
 <body>
 
 <script type="text/javascript">
 "use strict";
 
 const BASE = "http://mochi.test:8888/chrome/toolkit/components/extensions/test/mochitest";
 
 add_task(function* test_contentscript() {
-  function backgroundScript() {
+  function background() {
     browser.runtime.onMessage.addListener((msg) => {
       if (msg == "loaded") {
         browser.tabs.query({active: true, currentWindow: true}).then((tabs) => {
           // NOTE: we're removing the tab from here because doing a win.close()
           // from the chrome test code is raising a "TypeError: can 't access
           // dead object" exception.
           browser.tabs.remove(tabs[0].id);
 
@@ -42,20 +42,20 @@ add_task(function* test_contentscript() 
         {
           "matches": ["http://mochi.test/*/file_sample.html"],
           "js": ["content_script.js"],
           "run_at": "document_idle",
           "unrecognized_property": "with-a-random-value",
         },
       ],
     },
-    background: "(" + backgroundScript.toString() + ")()",
+    background,
 
     files: {
-      "content_script.js": "(" + contentScript.toString() + ")()",
+      "content_script.js": contentScript,
     },
   };
 
   let extension = ExtensionTestUtils.loadExtension(extensionData);
 
   SimpleTest.waitForExplicitFinish();
   let waitForConsole = new Promise(resolve => {
     SimpleTest.monitorConsole(resolve, [{
--- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_eventpage_warning.html
+++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_eventpage_warning.html
@@ -20,20 +20,20 @@ function createEventPageExtension(eventP
     browser.test.sendMessage("running", 1);
   }
 
   return ExtensionTestUtils.loadExtension({
     manifest: {
       "background": eventPage,
     },
     files: {
-      "event-page-script.js": `(${eventPageScript})()`,
+      "event-page-script.js": eventPageScript,
       "event-page.html": `<html><head>
         <meta charset="utf-8">
-        <script src="event-page-script.js"></${"script"}>
+        <script src="event-page-script.js"><\/script>
       </head></html>`,
     },
   });
 }
 
 add_task(function* test_eventpages() {
   // Used in other tests to prevent the monitorConsole to grip.
   SimpleTest.waitForExplicitFinish();
--- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_shutdown_cleanup.html
+++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_shutdown_cleanup.html
@@ -22,17 +22,17 @@ const {GlobalManager} = Cu.import("resou
 
 /* eslint-disable mozilla/balanced-listeners */
 
 add_task(function* testShutdownCleanup() {
   is(GlobalManager.initialized, false,
      "GlobalManager start as not initialized");
 
   let extension = ExtensionTestUtils.loadExtension({
-    background: "new " + function() {
+    background: function() {
       browser.test.notifyPass("background page loaded");
     },
   });
 
   yield extension.startup();
 
   yield extension.awaitFinish("background page loaded");
 
--- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_storage_cleanup.html
+++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_storage_cleanup.html
@@ -101,17 +101,17 @@ add_task(function* test_uninstall() {
   yield SpecialPowers.pushPrefEnv({
     set: [["extensions.webextensions.keepUuidOnUninstall", true]],
   });
   yield SpecialPowers.pushPrefEnv({
     set: [["extensions.webextensions.keepStorageOnUninstall", true]],
   });
 
   let extension = ExtensionTestUtils.loadExtension({
-    background: `(${writeData})()`,
+    background: writeData,
     manifest: {
       applications: {gecko: {id: ID}},
       permissions: ["storage"],
     },
     useAddonManager: "temporary",
   });
 
   yield extension.startup();
@@ -120,17 +120,17 @@ add_task(function* test_uninstall() {
 
   // Check that we can still see data we wrote to storage but clear the
   // "leave storage" flag so our storaged gets cleared on uninstall.
   // This effectively tests the keepUuidOnUninstall logic, which ensures
   // that when we read storage again and check that it is cleared, that
   // it is actually a meaningful test!
   yield SpecialPowers.popPrefEnv();
   extension = ExtensionTestUtils.loadExtension({
-    background: `(${readData})()`,
+    background: readData,
     manifest: {
       applications: {gecko: {id: ID}},
       permissions: ["storage"],
     },
     useAddonManager: "temporary",
   });
 
   yield extension.startup();
@@ -138,17 +138,17 @@ add_task(function* test_uninstall() {
   is(results.matchLocalStorage, true, "localStorage data is still present");
   is(results.matchIDB, true, "indexedDB data is still present");
   is(results.matchBrowserStorage, true, "browser.storage.local data is still present");
 
   yield extension.unload();
 
   // Read again.  This time, our data should be gone.
   extension = ExtensionTestUtils.loadExtension({
-    background: `(${readData})()`,
+    background: readData,
     manifest: {
       applications: {gecko: {id: ID}},
       permissions: ["storage"],
     },
     useAddonManager: "temporary",
   });
 
   yield extension.startup();
--- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_trustworthy_origin.html
+++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_trustworthy_origin.html
@@ -20,23 +20,22 @@
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gContentSecurityManager",
                                    "@mozilla.org/contentsecuritymanager;1",
                                    "nsIContentSecurityManager");
 
 add_task(function* () {
-  function backgroundScript() {
+  function background() {
     browser.test.sendMessage("ready", browser.runtime.getURL("/test.html"));
   }
 
   let extensionData = {
-    background: "(" + backgroundScript.toString() + ")()",
-    manifest: {},
+    background,
     files: {
       "test.html": `<html><head></head><body></body></html>`,
     },
   };
 
   let extension = ExtensionTestUtils.loadExtension(extensionData);
   yield extension.startup();
 
--- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_webnavigation_resolved_urls.html
+++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_webnavigation_resolved_urls.html
@@ -10,17 +10,17 @@
   <link rel="stylesheet" href="chrome://mochikit/contents/tests/SimpleTest/test.css"/>
 </head>
 <body>
 
 <script type="text/javascript">
 "use strict";
 
 add_task(function* webnav_unresolved_uri_on_expected_URI_scheme() {
-  function backgroundScript() {
+  function background() {
     let checkURLs;
 
     browser.webNavigation.onCompleted.addListener((msg) => {
       if (checkURLs.length > 0) {
         let expectedURL = checkURLs.shift();
         browser.test.assertEq(expectedURL, msg.url, "Got the expected URL");
         browser.tabs.remove(msg.tabId).then(() => {
           browser.test.sendMessage("next");
@@ -38,17 +38,17 @@ add_task(function* webnav_unresolved_uri
   }
 
   let extensionData = {
     manifest: {
       permissions: [
         "webNavigation",
       ],
     },
-    background: "new " + backgroundScript,
+    background,
     files: {
       "tab.html": `<!DOCTYPE html>
         <html>
           <head>
            <meta charset="utf-8">
           </head>
         </html>
       `,
--- a/toolkit/components/extensions/test/mochitest/test_ext_background_api_injection.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_background_api_injection.html
@@ -10,17 +10,17 @@
 </head>
 <body>
 
 <script type="text/javascript">
 "use strict";
 
 add_task(function* testBackgroundWindow() {
   let extension = ExtensionTestUtils.loadExtension({
-    background: "new " + function() {
+    background: function() {
       const BASE = "http://mochi.test:8888/tests/toolkit/components/extensions/test/mochitest";
 
       browser.test.log("background script executed");
       window.location = `${BASE}/file_privilege_escalation.html`;
     },
   });
 
   let awaitConsole = new Promise(resolve => {
--- a/toolkit/components/extensions/test/mochitest/test_ext_background_canvas.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_background_canvas.html
@@ -27,17 +27,17 @@ add_task(function* test_background_canva
       browser.test.notifyPass("background-canvas");
     } catch (e) {
       browser.test.fail(`Error: ${e} :: ${e.stack}`);
       browser.test.notifyFail("background-canvas");
     }
   }
 
   let extensionData = {
-    background: `(${background})()`,
+    background,
   };
 
   let extension = ExtensionTestUtils.loadExtension(extensionData);
 
   yield extension.startup();
   yield extension.awaitFinish("background-canvas");
   yield extension.unload();
 });
--- a/toolkit/components/extensions/test/mochitest/test_ext_background_generated_url.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_background_generated_url.html
@@ -22,17 +22,17 @@ add_task(function* test_url_of_generated
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       background: {
         scripts: ["bg.js"],
       },
       web_accessible_resources: ["_generated_background_page.html"],
     },
     files: {
-      "bg.js": `(${backgroundScript})();`,
+      "bg.js": backgroundScript,
     },
   });
 
   yield extension.startup();
   const EXPECTED_URL = yield extension.awaitMessage("script done");
 
   let win = window.open(EXPECTED_URL);
   ok(win, "Should open new tab at URL: " + EXPECTED_URL);
--- a/toolkit/components/extensions/test/mochitest/test_ext_background_teardown.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_background_teardown.html
@@ -9,30 +9,30 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
 </head>
 <body>
 
 <script>
 "use strict";
 
 add_task(function* test_background_reload_and_unload() {
-  function backgroundScript() {
+  function background() {
     browser.test.onMessage.addListener(msg => {
       browser.test.assertEq("reload-background", msg);
       location.reload();
     });
     browser.test.sendMessage("background-url", location.href);
   }
 
   let chromeScript = SpecialPowers.loadChromeScript(
       SimpleTest.getTestFileURL("file_teardown_test.js"));
   yield chromeScript.promiseOneMessage("chromescript-startup");
 
   let extensionData = {
-    background: `(${backgroundScript})();`,
+    background,
   };
 
   let extension = ExtensionTestUtils.loadExtension(extensionData);
   function* getContextEvents() {
     chromeScript.sendAsyncMessage("get-context-events");
     let contextEvents = yield chromeScript.promiseOneMessage("context-events");
     return contextEvents.filter(event => event.extensionId == extension.id);
   }
--- a/toolkit/components/extensions/test/mochitest/test_ext_contentscript.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_contentscript.html
@@ -9,17 +9,17 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
 
 <script type="text/javascript">
 "use strict";
 
 add_task(function* test_contentscript() {
-  function backgroundScript() {
+  function background() {
     browser.runtime.onMessage.addListener(([msg, expectedState, readyState], sender) => {
       if (msg == "chrome-namespace-ok") {
         browser.test.sendMessage(msg);
         return;
       }
 
       browser.test.assertEq(msg, "script-run", "message type is correct");
       browser.test.assertEq(readyState, expectedState, "readyState is correct");
@@ -64,23 +64,23 @@ add_task(function* test_contentscript() 
         },
         {
           "matches": ["http://mochi.test/*/file_sample.html"],
           "js": ["content_script.js"],
           "run_at": "document_idle",
         },
       ],
     },
-    background: "(" + backgroundScript.toString() + ")()",
+    background,
 
     files: {
-      "content_script_start.js": "(" + contentScriptStart.toString() + ")()",
-      "content_script_end.js": "(" + contentScriptEnd.toString() + ")()",
-      "content_script_idle.js": "(" + contentScriptIdle.toString() + ")()",
-      "content_script.js": "(" + contentScript.toString() + ")()",
+      "content_script_start.js": contentScriptStart,
+      "content_script_end.js": contentScriptEnd,
+      "content_script_idle.js": contentScriptIdle,
+      "content_script.js": contentScript,
     },
   };
 
   let extension = ExtensionTestUtils.loadExtension(extensionData);
 
   let loadingCount = 0;
   let interactiveCount = 0;
   let completeCount = 0;
--- a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_api_injection.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_api_injection.html
@@ -47,18 +47,18 @@ add_task(function* test_contentscript_ap
         },
       ],
       "web_accessible_resources": [
         "content_script_iframe.html",
       ],
     },
 
     files: {
-      "content_script.js": "new " + contentScript,
-      "content_script_iframe.js": "new " + contentScriptIframe,
+      "content_script.js": contentScript,
+      "content_script_iframe.js": contentScriptIframe,
       "content_script_iframe.html": document.querySelector("#test-asset").textContent,
     },
   };
 
   let extension = ExtensionTestUtils.loadExtension(extensionData);
 
   let awaitConsole = new Promise(resolve => {
     let chromeScript = SpecialPowers.loadChromeScript(
--- a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_context.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_context.html
@@ -31,17 +31,17 @@ add_task(function* test_contentscript_co
     manifest: {
       content_scripts: [{
         "matches": ["http://example.com/"],
         "js": ["content_script.js"],
       }],
     },
 
     files: {
-      "content_script.js": `(${contentScript})()`,
+      "content_script.js": contentScript,
     },
   });
 
   yield extension.startup();
 
   let win = window.open("http://example.com/");
   yield extension.awaitMessage("content-script-ready");
   yield extension.awaitMessage("content-script-show");
--- a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_create_iframe.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_create_iframe.html
@@ -20,17 +20,17 @@
     </head>
   </html>
 </textarea>
 
 <script type="text/javascript">
 "use strict";
 
 add_task(function* test_contentscript_create_iframe() {
-  function backgroundScript() {
+  function background() {
     browser.runtime.onMessage.addListener((msg, sender) => {
       let {name, availableAPIs, manifest, testGetManifest} = msg;
       let hasExtTabsAPI = availableAPIs.indexOf("tabs") > 0;
       let hasExtWindowsAPI = availableAPIs.indexOf("windows") > 0;
 
       browser.test.assertFalse(hasExtTabsAPI, "the created iframe should not be able to use privileged APIs (tabs)");
       browser.test.assertFalse(hasExtWindowsAPI, "the created iframe should not be able to use privileged APIs (windows)");
 
@@ -92,22 +92,22 @@ add_task(function* test_contentscript_cr
           "run_at": "document_idle",
         },
       ],
       web_accessible_resources: [
         "content_script_iframe.html",
       ],
     },
 
-    background: "(" + backgroundScript + ")()",
+    background,
 
     files: {
-      "content_script.js": "new " + contentScript,
+      "content_script.js": contentScript,
       "content_script_iframe.html": document.querySelector("#test-asset").textContent,
-      "content_script_iframe.js": "new " + contentScriptIframe,
+      "content_script_iframe.js": contentScriptIframe,
     },
   };
 
   let extension = ExtensionTestUtils.loadExtension(extensionData);
 
   let contentScriptIframeCreatedPromise = new Promise(resolve => {
     extension.onMessage("content-script-iframe-loaded", () => { resolve(); });
   });
--- a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_devtools_metadata.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_devtools_metadata.html
@@ -13,17 +13,17 @@
 <script type="text/javascript">
 "use strict";
 
 add_task(function* test_contentscript_devtools_sandbox_metadata() {
   function contentScript() {
     browser.runtime.sendMessage("contentScript.executed");
   }
 
-  function backgroundScript() {
+  function background() {
     browser.runtime.onMessage.addListener((msg) => {
       if (msg == "contentScript.executed") {
         browser.test.notifyPass("contentScript.executed");
       }
     });
   }
 
   let extensionData = {
@@ -32,19 +32,19 @@ add_task(function* test_contentscript_de
         {
           "matches": ["http://mochi.test/*/file_sample.html"],
           "js": ["content_script.js"],
           "run_at": "document_idle",
         },
       ],
     },
 
-    background: "new " + backgroundScript,
+    background,
     files: {
-      "content_script.js": "new " + contentScript,
+      "content_script.js": contentScript,
     },
   };
 
   let extension = ExtensionTestUtils.loadExtension(extensionData);
 
   yield extension.startup();
 
   let win = window.open("file_sample.html");
--- a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_exporthelpers.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_exporthelpers.html
@@ -69,17 +69,17 @@ add_task(function* test_contentscript_ex
       content_scripts: [{
         js: ["contentscript.js"],
         matches: ["http://mochi.test/*/file_sample.html"],
         run_at: "document_start",
       }],
     },
 
     files: {
-      "contentscript.js": `(${contentScript})();`,
+      "contentscript.js": contentScript,
     },
   };
 
   let extension = ExtensionTestUtils.loadExtension(extensionData);
 
   yield extension.startup();
 
   let win = window.open("file_sample.html");
--- a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_teardown.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_teardown.html
@@ -12,36 +12,36 @@
 
 <script>
 "use strict";
 
 add_task(function* test_contentscript_reload_and_unload() {
   function contentScript() {
     browser.test.sendMessage("contentscript-run");
   }
-  function backgroundScript() {
+  function background() {
     let removedTabs = 0;
     browser.tabs.onRemoved.addListener(() => {
       browser.test.assertEq(1, ++removedTabs,
           "Expected only one tab to be removed during the test");
       browser.test.sendMessage("tab-closed");
     });
   }
 
   let extensionData = {
-    background: `(${backgroundScript})();`,
+    background,
     manifest: {
       content_scripts: [{
         "matches": ["http://mochi.test/*/file_sample.html"],
         "js": ["contentscript.js"],
       }],
     },
 
     files: {
-      "contentscript.js": `(${contentScript})();`,
+      "contentscript.js": contentScript,
     },
   };
 
   let extension = ExtensionTestUtils.loadExtension(extensionData);
   yield extension.startup();
 
   let chromeScript = SpecialPowers.loadChromeScript(
       SimpleTest.getTestFileURL("file_teardown_test.js"));
--- a/toolkit/components/extensions/test/mochitest/test_ext_cookies.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_cookies.html
@@ -9,17 +9,17 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
 
 <script type="text/javascript">
 "use strict";
 
 add_task(function* test_cookies() {
-  function backgroundScript() {
+  function background() {
     function assertExpected(expected, cookie) {
       for (let key of Object.keys(cookie)) {
         browser.test.assertTrue(key in expected, `found property ${key}`);
         browser.test.assertEq(expected[key], cookie[key], `property value for ${key} is correct`);
       }
       browser.test.assertEq(Object.keys(expected).length, Object.keys(cookie).length, "all expected properties found");
     }
 
@@ -163,17 +163,17 @@ add_task(function* test_cookies() {
       browser.test.assertEq("", cookie.name, "default name set");
       browser.test.assertEq("", cookie.value, "default value set");
       browser.test.assertEq(true, cookie.session, "no expiry date created session cookie");
       browser.test.notifyPass("cookies");
     });
   }
 
   let extension = ExtensionTestUtils.loadExtension({
-    background: `(${backgroundScript})()`,
+    background,
     manifest: {
       permissions: ["cookies", "*://example.org/"],
     },
   });
 
   yield extension.startup();
   info("extension loaded");
   yield extension.awaitFinish("cookies");
--- a/toolkit/components/extensions/test/mochitest/test_ext_cookies_expiry.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_cookies_expiry.html
@@ -37,17 +37,17 @@ add_task(function* test_cookies_expiry()
     }, 1000);
   }
 
   let domain = ".example.com";
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["http://example.com/", "cookies"],
     },
-    background: `(${background})()`,
+    background,
   });
 
   let cookieSvc = SpecialPowers.Services.cookies;
 
   let cookie = {
     host: domain,
     name: "first",
     path: "/",
--- a/toolkit/components/extensions/test/mochitest/test_ext_exclude_include_globs.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_exclude_include_globs.html
@@ -9,17 +9,17 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
 
 <script type="text/javascript">
 "use strict";
 
 add_task(function* test_contentscript() {
-  function backgroundScript() {
+  function background() {
     browser.runtime.onMessage.addListener(([script], sender) => {
       browser.test.sendMessage("run", {script});
       browser.test.sendMessage("run-" + script);
     });
     browser.test.sendMessage("running");
   }
 
   function contentScriptAll() {
@@ -48,22 +48,22 @@ add_task(function* test_contentscript() 
         },
         {
           "matches": ["http://example.org/", "http://*.example.org/"],
           "exclude_globs": ["*test1*"],
           "js": ["content_script_excludes_test1.js"],
         },
       ],
     },
-    background: "(" + backgroundScript.toString() + ")()",
+    background,
 
     files: {
-      "content_script_all.js": "(" + contentScriptAll.toString() + ")()",
-      "content_script_includes_test1.js": "(" + contentScriptIncludesTest1.toString() + ")()",
-      "content_script_excludes_test1.js": "(" + contentScriptExcludesTest1.toString() + ")()",
+      "content_script_all.js": contentScriptAll,
+      "content_script_includes_test1.js": contentScriptIncludesTest1,
+      "content_script_excludes_test1.js": contentScriptExcludesTest1,
     },
 
   };
 
   let extension = ExtensionTestUtils.loadExtension(extensionData);
 
   let ran = 0;
   extension.onMessage("run", ({script}) => {
--- a/toolkit/components/extensions/test/mochitest/test_ext_generate.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_generate.html
@@ -8,31 +8,31 @@
   <script type="text/javascript" src="head.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
 
 <script type="text/javascript">
 "use strict";
 
-function backgroundScript() {
+function background() {
   browser.test.log("running background script");
 
   browser.test.onMessage.addListener((x, y) => {
     browser.test.assertEq(x, 10, "x is 10");
     browser.test.assertEq(y, 20, "y is 20");
 
     browser.test.notifyPass("background test passed");
   });
 
   browser.test.sendMessage("running", 1);
 }
 
 let extensionData = {
-  background: "(" + backgroundScript.toString() + ")()",
+  background,
 };
 
 add_task(function* test_background() {
   let extension = ExtensionTestUtils.loadExtension(extensionData);
   info("load complete");
   let [, x] = yield Promise.all([extension.startup(), extension.awaitMessage("running")]);
   is(x, 1, "got correct value from extension");
   info("startup complete");
--- a/toolkit/components/extensions/test/mochitest/test_ext_geturl.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_geturl.html
@@ -8,17 +8,17 @@
   <script type="text/javascript" src="head.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
 
 <script type="text/javascript">
 "use strict";
 
-function backgroundScript() {
+function background() {
   browser.runtime.onMessage.addListener(([url1, url2]) => {
     let url3 = browser.runtime.getURL("test_file.html");
     let url4 = browser.extension.getURL("test_file.html");
 
     browser.test.assertTrue(url1 !== undefined, "url1 defined");
 
     browser.test.assertTrue(url1.startsWith("moz-extension://"), "url1 has correct scheme");
     browser.test.assertTrue(url1.endsWith("test_file.html"), "url1 has correct leaf name");
@@ -33,27 +33,27 @@ function backgroundScript() {
 
 function contentScript() {
   let url1 = browser.runtime.getURL("test_file.html");
   let url2 = browser.extension.getURL("test_file.html");
   browser.runtime.sendMessage([url1, url2]);
 }
 
 let extensionData = {
-  background: "(" + backgroundScript.toString() + ")()",
+  background,
   manifest: {
     "content_scripts": [{
       "matches": ["http://mochi.test/*/file_sample.html"],
       "js": ["content_script.js"],
       "run_at": "document_idle",
     }],
   },
 
   files: {
-    "content_script.js": "(" + contentScript.toString() + ")()",
+    "content_script.js": contentScript,
   },
 };
 
 add_task(function* test_contentscript() {
   let extension = ExtensionTestUtils.loadExtension(extensionData);
   yield extension.startup();
 
   let win = window.open("file_sample.html");
--- a/toolkit/components/extensions/test/mochitest/test_ext_i18n.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_i18n.html
@@ -208,20 +208,20 @@ add_task(function* test_get_accept_langu
     manifest: {
       "content_scripts": [{
         "matches": ["http://mochi.test/*/file_sample.html"],
         "run_at": "document_start",
         "js": ["content_script.js"],
       }],
     },
 
-    background: `(${background})()`,
+    background,
 
     files: {
-      "content_script.js": `(${content})()`,
+      "content_script.js": content,
     },
   });
 
   let win = window.open("file_sample.html");
 
   yield extension.startup();
   yield extension.awaitMessage("ready");
 
@@ -326,17 +326,17 @@ add_task(function* test_detect_language(
     "of winkels nie en slegs oornagbesoekers word toegelaat bateleur";
   // String with intermixed French/English text
   const fr_en_string = "France is the largest country in Western Europe and the third-largest in Europe as a whole. " +
     "A accès aux chiens et aux frontaux qui lui ont été il peut consulter et modifier ses collections et exporter " +
     "Cet article concerne le pays européen aujourd’hui appelé République française. Pour d’autres usages du nom France, " +
     "Pour une aide rapide et effective, veuiller trouver votre aide dans le menu ci-dessus." +
     "Motoring events began soon after the construction of the first successful gasoline-fueled automobiles. The quick brown fox jumped over the lazy dog";
 
-  function backgroundScript() {
+  function background() {
     function checkResult(source, result, expected) {
       browser.test.assertEq(expected.isReliable, result.isReliable, "result.confident is true");
       browser.test.assertEq(
         expected.languages.length,
         result.languages.length,
         `result.languages contains the expected number of languages in ${source}`);
       expected.languages.forEach((lang, index) => {
         browser.test.assertEq(
@@ -382,20 +382,20 @@ add_task(function* test_detect_language(
     manifest: {
       "content_scripts": [{
         "matches": ["http://mochi.test/*/file_sample.html"],
         "run_at": "document_start",
         "js": ["content_script.js"],
       }],
     },
 
-    background: `(${backgroundScript})()`,
+    background,
 
     files: {
-      "content_script.js": `(${content})()`,
+      "content_script.js": content,
     },
   });
 
   let win = window.open("file_sample.html");
 
   yield extension.startup();
   yield extension.awaitMessage("ready");
 
--- a/toolkit/components/extensions/test/mochitest/test_ext_i18n_css.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_i18n_css.html
@@ -10,17 +10,17 @@
 </head>
 <body>
 
 <script type="text/javascript">
 "use strict";
 
 add_task(function* test_i18n_css() {
   let extension = ExtensionTestUtils.loadExtension({
-    background: "new " + function() {
+    background: function() {
       function fetch(url) {
         return new Promise((resolve, reject) => {
           let xhr = new XMLHttpRequest();
           xhr.open("GET", url);
           xhr.onload = () => { resolve(xhr.responseText); };
           xhr.onerror = reject;
           xhr.send();
         });
--- a/toolkit/components/extensions/test/mochitest/test_ext_inIncognitoContext_window.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_inIncognitoContext_window.html
@@ -23,23 +23,22 @@ add_task(function* test_in_incognito_con
     browser.windows.create({url: browser.runtime.getURL("/tab.html"), incognito: true});
   }
 
   function tabScript() {
     browser.runtime.sendMessage(browser.extension.inIncognitoContext);
   }
 
   let extension = ExtensionTestUtils.loadExtension({
-    background: `(${background})()`,
-    manifest: {},
+    background,
     files: {
-      "tab.js": `(${tabScript})()`,
+      "tab.js": tabScript,
       "tab.html": `<!DOCTYPE html><html><head>
         <meta charset="utf-8">
-        <script src="tab.js"></${"script"}>
+        <script src="tab.js"><\/script>
       </head></html>`,
     },
   });
 
   yield extension.startup();
   yield extension.awaitFinish("inIncognitoContext");
   yield extension.unload();
 });
--- a/toolkit/components/extensions/test/mochitest/test_ext_jsversion.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_jsversion.html
@@ -13,49 +13,49 @@
 <body>
 
 <script type="text/javascript">
 "use strict";
 
 add_task(function* test_versioned_js() {
   // We need to deal with escaping the close script tags.
   // May as well consolidate it into one place.
-  let script = attrs => `<script ${attrs}></${"script"}>`;
+  let script = attrs => `<script ${attrs}><\/script>`;
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "background": {"page": "background.html"},
     },
 
     files: {
       "background.html": `
         <meta charset="utf-8">
         ${script('src="background.js" type="application/javascript"')}
         ${script('src="background-1.js" type="application/javascript;version=1.8"')}
         ${script('src="background-2.js" type="application/javascript;version=latest"')}
         ${script('src="background-3.js" type="application/javascript"')}
       `,
 
-      "background.js": "new " + function() {
+      "background.js": function() {
         window.reportResult = msg => {
           browser.test.assertEq(
             msg, "background-script-3",
             "Expected a message only from the unversioned background script.");
 
           browser.test.sendMessage("finished");
         };
       },
 
-      "background-1.js": "new " + function() {
+      "background-1.js": function() {
         window.reportResult("background-script-1");
       },
-      "background-2.js": "new " + function() {
+      "background-2.js": function() {
         window.reportResult("background-script-2");
       },
-      "background-3.js": "new " + function() {
+      "background-3.js": function() {
         window.reportResult("background-script-3");
       },
     },
   });
 
   let messages = [/Versioned JavaScript.*not supported in WebExtension.*developer\.mozilla\.org/,
                   /Versioned JavaScript.*not supported in WebExtension.*developer\.mozilla\.org/];
 
--- a/toolkit/components/extensions/test/mochitest/test_ext_notifications.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_notifications.html
@@ -14,44 +14,44 @@
 
 // A 1x1 PNG image.
 // Source: https://commons.wikimedia.org/wiki/File:1x1.png (Public Domain)
 let image = atob("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAA" +
                  "ACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=");
 const IMAGE_ARRAYBUFFER = Uint8Array.from(image, byte => byte.charCodeAt(0)).buffer;
 
 add_task(function* test_notification() {
-  function backgroundScript() {
+  function background() {
     let opts = {
       type: "basic",
       title: "Testing Notification",
       message: "Carry on",
     };
 
     browser.notifications.create(opts).then(id => {
       browser.test.sendMessage("running", id);
       browser.test.notifyPass("background test passed");
     });
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       permissions: ["notifications"],
     },
-    background: `(${backgroundScript})()`,
+    background,
   });
   yield extension.startup();
   let x = yield extension.awaitMessage("running");
   is(x, "0", "got correct id from notifications.create");
   yield extension.awaitFinish();
   yield extension.unload();
 });
 
 add_task(function* test_notification_events() {
-  function backgroundScript() {
+  function background() {
     let opts = {
       type: "basic",
       title: "Testing Notification",
       message: "Carry on",
     };
 
     // Test an ignored listener.
     browser.notifications.onButtonClicked.addListener(function() {});
@@ -72,29 +72,29 @@ add_task(function* test_notification_eve
       browser.test.sendMessage("running", id);
     });
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       permissions: ["notifications"],
     },
-    background: `(${backgroundScript})()`,
+    background,
   });
   yield extension.startup();
   let x = yield extension.awaitMessage("closed");
   is(x, "5", "got correct id from onClosed listener");
   x = yield extension.awaitMessage("running");
   is(x, "5", "got correct id from notifications.create");
   yield extension.awaitFinish();
   yield extension.unload();
 });
 
 add_task(function* test_notification_clear() {
-  function backgroundScript() {
+  function background() {
     let opts = {
       type: "basic",
       title: "Testing Notification",
       message: "Carry on",
     };
 
     browser.notifications.onClosed.addListener(id => {
       browser.test.sendMessage("closed", id);
@@ -107,49 +107,49 @@ add_task(function* test_notification_cle
       browser.test.notifyPass("background test passed");
     });
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       permissions: ["notifications"],
     },
-    background: `(${backgroundScript})()`,
+    background,
   });
   yield extension.startup();
   let x = yield extension.awaitMessage("closed");
   is(x, "99", "got correct id from onClosed listener");
   x = yield extension.awaitMessage("cleared");
   is(x, true, "got correct boolean from notifications.clear");
   yield extension.awaitFinish();
   yield extension.unload();
 });
 
 add_task(function* test_notifications_empty_getAll() {
-  function backgroundScript() {
+  function background() {
     browser.notifications.getAll().then(notifications => {
       browser.test.assertEq("object", typeof notifications, "getAll() returned an object");
       browser.test.assertEq(0, Object.keys(notifications).length, "the object has no properties");
       browser.test.notifyPass("getAll empty");
     });
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       permissions: ["notifications"],
     },
-    background: `(${backgroundScript})()`,
+    background,
   });
   yield extension.startup();
   yield extension.awaitFinish("getAll empty");
   yield extension.unload();
 });
 
 add_task(function* test_notifications_populated_getAll() {
-  function backgroundScript() {
+  function background() {
     let opts = {
       type: "basic",
       iconUrl: "a.png",
       title: "Testing Notification",
       message: "Carry on",
     };
 
     browser.notifications.create("p1", opts).then(() => {
@@ -171,28 +171,28 @@ add_task(function* test_notifications_po
       browser.test.notifyPass("getAll populated");
     });
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       permissions: ["notifications"],
     },
-    background: `(${backgroundScript})()`,
+    background,
     files: {
       "a.png": IMAGE_ARRAYBUFFER,
     },
   });
   yield extension.startup();
   yield extension.awaitFinish("getAll populated");
   yield extension.unload();
 });
 
 add_task(function* test_buttons_unsupported() {
-  function backgroundScript() {
+  function background() {
     let opts = {
       type: "basic",
       title: "Testing Notification",
       message: "Carry on",
       buttons: [{title: "Button title"}],
     };
 
     let exception = {};
@@ -208,17 +208,17 @@ add_task(function* test_buttons_unsuppor
     );
     browser.test.notifyPass("buttons-unsupported");
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       permissions: ["notifications"],
     },
-    background: `(${backgroundScript})()`,
+    background,
   });
   yield extension.startup();
   yield extension.awaitFinish("buttons-unsupported");
   yield extension.unload();
 });
 
 </script>
 
--- a/toolkit/components/extensions/test/mochitest/test_ext_runtime_connect.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_runtime_connect.html
@@ -8,17 +8,17 @@
   <script type="text/javascript" src="head.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
 
 <script type="text/javascript">
 "use strict";
 
-function backgroundScript() {
+function background() {
   browser.runtime.onConnect.addListener(port => {
     browser.test.assertEq(port.name, "ernie", "port name correct");
     browser.test.assertTrue(port.sender.url.endsWith("file_sample.html"), "URL correct");
     browser.test.assertTrue(port.sender.tab.url.endsWith("file_sample.html"), "tab URL correct");
 
     let expected = "message 1";
     port.onMessage.addListener(msg => {
       browser.test.assertEq(msg, expected, "message is expected");
@@ -43,28 +43,28 @@ function contentScript() {
     if (msg == "message 2") {
       port.postMessage("message 3");
       port.disconnect();
     }
   });
 }
 
 let extensionData = {
-  background: "(" + backgroundScript.toString() + ")()",
+  background,
   manifest: {
     "permissions": ["tabs"],
     "content_scripts": [{
       "matches": ["http://mochi.test/*/file_sample.html"],
       "js": ["content_script.js"],
       "run_at": "document_start",
     }],
   },
 
   files: {
-    "content_script.js": "(" + contentScript.toString() + ")()",
+    "content_script.js": contentScript,
   },
 };
 
 add_task(function* test_contentscript() {
   let extension = ExtensionTestUtils.loadExtension(extensionData);
   yield extension.startup();
 
   let win = window.open("file_sample.html");
--- a/toolkit/components/extensions/test/mochitest/test_ext_runtime_connect_twoway.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_runtime_connect_twoway.html
@@ -9,17 +9,17 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
 </head>
 <body>
 
 <script>
 "use strict";
 
 add_task(function* test_connect_bidirectionally_and_postMessage() {
-  function backgroundScript() {
+  function background() {
     let onConnectCount = 0;
     browser.runtime.onConnect.addListener(port => {
       // 3. onConnect by connect() from CS.
       browser.test.assertEq("from-cs", port.name);
       browser.test.assertEq(1, ++onConnectCount,
           "BG onConnect should be called once");
 
       let tabId = port.sender.tab.id;
@@ -93,25 +93,25 @@ add_task(function* test_connect_bidirect
         "CS port.onMessage should be called once");
 
       // 10. should trigger port.onMessage in BG.
       port.postMessage("from CS to port");
     });
   }
 
   let extensionData = {
-    background: `(${backgroundScript})();`,
+    background,
     manifest: {
       content_scripts: [{
         js: ["contentscript.js"],
         matches: ["http://mochi.test/*/file_sample.html"],
       }],
     },
     files: {
-      "contentscript.js": `(${contentScript})();`,
+      "contentscript.js": contentScript,
     },
   };
 
   let extension = ExtensionTestUtils.loadExtension(extensionData);
   yield extension.startup();
   info("extension loaded");
 
   yield extension.awaitMessage("ready");
--- a/toolkit/components/extensions/test/mochitest/test_ext_runtime_disconnect.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_runtime_disconnect.html
@@ -8,17 +8,17 @@
   <script type="text/javascript" src="head.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
 
 <script type="text/javascript">
 "use strict";
 
-function backgroundScript() {
+function background() {
   browser.runtime.onConnect.addListener(port => {
     browser.test.assertEq(port.name, "ernie", "port name correct");
     port.onDisconnect.addListener(() => {
       // Closing an already-disconnected port is a no-op.
       port.disconnect();
       port.disconnect();
       browser.test.sendMessage("disconnected");
     });
@@ -26,28 +26,28 @@ function backgroundScript() {
   });
 }
 
 function contentScript() {
   browser.runtime.connect({name: "ernie"});
 }
 
 let extensionData = {
-  background: "(" + backgroundScript.toString() + ")()",
+  background,
   manifest: {
     "permissions": ["tabs"],
     "content_scripts": [{
       "matches": ["http://mochi.test/*/file_sample.html"],
       "js": ["content_script.js"],
       "run_at": "document_idle",
     }],
   },
 
   files: {
-    "content_script.js": "(" + contentScript.toString() + ")()",
+    "content_script.js": contentScript,
   },
 };
 
 add_task(function* test_contentscript() {
   let extension = ExtensionTestUtils.loadExtension(extensionData);
   yield extension.startup();
 
   let win = window.open("file_sample.html");
--- a/toolkit/components/extensions/test/mochitest/test_ext_runtime_id.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_runtime_id.html
@@ -31,20 +31,20 @@ add_task(function* test_runtime_id() {
       applications: {gecko: {id}},
       "content_scripts": [{
         "matches": ["http://mochi.test/*/file_sample.html"],
         "run_at": "document_start",
         "js": ["content_script.js"],
       }],
     },
 
-    background: `(${background})()`,
+    background,
 
     files: {
-      "content_script.js": `(${content})()`,
+      "content_script.js": content,
     },
   });
 
   yield extension.startup();
 
   let backgroundId = yield extension.awaitMessage("background-id");
   is(backgroundId, id, "runtime.id from background script is correct");
   let win = window.open("file_sample.html");
--- a/toolkit/components/extensions/test/mochitest/test_ext_sandbox_var.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_sandbox_var.html
@@ -8,41 +8,41 @@
   <script type="text/javascript" src="head.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
 
 <script type="text/javascript">
 "use strict";
 
-function backgroundScript() {
+function background() {
   browser.runtime.onMessage.addListener(result => {
     browser.test.assertEq(result, 12, "x is 12");
     browser.test.notifyPass("background test passed");
   });
 }
 
 function contentScript() {
   window.x = 12;
   browser.runtime.onMessage.addListener(function() {});
   browser.runtime.sendMessage(window.x);
 }
 
 let extensionData = {
-  background: "(" + backgroundScript.toString() + ")()",
+  background,
   manifest: {
     "content_scripts": [{
       "matches": ["http://mochi.test/*/file_sample.html"],
       "js": ["content_script.js"],
       "run_at": "document_idle",
     }],
   },
 
   files: {
-    "content_script.js": "(" + contentScript.toString() + ")()",
+    "content_script.js": contentScript,
   },
 };
 
 add_task(function* test_contentscript() {
   let extension = ExtensionTestUtils.loadExtension(extensionData);
   yield extension.startup();
 
   let win = window.open("file_sample.html");
--- a/toolkit/components/extensions/test/mochitest/test_ext_schema.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_schema.html
@@ -18,17 +18,17 @@ add_task(function* testEmptySchema() {
   function background() {
     browser.test.assertTrue(!("manifest" in browser), "browser.manifest is not defined");
     browser.test.assertTrue("storage" in browser, "browser.storage should be defined");
     browser.test.assertTrue(!("contextMenus" in browser), "browser.contextMenus should not be defined");
     browser.test.notifyPass("schema");
   }
 
   let extension = ExtensionTestUtils.loadExtension({
-    background: `(${background})()`,
+    background,
     manifest: {
       permissions: ["storage"],
     },
   });
 
   yield extension.startup();
   yield extension.awaitFinish("schema");
   yield extension.unload();
@@ -41,17 +41,17 @@ add_task(function* testUnknownProperties
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       permissions: ["unknownPermission"],
 
       unknown_property: {},
     },
 
-    background: `(${background})()`,
+    background,
   });
 
   let messages = [
     {message: /processing permissions\.0: Unknown permission "unknownPermission"/},
     {message: /processing unknown_property: An unexpected property was found in the WebExtension manifest/},
   ];
 
   let waitForConsole = new Promise(resolve => {
--- a/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_doublereply.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_doublereply.html
@@ -8,17 +8,17 @@
   <script type="text/javascript" src="head.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
 
 <script type="text/javascript">
 "use strict";
 
-function backgroundScript() {
+function background() {
   // Add two listeners that both send replies. We're supposed to ignore all but one
   // of them. Which one is chosen is non-deterministic.
 
   browser.runtime.onMessage.addListener((msg, sender, sendReply) => {
     browser.test.assertTrue(sender.tab.url.endsWith("file_sample.html"), "sender url correct");
 
     if (msg == "getreply") {
       sendReply("reply1");
@@ -62,28 +62,28 @@ function contentScript() {
     if (resp != "reply1" && resp != "reply2") {
       return; // test failed
     }
     browser.runtime.sendMessage("done");
   });
 }
 
 let extensionData = {
-  background: "(" + backgroundScript.toString() + ")()",
+  background,
   manifest: {
     "permissions": ["tabs"],
     "content_scripts": [{
       "matches": ["http://mochi.test/*/file_sample.html"],
       "js": ["content_script.js"],
       "run_at": "document_start",
     }],
   },
 
   files: {
-    "content_script.js": "(" + contentScript.toString() + ")()",
+    "content_script.js": contentScript,
   },
 };
 
 add_task(function* test_contentscript() {
   let extension = ExtensionTestUtils.loadExtension(extensionData);
   yield extension.startup();
 
   let win = window.open("file_sample.html");
--- a/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_no_receiver.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_no_receiver.html
@@ -17,17 +17,17 @@ function loadContentScriptExtension(cont
   let extensionData = {
     manifest: {
       "content_scripts": [{
         "js": ["contentscript.js"],
         "matches": ["http://mochi.test/*/file_sample.html"],
       }],
     },
     files: {
-      "contentscript.js": `(${contentScript})();`,
+      "contentscript.js": contentScript,
     },
   };
   return ExtensionTestUtils.loadExtension(extensionData);
 }
 
 add_task(function* test_content_script_sendMessage_without_listener() {
   function contentScript() {
     browser.runtime.sendMessage("msg").then(reply => {
--- a/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_reply.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_reply.html
@@ -8,17 +8,17 @@
   <script type="text/javascript" src="head.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
 
 <script type="text/javascript">
 "use strict";
 
-function backgroundScript() {
+function background() {
   browser.runtime.onMessage.addListener((msg, sender, sendReply) => {
     browser.test.assertTrue(sender.tab.url.endsWith("file_sample.html"), "sender url correct");
 
     if (msg == 0) {
       sendReply("reply1");
     } else if (msg == 1) {
       window.setTimeout(function() {
         sendReply("reply2");
@@ -40,28 +40,28 @@ function contentScript() {
         return; // test failed
       }
       browser.runtime.sendMessage(2);
     });
   });
 }
 
 let extensionData = {
-  background: "(" + backgroundScript.toString() + ")()",
+  background,
   manifest: {
     "permissions": ["tabs"],
     "content_scripts": [{
       "matches": ["http://mochi.test/*/file_sample.html"],
       "js": ["content_script.js"],
       "run_at": "document_idle",
     }],
   },
 
   files: {
-    "content_script.js": "(" + contentScript.toString() + ")()",
+    "content_script.js": contentScript,
   },
 };
 
 add_task(function* test_contentscript() {
   let extension = ExtensionTestUtils.loadExtension(extensionData);
   yield extension.startup();
 
   let win = window.open("file_sample.html");
--- a/toolkit/components/extensions/test/mochitest/test_ext_storage_content.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_storage_content.html
@@ -174,17 +174,17 @@ let extensionData = {
       "js": ["content_script.js"],
       "run_at": "document_idle",
     }],
 
     permissions: ["storage"],
   },
 
   files: {
-    "content_script.js": "(" + contentScript.toString() + ")()",
+    "content_script.js": contentScript,
   },
 };
 
 add_task(function* test_contentscript() {
   let win = window.open("file_sample.html");
   yield waitForLoad(win);
 
   let extension = ExtensionTestUtils.loadExtension(extensionData);
--- a/toolkit/components/extensions/test/mochitest/test_ext_storage_tab.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_storage_tab.html
@@ -81,28 +81,28 @@ add_task(function* test_multiple_pages()
         return browser.storage.local.set({key: {foo: {bar: "baz"}}});
       }
     });
 
     browser.runtime.sendMessage("tab-ready");
   }
 
   let extension = ExtensionTestUtils.loadExtension({
-    background: `(${background})()`,
+    background,
 
     files: {
       "tab.html": `<!DOCTYPE html>
         <html>
           <head>
             <meta charset="utf-8">
-            <script src="tab.js"></${"script"}>
+            <script src="tab.js"><\/script>
           </head>
         </html>`,
 
-      "tab.js": `(${tab})()`,
+      "tab.js": tab,
     },
 
     manifest: {
       permissions: ["storage"],
     },
   });
 
   yield extension.startup();
--- a/toolkit/components/extensions/test/mochitest/test_ext_subframes_privileges.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_subframes_privileges.html
@@ -9,17 +9,17 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
 
 <script type="text/javascript">
 "use strict";
 
 add_task(function* test_webext_tab_subframe_privileges() {
-  function backgroundScript() {
+  function background() {
     browser.runtime.onMessage.addListener(({msg, success, tabId, error}) => {
       if (msg == "webext-tab-subframe-privileges") {
         if (success) {
           browser.tabs.remove(tabId)
             .then(() => browser.test.notifyPass(msg));
         } else {
           browser.test.log(`Got an unexpected error: ${error}`);
           browser.tabs.query({active: true})
@@ -52,33 +52,33 @@ add_task(function* test_webext_tab_subfr
         msg: "webext-tab-subframe-privileges",
         success: false,
         error: `Privileged APIs missing in WebExtension tab sub-frame`,
       });
     }
   }
 
   let extensionData = {
-    background: "new " + backgroundScript,
+    background,
     files: {
       "tab.html": `<!DOCTYPE>
           <head>
             <meta charset="utf-8">
           </head>
           <body>
             <iframe src="tab-subframe.html"></iframe>
           </body>
         </html>`,
       "tab-subframe.html": `<!DOCTYPE>
           <head>
             <meta charset="utf-8">
-            <script src="tab-subframe.js"></${"script"}>
+            <script src="tab-subframe.js"><\/script>
           </head>
         </html>`,
-      "tab-subframe.js": `(${tabSubframeScript})()`,
+      "tab-subframe.js": tabSubframeScript,
     },
   };
   let extension = ExtensionTestUtils.loadExtension(extensionData);
 
   yield extension.startup();
 
   yield extension.awaitFinish("webext-tab-subframe-privileges");
   yield extension.unload();
@@ -104,32 +104,32 @@ add_task(function* test_webext_backgroun
          </head>
          <body>
            <iframe src="background-subframe.html"></iframe>
          </body>
        </html>`,
       "background-subframe.html": `<!DOCTYPE>
          <head>
            <meta charset="utf-8">
-           <script src="background-subframe.js"></${"script"}>
+           <script src="background-subframe.js"><\/script>
          </head>
        </html>`,
-      "background-subframe.js": `(${backgroundSubframeScript})()`,
+      "background-subframe.js": backgroundSubframeScript,
     },
   };
   let extension = ExtensionTestUtils.loadExtension(extensionData);
 
   yield extension.startup();
 
   yield extension.awaitFinish("webext-background-subframe-privileges");
   yield extension.unload();
 });
 
 add_task(function* test_webext_contentscript_iframe_subframe_privileges() {
-  function backgroundScript() {
+  function background() {
     browser.runtime.onMessage.addListener(({name, hasTabsAPI, hasStorageAPI}) => {
       if (name == "contentscript-iframe-loaded") {
         browser.test.assertFalse(hasTabsAPI,
                                  "Subframe of a content script privileged iframes has no access to privileged APIs");
         browser.test.assertTrue(hasStorageAPI,
                                  "Subframe of a content script privileged iframes has access to content script APIs");
 
         browser.test.notifyPass("webext-contentscript-subframe-privileges");
@@ -147,44 +147,44 @@ add_task(function* test_webext_contentsc
 
   function contentScript() {
     let iframe = document.createElement("iframe");
     iframe.setAttribute("src", browser.runtime.getURL("/contentscript-iframe.html"));
     document.body.appendChild(iframe);
   }
 
   let extensionData = {
-    background: "new " + backgroundScript,
+    background,
     manifest: {
       "permissions": ["storage"],
       "content_scripts": [{
         "matches": ["http://example.com/*"],
         "js": ["contentscript.js"],
       }],
       web_accessible_resources: [
         "contentscript-iframe.html",
       ],
     },
     files: {
-      "contentscript.js": `(${contentScript})()`,
+      "contentscript.js": contentScript,
       "contentscript-iframe.html": `<!DOCTYPE>
          <head>
            <meta charset="utf-8">
          </head>
          <body>
            <iframe src="contentscript-iframe-subframe.html"></iframe>
          </body>
        </html>`,
       "contentscript-iframe-subframe.html": `<!DOCTYPE>
          <head>
            <meta charset="utf-8">
-           <script src="contentscript-iframe-subframe.js"></${"script"}>
+           <script src="contentscript-iframe-subframe.js"><\/script>
          </head>
        </html>`,
-      "contentscript-iframe-subframe.js": `(${subframeScript})()`,
+      "contentscript-iframe-subframe.js": subframeScript,
     },
   };
   let extension = ExtensionTestUtils.loadExtension(extensionData);
 
   yield extension.startup();
 
   let win = window.open("http://example.com");
 
--- a/toolkit/components/extensions/test/mochitest/test_ext_tab_teardown.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_tab_teardown.html
@@ -59,17 +59,17 @@ function* runTabReloadAndCloseTest(exten
       "ExtensionContext URL at closing tab should be tab URL");
 
   chromeScript.sendAsyncMessage("cleanup");
   chromeScript.destroy();
   yield extension.unload();
 }
 
 add_task(function* test_extension_page_tabs_create_reload_and_close() {
-  function backgroundScript() {
+  function background() {
     let tabId;
     browser.test.onMessage.addListener(msg => {
       if (msg === "open extension page") {
         chrome.tabs.create({url: "page.html"}, tab => {
           tabId = tab.id;
         });
       } else if (msg === "reload extension page") {
         chrome.tabs.reload(tabId);
@@ -81,35 +81,35 @@ add_task(function* test_extension_page_t
     });
   }
 
   function pageScript() {
     browser.test.sendMessage("extension page loaded", document.URL);
   }
 
   let extensionData = {
-    background: `(${backgroundScript})();`,
+    background,
     files: {
       "page.html": `<!DOCTYPE html><meta charset="utf-8"><script src="page.js"><\/script>`,
-      "page.js": `(${pageScript})();`,
+      "page.js": pageScript,
     },
   };
 
   let extension = ExtensionTestUtils.loadExtension(extensionData);
   yield extension.startup();
 
   yield* runTabReloadAndCloseTest(extension);
 });
 
 add_task(function* test_extension_page_window_open_reload_and_close() {
   // This tests whether a context that is opened via window.open is properly
   // disposed when the tab closes.
   // The background page cannot use window.open (bugzil.la/1282021), so we open
   // another extension page that manages the window.open-tab for testing.
-  function backgroundScript() {
+  function background() {
     chrome.tabs.create({url: "window.open.html"});
   }
 
   function windowOpenScript() {
     let win;
     browser.test.onMessage.addListener(msg => {
       if (msg === "open extension page") {
         win = window.open("page.html");
@@ -126,22 +126,22 @@ add_task(function* test_extension_page_w
     browser.test.sendMessage("setup-intermediate-tab");
   }
 
   function pageScript() {
     browser.test.sendMessage("extension page loaded", document.URL);
   }
 
   let extensionData = {
-    background: `(${backgroundScript})();`,
+    background,
     files: {
       "page.html": `<!DOCTYPE html><meta charset="utf-8"><script src="page.js"><\/script>`,
-      "page.js": `(${pageScript})();`,
+      "page.js": pageScript,
       "window.open.html": `<!DOCTYPE html><meta charset="utf-8"><script src="window.open.js"><\/script>`,
-      "window.open.js": `(${windowOpenScript})();`,
+      "window.open.js": windowOpenScript,
     },
   };
   let extension = ExtensionTestUtils.loadExtension(extensionData);
   yield extension.startup();
   yield extension.awaitMessage("setup-intermediate-tab");
   yield* runTabReloadAndCloseTest(extension);
 });
 </script>
--- a/toolkit/components/extensions/test/mochitest/test_ext_web_accessible_resources.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_web_accessible_resources.html
@@ -136,43 +136,43 @@ add_task(function* test_web_accessible_r
       ],
 
       "web_accessible_resources": [
         "/accessible.html",
         "wild*.html",
       ],
     },
 
-    background: `(${background})()`,
+    background,
 
     files: {
-      "content_script.js": `(${contentScript})()`,
+      "content_script.js": contentScript,
 
       "accessible.html": `<html><head>
         <meta charset="utf-8">
-        <script src="accessible.js"></${"script"}>
+        <script src="accessible.js"><\/script>
       </head></html>`,
 
       "accessible.js": 'browser.runtime.sendMessage(["page-script", location.href]);',
 
       "inaccessible.html": `<html><head>
         <meta charset="utf-8">
-        <script src="inaccessible.js"></${"script"}>
+        <script src="inaccessible.js"><\/script>
       </head></html>`,
 
       "inaccessible.js": 'browser.runtime.sendMessage(["page-script", location.href]);',
 
       "wild1.html": `<html><head>
         <meta charset="utf-8">
-        <script src="wild.js"></${"script"}>
+        <script src="wild.js"><\/script>
       </head></html>`,
 
       "wild2.htm": `<html><head>
         <meta charset="utf-8">
-        <script src="wild.js"></${"script"}>
+        <script src="wild.js"><\/script>
       </head></html>`,
 
       "wild.js": 'browser.runtime.sendMessage(["page-script", location.href]);',
     },
   });
 
   yield extension.startup();
 
@@ -226,21 +226,21 @@ add_task(function* test_web_accessible_r
         "run_at": "document_start",
         "js": ["content_script_helper.js", "content_script.js"],
       }],
       "web_accessible_resources": [
         "image.png",
         "test_script.js",
       ],
     },
-    background: `(${background})()`,
+    background,
     files: {
       "content_script_helper.js": `${testImageLoading}`,
-      "content_script.js": `(${content})()`,
-      "test_script.js": `(${testScript})()`,
+      "content_script.js": content,
+      "test_script.js": testScript,
       "image.png": IMAGE_ARRAYBUFFER,
     },
   });
 
   // This is used to watch the blocked data bounce off CSP.
   function examiner() {
     SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
   }
@@ -321,21 +321,21 @@ add_task(function* test_web_accessible_r
         "run_at": "document_start",
         "js": ["content_script_helper.js", "content_script.js"],
       }],
       "web_accessible_resources": [
         "image.png",
         "test_script.js",
       ],
     },
-    background: `(${background})()`,
+    background,
     files: {
       "content_script_helper.js": `${testImageLoading}`,
-      "content_script.js": `(${content})()`,
-      "test_script.js": `(${testScript})()`,
+      "content_script.js": content,
+      "test_script.js": testScript,
       "image.png": IMAGE_ARRAYBUFFER,
     },
   });
 
   SpecialPowers.setBoolPref("security.mixed_content.block_display_content", true);
 
   yield Promise.all([extension.startup(), extension.awaitMessage("background-ready")]);
 
--- a/toolkit/components/extensions/test/mochitest/test_ext_webnavigation.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_webnavigation.html
@@ -127,17 +127,17 @@ add_task(function* webnav_transitions_pr
   }
 
   let extensionData = {
     manifest: {
       permissions: [
         "webNavigation",
       ],
     },
-    background: `(${backgroundScriptTransitions})()`,
+    background: backgroundScriptTransitions,
   };
 
   let extension = ExtensionTestUtils.loadExtension(extensionData);
 
   extension.onMessage("received", ({url, event, details}) => {
     received.push({url, event, details});
 
     if (event == waitingEvent && url == waitingURL) {
@@ -344,17 +344,17 @@ add_task(function* webnav_transitions_pr
 
 add_task(function* webnav_ordering() {
   let extensionData = {
     manifest: {
       permissions: [
         "webNavigation",
       ],
     },
-    background: "(" + backgroundScript.toString() + ")()",
+    background: backgroundScript,
   };
 
   let extension = ExtensionTestUtils.loadExtension(extensionData);
 
   extension.onMessage("received", ({url, event}) => {
     received.push({url, event});
 
     if (event == waitingEvent && url == waitingURL) {
@@ -510,17 +510,17 @@ add_task(function* webnav_error_event() 
   }
 
   let extensionData = {
     manifest: {
       permissions: [
         "webNavigation",
       ],
     },
-    background: `(${backgroundScriptErrorEvent})()`,
+    background: backgroundScriptErrorEvent,
   };
 
   let extension = ExtensionTestUtils.loadExtension(extensionData);
 
   extension.onMessage("received", ({url, event, details}) => {
     received.push({url, event, details});
 
     if (event == waitingEvent && url == waitingURL) {
--- a/toolkit/components/extensions/test/mochitest/test_ext_webnavigation_filters.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_webnavigation_filters.html
@@ -9,17 +9,17 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
 
 <script type="text/javascript">
 "use strict";
 
 add_task(function* test_webnav_unresolved_uri_on_expected_URI_scheme() {
-  function backgroundScript() {
+  function background() {
     let lastTest;
 
     function cleanupTestListeners() {
       if (lastTest) {
         let {event, okListener, failListener} = lastTest;
         lastTest = null;
         browser.test.log(`Cleanup previous test event listeners`);
         browser.webNavigation[event].removeListener(okListener);
@@ -65,17 +65,17 @@ add_task(function* test_webnav_unresolve
   }
 
   let extensionData = {
     manifest: {
       permissions: [
         "webNavigation",
       ],
     },
-    background: "new " + backgroundScript,
+    background,
   };
 
   let extension = ExtensionTestUtils.loadExtension(extensionData);
 
   yield extension.startup();
 
   yield extension.awaitMessage("ready");
 
@@ -252,17 +252,17 @@ add_task(function* test_webnav_unresolve
   info("WebNavigation event filters test scenarios completed.");
 
   yield extension.unload();
 
   win.close();
 });
 
 add_task(function* test_webnav_empty_filter_validation_error() {
-  function backgroundScript() {
+  function background() {
     let catchedException;
 
     try {
       browser.webNavigation.onCompleted.addListener(
         // Empty callback (not really used)
         () => {},
         // Empty filter (which should raise a validation error exception).
         {url: []}
@@ -282,17 +282,17 @@ add_task(function* test_webnav_empty_fil
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       permissions: [
         "webNavigation",
       ],
     },
-    background: "new " + backgroundScript,
+    background,
   });
 
   yield extension.startup();
 
   yield extension.awaitFinish("webNav.emptyFilterValidationError");
 
   yield extension.unload();
 });
--- a/toolkit/components/jsdownloads/src/DownloadIntegration.jsm
+++ b/toolkit/components/jsdownloads/src/DownloadIntegration.jsm
@@ -516,22 +516,16 @@ this.DownloadIntegration = {
    *           {
    *             shouldBlock: Whether the download should be blocked.
    *             verdict: Detailed reason for the block, according to the
    *                      "Downloads.Error.BLOCK_VERDICT_" constants, or empty
    *                      string if the reason is unknown.
    *           }
    */
   shouldBlockForReputationCheck(aDownload) {
-#ifndef MOZ_URL_CLASSIFIER
-    return Promise.resolve({
-      shouldBlock: false,
-      verdict: "",
-    });
-#else
     let hash;
     let sigInfo;
     let channelRedirects;
     try {
       hash = aDownload.saver.getSha256Hash();
       sigInfo = aDownload.saver.getSignatureInfo();
       channelRedirects = aDownload.saver.getRedirects();
     } catch (ex) {
@@ -562,17 +556,16 @@ this.DownloadIntegration = {
       redirects: channelRedirects },
       function onComplete(aShouldBlock, aRv, aVerdict) {
         deferred.resolve({
           shouldBlock: aShouldBlock,
           verdict: (aShouldBlock && kVerdictMap[aVerdict]) || "",
         });
       });
     return deferred.promise;
-#endif
   },
 
 #ifdef XP_WIN
   /**
    * Checks whether downloaded files should be marked as coming from
    * Internet Zone.
    *
    * @return true if files should be marked
--- a/toolkit/components/moz.build
+++ b/toolkit/components/moz.build
@@ -55,16 +55,17 @@ DIRS += [
     'startup',
     'statusfilter',
     'telemetry',
     'thumbnails',
     'timermanager',
     'tooltiptext',
     'typeaheadfind',
     'utils',
+    'url-classifier',
     'urlformatter',
     'viewconfig',
     'workerloader',
     'xulstore'
 ]
 
 if CONFIG['MOZ_BUILD_APP'] != 'mobile/android':
     DIRS += ['narrate', 'viewsource'];
@@ -82,19 +83,16 @@ if CONFIG['MOZ_FEEDS']:
     DIRS += ['feeds']
 
 if CONFIG['MOZ_XUL']:
     DIRS += ['autocomplete', 'satchel']
 
 if CONFIG['MOZ_TOOLKIT_SEARCH']:
     DIRS += ['search']
 
-if CONFIG['MOZ_URL_CLASSIFIER']:
-    DIRS += ['url-classifier']
-
 DIRS += ['captivedetect']
 
 if CONFIG['OS_TARGET'] != 'Android':
     DIRS += ['terminator']
 
 DIRS += ['build']
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android':
--- a/toolkit/components/passwordmgr/LoginManagerContent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerContent.jsm
@@ -913,25 +913,25 @@ var LoginManagerContent = {
     let mockPassword = { name: newPasswordField.name,
                          value: newPasswordField.value };
     let mockOldPassword = oldPasswordField ?
                             { name: oldPasswordField.name,
                               value: oldPasswordField.value } :
                             null;
 
     // Make sure to pass the opener's top in case it was in a frame.
-    let opener = win.opener ? win.opener.top : null;
+    let openerTopWindow = win.opener ? win.opener.top : null;
 
     messageManager.sendAsyncMessage("RemoteLogins:onFormSubmit",
                                     { hostname: hostname,
                                       formSubmitURL: formSubmitURL,
                                       usernameField: mockUsername,
                                       newPasswordField: mockPassword,
                                       oldPasswordField: mockOldPassword },
-                                    { openerWin: opener });
+                                    { openerTopWindow });
   },
 
   /**
    * Attempt to find the username and password fields in a form, and fill them
    * in using the provided logins and recipes.
    *
    * @param {HTMLFormElement} form
    * @param {bool} autofillForm denotes if we should fill the form in automatically
--- a/toolkit/components/passwordmgr/LoginManagerParent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerParent.jsm
@@ -79,17 +79,17 @@ var LoginManagerParent = {
 
       case "RemoteLogins:onFormSubmit": {
         // TODO Verify msg.target's principals against the formOrigin?
         this.onFormSubmit(data.hostname,
                           data.formSubmitURL,
                           data.usernameField,
                           data.newPasswordField,
                           data.oldPasswordField,
-                          msg.objects.openerWin,
+                          msg.objects.openerTopWindow,
                           msg.target);
         break;
       }
 
       case "RemoteLogins:updateLoginFormPresence": {
         this.updateLoginFormPresence(msg.target, data);
         break;
       }
@@ -282,31 +282,24 @@ var LoginManagerParent = {
     target.messageManager.sendAsyncMessage("RemoteLogins:loginsAutoCompleted", {
       requestId: requestId,
       logins: jsLogins,
     });
   },
 
   onFormSubmit: function(hostname, formSubmitURL,
                          usernameField, newPasswordField,
-                         oldPasswordField, opener,
+                         oldPasswordField, openerTopWindow,
                          target) {
     function getPrompter() {
       var prompterSvc = Cc["@mozilla.org/login-manager/prompter;1"].
                         createInstance(Ci.nsILoginManagerPrompter);
-      // XXX For E10S, we don't want to use the browser's contentWindow
-      // because it's in another process, so we use our chrome window as
-      // the window parent (the content process is responsible for
-      // making sure that its window is not in private browsing mode).
-      // In the same-process case, we can simply use the content window.
-      prompterSvc.init(target.isRemoteBrowser ?
-                          target.ownerDocument.defaultView :
-                          target.contentWindow);
-      if (target.isRemoteBrowser)
-        prompterSvc.setE10sData(target, opener);
+      prompterSvc.init(target.ownerDocument.defaultView);
+      prompterSvc.browser = target;
+      prompterSvc.opener = openerTopWindow;
       return prompterSvc;
     }
 
     function recordLoginUse(login) {
       // Update the lastUsed timestamp and increment the use count.
       let propBag = Cc["@mozilla.org/hash-property-bag;1"].
                     createInstance(Ci.nsIWritablePropertyBag);
       propBag.setProperty("timeLastUsed", Date.now());
--- a/toolkit/components/passwordmgr/nsILoginManagerPrompter.idl
+++ b/toolkit/components/passwordmgr/nsILoginManagerPrompter.idl
@@ -14,28 +14,36 @@ interface nsILoginManagerPrompter : nsIS
   /**
    * Initialize the prompter. Must be called before using other interfaces.
    *
    * @param aWindow
    *        The in which the user is doing some login-related action that's
    *        resulting in a need to prompt them for something. The prompt
    *        will be associated with this window (or, if a notification bar
    *        is being used, topmost opener in some cases).
+   *
+   *        If this window is a content window, the corresponding window and browser
+   *        elements will be calculated. If this window is a chrome window, the
+   *        corresponding browser element needs to be set using setBrowser.
    */
   void init(in nsIDOMWindow aWindow);
 
   /**
-   * If the caller knows which browser this prompter is being created for,
-   * they can call this function to avoid having to calculate it from the
-   * window passed to init.
-   *
-   * @param aBrowser the <browser> to use for this prompter.
-   * @param aOpener the opener to use for this prompter.
+   * The browser this prompter is being created for.
+   * This is required if the init function received a chrome window as argument.
    */
-  void setE10sData(in nsIDOMElement aBrowser, in nsIDOMWindow aOpener);
+  attribute nsIDOMElement browser;
+
+  /**
+   * The opener that was used to open the window passed to init.
+   * The opener can be used to determine in which window the prompt
+   * should be shown. Must be a content window that is not a frame window,
+   * make sure to pass the top window using e.g. window.top.
+   */
+  attribute nsIDOMWindow opener;
 
   /**
    * Ask the user if they want to save a login (Yes, Never, Not Now)
    *
    * @param aLogin
    *        The login to be saved.
    */
   void promptToSavePassword(in nsILoginInfo aLogin);
--- a/toolkit/components/passwordmgr/nsLoginManagerPrompter.js
+++ b/toolkit/components/passwordmgr/nsLoginManagerPrompter.js
@@ -252,18 +252,18 @@ LoginManagerPrompter.prototype = {
       } catch (e) { }
     }
     return this.__ellipsis;
   },
 
 
   // Whether we are in private browsing mode
   get _inPrivateBrowsing() {
-    if (this._window) {
-      return PrivateBrowsingUtils.isContentWindowPrivate(this._window);
+    if (this._chromeWindow) {
+      return PrivateBrowsingUtils.isWindowPrivate(this._chromeWindow);
     }
     // If we don't that we're in private browsing mode if the caller did
     // not provide a window.  The callers which really care about this
     // will indeed pass down a window to us, and for those who don't,
     // we can just assume that we don't want to save the entered login
     // information.
     return true;
   },
@@ -285,17 +285,17 @@ LoginManagerPrompter.prototype = {
                                      Cr.NS_ERROR_NOT_IMPLEMENTED);
 
     this.log("===== prompt() called =====");
 
     if (aDefaultText) {
       aResult.value = aDefaultText;
     }
 
-    return this._promptService.prompt(this._window,
+    return this._promptService.prompt(this._chromeWindow,
            aDialogTitle, aText, aResult, null, {});
   },
 
 
   /**
    * Looks up a username and password in the database. Will prompt the user
    * with a dialog, even if a username and password are found.
    */
@@ -347,17 +347,17 @@ LoginManagerPrompter.prototype = {
           aUsername.value = selectedLogin.username;
           // If the caller provided a password, prefer it.
           if (!aPassword.value)
             aPassword.value = selectedLogin.password;
         }
       }
     }
 
-    var ok = this._promptService.promptUsernameAndPassword(this._window,
+    var ok = this._promptService.promptUsernameAndPassword(this._chromeWindow,
                 aDialogTitle, aText, aUsername, aPassword,
                 checkBoxLabel, checkBox);
 
     if (!ok || !checkBox.value || !hostname)
       return ok;
 
     if (!aPassword.value) {
       this.log("No password entered, so won't offer to save.");
@@ -438,17 +438,17 @@ LoginManagerPrompter.prototype = {
             aPassword.value = foundLogins[i].password;
             // wallet returned straight away, so this mimics that code
             return true;
           }
         }
       }
     }
 
-    var ok = this._promptService.promptPassword(this._window, aDialogTitle,
+    var ok = this._promptService.promptPassword(this._chromeWindow, aDialogTitle,
                                                 aText, aPassword,
                                                 checkBoxLabel, checkBox);
 
     if (ok && checkBox.value && hostname && aPassword.value) {
       var newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].
                      createInstance(Ci.nsILoginInfo);
       newLogin.init(hostname, null, realm, username,
                     aPassword.value, "", "");
@@ -558,19 +558,19 @@ LoginManagerPrompter.prototype = {
       // Ignore any errors and display the prompt anyway.
       epicfail = true;
       Components.utils.reportError("LoginManagerPrompter: " +
           "Epic fail in promptAuth: " + e + "\n");
     }
 
     var ok = canAutologin;
     if (!ok) {
-      if (this._window)
-        PromptUtils.fireDialogEvent(this._window, "DOMWillOpenModalDialog", this._browser);
-      ok = this._promptService.promptAuth(this._window,
+      if (this._chromeWindow)
+        PromptUtils.fireDialogEvent(this._chromeWindow, "DOMWillOpenModalDialog", this._browser);
+      ok = this._promptService.promptAuth(this._chromeWindow,
                                           aChannel, aLevel, aAuthInfo,
                                           checkboxLabel, checkbox);
     }
 
     // If there's a notification box, use it to allow the user to
     // determine if the login should be saved. If there isn't a
     // notification box, only save the login if the user set the
     // checkbox to do so.
@@ -674,30 +674,37 @@ LoginManagerPrompter.prototype = {
   },
 
 
 
 
   /* ---------- nsILoginManagerPrompter prompts ---------- */
 
 
-
   init : function (aWindow, aFactory) {
-    this._window = aWindow;
+    if (aWindow instanceof Ci.nsIDOMChromeWindow) {
+      this._chromeWindow = aWindow;
+      // needs to be set explicitly using setBrowser
+      this._browser = null;
+    } else {
+      let {win, browser} = this._getChromeWindow(aWindow);
+      this._chromeWindow = win;
+      this._browser = browser;
+    }
+    this._opener = null;
     this._factory = aFactory || null;
-    this._browser = null;
-    this._opener = null;
 
     this.log("===== initialized =====");
   },
 
-  setE10sData : function (aBrowser, aOpener) {
-    if (!(this._window instanceof Ci.nsIDOMChromeWindow))
-      throw new Error("Unexpected call");
+  set browser(aBrowser) {
     this._browser = aBrowser;
+  },
+
+  set opener(aOpener) {
     this._opener = aOpener;
   },
 
   promptToSavePassword : function (aLogin) {
     this.log("promptToSavePassword");
     var notifyObj = this._getPopupNote() || this._getNotifyBox();
     if (notifyObj)
       this._showSaveLoginNotification(notifyObj, aLogin);
@@ -1116,17 +1123,17 @@ LoginManagerPrompter.prototype = {
     var neverButtonText    = this._getLocalizedString(
                                     "neverForSiteButtonText");
     var rememberButtonText = this._getLocalizedString(
                                     "rememberButtonText");
     var notNowButtonText   = this._getLocalizedString(
                                     "notNowButtonText");
 
     this.log("Prompting user to save/ignore login");
-    var userChoice = this._promptService.confirmEx(this._window,
+    var userChoice = this._promptService.confirmEx(this._chromeWindow,
                                         dialogTitle, dialogText,
                                         buttonFlags, rememberButtonText,
                                         notNowButtonText, neverButtonText,
                                         null, {});
     //  Returns:
     //   0 - Save the login
     //   1 - Ignore the login this time
     //   2 - Never save logins for this site
@@ -1248,17 +1255,17 @@ LoginManagerPrompter.prototype = {
     else
       dialogText  = this._getLocalizedString(
                               "updatePasswordMsgNoUser");
 
     var dialogTitle = this._getLocalizedString(
                                 "passwordChangeTitle");
 
     // returns 0 for yes, 1 for no.
-    var ok = !this._promptService.confirmEx(this._window,
+    var ok = !this._promptService.confirmEx(this._chromeWindow,
                             dialogTitle, dialogText, buttonFlags,
                             null, null, null,
                             null, {});
     if (ok) {
       this.log("Updating password for user " + aOldLogin.username);
       this._updateLogin(aOldLogin, aNewLogin);
     }
   },
@@ -1281,17 +1288,17 @@ LoginManagerPrompter.prototype = {
 
     var usernames = logins.map(l => l.username);
     var dialogText  = this._getLocalizedString("userSelectText");
     var dialogTitle = this._getLocalizedString("passwordChangeTitle");
     var selectedIndex = { value: null };
 
     // If user selects ok, outparam.value is set to the index
     // of the selected username.
-    var ok = this._promptService.select(this._window,
+    var ok = this._promptService.select(this._chromeWindow,
                             dialogTitle, dialogText,
                             usernames.length, usernames,
                             selectedIndex);
     if (ok) {
       // Now that we know which login to use, modify its password.
       var selectedLogin = logins[selectedIndex.value];
       this.log("Updating password for user " + selectedLogin.username);
       var newLoginWithUsername = Cc["@mozilla.org/login-manager/loginInfo;1"].
@@ -1326,135 +1333,83 @@ LoginManagerPrompter.prototype = {
       // value as timeLastUsed.
       propBag.setProperty("timePasswordChanged", now);
     }
     propBag.setProperty("timeLastUsed", now);
     propBag.setProperty("timesUsedIncrement", 1);
     this._pwmgr.modifyLogin(login, propBag);
   },
 
-
   /**
-   * Given a content DOM window, returns the chrome window it's in.
+   * Given a content DOM window, returns the chrome window and browser it's in.
    */
   _getChromeWindow: function (aWindow) {
-    // In e10s, aWindow may already be a chrome window.
-    if (aWindow instanceof Ci.nsIDOMChromeWindow)
-      return aWindow;
-    var chromeWin = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
-                           .getInterface(Ci.nsIWebNavigation)
-                           .QueryInterface(Ci.nsIDocShell)
-                           .chromeEventHandler.ownerDocument.defaultView;
-    return chromeWin;
+    let windows = Services.wm.getEnumerator(null);
+    while (windows.hasMoreElements()) {
+      let win = windows.getNext();
+      let browser = win.gBrowser.getBrowserForContentWindow(aWindow);
+      if (browser) {
+        return { win, browser };
+      }
+    }
+    return null;
   },
 
-
   _getNotifyWindow: function () {
-
-    try {
-      // Get topmost window, in case we're in a frame.
-      var notifyWin = this._window.top;
-      var isE10s = (notifyWin instanceof Ci.nsIDOMChromeWindow);
-      var useOpener = false;
-
-      // Some sites pop up a temporary login window, which disappears
-      // upon submission of credentials. We want to put the notification
-      // bar in the opener window if this seems to be happening.
-      if (notifyWin.opener) {
-        var chromeDoc = this._getChromeWindow(notifyWin).
-                             document.documentElement;
-
-        var hasHistory;
-        if (isE10s) {
-          if (!this._browser)
-            throw new Error("Expected a browser in e10s");
-          hasHistory = this._browser.canGoBack;
-        } else {
-          var webnav = notifyWin.
-                       QueryInterface(Ci.nsIInterfaceRequestor).
-                       getInterface(Ci.nsIWebNavigation);
-          hasHistory = webnav.sessionHistory.count > 1;
-        }
+    // Some sites pop up a temporary login window, which disappears
+    // upon submission of credentials. We want to put the notification
+    // bar in the opener window if this seems to be happening.
+    if (this._opener) {
+      let chromeDoc = this._chromeWindow.document.documentElement;
 
-        // Check to see if the current window was opened with chrome
-        // disabled, and if so use the opener window. But if the window
-        // has been used to visit other pages (ie, has a history),
-        // assume it'll stick around and *don't* use the opener.
-        if (chromeDoc.getAttribute("chromehidden") && !hasHistory) {
-          this.log("Using opener window for notification bar.");
-          notifyWin = notifyWin.opener;
-          useOpener = true;
-        }
+      // Check to see if the current window was opened with chrome
+      // disabled, and if so use the opener window. But if the window
+      // has been used to visit other pages (ie, has a history),
+      // assume it'll stick around and *don't* use the opener.
+      if (chromeDoc.getAttribute("chromehidden") && !this._browser.canGoBack) {
+        this.log("Using opener window for notification bar.");
+        return this._getChromeWindow(this._opener);
       }
-
-      let browser;
-      if (useOpener && this._opener && isE10s) {
-        // In e10s, we have to reconstruct the opener browser from
-        // the CPOW passed in the message (and then passed to us in
-        // setE10sData).
-        // NB: notifyWin is now the chrome window for the opening
-        // window.
+    }
 
-        browser = notifyWin.gBrowser.getBrowserForContentWindow(this._opener);
-      } else if (isE10s) {
-        browser = this._browser;
-      } else {
-        var chromeWin = this._getChromeWindow(notifyWin).wrappedJSObject;
-        browser = chromeWin.gBrowser
-                           .getBrowserForDocument(notifyWin.top.document);
-      }
-
-      return { notifyWin: notifyWin, browser: browser };
-
-    } catch (e) {
-      // If any errors happen, just assume no notification box.
-      this.log("Unable to get notify window: " + e.fileName + ":" + e.lineNumber + ": " + e.message);
-      return null;
-    }
+    return { win: this._chromeWindow, browser: this._browser };
   },
 
-
   /**
    * Returns the popup notification to this prompter,
    * or null if there isn't one available.
    */
   _getPopupNote : function () {
     let popupNote = null;
 
     try {
-      let { notifyWin } = this._getNotifyWindow();
+      let { win: notifyWin } = this._getNotifyWindow();
 
-      // Get the chrome window for the content window we're using.
       // .wrappedJSObject needed here -- see bug 422974 comment 5.
-      let chromeWin = this._getChromeWindow(notifyWin).wrappedJSObject;
-
-      popupNote = chromeWin.PopupNotifications;
+      popupNote = notifyWin.wrappedJSObject.PopupNotifications;
     } catch (e) {
       this.log("Popup notifications not available on window");
     }
 
     return popupNote;
   },
 
 
   /**
    * Returns the notification box to this prompter, or null if there isn't
    * a notification box available.
    */
   _getNotifyBox : function () {
     let notifyBox = null;
 
     try {
-      let { notifyWin } = this._getNotifyWindow();
+      let { win: notifyWin } = this._getNotifyWindow();
 
-      // Get the chrome window for the content window we're using.
       // .wrappedJSObject needed here -- see bug 422974 comment 5.
-      let chromeWin = this._getChromeWindow(notifyWin).wrappedJSObject;
-
-      notifyBox = chromeWin.getNotificationBox(notifyWin);
+      notifyBox = notifyWin.wrappedJSObject.getNotificationBox(notifyWin);
     } catch (e) {
       this.log("Notification bars not available on window");
     }
 
     return notifyBox;
   },
 
 
--- a/toolkit/components/passwordmgr/test/browser/browser.ini
+++ b/toolkit/components/passwordmgr/test/browser/browser.ini
@@ -25,17 +25,16 @@ support-files =
   subtst_notifications_4.html
   subtst_notifications_5.html
   subtst_notifications_6.html
   subtst_notifications_8.html
   subtst_notifications_9.html
   subtst_notifications_10.html
   subtst_notifications_change_p.html
 [browser_capture_doorhanger_window_open.js]
-skip-if = e10s # Bug 1266836 - Prompt code is broken with popups in e10s
 support-files =
   subtst_notifications_11.html
   subtst_notifications_11_popup.html
 [browser_username_select_dialog.js]
 support-files =
   subtst_notifications_change_p.html
 [browser_DOMFormHasPassword.js]
 [browser_DOMInputPasswordAdded.js]
--- a/toolkit/components/perfmonitoring/tests/browser/browser_addonPerformanceAlerts.js
+++ b/toolkit/components/perfmonitoring/tests/browser/browser_addonPerformanceAlerts.js
@@ -50,17 +50,17 @@ add_task(function* test_install_addon_th
     yield new Promise(resolve => setTimeout(resolve, 100));
     yield addon.run(topic, 10, realListener);
     // Waiting a little – listeners are buffered.
     yield new Promise(resolve => setTimeout(resolve, 100));
 
     Assert.ok(realListener.triggered, `1. The real listener was triggered ${topic}`);
     Assert.ok(universalListener.triggered, `1. The universal listener was triggered ${topic}`);
     Assert.ok(!fakeListener.triggered, `1. The fake listener was not triggered ${topic}`);
-    Assert.ok(realListener.result >= 200000, `1. jank is at least 300ms (${realListener.result/1000}ms) ${topic}`);
+    Assert.ok(realListener.result >= addon.jankThreshold, `1. jank is at least ${addon.jankThreshold/1000}ms (${realListener.result/1000}ms) ${topic}`);
 
     info(`Attempting to remove a performance listener incorrectly, check that this does not hurt our real listener ${topic}`);
     Assert.throws(() => PerformanceWatcher.removePerformanceListener({addonId: addon.addonId}, () => {}));
     Assert.throws(() => PerformanceWatcher.removePerformanceListener({addonId: addon.addonId + "-unbound-id-" + Math.random()}, realListener.listener));
 
     yield addon.run(topic, 10, realListener);
     // Waiting a little – listeners are buffered.
     yield new Promise(resolve => setTimeout(resolve, 300));
--- a/toolkit/components/perfmonitoring/tests/browser/browser_addonPerformanceAlerts_2.js
+++ b/toolkit/components/perfmonitoring/tests/browser/browser_addonPerformanceAlerts_2.js
@@ -13,13 +13,13 @@ add_task(function* test_watch_addon_then
       }
       throw new Error(`I shouldn't have been called with addon ${group.addonId}`);
     });
 
     info("Now install the add-on, *after* having installed the listener");
     let addon = new AddonBurner(addonId);
 
     Assert.ok((yield addon.run(topic, 10, realListener)), `5. The real listener was triggered ${topic}`);
-    Assert.ok(realListener.result >= 200000, `5. jank is at least 200ms (${realListener.result}µs) ${topic}`);
+    Assert.ok(realListener.result >= addon.jankThreshold, `5. jank is at least ${addon.jankThreshold/1000}ms (${realListener.result}µs) ${topic}`);
     realListener.unregister();
     addon.dispose();
   }
 });
--- a/toolkit/components/perfmonitoring/tests/browser/browser_webpagePerformanceAlerts.js
+++ b/toolkit/components/perfmonitoring/tests/browser/browser_webpagePerformanceAlerts.js
@@ -3,17 +3,17 @@
 /**
  * Tests for PerformanceWatcher watching slow web pages.
  */
 
  /**
   * Simulate a slow webpage.
   */
 function WebpageBurner() {
-  CPUBurner.call(this, "http://example.com/browser/toolkit/components/perfmonitoring/tests/browser/browser_compartments.html?test=" + Math.random());
+  CPUBurner.call(this, "http://example.com/browser/toolkit/components/perfmonitoring/tests/browser/browser_compartments.html?test=" + Math.random(), 300000);
 }
 WebpageBurner.prototype = Object.create(CPUBurner.prototype);
 WebpageBurner.prototype.promiseBurnContentCPU = function() {
   return promiseContentResponse(this._browser, "test-performance-watcher:burn-content-cpu", {});
 };
 
 function WebpageListener(windowId, accept) {
   info(`Creating WebpageListener for ${windowId}`);
--- a/toolkit/components/perfmonitoring/tests/browser/head.js
+++ b/toolkit/components/perfmonitoring/tests/browser/head.js
@@ -3,46 +3,48 @@
 
 var { utils: Cu, interfaces: Ci, classes: Cc } = Components;
 
 Cu.import("resource://gre/modules/Promise.jsm", this);
 Cu.import("resource://gre/modules/AddonManager.jsm", this);
 Cu.import("resource://gre/modules/AddonWatcher.jsm", this);
 Cu.import("resource://gre/modules/PerformanceWatcher.jsm", this);
 Cu.import("resource://gre/modules/Services.jsm", this);
+Cu.import("resource://testing-common/ContentTaskUtils.jsm", this);
 
 /**
  * Base class for simulating slow addons/webpages.
  */
-function CPUBurner(url) {
+function CPUBurner(url, jankThreshold) {
   info(`CPUBurner: Opening tab for ${url}\n`);
   this.url = url;
   this.tab = gBrowser.addTab(url);
+  this.jankThreshold = jankThreshold;
   let browser = this.tab.linkedBrowser;
   this._browser = browser;
   ContentTask.spawn(this._browser, null, CPUBurner.frameScript);
   this.promiseInitialized = BrowserTestUtils.browserLoaded(browser);
 }
 CPUBurner.prototype = {
   get windowId() {
     return this._browser.outerWindowID;
   },
   /**
-   * Burn CPU until it triggers a listener.
+   * Burn CPU until it triggers a listener with the specified jank threshold.
    */
   run: Task.async(function*(burner, max, listener) {
     listener.reset();
     for (let i = 0; i < max; ++i) {
       yield new Promise(resolve => setTimeout(resolve, 50));
       try {
         yield this[burner]();
       } catch (ex) {
         return false;
       }
-      if (listener.triggered) {
+      if (listener.triggered && listener.result >= this.jankThreshold) {
         return true;
       }
     }
     return false;
   }),
   dispose: function() {
     info(`CPUBurner: Closing tab for ${this.url}\n`);
     gBrowser.removeTab(this.tab);
@@ -156,17 +158,18 @@ AlertListener.prototype = {
     this.result = null;
   },
 };
 
 /**
  * Simulate a slow add-on.
  */
 function AddonBurner(addonId = "fake add-on id: " + Math.random()) {
-  CPUBurner.call(this, `http://example.com/?uri=${addonId}`)
+  this.jankThreshold = 200000;
+  CPUBurner.call(this, `http://example.com/?uri=${addonId}`, this.jankThreshold);
   this._addonId = addonId;
   this._sandbox = Components.utils.Sandbox(Services.scriptSecurityManager.getSystemPrincipal(), { addonId: this._addonId });
   this._CPOWBurner = null;
 
   this._promiseCPOWBurner = new Promise(resolve => {
     this._browser.messageManager.addMessageListener("test-performance-watcher:cpow-init", msg => {
       // Note that we cannot resolve Promises with CPOWs now that they
       // have been outlawed in bug 1233497, so we stash it in the
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -8204,52 +8204,16 @@
     "n_buckets": 20,
     "description": "Tracking how ServiceWorkerRegistrar loads data before the first content is shown. File bugs in Core::DOM in case of a Telemetry regression."
   },
   "SERVICE_WORKER_REQUEST_PASSTHROUGH": {
     "expires_in_version": "50",
     "kind": "boolean",
     "description": "Intercepted fetch sending back same Request object. File bugs in Core::DOM in case of a Telemetry regression."
   },
-  "LOOP_CLIENT_CALL_URL_REQUESTS_SUCCESS": {
-    "expires_in_version": "never",
-    "kind": "boolean",
-    "description": "Stores 1 if generating a call URL succeeded, and 0 if it failed."
-  },
-  "LOOP_CLIENT_CALL_URL_SHARED": {
-    "expires_in_version": "never",
-    "kind": "boolean",
-    "description": "Stores 1 every time the URL is copied or shared."
-  },
-  "LOOP_ROOM_CREATE": {
-    "alert_emails": ["firefox-dev@mozilla.org", "mdeboer@mozilla.com"],
-    "expires_in_version": "54",
-    "kind": "enumerated",
-    "n_values": 4,
-    "releaseChannelCollection": "opt-out",
-    "description": "Number of times a room create action is performed (0=CREATE_SUCCESS, 1=CREATE_FAIL)"
-  },
-  "LOOP_COPY_PANEL_ACTIONS": {
-    "alert_emails": ["firefox-dev@mozilla.org", "edilee@mozilla.com"],
-    "expires_in_version": "54",
-    "kind": "enumerated",
-    "n_values": 5,
-    "releaseChannelCollection": "opt-out",
-    "bug_numbers": [1239965, 1259506],
-    "description": "Number of times each of the following copy panel actions are triggered: 0=SHOWN, 1=NO_AGAIN, 2=NO_NEVER, 3=YES_AGAIN, 4=YES_NEVER"
-  },
-  "LOOP_ACTIVITY_COUNTER": {
-    "alert_emails": ["firefox-dev@mozilla.org", "mbanner@mozilla.com"],
-    "expires_in_version": "54",
-    "kind": "enumerated",
-    "n_values": 5,
-    "releaseChannelCollection": "opt-out",
-    "bug_numbers": [1208416],
-    "description": "Number of times an action is performed by an user (0=OPEN_PANEL, 1=OPEN_CONVERSATION, 2=ROOM_OPEN, 3=ROOM_SHARE, 4=ROOM_DELETE)"
-  },
   "E10S_STATUS": {
     "alert_emails": ["firefox-dev@mozilla.org"],
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 12,
     "releaseChannelCollection": "opt-out",
     "bug_numbers": [1241294],
     "description": "Why e10s is enabled or disabled (0=ENABLED_BY_USER, 1=ENABLED_BY_DEFAULT, 2=DISABLED_BY_USER, 3=DISABLED_IN_SAFE_MODE, 4=DISABLED_FOR_ACCESSIBILITY, 5=DISABLED_FOR_MAC_GFX, 6=DISABLED_FOR_BIDI, 7=DISABLED_FOR_ADDONS, 8=FORCE_DISABLED, 9=DISABLED_FOR_XPLAYERS, 10=DISABLED_FOR_OS_VERSION)"
--- a/toolkit/components/telemetry/histogram-whitelists.json
+++ b/toolkit/components/telemetry/histogram-whitelists.json
@@ -402,18 +402,16 @@
     "LOCALDOMSTORAGE_SHUTDOWN_DATABASE_MS",
     "LOCALDOMSTORAGE_UNLOAD_BLOCKING_MS",
     "LONG_REFLOW_INTERRUPTIBLE",
     "LOOP_AUDIO_QUALITY_INBOUND_BANDWIDTH_KBITS",
     "LOOP_AUDIO_QUALITY_OUTBOUND_BANDWIDTH_KBITS",
     "LOOP_AUDIO_QUALITY_OUTBOUND_RTT",
     "LOOP_CALL_DURATION",
     "LOOP_CALL_TYPE",
-    "LOOP_CLIENT_CALL_URL_REQUESTS_SUCCESS",
-    "LOOP_CLIENT_CALL_URL_SHARED",
     "LOOP_DATACHANNEL_NEGOTIATED",
     "LOOP_GET_USER_MEDIA_TYPE",
     "LOOP_ICE_ADD_CANDIDATE_ERRORS_GIVEN_FAILURE",
     "LOOP_ICE_ADD_CANDIDATE_ERRORS_GIVEN_SUCCESS",
     "LOOP_ICE_FAILURE_TIME",
     "LOOP_ICE_FINAL_CONNECTION_STATE",
     "LOOP_ICE_LATE_TRICKLE_ARRIVAL_TIME",
     "LOOP_ICE_ON_TIME_TRICKLE_ARRIVAL_TIME",
@@ -1297,34 +1295,31 @@
     "LOCALDOMSTORAGE_SHUTDOWN_DATABASE_MS",
     "LOCALDOMSTORAGE_UNLOAD_BLOCKING_MS",
     "LONG_REFLOW_INTERRUPTIBLE",
     "LOOP_AUDIO_QUALITY_INBOUND_BANDWIDTH_KBITS",
     "LOOP_AUDIO_QUALITY_OUTBOUND_BANDWIDTH_KBITS",
     "LOOP_AUDIO_QUALITY_OUTBOUND_RTT",
     "LOOP_CALL_DURATION",
     "LOOP_CALL_TYPE",
-    "LOOP_CLIENT_CALL_URL_REQUESTS_SUCCESS",
-    "LOOP_CLIENT_CALL_URL_SHARED",
     "LOOP_DATACHANNEL_NEGOTIATED",
     "LOOP_GET_USER_MEDIA_TYPE",
     "LOOP_ICE_ADD_CANDIDATE_ERRORS_GIVEN_FAILURE",
     "LOOP_ICE_ADD_CANDIDATE_ERRORS_GIVEN_SUCCESS",
     "LOOP_ICE_FAILURE_TIME",
     "LOOP_ICE_FINAL_CONNECTION_STATE",
     "LOOP_ICE_LATE_TRICKLE_ARRIVAL_TIME",
     "LOOP_ICE_ON_TIME_TRICKLE_ARRIVAL_TIME",
     "LOOP_ICE_SUCCESS_RATE",
     "LOOP_ICE_SUCCESS_TIME",
     "LOOP_MAX_AUDIO_RECEIVE_TRACK",
     "LOOP_MAX_AUDIO_SEND_TRACK",
     "LOOP_MAX_VIDEO_RECEIVE_TRACK",
     "LOOP_MAX_VIDEO_SEND_TRACK",
     "LOOP_RENEGOTIATIONS",
-    "LOOP_ROOM_CREATE",
     "LOOP_VIDEO_DECODER_BITRATE_AVG_PER_CALL_KBPS",
     "LOOP_VIDEO_DECODER_BITRATE_STD_DEV_PER_CALL_KBPS",
     "LOOP_VIDEO_DECODER_DISCARDED_PACKETS_PER_CALL_PPM",
     "LOOP_VIDEO_DECODER_FRAMERATE_10X_STD_DEV_PER_CALL",
     "LOOP_VIDEO_DECODER_FRAMERATE_AVG_PER_CALL",
     "LOOP_VIDEO_DECODE_ERROR_TIME_PERMILLE",
     "LOOP_VIDEO_ENCODER_BITRATE_AVG_PER_CALL_KBPS",
     "LOOP_VIDEO_ENCODER_BITRATE_STD_DEV_PER_CALL_KBPS",
--- a/toolkit/modules/AppConstants.jsm
+++ b/toolkit/modules/AppConstants.jsm
@@ -69,23 +69,16 @@ this.AppConstants = Object.freeze({
 
   MOZ_DATA_REPORTING:
 #ifdef MOZ_DATA_REPORTING
   true,
 #else
   false,
 #endif
 
-  MOZ_SAFE_BROWSING:
-#ifdef MOZ_SAFE_BROWSING
-  true,
-#else
-  false,
-#endif
-
   MOZ_SANDBOX:
 #ifdef MOZ_SANDBOX
   true,
 #else
   false,
 #endif
 
   MOZ_CONTENT_SANDBOX:
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -6487,18 +6487,18 @@ AddonInstall.prototype = {
       let win = this.window;
       if (!win && this.browser)
         win = this.browser.ownerDocument.defaultView;
 
       let factory = Cc["@mozilla.org/prompter;1"].
                     getService(Ci.nsIPromptFactory);
       let prompt = factory.getPrompt(win, Ci.nsIAuthPrompt2);
 
-      if (this.browser && this.browser.isRemoteBrowser && prompt instanceof Ci.nsILoginManagerPrompter)
-        prompt.setE10sData(this.browser, null);
+      if (this.browser && prompt instanceof Ci.nsILoginManagerPrompter)
+        prompt.browser = this.browser;
 
       return prompt;
     }
     else if (iid.equals(Ci.nsIChannelEventSink)) {
       return this;
     }
 
     return this.badCertHandler.getInterface(iid);
--- a/widget/gtk/nsClipboard.cpp
+++ b/widget/gtk/nsClipboard.cpp
@@ -1025,17 +1025,17 @@ wait_for_text(GtkClipboard *clipboard)
     return static_cast<gchar*>(context->Wait());
 }
 
 static GdkFilterReturn
 selection_request_filter(GdkXEvent *gdk_xevent, GdkEvent *event, gpointer data)
 {
     XEvent *xevent = static_cast<XEvent*>(gdk_xevent);
     if (xevent->xany.type == SelectionRequest) {
-        if (xevent->xselectionrequest.requestor == None)
+        if (xevent->xselectionrequest.requestor == X11None)
             return GDK_FILTER_REMOVE;
 
         GdkDisplay *display = gdk_x11_lookup_xdisplay(
                 xevent->xselectionrequest.display);
         if (!display)
             return GDK_FILTER_REMOVE;
 
         GdkWindow *window = gdk_x11_window_foreign_new_for_display(display,
--- a/widget/gtk/nsWindow.cpp
+++ b/widget/gtk/nsWindow.cpp
@@ -447,17 +447,17 @@ nsWindow::nsWindow()
     mSizeState           = nsSizeMode_Normal;
     mLastSizeMode        = nsSizeMode_Normal;
     mSizeConstraints.mMaxSize = GetSafeWindowSize(mSizeConstraints.mMaxSize);
 
 #ifdef MOZ_X11
     mOldFocusWindow      = 0;
 
     mXDisplay = nullptr;
-    mXWindow  = None;
+    mXWindow  = X11None;
     mXVisual  = nullptr;
     mXDepth   = 0;
 #endif /* MOZ_X11 */
     mPluginType          = PluginType_NONE;
 
     if (!gGlobalsInitialized) {
         gGlobalsInitialized = true;
 
@@ -4582,17 +4582,17 @@ nsWindow::ClearTransparencyBitmap()
 
 #ifdef MOZ_X11
     if (!mGdkWindow)
         return;
 
     Display* xDisplay = GDK_WINDOW_XDISPLAY(mGdkWindow);
     Window xWindow = gdk_x11_window_get_xid(mGdkWindow);
 
-    XShapeCombineMask(xDisplay, xWindow, ShapeBounding, 0, 0, None, ShapeSet);
+    XShapeCombineMask(xDisplay, xWindow, ShapeBounding, 0, 0, X11None, ShapeSet);
 #endif
 }
 
 nsresult
 nsWindow::UpdateTranslucentWindowAlphaInternal(const nsIntRect& aRect,
                                                uint8_t* aAlphas, int32_t aStride)
 {
     if (!mShell) {
--- a/widget/tests/test_keycodes.xul
+++ b/widget/tests/test_keycodes.xul
@@ -3239,26 +3239,26 @@ function* runXULKeyTests()
                           modifiers:{metaKey:1}, chars:"'", unmodifiedChars:"'"},
                          "reservedUnshiftedKey");
     yield testKeyElement({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Quote,
                           modifiers:{metaKey:1, shiftKey:1}, chars:"\"", unmodifiedChars:"'"},
                          "reservedShiftedKey");
   }
   else if (IS_WIN) {
     yield testKeyElement({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_1,
-                          modifiers:{ctrlKey:1}, chars:";"},
+                          modifiers:{ctrlKey:1}, chars:""},
                          "unshiftedKey");
     yield testKeyElement({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_1,
-                          modifiers:{ctrlKey:1, shiftKey:1}, chars:";"},
+                          modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
                          "shiftedKey");
     yield testKeyElement({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_7,
-                          modifiers:{ctrlKey:1}, chars:"'"},
+                          modifiers:{ctrlKey:1}, chars:""},
                          "reservedUnshiftedKey");
     yield testKeyElement({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_7,
-                          modifiers:{ctrlKey:1, shiftKey:1}, chars:"'"},
+                          modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
                          "reservedShiftedKey");
   }
 
   // 429160
   if (IS_MAC) {
     yield testKeyElement({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_F,
                           modifiers:{metaKey:1, altKey:1}, chars:"\u0192", unmodifiedChars:"f"},
                          "commandOptionF");
@@ -3434,17 +3434,17 @@ function* runReservedKeyTests()
 
   if (IS_MAC) {
     // Cmd+T is reserved for opening new tab.
     yield testReservedKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_T,
                            modifiers:{metaKey:1}, chars:"t", unmodifiedChars:"t"});
   } else if (IS_WIN) {
     // Ctrl+T is reserved for opening new tab.
     yield testReservedKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_T,
-                           modifiers:{ctrlKey:1}, chars:"t"});
+                           modifiers:{ctrlKey:1}, chars:"\u0014"});
   }
 
   finializeKeyElementTest();
 }
 
 function* runTextInputTests()
 {
   var textbox = document.getElementById("textbox");
--- a/widget/windows/KeyboardLayout.cpp
+++ b/widget/windows/KeyboardLayout.cpp
@@ -690,16 +690,17 @@ NativeKey::NativeKey(nsWindowBase* aWidg
   , mModKeyState(aModKeyState)
   , mVirtualKeyCode(0)
   , mOriginalVirtualKeyCode(0)
   , mShiftedLatinChar(0)
   , mUnshiftedLatinChar(0)
   , mScanCode(0)
   , mIsExtended(false)
   , mIsDeadKey(false)
+  , mIsFollowedByNonControlCharMessage(false)
   , mFakeCharMsgs(aFakeCharMsgs && aFakeCharMsgs->Length() ?
                     aFakeCharMsgs : nullptr)
 {
   MOZ_ASSERT(aWidget);
   MOZ_ASSERT(mDispatcher);
   KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance();
   mKeyboardLayout = keyboardLayout->GetLayout();
   if (aOverrideKeyboardLayout && mKeyboardLayout != aOverrideKeyboardLayout) {
@@ -874,28 +875,35 @@ NativeKey::NativeKey(nsWindowBase* aWidg
   }
 
   if (!mVirtualKeyCode) {
     mVirtualKeyCode = mOriginalVirtualKeyCode;
   }
 
   mDOMKeyCode =
     keyboardLayout->ConvertNativeKeyCodeToDOMKeyCode(mOriginalVirtualKeyCode);
-  mKeyNameIndex =
+  // Be aware, keyboard utilities can change non-printable keys to printable
+  // keys.  In such case, we should make the key value as a printable key.
+  mIsFollowedByNonControlCharMessage =
+    IsKeyDownMessage() && IsFollowedByNonControlCharMessage();
+  mKeyNameIndex = mIsFollowedByNonControlCharMessage ?
+    KEY_NAME_INDEX_USE_STRING :
     keyboardLayout->ConvertNativeKeyCodeToKeyNameIndex(mOriginalVirtualKeyCode);
   mCodeNameIndex =
     KeyboardLayout::ConvertScanCodeToCodeNameIndex(
       GetScanCodeWithExtendedFlag());
 
   keyboardLayout->InitNativeKey(*this, mModKeyState);
 
   mIsDeadKey =
     (IsFollowedByDeadCharMessage() ||
      keyboardLayout->IsDeadKey(mOriginalVirtualKeyCode, mModKeyState));
-  mIsPrintableKey = KeyboardLayout::IsPrintableCharKey(mOriginalVirtualKeyCode);
+  mIsPrintableKey =
+    mKeyNameIndex == KEY_NAME_INDEX_USE_STRING ||
+    KeyboardLayout::IsPrintableCharKey(mOriginalVirtualKeyCode);
 
   if (IsKeyDownMessage()) {
     // Compute some strings which may be inputted by the key with various
     // modifier state if this key event won't cause text input actually.
     // They will be used for setting mAlternativeCharCodes in the callback
     // method which will be called by TextEventDispatcher.
     if (NeedsToHandleWithoutFollowingCharMessages()) {
       ComputeInputtingStringWithKeyboardLayout();
@@ -1050,16 +1058,34 @@ NativeKey::IsFollowedByDeadCharMessage()
                                PM_NOREMOVE | PM_NOYIELD)) {
       return false;
     }
   }
   return IsDeadCharMessage(nextMsg);
 }
 
 bool
+NativeKey::IsFollowedByNonControlCharMessage() const
+{
+  MSG nextMsg;
+  if (mFakeCharMsgs) {
+    nextMsg = mFakeCharMsgs->ElementAt(0).GetCharMsg(mMsg.hwnd);
+  } else if (IsKeyMessageOnPlugin()) {
+    return false;
+  } else {
+    if (!WinUtils::PeekMessage(&nextMsg, mMsg.hwnd, WM_KEYFIRST, WM_KEYLAST,
+                               PM_NOREMOVE | PM_NOYIELD)) {
+      return false;
+    }
+  }
+  return nextMsg.message == WM_CHAR &&
+         !IsControlChar(static_cast<char16_t>(nextMsg.wParam));
+}
+
+bool
 NativeKey::IsIMEDoingKakuteiUndo() const
 {
   // Following message pattern is caused by "Kakutei-Undo" of ATOK or WXG:
   // ---------------------------------------------------------------------------
   // WM_KEYDOWN              * n (wParam = VK_BACK, lParam = 0x1)
   // WM_KEYUP                * 1 (wParam = VK_BACK, lParam = 0xC0000001) # ATOK
   // WM_IME_STARTCOMPOSITION * 1 (wParam = 0x0, lParam = 0x0)
   // WM_IME_COMPOSITION      * 1 (wParam = 0x0, lParam = 0x1BF)
@@ -1838,16 +1864,23 @@ NativeKey::NeedsToHandleWithoutFollowing
   }
 
   // If inputting two or more characters, should be dispatched after removing
   // whole following char messages.
   if (mCommittedCharsAndModifiers.mLength > 1) {
     return true;
   }
 
+  // If keydown message is followed by WM_CHAR whose wParam isn't a control
+  // character, we should dispatch keypress event with the char message
+  // even with any modifier state.
+  if (mIsFollowedByNonControlCharMessage) {
+    return false;
+  }
+
   // If any modifier keys which may cause printable keys becoming non-printable
   // are not pressed, we don't need special handling for the key.
   if (!mModKeyState.IsControl() && !mModKeyState.IsAlt() &&
       !mModKeyState.IsWin()) {
     return false;
   }
 
   // If the key event causes dead key event, we don't need to dispatch keypress
@@ -2517,16 +2550,43 @@ KeyboardLayout::IsDeadKey(uint8_t aVirtu
   if (virtualKeyIndex < 0) {
     return false;
   }
 
   return mVirtualKeys[virtualKeyIndex].IsDeadKey(
            VirtualKey::ModifiersToShiftState(aModKeyState.GetModifiers()));
 }
 
+bool
+KeyboardLayout::IsSysKey(uint8_t aVirtualKey,
+                         const ModifierKeyState& aModKeyState) const
+{
+  // If Alt key is not pressed, it's never a system key combination.
+  // Additionally, if Ctrl key is pressed, it's never a system key combination
+  // too.
+  if (!aModKeyState.IsAlt() || aModKeyState.IsControl()) {
+    return false;
+  }
+
+  int32_t virtualKeyIndex = GetKeyIndex(aVirtualKey);
+  if (virtualKeyIndex < 0) {
+    return true;
+  }
+
+  UniCharsAndModifiers inputCharsAndModifiers =
+    GetUniCharsAndModifiers(aVirtualKey, aModKeyState);
+  if (inputCharsAndModifiers.IsEmpty()) {
+    return true;
+  }
+
+  // If the Alt key state isn't consumed, that means that the key with Alt
+  // doesn't cause text input.  So, the combination is a system key.
+  return inputCharsAndModifiers.mModifiers[0] != MODIFIER_ALT;
+}
+
 void
 KeyboardLayout::InitNativeKey(NativeKey& aNativeKey,
                               const ModifierKeyState& aModKeyState)
 {
   if (mIsPendingToRestoreKeyboardLayout) {
     LoadLayout(::GetKeyboardLayout(0));
   }
 
@@ -3425,18 +3485,20 @@ KeyboardLayout::SynthesizeNativeKeyEvent
     UINT scanCode =
       ComputeScanCodeForVirtualKeyCode(keySpecific ? keySpecific : key);
     LPARAM lParam = static_cast<LPARAM>(scanCode << 16);
     // Add extended key flag to the lParam for right control key and right alt
     // key.
     if (keySpecific == VK_RCONTROL || keySpecific == VK_RMENU) {
       lParam |= 0x1000000;
     }
-    MSG keyDownMsg = WinUtils::InitMSG(WM_KEYDOWN, key, lParam,
-                                       aWidget->GetWindowHandle());
+    bool makeSysKeyMsg = IsSysKey(key, modKeyState);
+    MSG keyDownMsg =
+      WinUtils::InitMSG(makeSysKeyMsg ? WM_SYSKEYDOWN : WM_KEYDOWN,
+                        key, lParam, aWidget->GetWindowHandle());
     if (i == keySequence.Length() - 1) {
       bool makeDeadCharMsg =
         (IsDeadKey(key, modKeyState) && aCharacters.IsEmpty());
       nsAutoString chars(aCharacters);
       if (makeDeadCharMsg) {
         UniCharsAndModifiers deadChars =
           GetUniCharsAndModifiers(key, modKeyState);
         chars = deadChars.ToString();
@@ -3447,16 +3509,17 @@ KeyboardLayout::SynthesizeNativeKeyEvent
         NativeKey nativeKey(aWidget, keyDownMsg, modKeyState);
         nativeKey.HandleKeyDownMessage();
       } else {
         AutoTArray<NativeKey::FakeCharMsg, 10> fakeCharMsgs;
         for (uint32_t j = 0; j < chars.Length(); j++) {
           NativeKey::FakeCharMsg* fakeCharMsg = fakeCharMsgs.AppendElement();
           fakeCharMsg->mCharCode = chars.CharAt(j);
           fakeCharMsg->mScanCode = scanCode;
+          fakeCharMsg->mIsSysKey = makeSysKeyMsg;
           fakeCharMsg->mIsDeadKey = makeDeadCharMsg;
         }
         NativeKey nativeKey(aWidget, keyDownMsg, modKeyState, 0, &fakeCharMsgs);
         bool dispatched;
         nativeKey.HandleKeyDownMessage(&dispatched);
         // If some char messages are not consumed, let's emulate the widget
         // receiving the message directly.
         for (uint32_t j = 1; j < fakeCharMsgs.Length(); j++) {
@@ -3485,17 +3548,20 @@ KeyboardLayout::SynthesizeNativeKeyEvent
     UINT scanCode =
       ComputeScanCodeForVirtualKeyCode(keySpecific ? keySpecific : key);
     LPARAM lParam = static_cast<LPARAM>(scanCode << 16);
     // Add extended key flag to the lParam for right control key and right alt
     // key.
     if (keySpecific == VK_RCONTROL || keySpecific == VK_RMENU) {
       lParam |= 0x1000000;
     }
-    MSG keyUpMsg = WinUtils::InitMSG(WM_KEYUP, key, lParam,
+    // Don't use WM_SYSKEYUP for Alt keyup.
+    bool makeSysKeyMsg = IsSysKey(key, modKeyState) && key != VK_MENU;
+    MSG keyUpMsg = WinUtils::InitMSG(makeSysKeyMsg ? WM_SYSKEYUP : WM_KEYUP,
+                                     key, lParam,
                                      aWidget->GetWindowHandle());
     NativeKey nativeKey(aWidget, keyUpMsg, modKeyState);
     nativeKey.HandleKeyUpMessage();
   }
 
   // Restore old key state and layout
   ::SetKeyboardState(originalKbdState);
   RestoreLayout();
--- a/widget/windows/KeyboardLayout.h
+++ b/widget/windows/KeyboardLayout.h
@@ -175,29 +175,37 @@ class MOZ_STACK_CLASS NativeKey final
 {
   friend class KeyboardLayout;
 
 public:
   struct FakeCharMsg
   {
     UINT mCharCode;
     UINT mScanCode;
+    bool mIsSysKey;
     bool mIsDeadKey;
     bool mConsumed;
 
-    FakeCharMsg() :
-      mCharCode(0), mScanCode(0), mIsDeadKey(false), mConsumed(false)
+    FakeCharMsg()
+      : mCharCode(0)
+      , mScanCode(0)
+      , mIsSysKey(false)
+      , mIsDeadKey(false)
+      , mConsumed(false)
     {
     }
 
     MSG GetCharMsg(HWND aWnd) const
     {
       MSG msg;
       msg.hwnd = aWnd;
-      msg.message = mIsDeadKey ? WM_DEADCHAR : WM_CHAR;
+      msg.message = mIsDeadKey && mIsSysKey ? WM_SYSDEADCHAR :
+                                 mIsDeadKey ? WM_DEADCHAR :
+                                  mIsSysKey ? WM_SYSCHAR :
+                                              WM_CHAR;
       msg.wParam = static_cast<WPARAM>(mCharCode);
       msg.lParam = static_cast<LPARAM>(mScanCode << 16);
       msg.time = 0;
       msg.pt.x = msg.pt.y = 0;
       return msg;
     }
   };
 
@@ -300,16 +308,20 @@ private:
   // mIsPrintableKey is true if the key may be a printable key without
   // any modifier keys.  Otherwise, false.
   // Please note that the event may not cause any text input even if this
   // is true.  E.g., it might be dead key state or Ctrl key may be pressed.
   bool    mIsPrintableKey;
   // mIsOverridingKeyboardLayout is true if the instance temporarily overriding
   // keyboard layout with specified by the constructor.
   bool    mIsOverridingKeyboardLayout;
+  // mIsFollowedByNonControlCharMessage may be true when mMsg is a keydown
+  // message.  When the keydown message is followed by a char message, this
+  // is true.
+  bool    mIsFollowedByNonControlCharMessage;
 
   nsTArray<FakeCharMsg>* mFakeCharMsgs;
 
   // When a keydown event is dispatched at handling WM_APPCOMMAND, the computed
   // virtual keycode is set to this.  Even if we consume WM_APPCOMMAND message,
   // Windows may send WM_KEYDOWN and WM_KEYUP message for them.
   // At that time, we should not dispatch key events for them.
   static uint8_t sDispatchedKeyOfAppCommand;
@@ -407,16 +419,17 @@ private:
   {
     return IsSysCharMessage(aMSG.message);
   }
   bool IsSysCharMessage(UINT aMessage) const
   {
     return (aMessage == WM_SYSCHAR || aMessage == WM_SYSDEADCHAR);
   }
   bool MayBeSameCharMessage(const MSG& aCharMsg1, const MSG& aCharMsg2) const;
+  bool IsFollowedByNonControlCharMessage() const;
   bool IsFollowedByDeadCharMessage() const;
   bool IsKeyMessageOnPlugin() const
   {
     return (mMsg.message == MOZ_WM_KEYDOWN ||
             mMsg.message == MOZ_WM_KEYUP);
   }
 
   /**
@@ -580,16 +593,23 @@ public:
   /**
    * IsDeadKey() returns true if aVirtualKey is a dead key with aModKeyState.
    * This method isn't stateful.
    */
   bool IsDeadKey(uint8_t aVirtualKey,
                  const ModifierKeyState& aModKeyState) const;
 
   /**
+   * IsSysKey() returns true if aVirtualKey with aModKeyState causes WM_SYSKEY*
+   * or WM_SYS*CHAR messages.
+   */
+  bool IsSysKey(uint8_t aVirtualKey,
+                const ModifierKeyState& aModKeyState) const;
+
+  /**
    * GetUniCharsAndModifiers() returns characters which is inputted by the
    * aVirtualKey with aModKeyState.  This method isn't stateful.
    */
   UniCharsAndModifiers GetUniCharsAndModifiers(
                          uint8_t aVirtualKey,
                          const ModifierKeyState& aModKeyState) const;
 
   /**