Merge mozilla-central to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 08 Feb 2017 11:30:50 +0100
changeset 341334 c5b88e4e70f48955661dc7900b43a95c3c785836
parent 341278 fdf4f0e8ff1f754cc3d49830e102e978ea181c25 (current diff)
parent 341333 3a95aa4246653a7863914ffec032897d13359fb0 (diff)
child 341335 43ac95c99af6c7edea7328427d78605583b14e94
push id86684
push usercbook@mozilla.com
push dateWed, 08 Feb 2017 10:31:03 +0000
treeherdermozilla-inbound@c5b88e4e70f4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone54.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to mozilla-inbound
browser/base/content/test/general/file_register_about_page.js
dom/ipc/ContentParent.cpp
mobile/android/base/java/org/mozilla/gecko/db/URLMetadataTable.java
testing/web-platform/tests/web-animations/animation-model/keyframe-effects/the-effect-value-of-a-keyframe-effect.html
testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/effect-easing.html
testing/web-platform/tests/web-animations/resources/effect-easing-tests.js
--- a/browser/base/content/aboutNetError.xhtml
+++ b/browser/base/content/aboutNetError.xhtml
@@ -72,22 +72,16 @@
         } catch (e) {
           // We probably tried to reload a URI that caused an exception to
           // occur;  e.g. a nonexistent file.
         }
 
         buttonEl.disabled = true;
       }
 
-      function doOverride(buttonEl) {
-        var event = new CustomEvent("AboutNetErrorOverride", {bubbles:true});
-        document.dispatchEvent(event);
-        retryThis(buttonEl);
-      }
-
       function toggleDisplay(node) {
         const toggle = {
           "": "block",
           "none": "block",
           "block": "none"
         };
         return (node.style.display = toggle[node.style.display]);
       }
@@ -102,20 +96,19 @@
         panel.style.display = "block";
         document.getElementById("netErrorButtonContainer").style.display = "none";
         document.getElementById("prefResetButton").addEventListener("click", function resetPreferences(e) {
           const event = new CustomEvent("AboutNetErrorResetPreferences", {bubbles:true});
           document.dispatchEvent(event);
         });
       }
 
-      function setupAdvancedButton(allowOverride) {
+      function setupAdvancedButton() {
         // Get the hostname and add it to the panel
-        var panelId = gIsCertError ? "badCertAdvancedPanel" : "weakCryptoAdvancedPanel";
-        var panel = document.getElementById(panelId);
+        var panel = document.getElementById("badCertAdvancedPanel");
         for (var span of panel.querySelectorAll("span.hostname")) {
           span.textContent = document.location.hostname;
         }
         if (!gIsCertError) {
           panel.replaceChild(document.getElementById("errorLongDesc"),
                              document.getElementById("advancedLongDesc"));
         }
 
@@ -133,22 +126,16 @@
 
           if (panel.style.display == "block") {
             // send event to trigger telemetry ping
             var event = new CustomEvent("AboutNetErrorUIExpanded", {bubbles:true});
             document.dispatchEvent(event);
           }
         });
 
-        if (allowOverride) {
-          document.getElementById("overrideWeakCryptoPanel").style.display = "flex";
-          var overrideLink = document.getElementById("overrideWeakCrypto");
-          overrideLink.addEventListener("click", () => doOverride(overrideLink));
-        }
-
         if (!gIsCertError) {
           return;
         }
 
         if (getCSSClass() == "expertBadCert") {
           toggleDisplay(document.getElementById("badCertAdvancedPanel"));
           // Toggling the advanced panel must ensure that the debugging
           // information panel is hidden as well, since it's opened by the
@@ -221,20 +208,16 @@
 
         if (err == "sslv3Used") {
           document.getElementById("learnMoreContainer").style.display = "block";
           let learnMoreLink = document.getElementById("learnMoreLink");
           learnMoreLink.href = "https://support.mozilla.org/kb/how-resolve-sslv3-error-messages-firefox";
           document.body.className = "certerror";
         }
 
-        if (err == "weakCryptoUsed") {
-          document.body.className = "certerror";
-        }
-
         // remove undisplayed errors to avoid bug 39098
         var errContainer = document.getElementById("errorContainer");
         errContainer.remove();
 
         var className = getCSSClass();
         if (className && className != "expertBadCert") {
           // Associate a CSS class with the root of the page, if one was passed in,
           // to allow custom styling.
@@ -260,27 +243,24 @@
         if (err == "cspBlocked") {
           // Remove the "Try again" button for CSP violations, since it's
           // almost certainly useless. (Bug 553180)
           document.getElementById("netErrorButtonContainer").style.display = "none";
         }
 
         window.addEventListener("AboutNetErrorOptions", function(evt) {
         // Pinning errors are of type nssFailure2
-          if (getErrorCode() == "nssFailure2" || getErrorCode() == "weakCryptoUsed") {
+          if (getErrorCode() == "nssFailure2") {
             document.getElementById("learnMoreContainer").style.display = "block";
             let learnMoreLink = document.getElementById("learnMoreLink");
             // nssFailure2 also gets us other non-overrideable errors. Choose
             // a "learn more" link based on description:
             if (getDescription().includes("mozilla_pkix_error_key_pinning_failure")) {
               learnMoreLink.href = "https://support.mozilla.org/kb/certificate-pinning-reports";
             }
-            if (getErrorCode() == "weakCryptoUsed") {
-              learnMoreLink.href = "https://support.mozilla.org/kb/how-resolve-weak-crypto-error-messages-firefox";
-            }
 
             var options = JSON.parse(evt.detail);
             if (options && options.enabled) {
               var checkbox = document.getElementById("automaticallyReportInFuture");
               showCertificateErrorReporting();
               if (options.automatic) {
                 // set the checkbox
                 checkbox.checked = true;
@@ -300,18 +280,18 @@
               "SSL_ERROR_NO_CYPHER_OVERLAP",
               "SSL_ERROR_NO_CIPHERS_SUPPORTED"
             ].some((substring) => getDescription().includes(substring));
             // If it looks like an error that is user config based
             if (getErrorCode() == "nssFailure2" && hasPrefStyleError && options && options.changedCertPrefs) {
               showPrefChangeContainer();
             }
           }
-          if (getErrorCode() == "weakCryptoUsed" || getErrorCode() == "sslv3Used") {
-            setupAdvancedButton(getErrorCode() == "weakCryptoUsed");
+          if (getErrorCode() == "sslv3Used") {
+            setupAdvancedButton();
           }
         }, true, true);
 
         var event = new CustomEvent("AboutNetErrorLoad", {bubbles:true});
         document.dispatchEvent(event);
 
         if (err == "inadequateSecurityError") {
           // Remove the "Try again" button for HTTP/2 inadequate security as it
@@ -332,17 +312,17 @@
         document.title = document.getElementById("captivePortalPageTitle").textContent;
 
         document.getElementById("openPortalLoginPageButton")
                 .addEventListener("click", () => {
           let event = new CustomEvent("AboutNetErrorOpenCaptivePortal", {bubbles:true});
           document.dispatchEvent(event);
         });
 
-        setupAdvancedButton(true);
+        setupAdvancedButton();
 
         addDomainErrorLinks();
 
         // When the portal is freed, an event is generated by the frame script
         // that we can pick up and attempt to reload the original page.
         window.addEventListener("AboutNetErrorCaptivePortalFreed", () => {
           document.location.reload();
         });
@@ -350,17 +330,17 @@
 
       function initPageCertError() {
         document.body.className = "certerror";
         document.title = document.getElementById("certErrorPageTitle").textContent;
         for (let host of document.querySelectorAll(".hostname")) {
           host.textContent = document.location.hostname;
         }
 
-        setupAdvancedButton(true);
+        setupAdvancedButton();
 
         document.getElementById("learnMoreContainer").style.display = "block";
 
         let checkbox = document.getElementById("automaticallyReportInFuture");
         checkbox.addEventListener("change", function({target: {checked}}) {
           document.dispatchEvent(new CustomEvent("AboutNetErrorSetAutomatic", {
             detail: checked,
             bubbles: true
@@ -484,18 +464,17 @@
          * The certificate is only valid for garage.maemo.org
          */
         if (thisHost.endsWith("." + okHost))
           link.href = proto + okHost;
 
         // If we set a link, meaning there's something helpful for
         // the user here, expand the section by default
         if (link.href && getCSSClass() != "expertBadCert") {
-          var panelId = gIsCertError ? "badCertAdvancedPanel" : "weakCryptoAdvancedPanel"
-          toggleDisplay(document.getElementById(panelId));
+          toggleDisplay(document.getElementById("badCertAdvancedPanel"));
           if (gIsCertError) {
             // Toggling the advanced panel must ensure that the debugging
             // information panel is hidden as well, since it's opened by the
             // error code link in the advanced panel.
             var div = document.getElementById("certificateErrorDebugInformation");
             div.style.display = "none";
           }
         }
@@ -540,17 +519,16 @@
         <h1 id="et_contentEncodingError">&contentEncodingError.title;</h1>
         <h1 id="et_unsafeContentType">&unsafeContentType.title;</h1>
         <h1 id="et_nssFailure2">&nssFailure2.title;</h1>
         <h1 id="et_nssBadCert">&certerror.longpagetitle1;</h1>
         <h1 id="et_cspBlocked">&cspBlocked.title;</h1>
         <h1 id="et_remoteXUL">&remoteXUL.title;</h1>
         <h1 id="et_corruptedContentErrorv2">&corruptedContentErrorv2.title;</h1>
         <h1 id="et_sslv3Used">&sslv3Used.title;</h1>
-        <h1 id="et_weakCryptoUsed">&weakCryptoUsed.title;</h1>
         <h1 id="et_inadequateSecurityError">&inadequateSecurityError.title;</h1>
       </div>
       <div id="errorDescriptionsContainer">
         <div id="ed_generic">&generic.longDesc;</div>
         <div id="ed_captivePortal">&captivePortal.longDesc2;</div>
         <div id="ed_dnsNotFound">&dnsNotFound.longDesc;</div>
         <div id="ed_fileNotFound">&fileNotFound.longDesc;</div>
         <div id="ed_fileAccessDenied">&fileAccessDenied.longDesc;</div>
@@ -570,17 +548,16 @@
         <div id="ed_contentEncodingError">&contentEncodingError.longDesc;</div>
         <div id="ed_unsafeContentType">&unsafeContentType.longDesc;</div>
         <div id="ed_nssFailure2">&nssFailure2.longDesc2;</div>
         <div id="ed_nssBadCert">&certerror.introPara;</div>
         <div id="ed_cspBlocked">&cspBlocked.longDesc;</div>
         <div id="ed_remoteXUL">&remoteXUL.longDesc;</div>
         <div id="ed_corruptedContentErrorv2">&corruptedContentErrorv2.longDesc;</div>
         <div id="ed_sslv3Used">&sslv3Used.longDesc2;</div>
-        <div id="ed_weakCryptoUsed">&weakCryptoUsed.longDesc2;</div>
         <div id="ed_inadequateSecurityError">&inadequateSecurityError.longDesc;</div>
       </div>
     </div>
 
     <!-- PAGE CONTAINER (for styling purposes only) -->
     <div id="errorPageContainer" class="container">
 
       <!-- Error Title -->
@@ -649,26 +626,16 @@
       <div id="certificateErrorReporting">
         <p class="toggle-container-with-text">
           <input type="checkbox" id="automaticallyReportInFuture" />
           <label for="automaticallyReportInFuture" id="automaticallyReportInFuture">&errorReporting.automatic2;</label>
         </p>
       </div>
 
       <div id="advancedPanelContainer">
-        <div id="weakCryptoAdvancedPanel" class="advanced-panel">
-          <div id="weakCryptoAdvancedDescription">
-            <p>&weakCryptoAdvanced.longDesc;</p>
-          </div>
-          <div id="advancedLongDesc" />
-          <div id="overrideWeakCryptoPanel">
-            <a id="overrideWeakCrypto" href="#">&weakCryptoAdvanced.override;</a>
-          </div>
-        </div>
-
         <div id="badCertAdvancedPanel" class="advanced-panel">
           <p id="badCertTechnicalInfo"/>
           <button id="exceptionDialogButton">&securityOverride.exceptionButtonLabel;</button>
         </div>
       </div>
 
     </div>
 
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -2841,33 +2841,31 @@ var BrowserOnClick = {
     mm.addMessageListener("Browser:CertExceptionError", this);
     mm.addMessageListener("Browser:OpenCaptivePortalPage", this);
     mm.addMessageListener("Browser:SiteBlockedError", this);
     mm.addMessageListener("Browser:EnableOnlineMode", this);
     mm.addMessageListener("Browser:SendSSLErrorReport", this);
     mm.addMessageListener("Browser:SetSSLErrorReportAuto", this);
     mm.addMessageListener("Browser:ResetSSLPreferences", this);
     mm.addMessageListener("Browser:SSLErrorReportTelemetry", this);
-    mm.addMessageListener("Browser:OverrideWeakCrypto", this);
     mm.addMessageListener("Browser:SSLErrorGoBack", this);
 
     Services.obs.addObserver(this, "captive-portal-login-abort", false);
     Services.obs.addObserver(this, "captive-portal-login-success", false);
   },
 
   uninit() {
     let mm = window.messageManager;
     mm.removeMessageListener("Browser:CertExceptionError", this);
     mm.removeMessageListener("Browser:SiteBlockedError", this);
     mm.removeMessageListener("Browser:EnableOnlineMode", this);
     mm.removeMessageListener("Browser:SendSSLErrorReport", this);
     mm.removeMessageListener("Browser:SetSSLErrorReportAuto", this);
     mm.removeMessageListener("Browser:ResetSSLPreferences", this);
     mm.removeMessageListener("Browser:SSLErrorReportTelemetry", this);
-    mm.removeMessageListener("Browser:OverrideWeakCrypto", this);
     mm.removeMessageListener("Browser:SSLErrorGoBack", this);
 
     Services.obs.removeObserver(this, "captive-portal-login-abort");
     Services.obs.removeObserver(this, "captive-portal-login-success");
   },
 
   observe(aSubject, aTopic, aData) {
     switch (aTopic) {
@@ -2938,23 +2936,16 @@ var BrowserOnClick = {
         }
         Services.telemetry.getHistogramById("TLS_ERROR_REPORT_UI").add(bin);
       break;
       case "Browser:SSLErrorReportTelemetry":
         let reportStatus = msg.data.reportStatus;
         Services.telemetry.getHistogramById("TLS_ERROR_REPORT_UI")
           .add(reportStatus);
       break;
-      case "Browser:OverrideWeakCrypto":
-        let weakCryptoOverride = Cc["@mozilla.org/security/weakcryptooverride;1"]
-                                   .getService(Ci.nsIWeakCryptoOverride);
-        weakCryptoOverride.addWeakCryptoOverride(
-          msg.data.uri.host,
-          PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser));
-      break;
       case "Browser:SSLErrorGoBack":
         goBackFromErrorPage();
       break;
     }
   },
 
   onSSLErrorReport(browser, uri, securityInfo) {
     if (!Services.prefs.getBoolPref("security.ssl.errorReporting.enabled")) {
@@ -6937,17 +6928,16 @@ var gIdentityHandler = {
 
     // Then, update the user interface with the available data.
     this.refreshIdentityBlock();
     // Handle a location change while the Control Center is focused
     // by closing the popup (bug 1207542)
     if (shouldHidePopup) {
       this._identityPopup.hidePopup();
     }
-    this.showWeakCryptoInfoBar();
 
     // NOTE: We do NOT update the identity popup (the control center) when
     // we receive a new security state on the existing page (i.e. from a
     // subframe). If the user opened the popup and looks at the provided
     // information we don't want to suddenly change the panel contents.
   },
 
   /**
@@ -7127,65 +7117,16 @@ var gIdentityHandler = {
     // Set cropping and direction
     this._identityIconLabel.crop = icon_country_label ? "end" : "center";
     this._identityIconLabel.parentNode.style.direction = icon_labels_dir;
     // Hide completely if the organization label is empty
     this._identityIconLabel.parentNode.collapsed = icon_label ? false : true;
   },
 
   /**
-   * Show the weak crypto notification bar.
-   */
-  showWeakCryptoInfoBar() {
-    if (!this._uriHasHost || !this._isBroken || !this._sslStatus.cipherName ||
-        this._sslStatus.cipherName.indexOf("_RC4_") < 0) {
-      return;
-    }
-
-    let notificationBox = gBrowser.getNotificationBox();
-    let notification = notificationBox.getNotificationWithValue("weak-crypto");
-    if (notification) {
-      return;
-    }
-
-    let brandBundle = document.getElementById("bundle_brand");
-    let brandShortName = brandBundle.getString("brandShortName");
-    let message = gNavigatorBundle.getFormattedString("weakCryptoOverriding.message",
-                                                      [brandShortName]);
-
-    let host = this._uri.host;
-    let port = 443;
-    try {
-      if (this._uri.port > 0) {
-        port = this._uri.port;
-      }
-    } catch (e) {}
-
-    let buttons = [{
-      label: gNavigatorBundle.getString("revokeOverride.label"),
-      accessKey: gNavigatorBundle.getString("revokeOverride.accesskey"),
-      callback(aNotification, aButton) {
-        try {
-          let weakCryptoOverride = Cc["@mozilla.org/security/weakcryptooverride;1"]
-                                     .getService(Ci.nsIWeakCryptoOverride);
-          weakCryptoOverride.removeWeakCryptoOverride(host, port,
-            PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser));
-          BrowserReloadWithFlags(nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE);
-        } catch (e) {
-          Cu.reportError(e);
-        }
-      }
-    }];
-
-    const priority = notificationBox.PRIORITY_WARNING_MEDIUM;
-    notificationBox.appendNotification(message, "weak-crypto", null,
-                                       priority, buttons);
-  },
-
-  /**
    * Set up the title and content messages for the identity message popup,
    * based on the specified mode, and the details of the SSL cert, where
    * applicable
    */
   refreshIdentityPopup() {
     // Update "Learn More" for Mixed Content Blocking and Insecure Login Forms.
     let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
     this._identityPopupMixedContentLearnMore
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -262,17 +262,16 @@ function getSerializedSecurityInfo(docSh
 
 var AboutNetAndCertErrorListener = {
   init(chromeGlobal) {
     addMessageListener("CertErrorDetails", this);
     addMessageListener("Browser:CaptivePortalFreed", this);
     chromeGlobal.addEventListener("AboutNetErrorLoad", this, false, true);
     chromeGlobal.addEventListener("AboutNetErrorOpenCaptivePortal", this, false, true);
     chromeGlobal.addEventListener("AboutNetErrorSetAutomatic", this, false, true);
-    chromeGlobal.addEventListener("AboutNetErrorOverride", this, false, true);
     chromeGlobal.addEventListener("AboutNetErrorResetPreferences", this, false, true);
   },
 
   get isAboutNetError() {
     return content.document.documentURI.startsWith("about:neterror");
   },
 
   get isAboutCertError() {
@@ -385,19 +384,16 @@ var AboutNetAndCertErrorListener = {
       this.onPageLoad(aEvent);
       break;
     case "AboutNetErrorOpenCaptivePortal":
       this.openCaptivePortalPage(aEvent);
       break;
     case "AboutNetErrorSetAutomatic":
       this.onSetAutomatic(aEvent);
       break;
-    case "AboutNetErrorOverride":
-      this.onOverride(aEvent);
-      break;
     case "AboutNetErrorResetPreferences":
       this.onResetPreferences(aEvent);
       break;
     }
   },
 
   changedCertPrefs() {
     for (let prefName of PREF_SSL_IMPACT) {
@@ -448,21 +444,16 @@ var AboutNetAndCertErrorListener = {
       let {host, port} = content.document.mozDocumentURIIfNotForErrorPages;
       sendAsyncMessage("Browser:SendSSLErrorReport", {
         uri: { host, port },
         securityInfo: getSerializedSecurityInfo(docShell),
       });
 
     }
   },
-
-  onOverride(evt) {
-    let {host, port} = content.document.mozDocumentURIIfNotForErrorPages;
-    sendAsyncMessage("Browser:OverrideWeakCrypto", { uri: {host, port} });
-  }
 }
 
 AboutNetAndCertErrorListener.init(this);
 
 
 var ClickEventHandler = {
   init: function init() {
     Cc["@mozilla.org/eventlistenerservice;1"]
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -89,19 +89,16 @@ support-files =
   !/toolkit/mozapps/extensions/test/xpinstall/corrupt.xpi
   !/toolkit/mozapps/extensions/test/xpinstall/incompatible.xpi
   !/toolkit/mozapps/extensions/test/xpinstall/installtrigger.html
   !/toolkit/mozapps/extensions/test/xpinstall/redirect.sjs
   !/toolkit/mozapps/extensions/test/xpinstall/restartless-unsigned.xpi
   !/toolkit/mozapps/extensions/test/xpinstall/restartless.xpi
   !/toolkit/mozapps/extensions/test/xpinstall/theme.xpi
   !/toolkit/mozapps/extensions/test/xpinstall/slowinstall.sjs
-  file_about_child.html
-  file_about_parent.html
-  file_register_about_page.js
 
 [browser_aboutAccounts.js]
 skip-if = os == "linux" # Bug 958026
 support-files =
   content_aboutAccounts.js
 [browser_aboutCertError.js]
 [browser_aboutNetError.js]
 [browser_aboutSupport_newtab_security_state.js]
@@ -422,16 +419,19 @@ skip-if = true # Bug 1005420 - fails int
 skip-if = (os == "win" && !debug)
 [browser_web_channel.js]
 [browser_windowopen_reflows.js]
 [browser_zbug569342.js]
 skip-if = e10s || debug # Bug 1094240 - has findbar-related failures
 [browser_registerProtocolHandler_notification.js]
 [browser_addCertException.js]
 [browser_e10s_about_page_triggeringprincipal.js]
+support-files =
+  file_about_child.html
+  file_about_parent.html
 [browser_e10s_switchbrowser.js]
 [browser_e10s_about_process.js]
 [browser_e10s_chrome_process.js]
 [browser_e10s_javascript.js]
 [browser_blockHPKP.js]
 tags = psm
 [browser_windowactivation.js]
 [browser_contextmenu_childprocess.js]
--- a/browser/base/content/test/general/browser_e10s_about_page_triggeringprincipal.js
+++ b/browser/base/content/test/general/browser_e10s_about_page_triggeringprincipal.js
@@ -1,26 +1,24 @@
 "use strict";
-Cu.import("resource://gre/modules/Services.jsm");
+
+const kChildPage = getRootDirectory(gTestPath) + "file_about_child.html";
+const kParentPage = getRootDirectory(gTestPath) + "file_about_parent.html";
 
-registerCleanupFunction(function() {
-  Services.ppmm.broadcastAsyncMessage("AboutPrincipalTest:Unregister");
-  BrowserTestUtils.waitForMessage(Services.ppmm, "AboutPrincipalTest:Unregistered").then(
-    Services.ppmm.removeDelayedProcessScript(
-      "chrome://mochitests/content/browser/browser/base/content/test/general/file_register_about_page.js"
-    )
-  );
-});
+const kAboutPagesRegistered = Promise.all([
+  BrowserTestUtils.registerAboutPage(
+    registerCleanupFunction, "test-about-principal-child", kChildPage,
+    Ci.nsIAboutModule.URI_MUST_LOAD_IN_CHILD | Ci.nsIAboutModule.ALLOW_SCRIPT),
+  BrowserTestUtils.registerAboutPage(
+    registerCleanupFunction, "test-about-principal-parent", kParentPage,
+    Ci.nsIAboutModule.ALLOW_SCRIPT)
+]);
 
 add_task(function* test_principal_click() {
-  Services.ppmm.loadProcessScript(
-    "chrome://mochitests/content/browser/browser/base/content/test/general/file_register_about_page.js",
-    true
-  );
-
+  yield kAboutPagesRegistered;
   yield BrowserTestUtils.withNewTab("about:test-about-principal-parent", function*(browser) {
     let loadPromise = BrowserTestUtils.browserLoaded(browser, false, "about:test-about-principal-child");
     let myLink = browser.contentDocument.getElementById("aboutchildprincipal");
     myLink.click();
     yield loadPromise;
 
     yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function*() {
       let channel = content.document.docShell.currentDocumentChannel;
@@ -39,16 +37,17 @@ add_task(function* test_principal_click(
       let loadingPrincipal = channel.loadInfo.loadingPrincipal;
       is(loadingPrincipal, null,
          "sanity check - load of TYPE_DOCUMENT must have a null loadingPrincipal");
     });
   });
 });
 
 add_task(function* test_principal_ctrl_click() {
+  yield kAboutPagesRegistered;
   yield SpecialPowers.pushPrefEnv({
     "set": [["security.sandbox.content.level", 1]],
   });
 
   yield BrowserTestUtils.withNewTab("about:test-about-principal-parent", function*(browser) {
     let loadPromise = BrowserTestUtils.waitForNewTab(gBrowser, "about:test-about-principal-child");
     // simulate ctrl+click
     BrowserTestUtils.synthesizeMouseAtCenter("#aboutchildprincipal",
@@ -75,16 +74,17 @@ add_task(function* test_principal_ctrl_c
       is(loadingPrincipal, null,
          "sanity check - load of TYPE_DOCUMENT must have a null loadingPrincipal");
     });
     yield BrowserTestUtils.removeTab(tab);
   });
 });
 
 add_task(function* test_principal_right_click_open_link_in_new_tab() {
+  yield kAboutPagesRegistered;
   yield SpecialPowers.pushPrefEnv({
     "set": [["security.sandbox.content.level", 1]],
   });
 
   yield BrowserTestUtils.withNewTab("about:test-about-principal-parent", function*(browser) {
     let loadPromise = BrowserTestUtils.waitForNewTab(gBrowser, "about:test-about-principal-child");
 
     // simulate right-click open link in tab
--- a/browser/base/content/test/general/browser_misused_characters_in_strings.js
+++ b/browser/base/content/test/general/browser_misused_characters_in_strings.js
@@ -13,24 +13,16 @@ let gWhitelist = [{
     key: "searchForSomethingWith",
     type: "single-quote"
   }, {
     file: "netError.dtd",
     key: "certerror.introPara",
     type: "single-quote"
   }, {
     file: "netError.dtd",
-    key: "weakCryptoAdvanced.longDesc",
-    type: "single-quote"
-  }, {
-    file: "netError.dtd",
-    key: "weakCryptoAdvanced.override",
-    type: "single-quote"
-  }, {
-    file: "netError.dtd",
     key: "inadequateSecurityError.longDesc",
     type: "single-quote"
   }, {
     file: "netError.dtd",
     key: "certerror.wrongSystemTime2",
     type: "single-quote"
   }, {
     file: "netError.dtd",
deleted file mode 100644
--- a/browser/base/content/test/general/file_register_about_page.js
+++ /dev/null
@@ -1,81 +0,0 @@
-const { interfaces: Ci, results: Cr, manager: Cm, utils: Cu } = Components;
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-function AboutPage(chromeURL, aboutHost, classID, description, uriFlags) {
-  this.chromeURL = chromeURL;
-  this.aboutHost = aboutHost;
-  this.classID = Components.ID(classID);
-  this.description = description;
-  this.uriFlags = uriFlags;
-}
-
-AboutPage.prototype = {
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutModule]),
-  getURIFlags(aURI) { // eslint-disable-line no-unused-vars
-    return this.uriFlags;
-  },
-
-  newChannel(aURI, aLoadInfo) {
-    let newURI = Services.io.newURI(this.chromeURL);
-    let channel = Services.io.newChannelFromURIWithLoadInfo(newURI,
-                                                            aLoadInfo);
-    channel.originalURI = aURI;
-
-    if (this.uriFlags & Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT) {
-      channel.owner = null;
-    }
-    return channel;
-  },
-
-  createInstance(outer, iid) {
-    if (outer !== null) {
-      throw Cr.NS_ERROR_NO_AGGREGATION;
-    }
-    return this.QueryInterface(iid);
-  },
-
-  register() {
-    Cm.QueryInterface(Ci.nsIComponentRegistrar).registerFactory(
-      this.classID, this.description,
-      "@mozilla.org/network/protocol/about;1?what=" + this.aboutHost, this);
-  },
-
-  unregister() {
-    Cm.QueryInterface(Ci.nsIComponentRegistrar).unregisterFactory(
-      this.classID, this);
-  }
-};
-
-/* exported AboutPrincipalTest */
-var AboutPrincipalTest = {};
-
-XPCOMUtils.defineLazyGetter(AboutPrincipalTest, "aboutChild", () =>
-  new AboutPage("chrome://mochitests/content/browser/browser/base/content/test/general/file_about_child.html",
-                "test-about-principal-child",
-                "{df6cbd19-c95b-4011-874b-315347c0832c}",
-                "About Principal Child Test",
-                Ci.nsIAboutModule.URI_MUST_LOAD_IN_CHILD |
-                Ci.nsIAboutModule.ALLOW_SCRIPT)
-
-);
-
-XPCOMUtils.defineLazyGetter(AboutPrincipalTest, "aboutParent", () =>
-  new AboutPage("chrome://mochitests/content/browser/browser/base/content/test/general/file_about_parent.html",
-                "test-about-principal-parent",
-                "{15e1a03d-9f94-4352-bfb8-94216140d3ab}",
-                "About Principal Parent Test",
-                Ci.nsIAboutModule.ALLOW_SCRIPT)
-);
-
-AboutPrincipalTest.aboutParent.register();
-AboutPrincipalTest.aboutChild.register();
-
-function onUnregisterMessage() {
-  removeMessageListener("AboutPrincipalTest:Unregister", onUnregisterMessage);
-  AboutPrincipalTest.aboutParent.unregister();
-  AboutPrincipalTest.aboutChild.unregister();
-  sendAsyncMessage("AboutPrincipalTest:Unregistered");
-}
-
-addMessageListener("AboutPrincipalTest:Unregister", onUnregisterMessage);
--- a/browser/components/search/test/browser.ini
+++ b/browser/components/search/test/browser.ini
@@ -23,16 +23,17 @@ support-files =
 [browser_bing_behavior.js]
 [browser_contextmenu.js]
 [browser_contextSearchTabPosition.js]
 skip-if = os == "mac" # bug 967013
 [browser_ddg.js]
 [browser_ddg_behavior.js]
 [browser_google.js]
 [browser_google_codes.js]
+[browser_google_nocodes.js]
 [browser_google_behavior.js]
 [browser_healthreport.js]
 [browser_hiddenOneOffs_cleanup.js]
 [browser_hiddenOneOffs_diacritics.js]
 [browser_oneOffContextMenu.js]
 [browser_oneOffContextMenu_setDefault.js]
 [browser_oneOffHeader.js]
 [browser_private_search_perwindowpb.js]
--- a/browser/components/search/test/browser_google_codes.js
+++ b/browser/components/search/test/browser_google_codes.js
@@ -2,16 +2,18 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 const kUrlPref = "geoSpecificDefaults.url";
 const BROWSER_SEARCH_PREF = "browser.search.";
 
 var originalGeoURL;
+var originalCountryCode;
+var originalRegion;
 
 /**
  * Clean the profile of any cache file left from a previous run.
  * Returns a boolean indicating if the cache file existed.
  */
 function removeCacheFile() {
   const CACHE_FILENAME = "search.json.mozlz4";
 
@@ -84,40 +86,42 @@ add_task(function* preparation() {
   gEngineCount = Services.search.getVisibleEngines().length;
 
   waitForSearchNotification("uninit-complete", () => {
     // Verify search service is not initialized
     is(Services.search.isInitialized, false, "Search service should NOT be initialized");
 
     removeCacheFile();
 
+    // Make sure we get the new country/region values, but save the old
+    originalCountryCode = Services.prefs.getCharPref(BROWSER_SEARCH_PREF + "countryCode");
+    originalRegion = Services.prefs.getCharPref(BROWSER_SEARCH_PREF + "region");
+    Services.prefs.clearUserPref(BROWSER_SEARCH_PREF + "countryCode");
+    Services.prefs.clearUserPref(BROWSER_SEARCH_PREF + "region");
+
     // Geo specific defaults won't be fetched if there's no country code.
     Services.prefs.setCharPref("browser.search.geoip.url",
-                               'data:application/json,{"country_code": "US"}');
+                               'data:application/json,{"country_code": "DE"}');
 
     Services.prefs.setBoolPref("browser.search.geoSpecificDefaults", true);
 
-    // Make the new Google the only engine
+    // Avoid going to the server for the geo lookup. We take the defaults
     originalGeoURL = Services.prefs.getCharPref(BROWSER_SEARCH_PREF + kUrlPref);
-    let geoUrl = 'data:application/json,{"interval": 31536000, "settings": {"searchDefault": "Google", "visibleDefaultEngines": ["google"]}}';
+    let geoUrl = "data:application/json,{}";
     Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF).setCharPref(kUrlPref, geoUrl);
   });
 
   yield asyncReInit();
 
   yield new Promise(resolve => {
     waitForSearchNotification("write-cache-to-disk-complete", resolve);
   });
 });
 
 add_task(function* tests() {
-  let engines = Services.search.getEngines();
-  is(Services.search.currentEngine.name, "Google", "Search engine should be Google");
-  is(engines.length, 1, "There should only be one engine");
-
   let engine = Services.search.getEngineByName("Google");
   ok(engine, "Google");
 
   let base = "https://www.google.com/search?q=foo&ie=utf-8&oe=utf-8&client=firefox-b";
 
   // Keyword uses a slightly different code
   let keywordBase = base + "-ab";
 
@@ -143,16 +147,19 @@ add_task(function* cleanup() {
   waitForSearchNotification("uninit-complete", () => {
     // Verify search service is not initialized
     is(Services.search.isInitialized, false,
        "Search service should NOT be initialized");
     removeCacheFile();
 
     Services.prefs.clearUserPref("browser.search.geoip.url");
 
+    Services.prefs.setCharPref(BROWSER_SEARCH_PREF + "countryCode", originalCountryCode)
+    Services.prefs.setCharPref(BROWSER_SEARCH_PREF + "region", originalRegion)
+
     // We can't clear the pref because it's set to false by testing/profiles/prefs_general.js
     Services.prefs.setBoolPref("browser.search.geoSpecificDefaults", false);
 
     Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF).setCharPref(kUrlPref, originalGeoURL);
   });
 
   yield asyncReInit();
   is(gEngineCount, Services.search.getVisibleEngines().length,
new file mode 100644
--- /dev/null
+++ b/browser/components/search/test/browser_google_nocodes.js
@@ -0,0 +1,164 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const kUrlPref = "geoSpecificDefaults.url";
+const BROWSER_SEARCH_PREF = "browser.search.";
+
+var originalGeoURL;
+var originalCountryCode;
+var originalRegion;
+
+/**
+ * Clean the profile of any cache file left from a previous run.
+ * Returns a boolean indicating if the cache file existed.
+ */
+function removeCacheFile() {
+  const CACHE_FILENAME = "search.json.mozlz4";
+
+  let file =  Services.dirsvc.get("ProfD", Ci.nsIFile);
+  file.append(CACHE_FILENAME);
+  if (file.exists()) {
+    file.remove(false);
+    return true;
+  }
+  return false;
+}
+
+/**
+ * Returns a promise that is resolved when an observer notification from the
+ * search service fires with the specified data.
+ *
+ * @param aExpectedData
+ *        The value the observer notification sends that causes us to resolve
+ *        the promise.
+ */
+function waitForSearchNotification(aExpectedData, aCallback) {
+  const SEARCH_SERVICE_TOPIC = "browser-search-service";
+  Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
+    if (aData != aExpectedData)
+      return;
+
+    Services.obs.removeObserver(observer, SEARCH_SERVICE_TOPIC);
+    aCallback();
+  }, SEARCH_SERVICE_TOPIC, false);
+}
+
+function asyncInit() {
+  return new Promise(resolve => {
+    Services.search.init(function() {
+      ok(Services.search.isInitialized, "search service should be initialized");
+      resolve();
+    });
+  });
+}
+
+function asyncReInit() {
+  const kLocalePref = "general.useragent.locale";
+
+  let promise = new Promise(resolve => {
+    waitForSearchNotification("reinit-complete", resolve);
+  });
+
+  Services.search.QueryInterface(Ci.nsIObserver)
+          .observe(null, "nsPref:changed", kLocalePref);
+
+  return promise;
+}
+
+let gEngineCount;
+
+add_task(function* preparation() {
+  // ContentSearch is interferring with our async re-initializations of the
+  // search service: once _initServicePromise has resolved, it will access
+  // the search service, thus causing unpredictable behavior due to
+  // synchronous initializations of the service.
+  let originalContentSearchPromise = ContentSearch._initServicePromise;
+  ContentSearch._initServicePromise = new Promise(resolve => {
+    registerCleanupFunction(() => {
+      ContentSearch._initServicePromise = originalContentSearchPromise;
+      resolve();
+    });
+  });
+
+  yield asyncInit();
+  gEngineCount = Services.search.getVisibleEngines().length;
+
+  waitForSearchNotification("uninit-complete", () => {
+    // Verify search service is not initialized
+    is(Services.search.isInitialized, false, "Search service should NOT be initialized");
+
+    removeCacheFile();
+
+    // Make sure we get the new country/region values, but save the old
+    originalCountryCode = Services.prefs.getCharPref(BROWSER_SEARCH_PREF + "countryCode");
+    originalRegion = Services.prefs.getCharPref(BROWSER_SEARCH_PREF + "region");
+    Services.prefs.clearUserPref(BROWSER_SEARCH_PREF + "countryCode");
+    Services.prefs.clearUserPref(BROWSER_SEARCH_PREF + "region");
+
+    // Geo specific defaults won't be fetched if there's no country code.
+    Services.prefs.setCharPref("browser.search.geoip.url",
+                               'data:application/json,{"country_code": "US"}');
+
+    Services.prefs.setBoolPref("browser.search.geoSpecificDefaults", true);
+
+    // Avoid going to the server for the geo lookup. We take the defaults
+    originalGeoURL = Services.prefs.getCharPref(BROWSER_SEARCH_PREF + kUrlPref);
+    let geoUrl = "data:application/json,{}";
+    Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF).setCharPref(kUrlPref, geoUrl);
+  });
+
+  yield asyncReInit();
+
+  yield new Promise(resolve => {
+    waitForSearchNotification("write-cache-to-disk-complete", resolve);
+  });
+});
+
+add_task(function* tests() {
+  let engine = Services.search.getEngineByName("Google");
+  ok(engine, "Google");
+
+  let base = "https://www.google.com/search?q=foo&ie=utf-8&oe=utf-8";
+
+  let url;
+
+  // Test search URLs (including purposes).
+  url = engine.getSubmission("foo", null, "contextmenu").uri.spec;
+  is(url, base, "Check context menu search URL for 'foo'");
+  url = engine.getSubmission("foo", null, "keyword").uri.spec;
+  is(url, base, "Check keyword search URL for 'foo'");
+  url = engine.getSubmission("foo", null, "searchbar").uri.spec;
+  is(url, base, "Check search bar search URL for 'foo'");
+  url = engine.getSubmission("foo", null, "homepage").uri.spec;
+  is(url, base, "Check homepage search URL for 'foo'");
+  url = engine.getSubmission("foo", null, "newtab").uri.spec;
+  is(url, base, "Check newtab search URL for 'foo'");
+  url = engine.getSubmission("foo", null, "system").uri.spec;
+  is(url, base, "Check system search URL for 'foo'");
+});
+
+
+add_task(function* cleanup() {
+  waitForSearchNotification("uninit-complete", () => {
+    // Verify search service is not initialized
+    is(Services.search.isInitialized, false,
+       "Search service should NOT be initialized");
+    removeCacheFile();
+
+    Services.prefs.clearUserPref("browser.search.geoip.url");
+
+    Services.prefs.setCharPref(BROWSER_SEARCH_PREF + "countryCode", originalCountryCode)
+    Services.prefs.setCharPref(BROWSER_SEARCH_PREF + "region", originalRegion)
+
+    // We can't clear the pref because it's set to false by testing/profiles/prefs_general.js
+    Services.prefs.setBoolPref("browser.search.geoSpecificDefaults", false);
+
+    Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF).setCharPref(kUrlPref, originalGeoURL);
+  });
+
+  yield asyncReInit();
+  is(gEngineCount, Services.search.getVisibleEngines().length,
+     "correct engine count after cleanup");
+});
--- a/browser/extensions/webcompat-reporter/moz.build
+++ b/browser/extensions/webcompat-reporter/moz.build
@@ -15,8 +15,11 @@ FINAL_TARGET_FILES.features['webcompat-r
 
 FINAL_TARGET_PP_FILES.features['webcompat-reporter@mozilla.org'] += [
   'install.rdf.in'
 ]
 
 JAR_MANIFESTS += ['jar.mn']
 
 BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
+
+with Files('**'):
+    BUG_COMPONENT = ('Web Compatibility', 'General')
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -813,21 +813,16 @@ userContextOpenLink.label = Open Link in
 
 muteTab.label = Mute Tab
 muteTab.accesskey = M
 unmuteTab.label = Unmute Tab
 unmuteTab.accesskey = m
 playTab.label = Play Tab
 playTab.accesskey = l
 
-# LOCALIZATION NOTE (weakCryptoOverriding.message): %S is brandShortName
-weakCryptoOverriding.message = %S recommends that you don’t enter your password, credit card and other personal information on this website.
-revokeOverride.label = Don’t Trust This Website
-revokeOverride.accesskey = D
-
 # LOCALIZATION NOTE (certErrorDetails*.label): These are text strings that
 # appear in the about:certerror page, so that the user can copy and send them to
 # the server administrators for troubleshooting.
 certErrorDetailsHSTS.label = HTTP Strict Transport Security: %S
 certErrorDetailsKeyPinning.label = HTTP Public Key Pinning: %S
 certErrorDetailsCertChain.label = Certificate chain:
 
 # LOCALIZATION NOTE (pendingCrashReports2.label): Semi-colon list of plural forms
--- a/browser/locales/en-US/chrome/overrides/appstrings.properties
+++ b/browser/locales/en-US/chrome/overrides/appstrings.properties
@@ -33,11 +33,9 @@ externalProtocolLaunchBtn=Launch applica
 malwareBlocked=The site at %S has been reported as an attack site and has been blocked based on your security preferences.
 unwantedBlocked=The site at %S has been reported as serving unwanted software and has been blocked based on your security preferences.
 deceptiveBlocked=This web page at %S has been reported as a deceptive site and has been blocked based on your security preferences.
 cspBlocked=This page has a content security policy that prevents it from being loaded in this way.
 corruptedContentErrorv2=The site at %S has experienced a network protocol violation that cannot be repaired.
 remoteXUL=This page uses an unsupported technology that is no longer available by default in Firefox.
 ## LOCALIZATION NOTE (sslv3Used) - Do not translate "%S".
 sslv3Used=Firefox cannot guarantee the safety of your data on %S because it uses SSLv3, a broken security protocol.
-## LOCALIZATION NOTE (weakCryptoUsed) - Do not translate "%S".
-weakCryptoUsed=The owner of %S has configured their website improperly. To protect your information from being stolen, Firefox has not connected to this website.
 inadequateSecurityError=The website tried to negotiate an inadequate level of security.
--- a/browser/locales/en-US/chrome/overrides/netError.dtd
+++ b/browser/locales/en-US/chrome/overrides/netError.dtd
@@ -184,24 +184,16 @@ was trying to connect. -->
 <!ENTITY remoteXUL.title "Remote XUL">
 <!ENTITY remoteXUL.longDesc "<p><ul><li>Please contact the website owners to inform them of this problem.</li></ul></p>">
 
 <!ENTITY sslv3Used.title "Unable to Connect Securely">
 <!-- LOCALIZATION NOTE (sslv3Used.longDesc2) - Do not translate
      "SSL_ERROR_UNSUPPORTED_VERSION". -->
 <!ENTITY sslv3Used.longDesc2 "Advanced info: SSL_ERROR_UNSUPPORTED_VERSION">
 
-<!ENTITY weakCryptoUsed.title "Your connection is not secure">
-<!-- LOCALIZATION NOTE (weakCryptoUsed.longDesc2) - Do not translate
-     "SSL_ERROR_NO_CYPHER_OVERLAP". -->
-<!ENTITY weakCryptoUsed.longDesc2 "Advanced info: SSL_ERROR_NO_CYPHER_OVERLAP">
-<!ENTITY weakCryptoAdvanced.title "Advanced">
-<!ENTITY weakCryptoAdvanced.longDesc "<span class='hostname'></span> uses security technology that is outdated and vulnerable to attack. An attacker could easily reveal information which you thought to be safe.">
-<!ENTITY weakCryptoAdvanced.override "(Not secure) Try loading <span class='hostname'></span> using outdated security">
-
 <!-- LOCALIZATION NOTE (certerror.wrongSystemTime2,
                         certerror.wrongSystemTimeWithoutReference) - The <span id='..' />
      tags will be injected with actual values, please leave them unchanged. -->
 <!ENTITY certerror.wrongSystemTime2 "<p> &brandShortName; did not connect to <span id='wrongSystemTime_URL'/> because your computer’s clock appears to show the wrong time and this is preventing a secure connection.</p> <p>Your computer is set to <span id='wrongSystemTime_systemDate'/>, when it should be <span id='wrongSystemTime_actualDate'/>. To fix this problem, change your date and time settings to match the correct time.</p>">
 <!ENTITY certerror.wrongSystemTimeWithoutReference "<p>&brandShortName; did not connect to <span id='wrongSystemTimeWithoutReference_URL'/> because your computer’s clock appears to show the wrong time and this is preventing a secure connection.</p> <p>Your computer is set to <span id='wrongSystemTimeWithoutReference_systemDate'/>. To fix this problem, change your date and time settings to match the correct time.</p>">
 
 <!ENTITY certerror.pagetitle1  "Insecure Connection">
 <!ENTITY certerror.whatShouldIDo.badStsCertExplanation "This site uses HTTP
--- a/caps/nsScriptSecurityManager.cpp
+++ b/caps/nsScriptSecurityManager.cpp
@@ -727,24 +727,58 @@ nsScriptSecurityManager::CheckLoadURIWit
     if (NS_FAILED(rv)) return rv;
 
     while (currentURI && currentOtherURI) {
         nsAutoCString scheme, otherScheme;
         currentURI->GetScheme(scheme);
         currentOtherURI->GetScheme(otherScheme);
 
         bool schemesMatch = scheme.Equals(otherScheme, stringComparator);
-        bool isSamePage;
+        bool isSamePage = false;
         // about: URIs are special snowflakes.
-        if (scheme.EqualsLiteral("about")) {
-            nsAutoCString module, otherModule;
-            isSamePage = schemesMatch &&
-                NS_SUCCEEDED(NS_GetAboutModuleName(currentURI, module)) &&
-                NS_SUCCEEDED(NS_GetAboutModuleName(currentOtherURI, otherModule)) &&
-                module.Equals(otherModule);
+        if (scheme.EqualsLiteral("about") && schemesMatch) {
+            nsAutoCString moduleName, otherModuleName;
+            // about: pages can always link to themselves:
+            isSamePage =
+              NS_SUCCEEDED(NS_GetAboutModuleName(currentURI, moduleName)) &&
+              NS_SUCCEEDED(NS_GetAboutModuleName(currentOtherURI, otherModuleName)) &&
+              moduleName.Equals(otherModuleName);
+            if (!isSamePage) {
+                // We will have allowed the load earlier if the source page has
+                // system principal. So we know the source has a content
+                // principal, and it's trying to link to something else.
+                // Linkable about: pages are always reachable, even if we hit
+                // the CheckLoadURIFlags call below.
+                // We punch only 1 other hole: iff the source is unlinkable,
+                // we let them link to other pages explicitly marked SAFE
+                // for content. This avoids world-linkable about: pages linking
+                // to non-world-linkable about: pages.
+                nsCOMPtr<nsIAboutModule> module, otherModule;
+                bool knowBothModules =
+                    NS_SUCCEEDED(NS_GetAboutModule(currentURI, getter_AddRefs(module))) &&
+                    NS_SUCCEEDED(NS_GetAboutModule(currentOtherURI, getter_AddRefs(otherModule)));
+                uint32_t aboutModuleFlags = 0;
+                uint32_t otherAboutModuleFlags = 0;
+                knowBothModules = knowBothModules &&
+                    NS_SUCCEEDED(module->GetURIFlags(currentURI, &aboutModuleFlags)) &&
+                    NS_SUCCEEDED(otherModule->GetURIFlags(currentOtherURI, &otherAboutModuleFlags));
+                if (knowBothModules) {
+                    isSamePage =
+                        !(aboutModuleFlags & nsIAboutModule::MAKE_LINKABLE) &&
+                        (otherAboutModuleFlags & nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT);
+                    if (isSamePage && otherAboutModuleFlags & nsIAboutModule::MAKE_LINKABLE) {
+                        //XXXgijs: this is a hack. The target will be nested
+                        // (with innerURI of moz-safe-about:whatever), and
+                        // the source isn't, so we won't pass if we finish
+                        // the loop. We *should* pass, though, so return here.
+                        // This hack can go away when bug 1228118 is fixed.
+                        return NS_OK;
+                    }
+                }
+            }
         } else {
             bool equalExceptRef = false;
             rv = currentURI->EqualsExceptRef(currentOtherURI, &equalExceptRef);
             isSamePage = NS_SUCCEEDED(rv) && equalExceptRef;
         }
 
         // If schemes are not equal, or they're equal but the target URI
         // is different from the source URI and doesn't always allow linking
--- a/caps/tests/mochitest/browser_checkloaduri.js
+++ b/caps/tests/mochitest/browser_checkloaduri.js
@@ -1,11 +1,48 @@
 "use strict";
 
 let ssm = Services.scriptSecurityManager;
+// This will show a directory listing, but we never actually load these so that's OK.
+const kDummyPage = getRootDirectory(gTestPath);
+
+const kAboutPagesRegistered = Promise.all([
+  BrowserTestUtils.registerAboutPage(
+    registerCleanupFunction, "test-chrome-privs", kDummyPage,
+    Ci.nsIAboutModule.ALLOW_SCRIPT),
+  BrowserTestUtils.registerAboutPage(
+    registerCleanupFunction, "test-chrome-privs2", kDummyPage,
+    Ci.nsIAboutModule.ALLOW_SCRIPT),
+  BrowserTestUtils.registerAboutPage(
+    registerCleanupFunction, "test-unknown-linkable", kDummyPage,
+    Ci.nsIAboutModule.MAKE_LINKABLE | Ci.nsIAboutModule.ALLOW_SCRIPT),
+  BrowserTestUtils.registerAboutPage(
+    registerCleanupFunction, "test-unknown-linkable2", kDummyPage,
+    Ci.nsIAboutModule.MAKE_LINKABLE | Ci.nsIAboutModule.ALLOW_SCRIPT),
+  BrowserTestUtils.registerAboutPage(
+    registerCleanupFunction, "test-unknown-unlinkable", kDummyPage,
+    Ci.nsIAboutModule.ALLOW_SCRIPT),
+  BrowserTestUtils.registerAboutPage(
+    registerCleanupFunction, "test-unknown-unlinkable2", kDummyPage,
+    Ci.nsIAboutModule.ALLOW_SCRIPT),
+  BrowserTestUtils.registerAboutPage(
+    registerCleanupFunction, "test-content-unlinkable", kDummyPage,
+    Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT | Ci.nsIAboutModule.ALLOW_SCRIPT),
+  BrowserTestUtils.registerAboutPage(
+    registerCleanupFunction, "test-content-unlinkable2", kDummyPage,
+    Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT | Ci.nsIAboutModule.ALLOW_SCRIPT),
+  BrowserTestUtils.registerAboutPage(
+    registerCleanupFunction, "test-content-linkable", kDummyPage,
+    Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT | Ci.nsIAboutModule.MAKE_LINKABLE |
+    Ci.nsIAboutModule.ALLOW_SCRIPT),
+  BrowserTestUtils.registerAboutPage(
+    registerCleanupFunction, "test-content-linkable2", kDummyPage,
+    Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT | Ci.nsIAboutModule.MAKE_LINKABLE |
+    Ci.nsIAboutModule.ALLOW_SCRIPT),
+]);
 
 const URLs = new Map([
   ["http://www.example.com", [
   // For each of these entries, the booleans represent whether the parent URI can:
   // - load them
   // - load them without principal inheritance
   // - whether the URI can be created at all (some protocol handlers will
   //   refuse to create certain variants)
@@ -17,65 +54,171 @@ const URLs = new Map([
     ["view-source:http://www.example2.com", false, false, true],
     ["view-source:https://www.example2.com", false, false, true],
     ["view-source:feed:http://www.example2.com", false, false, true],
     ["feed:view-source:http://www.example2.com", false, false, false],
     ["data:text/html,Hi", true, false, true],
     ["view-source:data:text/html,Hi", false, false, true],
     ["javascript:alert('hi')", true, false, true],
     ["moz://a", false, false, true],
+    ["about:test-chrome-privs", false, false, true],
+    ["about:test-unknown-unlinkable", false, false, true],
+    ["about:test-content-unlinkable", false, false, true],
+    ["about:test-content-linkable", true, true, true],
+    // Because this page doesn't have SAFE_FOR_UNTRUSTED, the web can't link to it:
+    ["about:test-unknown-linkable", false, false, true],
   ]],
   ["feed:http://www.example.com", [
     ["http://www.example2.com", true, true, true],
     ["feed:http://www.example2.com", true, true, true],
     ["https://www.example2.com", true, true, true],
     ["feed:https://www.example2.com", true, true, true],
     ["chrome://foo/content/bar.xul", false, false, true],
     ["feed:chrome://foo/content/bar.xul", false, false, false],
     ["view-source:http://www.example2.com", false, false, true],
     ["view-source:https://www.example2.com", false, false, true],
     ["view-source:feed:http://www.example2.com", false, false, true],
     ["feed:view-source:http://www.example2.com", false, false, false],
     ["data:text/html,Hi", true, false, true],
     ["view-source:data:text/html,Hi", false, false, true],
     ["javascript:alert('hi')", true, false, true],
     ["moz://a", false, false, true],
+    ["about:test-chrome-privs", false, false, true],
+    ["about:test-unknown-unlinkable", false, false, true],
+    ["about:test-content-unlinkable", false, false, true],
+    ["about:test-content-linkable", true, true, true],
+    // Because this page doesn't have SAFE_FOR_UNTRUSTED, the web can't link to it:
+    ["about:test-unknown-linkable", false, false, true],
   ]],
   ["view-source:http://www.example.com", [
     ["http://www.example2.com", true, true, true],
     ["feed:http://www.example2.com", false, false, true],
     ["https://www.example2.com", true, true, true],
     ["feed:https://www.example2.com", false, false, true],
     ["chrome://foo/content/bar.xul", false, false, true],
     ["feed:chrome://foo/content/bar.xul", false, false, false],
     ["view-source:http://www.example2.com", true, true, true],
     ["view-source:https://www.example2.com", true, true, true],
     ["view-source:feed:http://www.example2.com", false, false, true],
     ["feed:view-source:http://www.example2.com", false, false, false],
     ["data:text/html,Hi", true, false, true],
     ["view-source:data:text/html,Hi", true, false, true],
     ["javascript:alert('hi')", true, false, true],
     ["moz://a", false, false, true],
+    ["about:test-chrome-privs", false, false, true],
+    ["about:test-unknown-unlinkable", false, false, true],
+    ["about:test-content-unlinkable", false, false, true],
+    ["about:test-content-linkable", true, true, true],
+    // Because this page doesn't have SAFE_FOR_UNTRUSTED, the web can't link to it:
+    ["about:test-unknown-linkable", false, false, true],
   ]],
-  ["about:foo", [
-    ["about:foo?", true, true, true],
-    ["about:foo?bar", true, true, true],
-    ["about:foo#", true, true, true],
-    ["about:foo#bar", true, true, true],
-    ["about:foo?#", true, true, true],
-    ["about:foo?bar#baz", true, true, true],
-    ["about:bar", false, false, true],
-    ["about:bar?foo#baz", false, false, true],
-    ["about:bar?foo", false, false, true],
-    ["http://www.example.com/", true, true, true],
-    ["moz://a", false, false, true],
+  // about: related tests.
+  ["about:test-chrome-privs", [
+    ["about:test-chrome-privs", true, true, true],
+    ["about:test-chrome-privs2", true, true, true],
+    ["about:test-chrome-privs2?foo#bar", true, true, true],
+    ["about:test-chrome-privs2?foo", true, true, true],
+    ["about:test-chrome-privs2#bar", true, true, true],
+
+    ["about:test-unknown-unlinkable", true, true, true],
+
+    ["about:test-content-unlinkable", true, true, true],
+    ["about:test-content-unlinkable?foo", true, true, true],
+    ["about:test-content-unlinkable?foo#bar", true, true, true],
+    ["about:test-content-unlinkable#bar", true, true, true],
+
+    ["about:test-content-linkable", true, true, true],
+
+    ["about:test-unknown-linkable", true, true, true],
+  ]],
+  ["about:test-unknown-unlinkable", [
+    ["about:test-chrome-privs", false, false, true],
+
+    // Can link to ourselves:
+    ["about:test-unknown-unlinkable", true, true, true],
+    // Can't link to unlinkable content if we're not sure it's privileged:
+    ["about:test-unknown-unlinkable2", false, false, true],
+
+    ["about:test-content-unlinkable", true, true, true],
+    ["about:test-content-unlinkable2", true, true, true],
+    ["about:test-content-unlinkable2?foo", true, true, true],
+    ["about:test-content-unlinkable2?foo#bar", true, true, true],
+    ["about:test-content-unlinkable2#bar", true, true, true],
+
+    ["about:test-content-linkable", true, true, true],
+
+    // Because this page doesn't have SAFE_FOR_UNTRUSTED, the web can't link to it:
+    ["about:test-unknown-linkable", false, false, true],
+  ]],
+  ["about:test-content-unlinkable", [
+    ["about:test-chrome-privs", false, false, true],
+
+    // Can't link to unlinkable content if we're not sure it's privileged:
+    ["about:test-unknown-unlinkable", false, false, true],
+
+    ["about:test-content-unlinkable", true, true, true],
+    ["about:test-content-unlinkable2", true, true, true],
+    ["about:test-content-unlinkable2?foo", true, true, true],
+    ["about:test-content-unlinkable2?foo#bar", true, true, true],
+    ["about:test-content-unlinkable2#bar", true, true, true],
+
+    ["about:test-content-linkable", true, true, true],
+    ["about:test-unknown-linkable", false, false, true],
+  ]],
+  ["about:test-unknown-linkable", [
+    ["about:test-chrome-privs", false, false, true],
+
+    // Linkable content can't link to unlinkable content.
+    ["about:test-unknown-unlinkable", false, false, true],
+
+    ["about:test-content-unlinkable", false, false, true],
+    ["about:test-content-unlinkable2", false, false, true],
+    ["about:test-content-unlinkable2?foo", false, false, true],
+    ["about:test-content-unlinkable2?foo#bar", false, false, true],
+    ["about:test-content-unlinkable2#bar", false, false, true],
+
+    // ... but it can link to other linkable content.
+    ["about:test-content-linkable", true, true, true],
+
+    // Can link to ourselves:
+    ["about:test-unknown-linkable", true, true, true],
+
+    // Because this page doesn't have SAFE_FOR_UNTRUSTED, the web can't link to it:
+    ["about:test-unknown-linkable2", false, false, true],
+  ]],
+  ["about:test-content-linkable", [
+    ["about:test-chrome-privs", false, false, true],
+
+    // Linkable content can't link to unlinkable content.
+    ["about:test-unknown-unlinkable", false, false, true],
+
+    ["about:test-content-unlinkable", false, false, true],
+
+    // ... but it can link to itself and other linkable content.
+    ["about:test-content-linkable", true, true, true],
+    ["about:test-content-linkable2", true, true, true],
+
+    // Because this page doesn't have SAFE_FOR_UNTRUSTED, the web can't link to it:
+    ["about:test-unknown-linkable", false, false, true],
   ]],
 ]);
 
 function testURL(source, target, canLoad, canLoadWithoutInherit, canCreate, flags) {
+  function getPrincipalDesc(principal) {
+    if (principal.URI) {
+      return principal.URI.spec;
+    }
+    if (principal.isSystemPrincipal) {
+      return "system principal";
+    }
+    if (principal.isNullPrincipal) {
+      return "null principal";
+    }
+    return "unknown principal";
+  }
   let threw = false;
   let targetURI;
   try {
     targetURI = makeURI(target);
   } catch (ex) {
     ok(!canCreate, "Shouldn't be passing URIs that we can't create. Failed to create: " + target);
     return;
   }
@@ -86,24 +229,30 @@ function testURL(source, target, canLoad
   } catch (ex) {
     info(ex.message);
     threw = true;
   }
   let inheritDisallowed = flags & ssm.DISALLOW_INHERIT_PRINCIPAL;
   let shouldThrow = inheritDisallowed ? !canLoadWithoutInherit : !canLoad;
   ok(threw == shouldThrow,
      "Should " + (shouldThrow ? "" : "not ") + "throw an error when loading " +
-     target + " from " + source.URI.spec +
+     target + " from " + getPrincipalDesc(source) +
      (inheritDisallowed ? " without" : " with") + " principal inheritance.");
 }
 
 add_task(function* () {
+  yield kAboutPagesRegistered;
   let baseFlags = ssm.STANDARD | ssm.DONT_REPORT_ERRORS;
   for (let [sourceString, targetsAndExpectations] of URLs) {
-    let source = ssm.createCodebasePrincipal(makeURI(sourceString), {});
+    let source;
+    if (sourceString.startsWith("about:test-chrome-privs")) {
+      source = ssm.getSystemPrincipal();
+    } else {
+      source = ssm.createCodebasePrincipal(makeURI(sourceString), {});
+    }
     for (let [target, canLoad, canLoadWithoutInherit, canCreate] of targetsAndExpectations) {
       testURL(source, target, canLoad, canLoadWithoutInherit, canCreate, baseFlags);
       testURL(source, target, canLoad, canLoadWithoutInherit, canCreate,
               baseFlags | ssm.DISALLOW_INHERIT_PRINCIPAL);
     }
   }
 
   // Now test blob URIs, which we need to do in-content.
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -14249,29 +14249,27 @@ nsDocShell::ShouldBlockLoadingForBackBut
   bool canGoForward = false;
   GetCanGoForward(&canGoForward);
   return canGoForward;
 }
 
 bool
 nsDocShell::PluginsAllowedInCurrentDoc()
 {
-  bool pluginsAllowed = false;
 
   if (!mContentViewer) {
     return false;
   }
 
   nsIDocument* doc = mContentViewer->GetDocument();
   if (!doc) {
     return false;
   }
 
-  doc->GetAllowPlugins(&pluginsAllowed);
-  return pluginsAllowed;
+  return doc->GetAllowPlugins();
 }
 
 //----------------------------------------------------------------------
 // Web Shell Services API
 
 // This functions is only called when a new charset is detected in loading a
 // document. Its name should be changed to "CharsetReloadDocument"
 NS_IMETHODIMP
--- a/dom/animation/ComputedTimingFunction.cpp
+++ b/dom/animation/ComputedTimingFunction.cpp
@@ -23,43 +23,48 @@ ComputedTimingFunction::Init(const nsTim
 }
 
 static inline double
 StepTiming(uint32_t aSteps,
            double aPortion,
            ComputedTimingFunction::BeforeFlag aBeforeFlag,
            nsTimingFunction::Type aType)
 {
-  MOZ_ASSERT(0.0 <= aPortion && aPortion <= 1.0, "out of range");
   MOZ_ASSERT(aType == nsTimingFunction::Type::StepStart ||
              aType == nsTimingFunction::Type::StepEnd, "invalid type");
 
-  if (aPortion == 1.0) {
-    return 1.0;
-  }
-
   // Calculate current step using step-end behavior
-  uint32_t step = uint32_t(aPortion * aSteps); // floor
+  int32_t step = floor(aPortion * aSteps);
 
   // step-start is one step ahead
   if (aType == nsTimingFunction::Type::StepStart) {
     step++;
   }
 
   // If the "before flag" is set and we are at a transition point,
-  // drop back a step (but only if we are not already at the zero point--
-  // we do this clamping here since |step| is an unsigned integer)
-  if (step != 0 &&
-      aBeforeFlag == ComputedTimingFunction::BeforeFlag::Set &&
+  // drop back a step
+  if (aBeforeFlag == ComputedTimingFunction::BeforeFlag::Set &&
       fmod(aPortion * aSteps, 1) == 0) {
     step--;
   }
 
   // Convert to a progress value
-  return double(step) / double(aSteps);
+  double result = double(step) / double(aSteps);
+
+  // We should not produce a result outside [0, 1] unless we have an
+  // input outside that range. This takes care of steps that would otherwise
+  // occur at boundaries.
+  if (result < 0.0 && aPortion >= 0.0) {
+    return 0.0;
+  }
+  if (result > 1.0 && aPortion <= 1.0) {
+    return 1.0;
+  }
+
+  return result;
 }
 
 double
 ComputedTimingFunction::GetValue(
     double aPortion,
     ComputedTimingFunction::BeforeFlag aBeforeFlag) const
 {
   if (HasSpline()) {
@@ -103,28 +108,16 @@ ComputedTimingFunction::GetValue(
       }
       // If we can't calculate a sensible tangent, don't extrapolate at all.
       return 1.0;
     }
 
     return mTimingFunction.GetSplineValue(aPortion);
   }
 
-  // Since we use endpoint-exclusive timing, the output of a steps(start) timing
-  // function when aPortion = 0.0 is the top of the first step. When aPortion is
-  // negative, however, we should use the bottom of the first step. We handle
-  // negative values of aPortion specially here since once we clamp aPortion
-  // to [0,1] below we will no longer be able to distinguish to the two cases.
-  if (aPortion < 0.0) {
-    return 0.0;
-  }
-
-  // Clamp in case of steps(end) and steps(start) for values greater than 1.
-  aPortion = clamped(aPortion, 0.0, 1.0);
-
   return StepTiming(mSteps, aPortion, aBeforeFlag, mType);
 }
 
 int32_t
 ComputedTimingFunction::Compare(const ComputedTimingFunction& aRhs) const
 {
   if (mType != aRhs.mType) {
     return int32_t(mType) - int32_t(aRhs.mType);
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -3139,37 +3139,42 @@ nsDocument::GetContentType(nsAString& aC
 }
 
 void
 nsDocument::SetContentType(const nsAString& aContentType)
 {
   SetContentTypeInternal(NS_ConvertUTF16toUTF8(aContentType));
 }
 
-nsresult
-nsDocument::GetAllowPlugins(bool * aAllowPlugins)
+bool
+nsDocument::GetAllowPlugins()
 {
   // First, we ask our docshell if it allows plugins.
   nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
 
   if (docShell) {
-    docShell->GetAllowPlugins(aAllowPlugins);
+    bool allowPlugins = false;
+    docShell->GetAllowPlugins(&allowPlugins);
+    if (!allowPlugins) {
+      return false;
+    }
 
     // If the docshell allows plugins, we check whether
     // we are sandboxed and plugins should not be allowed.
-    if (*aAllowPlugins)
-      *aAllowPlugins = !(mSandboxFlags & SANDBOXED_PLUGINS);
-  }
-
-  if (*aAllowPlugins) {
-    FlashClassification classification = DocumentFlashClassification();
-    *aAllowPlugins = (classification != FlashClassification::Denied);
-  }
-
-  return NS_OK;
+    if (mSandboxFlags & SANDBOXED_PLUGINS) {
+      return false;
+    }
+  }
+
+  FlashClassification classification = DocumentFlashClassification();
+  if (classification == FlashClassification::Denied) {
+    return false;
+  }
+
+  return true;
 }
 
 bool
 nsDocument::IsElementAnimateEnabled(JSContext* aCx, JSObject* /*unused*/)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   return nsContentUtils::IsSystemCaller(aCx) ||
@@ -13136,16 +13141,19 @@ nsDocument::ComputeFlashClassification()
              "nsIDocShellTreeItem::GetSameTypeParent should never fail");
 
   bool isTopLevel = !parent;
   FlashClassification classification;
   if (isTopLevel) {
     classification = PrincipalFlashClassification(isTopLevel);
   } else {
     nsCOMPtr<nsIDocument> parentDocument = GetParentDocument();
+    if (!parentDocument) {
+      return FlashClassification::Denied;
+    }
     FlashClassification parentClassification =
       parentDocument->DocumentFlashClassification();
 
     if (parentClassification == FlashClassification::Denied) {
       classification = FlashClassification::Denied;
     } else {
       classification = PrincipalFlashClassification(isTopLevel);
 
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -594,17 +594,17 @@ public:
    * shared among multiple presentation shells).
    */
   already_AddRefed<nsIPresShell> CreateShell(nsPresContext* aContext,
                                              nsViewManager* aViewManager,
                                              mozilla::StyleSetHandle aStyleSet)
     final;
   virtual void DeleteShell() override;
 
-  virtual nsresult GetAllowPlugins(bool* aAllowPlugins) override;
+  virtual bool GetAllowPlugins() override;
 
   static bool IsElementAnimateEnabled(JSContext* aCx, JSObject* aObject);
   static bool IsWebAnimationsEnabled(JSContext* aCx, JSObject* aObject);
   virtual mozilla::dom::DocumentTimeline* Timeline() override;
   virtual void GetAnimations(
       nsTArray<RefPtr<mozilla::dom::Animation>>& aAnimations) override;
   mozilla::LinkedList<mozilla::dom::DocumentTimeline>& Timelines() override
   {
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -876,17 +876,17 @@ public:
   void SetParentDocument(nsIDocument* aParent)
   {
     mParentDocument = aParent;
   }
 
   /**
    * Are plugins allowed in this document ?
    */
-  virtual nsresult GetAllowPlugins (bool* aAllowPlugins) = 0;
+  virtual bool GetAllowPlugins () = 0;
 
   /**
    * Set the sub document for aContent to aSubDoc.
    */
   virtual nsresult SetSubDocumentFor(Element* aContent,
                                      nsIDocument* aSubDoc) = 0;
 
   /**
--- a/dom/base/nsPluginArray.cpp
+++ b/dom/base/nsPluginArray.cpp
@@ -306,19 +306,25 @@ nsPluginArray::Observe(nsISupports *aSub
   }
 
   return NS_OK;
 }
 
 bool
 nsPluginArray::AllowPlugins() const
 {
-  nsCOMPtr<nsIDocShell> docShell = mWindow ? mWindow->GetDocShell() : nullptr;
+  if (!mWindow) {
+    return false;
+  }
+  nsCOMPtr<nsIDocument> doc = mWindow->GetDoc();
+  if (!doc) {
+    return false;
+  }
 
-  return docShell && docShell->PluginsAllowedInCurrentDoc();
+  return doc->GetAllowPlugins();
 }
 
 static bool
 operator<(const RefPtr<nsPluginElement>& lhs,
           const RefPtr<nsPluginElement>& rhs)
 {
   // Sort plugins alphabetically by name.
   return lhs->PluginTag()->Name() < rhs->PluginTag()->Name();
--- a/dom/base/test/file_use_counter_svg_currentScale.svg
+++ b/dom/base/test/file_use_counter_svg_currentScale.svg
@@ -1,12 +1,9 @@
 <?xml version="1.0" standalone="no"?>
-
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
-  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
 <svg width="4in" height="3in" version="1.1"
      xmlns="http://www.w3.org/2000/svg"
      xmlns:xlink="http://www.w3.org/1999/xlink">
   <desc>Test graphic for hitting currentScale
   </desc>
   <script type="text/javascript"> <![CDATA[
     document.documentElement.currentScale = document.documentElement.currentScale;
     ]]>
--- a/dom/base/test/file_use_counter_svg_fill_pattern.svg
+++ b/dom/base/test/file_use_counter_svg_fill_pattern.svg
@@ -1,11 +1,9 @@
 <?xml version="1.0" standalone="no"?>
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" 
-  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
 <svg width="8cm" height="4cm" viewBox="0 0 800 400" version="1.1"
      xmlns="http://www.w3.org/2000/svg">
   <desc>Borrowed from http://www.w3.org/TR/SVG/pservers.html</desc>
   <!-- Outline the drawing area in blue -->
   <rect fill="none" stroke="blue" 
         x="1" y="1" width="798" height="398"/>
 
   <!-- The ellipse is filled using a triangle pattern paint server
--- a/dom/base/test/file_use_counter_svg_fill_pattern_data.svg
+++ b/dom/base/test/file_use_counter_svg_fill_pattern_data.svg
@@ -1,11 +1,9 @@
 <?xml version="1.0" standalone="no"?>
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" 
-  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
 <svg width="8cm" height="4cm" viewBox="0 0 800 400" version="1.1"
      xmlns="http://www.w3.org/2000/svg">
   <desc>Borrowed from http://www.w3.org/TR/SVG/pservers.html</desc>
   <!-- Outline the drawing area in blue -->
   <rect fill="none" stroke="blue" 
         x="1" y="1" width="798" height="398"/>
 
   <!-- The ellipse is filled using a triangle pattern paint server
--- a/dom/base/test/file_use_counter_svg_fill_pattern_definition.svg
+++ b/dom/base/test/file_use_counter_svg_fill_pattern_definition.svg
@@ -1,11 +1,9 @@
 <?xml version="1.0" standalone="no"?>
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" 
-  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
 <svg width="8cm" height="4cm" viewBox="0 0 800 400" version="1.1"
      xmlns="http://www.w3.org/2000/svg">
   <desc>Borrowed from http://www.w3.org/TR/SVG/pservers.html</desc>
   <defs>
     <pattern id="TrianglePattern" patternUnits="userSpaceOnUse"
              x="0" y="0" width="100" height="100"
              viewBox="0 0 10 10" >
       <path d="M 0 0 L 7 0 L 3.5 7 z" fill="red" fill-opacity="0.7" stroke="blue" />
--- a/dom/base/test/file_use_counter_svg_fill_pattern_internal.svg
+++ b/dom/base/test/file_use_counter_svg_fill_pattern_internal.svg
@@ -1,11 +1,9 @@
 <?xml version="1.0" standalone="no"?>
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" 
-  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
 <svg width="8cm" height="4cm" viewBox="0 0 800 400" version="1.1"
      xmlns="http://www.w3.org/2000/svg">
   <desc>Borrowed from http://www.w3.org/TR/SVG/pservers.html</desc>
   <!-- Outline the drawing area in blue -->
   <rect fill="none" stroke="blue" 
         x="1" y="1" width="798" height="398"/>
 
   <defs>
--- a/dom/base/test/file_use_counter_svg_getElementById.svg
+++ b/dom/base/test/file_use_counter_svg_getElementById.svg
@@ -1,12 +1,9 @@
 <?xml version="1.0" standalone="no"?>
-
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
-  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
 <svg width="4in" height="3in" version="1.1"
      xmlns="http://www.w3.org/2000/svg"
      xmlns:xlink="http://www.w3.org/1999/xlink">
   <desc>Test graphic for hitting getElementById
   </desc>
   <image id="i1" x="200" y="200" width="100px" height="80px">
   </image>
   <circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>
--- a/dom/base/test/w3element_traversal.svg
+++ b/dom/base/test/w3element_traversal.svg
@@ -1,9 +1,9 @@
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"
+<!DOCTYPE svg
 [
 <!ENTITY tree "<tspan id='first_element_child_entity'></tspan>">
 ]>
 
 <svg xmlns="http://www.w3.org/2000/svg"
      xmlns:xlink="http://www.w3.org/1999/xlink"
      xmlns:pickle="http://ns.example.org/pickle"
      version="1.1"
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -7126,64 +7126,110 @@ HTMLMediaElement::MarkAsContentSource(Ca
   const bool isVisible = mVisibilityState != Visibility::APPROXIMATELY_NONVISIBLE;
 
   if (isVisible) {
     // 0 = ALL_VISIBLE
     Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 0);
   } else {
     // 1 = ALL_INVISIBLE
     Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 1);
+
+    if (IsInUncomposedDoc()) {
+      // 0 = ALL_IN_TREE
+      Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT, 0);
+    } else {
+      // 1 = ALL_NOT_IN_TREE
+      Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT, 1);
+    }
   }
 
   switch (aAPI) {
     case CallerAPI::DRAW_IMAGE: {
       if (isVisible) {
         // 2 = drawImage_VISIBLE
         Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 2);
       } else {
         // 3 = drawImage_INVISIBLE
         Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 3);
+
+        if (IsInUncomposedDoc()) {
+          // 2 = drawImage_IN_TREE
+          Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT, 2);
+        } else {
+          // 3 = drawImage_NOT_IN_TREE
+          Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT, 3);
+        }
       }
       break;
     }
     case CallerAPI::CREATE_PATTERN: {
       if (isVisible) {
         // 4 = createPattern_VISIBLE
         Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 4);
       } else {
         // 5 = createPattern_INVISIBLE
         Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 5);
+
+        if (IsInUncomposedDoc()) {
+          // 4 = createPattern_IN_TREE
+          Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT, 4);
+        } else {
+          // 5 = createPattern_NOT_IN_TREE
+          Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT, 5);
+        }
       }
       break;
     }
     case CallerAPI::CREATE_IMAGEBITMAP: {
       if (isVisible) {
         // 6 = createImageBitmap_VISIBLE
         Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 6);
       } else {
         // 7 = createImageBitmap_INVISIBLE
         Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 7);
+
+        if (IsInUncomposedDoc()) {
+          // 6 = createImageBitmap_IN_TREE
+          Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT, 6);
+        } else {
+          // 7 = createImageBitmap_NOT_IN_TREE
+          Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT, 7);
+        }
       }
       break;
     }
     case CallerAPI::CAPTURE_STREAM: {
       if (isVisible) {
         // 8 = captureStream_VISIBLE
         Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 8);
       } else {
         // 9 = captureStream_INVISIBLE
         Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 9);
+
+        if (IsInUncomposedDoc()) {
+          // 8 = captureStream_IN_TREE
+          Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT, 8);
+        } else {
+          // 9 = captureStream_NOT_IN_TREE
+          Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT, 9);
+        }
       }
       break;
     }
   }
 
   LOG(LogLevel::Debug,
       ("%p Log VIDEO_AS_CONTENT_SOURCE: visibility = %u, API: '%d' and 'All'",
        this, isVisible, aAPI));
+
+  if (!isVisible) {
+    LOG(LogLevel::Debug,
+        ("%p Log VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT: inTree = %u, API: '%d' and 'All'",
+         this, IsInUncomposedDoc(), aAPI));
+  }
 }
 
 void
 HTMLMediaElement::UpdateCustomPolicyAfterPlayed()
 {
   OpenUnsupportedMediaWithExternalAppIfNeeded();
   if (mAudioChannelWrapper) {
     mAudioChannelWrapper->NotifyPlayStateChanged();
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -183,16 +183,17 @@
 #include "nsHostObjectProtocolHandler.h"
 #include "nsICaptivePortalService.h"
 
 #include "nsIBidiKeyboard.h"
 
 #include "nsLayoutStylesheetCache.h"
 
 #include "ContentPrefs.h"
+#include "mozilla/Sprintf.h"
 
 #ifdef MOZ_WEBRTC
 #include "signaling/src/peerconnection/WebrtcGlobalParent.h"
 #endif
 
 #if defined(ANDROID) || defined(LINUX)
 #include "nsSystemInfo.h"
 #endif
--- a/dom/media/WebVTTListener.cpp
+++ b/dom/media/WebVTTListener.cpp
@@ -6,16 +6,17 @@
 #include "WebVTTListener.h"
 #include "mozilla/dom/TextTrackCue.h"
 #include "mozilla/dom/TextTrackRegion.h"
 #include "mozilla/dom/VTTRegionBinding.h"
 #include "mozilla/dom/HTMLTrackElement.h"
 #include "nsIInputStream.h"
 #include "nsIWebVTTParserWrapper.h"
 #include "nsComponentManagerUtils.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION(WebVTTListener, mElement, mParserWrapper)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebVTTListener)
   NS_INTERFACE_MAP_ENTRY(nsIWebVTTListener)
@@ -72,16 +73,17 @@ NS_IMETHODIMP
 WebVTTListener::AsyncOnChannelRedirect(nsIChannel* aOldChannel,
                                        nsIChannel* aNewChannel,
                                        uint32_t aFlags,
                                        nsIAsyncVerifyRedirectCallback* cb)
 {
   if (mElement) {
     mElement->OnChannelRedirect(aOldChannel, aNewChannel, aFlags);
   }
+  cb->OnRedirectVerifyCallback(NS_OK);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 WebVTTListener::OnStartRequest(nsIRequest* aRequest,
                                nsISupports* aContext)
 {
   VTT_LOG("WebVTTListener::OnStartRequest\n");
--- a/dom/xslt/xslt/txXPathResultComparator.cpp
+++ b/dom/xslt/xslt/txXPathResultComparator.cpp
@@ -4,18 +4,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/FloatingPoint.h"
 
 #include "txXPathResultComparator.h"
 #include "txExpr.h"
 #include "txCore.h"
 #include "nsCollationCID.h"
-#include "nsILocale.h"
-#include "nsILocaleService.h"
 #include "nsIServiceManager.h"
 #include "prmem.h"
 
 #define kAscending (1<<0)
 #define kUpperFirst (1<<1)
 
 txResultStringComparator::txResultStringComparator(bool aAscending,
                                                    bool aUpperFirst,
@@ -30,35 +28,26 @@ txResultStringComparator::txResultString
     if (NS_FAILED(rv))
         NS_ERROR("Failed to initialize txResultStringComparator");
 }
 
 nsresult txResultStringComparator::init(const nsAFlatString& aLanguage)
 {
     nsresult rv;
 
-    nsCOMPtr<nsILocaleService> localeService =
-                    do_GetService(NS_LOCALESERVICE_CONTRACTID, &rv);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    nsCOMPtr<nsILocale> locale;
-    if (!aLanguage.IsEmpty()) {
-        rv = localeService->NewLocale(aLanguage,
-                                      getter_AddRefs(locale));
-    }
-    else {
-        rv = localeService->GetApplicationLocale(getter_AddRefs(locale));
-    }
-    NS_ENSURE_SUCCESS(rv, rv);
-
     nsCOMPtr<nsICollationFactory> colFactory =
                     do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    rv = colFactory->CreateCollation(locale, getter_AddRefs(mCollation));
+    if (aLanguage.IsEmpty()) {
+      rv = colFactory->CreateCollation(getter_AddRefs(mCollation));
+    } else {
+      rv = colFactory->CreateCollationForLocale(NS_ConvertUTF16toUTF8(aLanguage), getter_AddRefs(mCollation));
+    }
+
     NS_ENSURE_SUCCESS(rv, rv);
 
     return NS_OK;
 }
 
 nsresult
 txResultStringComparator::createSortableValue(Expr *aExpr,
                                               txIEvalContext *aContext,
--- a/dom/xul/templates/nsXULContentUtils.cpp
+++ b/dom/xul/templates/nsXULContentUtils.cpp
@@ -47,18 +47,16 @@
 #include "nsGkAtoms.h"
 #include "mozilla/Logging.h"
 #include "prtime.h"
 #include "rdf.h"
 #include "nsContentUtils.h"
 #include "nsIScriptableDateFormat.h"
 #include "nsICollation.h"
 #include "nsCollationCID.h"
-#include "nsILocale.h"
-#include "nsILocaleService.h"
 #include "nsIConsoleService.h"
 #include "nsEscape.h"
 
 using namespace mozilla;
 
 //------------------------------------------------------------------------
 
 nsIRDFService* nsXULContentUtils::gRDF;
@@ -120,37 +118,24 @@ nsXULContentUtils::Finish()
 
     return NS_OK;
 }
 
 nsICollation*
 nsXULContentUtils::GetCollation()
 {
     if (!gCollation) {
-        nsresult rv;
-
-        // get a locale service 
-        nsCOMPtr<nsILocaleService> localeService =
-            do_GetService(NS_LOCALESERVICE_CONTRACTID, &rv);
-        if (NS_SUCCEEDED(rv)) {
-            nsCOMPtr<nsILocale> locale;
-            rv = localeService->GetApplicationLocale(getter_AddRefs(locale));
-            if (NS_SUCCEEDED(rv) && locale) {
-                nsCOMPtr<nsICollationFactory> colFactory =
-                    do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID);
-                if (colFactory) {
-                    rv = colFactory->CreateCollation(locale, &gCollation);
-                    NS_ASSERTION(NS_SUCCEEDED(rv),
-                                 "couldn't create collation instance");
-                } else
-                    NS_ERROR("couldn't create instance of collation factory");
-            } else
-                NS_ERROR("unable to get application locale");
+        nsCOMPtr<nsICollationFactory> colFactory =
+            do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID);
+        if (colFactory) {
+            DebugOnly<nsresult> rv = colFactory->CreateCollation(&gCollation);
+            NS_ASSERTION(NS_SUCCEEDED(rv),
+                         "couldn't create collation instance");
         } else
-            NS_ERROR("couldn't get locale factory");
+            NS_ERROR("couldn't create instance of collation factory");
     }
 
     return gCollation;
 }
 
 //------------------------------------------------------------------------
 
 nsresult
--- a/intl/locale/mac/nsCollationMacUC.cpp
+++ b/intl/locale/mac/nsCollationMacUC.cpp
@@ -1,42 +1,36 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsCollationMacUC.h"
-#include "nsILocaleService.h"
 #include "nsIPrefBranch.h"
 #include "nsIPrefService.h"
 #include "nsIServiceManager.h"
 #include "prmem.h"
 #include "nsString.h"
 
 NS_IMPL_ISUPPORTS(nsCollationMacUC, nsICollation)
 
 nsCollationMacUC::nsCollationMacUC()
   : mInit(false)
   , mHasCollator(false)
-  , mLocaleICU(nullptr)
   , mLastStrength(-1)
   , mCollatorICU(nullptr)
 { }
 
 nsCollationMacUC::~nsCollationMacUC()
 {
 #ifdef DEBUG
   nsresult res =
 #endif
     CleanUpCollator();
   NS_ASSERTION(NS_SUCCEEDED(res), "CleanUpCollator failed");
-  if (mLocaleICU) {
-    free(mLocaleICU);
-    mLocaleICU = nullptr;
-  }
 }
 
 nsresult nsCollationMacUC::ConvertStrength(const int32_t aNSStrength,
                                            UCollationStrength* aICUStrength,
                                            UColAttributeValue* aCaseLevelOut)
 {
   NS_ENSURE_ARG_POINTER(aICUStrength);
   NS_ENSURE_TRUE((aNSStrength < 4), NS_ERROR_FAILURE);
@@ -63,53 +57,29 @@ nsresult nsCollationMacUC::ConvertStreng
   }
 
   *aICUStrength = strength;
   *aCaseLevelOut = caseLevel;
 
   return NS_OK;
 }
 
-nsresult nsCollationMacUC::ConvertLocaleICU(nsILocale* aNSLocale, char** aICULocale)
-{
-  NS_ENSURE_ARG_POINTER(aNSLocale);
-  NS_ENSURE_ARG_POINTER(aICULocale);
-
-  nsAutoString localeString;
-  nsresult res = aNSLocale->GetCategory(NS_LITERAL_STRING("NSILOCALE_COLLATE"), localeString);
-  NS_ENSURE_TRUE(NS_SUCCEEDED(res) && !localeString.IsEmpty(),
-                 NS_ERROR_FAILURE);
-  NS_LossyConvertUTF16toASCII tmp(localeString);
-  tmp.ReplaceChar('-', '_');
-  char* locale = (char*)malloc(tmp.Length() + 1);
-  if (!locale) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-  strcpy(locale, tmp.get());
-
-  *aICULocale = locale;
-
-  return NS_OK;
-}
-
 nsresult nsCollationMacUC::EnsureCollator(const int32_t newStrength)
 {
   NS_ENSURE_TRUE(mInit, NS_ERROR_NOT_INITIALIZED);
   if (mHasCollator && (mLastStrength == newStrength))
     return NS_OK;
 
   nsresult res;
   res = CleanUpCollator();
   NS_ENSURE_SUCCESS(res, res);
 
-  NS_ENSURE_TRUE(mLocaleICU, NS_ERROR_NOT_INITIALIZED);
-
   UErrorCode status;
   status = U_ZERO_ERROR;
-  mCollatorICU = ucol_open(mLocaleICU, &status);
+  mCollatorICU = ucol_open(mLocale.get(), &status);
   NS_ENSURE_TRUE(U_SUCCESS(status), NS_ERROR_FAILURE);
 
   UCollationStrength strength;
   UColAttributeValue caseLevel;
   res = ConvertStrength(newStrength, &strength, &caseLevel);
   NS_ENSURE_SUCCESS(res, res);
 
   status = U_ZERO_ERROR;
@@ -137,32 +107,22 @@ nsresult nsCollationMacUC::CleanUpCollat
   if (mHasCollator) {
     ucol_close(mCollatorICU);
     mHasCollator = false;
   }
 
   return NS_OK;
 }
 
-NS_IMETHODIMP nsCollationMacUC::Initialize(nsILocale* locale) 
+NS_IMETHODIMP nsCollationMacUC::Initialize(const nsACString& locale)
 {
   NS_ENSURE_TRUE((!mInit), NS_ERROR_ALREADY_INITIALIZED);
   nsCOMPtr<nsILocale> appLocale;
 
-  nsresult rv;
-  if (!locale) {
-    nsCOMPtr<nsILocaleService> localeService = do_GetService(NS_LOCALESERVICE_CONTRACTID, &rv);
-    NS_ENSURE_SUCCESS(rv, rv);
-    rv = localeService->GetApplicationLocale(getter_AddRefs(appLocale));
-    NS_ENSURE_SUCCESS(rv, rv);
-    locale = appLocale;
-  }
-
-  rv = ConvertLocaleICU(locale, &mLocaleICU);
-  NS_ENSURE_SUCCESS(rv, rv);
+  mLocale = locale;
 
   mInit = true;
   return NS_OK;
 }
 
 NS_IMETHODIMP nsCollationMacUC::AllocateRawSortKey(int32_t strength, const nsAString& stringIn,
                                                    uint8_t** key, uint32_t* outLen)
 {
--- a/intl/locale/mac/nsCollationMacUC.h
+++ b/intl/locale/mac/nsCollationMacUC.h
@@ -1,19 +1,20 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsCollationMacUC_h_
 #define nsCollationMacUC_h_
 
+#include "mozilla/Attributes.h"
 #include "nsICollation.h"
 #include "nsCollation.h"
-#include "mozilla/Attributes.h"
+#include "nsString.h"
 
 #include "unicode/ucol.h"
 
 class nsCollationMacUC final : public nsICollation {
 
 public:
   nsCollationMacUC();
 
@@ -21,24 +22,23 @@ public:
   NS_DECL_ISUPPORTS
 
   // nsICollation interface
   NS_DECL_NSICOLLATION
 
 protected:
   ~nsCollationMacUC();
 
-  nsresult ConvertLocaleICU(nsILocale* aNSLocale, char** aICULocale);
   nsresult ConvertStrength(const int32_t aStrength,
                            UCollationStrength* aStrengthOut,
                            UColAttributeValue* aCaseLevelOut);
   nsresult EnsureCollator(const int32_t newStrength);
   nsresult CleanUpCollator(void);
 
 private:
   bool mInit;
   bool mHasCollator;
-  char* mLocaleICU;
+  nsCString mLocale;
   int32_t mLastStrength;
   UCollator* mCollatorICU;
 };
 
 #endif  /* nsCollationMacUC_h_ */
--- a/intl/locale/nsCollation.cpp
+++ b/intl/locale/nsCollation.cpp
@@ -5,38 +5,49 @@
 
 #include "nsCollation.h"
 #include "nsCollationCID.h"
 #include "nsUnicharUtils.h"
 #include "prmem.h"
 #include "nsIUnicodeEncoder.h"
 #include "nsServiceManagerUtils.h"
 #include "mozilla/dom/EncodingUtils.h"
+#include "mozilla/intl/LocaleService.h"
 
 using mozilla::dom::EncodingUtils;
 
 ////////////////////////////////////////////////////////////////////////////////
 
 NS_DEFINE_CID(kCollationCID, NS_COLLATION_CID);
 
 NS_IMPL_ISUPPORTS(nsCollationFactory, nsICollationFactory)
 
-nsresult nsCollationFactory::CreateCollation(nsILocale* locale, nsICollation** instancePtr)
+nsresult nsCollationFactory::CreateCollation(nsICollation** instancePtr)
+{
+  nsAutoCString appLocale;
+  mozilla::intl::LocaleService::GetInstance()->GetAppLocale(appLocale);
+
+  return CreateCollationForLocale(appLocale, instancePtr);
+}
+
+nsresult
+nsCollationFactory::CreateCollationForLocale(const nsACString& locale, nsICollation** instancePtr)
 {
   // Create a collation interface instance.
   //
   nsICollation *inst;
   nsresult res;
-  
+
   res = CallCreateInstance(kCollationCID, &inst);
   if (NS_FAILED(res)) {
     return res;
   }
 
   inst->Initialize(locale);
+
   *instancePtr = inst;
 
   return res;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 
 nsCollation::nsCollation()
--- a/intl/locale/nsCollation.h
+++ b/intl/locale/nsCollation.h
@@ -9,26 +9,27 @@
 
 
 #include "nsICollation.h"
 #include "nsCOMPtr.h"
 #include "mozilla/Attributes.h"
 
 class nsIUnicodeEncoder;
 
-// Create a collation interface for an input locale.
+// Create a collation interface for the current app's locale.
 // 
 class nsCollationFactory final : public nsICollationFactory {
 
   ~nsCollationFactory() {}
 
 public: 
   NS_DECL_ISUPPORTS 
 
-  NS_IMETHOD CreateCollation(nsILocale* locale, nsICollation** instancePtr) override;
+  NS_IMETHOD CreateCollation(nsICollation** instancePtr) override;
+  NS_IMETHOD CreateCollationForLocale(const nsACString& locale, nsICollation** instancePtr) override;
 
   nsCollationFactory() {}
 };
 
 
 struct nsCollation {
 
 public: 
--- a/intl/locale/nsICollation.idl
+++ b/intl/locale/nsICollation.idl
@@ -6,27 +6,28 @@
 #include "nsILocale.idl"
 
 interface nsICollation;
 
 [scriptable, uuid(04971e14-d6b3-4ada-8cbb-c3a13842b349)]
 interface nsICollationFactory : nsISupports
 {
     /**
-     * Create the collation for a given locale.
-     *
-     * Use NULL as the locale parameter to use the user's locale preference
-     * from the operating system.
+     * Create a new collation for the current application locale.
      *
-     * @param locale
-     *        The locale for which to create the collation or null to use
-     *        user preference.
-     * @return A collation for the given locale.
+     * @return A new collation.
      */
-    nsICollation CreateCollation(in nsILocale locale);
+    nsICollation CreateCollation();
+
+    /**
+     * Create a new collation for a given locale.
+     *
+     * @return A new collation.
+     */
+    nsICollation CreateCollationForLocale(in ACString locale);
 };
 
 [scriptable, uuid(b0132cc0-3786-4557-9874-910d7def5f93)]
 interface nsICollation : nsISupports {
 
   // use the primary comparison for the given locale - no flags
   const long kCollationStrengthDefault = 0;
 
@@ -38,17 +39,17 @@ interface nsICollation : nsISupports {
 
   // case sensitive collation (default)
   const long kCollationCaseSensitive = kCollationStrengthDefault;
 
   // case insensitive collation
   const long kCollationCaseInSensitive = (kCollationCaseInsensitiveAscii | kCollationAccentInsenstive);
 
   // init this interface to a specified locale (should only be called by collation factory)
-  void initialize(in nsILocale locale);
+  void initialize(in ACString locale);
 
   // compare two strings
   // result is same as strcmp
   long compareString(in long strength, in AString string1, in AString string2);
 
   // allocate sort key from input string
   // returns newly allocated key, and its band its byte length
   [noscript] void allocateRawSortKey(in long strength, 
--- a/intl/locale/tests/unit/test_collation_mac_icu.js
+++ b/intl/locale/tests/unit/test_collation_mac_icu.js
@@ -15,21 +15,19 @@ function run_test()
     "¡viva España!",
     "Österreich",
     "中国",
     "日本",
     "한국",
   ];
 
   function test(locale, expected) {
-    var localeSvc = Cc["@mozilla.org/intl/nslocaleservice;1"].
-      getService(Ci.nsILocaleService);
     var collator = Cc["@mozilla.org/intl/collation-factory;1"].
       createInstance(Ci.nsICollationFactory).
-      CreateCollation(localeSvc.newLocale(locale));
+      CreateCollationForLocale(locale);
     var strength = Ci.nsICollation.kCollationStrengthDefault;
     var actual = input.sort((x, y) => collator.compareString(strength, x,y));
     deepEqual(actual, expected, locale);
   }
 
   // Locale en-US; default options.
   test("en-US", [
     "¡viva España!",
--- a/intl/locale/unix/nsCollationUnix.cpp
+++ b/intl/locale/unix/nsCollationUnix.cpp
@@ -3,17 +3,16 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include  <locale.h>
 #include "prmem.h"
 #include "nsCollationUnix.h"
 #include "nsIServiceManager.h"
 #include "nsIComponentManager.h"
-#include "nsILocaleService.h"
 #include "nsIPlatformCharset.h"
 #include "nsPosixLocale.h"
 #include "nsCOMPtr.h"
 #include "nsUnicharUtils.h"
 #include "nsCRT.h"
 //#define DEBUG_UNIX_COLLATION
 
 inline void nsCollationUnix::DoSetLocale()
@@ -39,65 +38,31 @@ nsCollationUnix::nsCollationUnix() : mCo
 nsCollationUnix::~nsCollationUnix() 
 {
   if (mCollation)
     delete mCollation;
 }
 
 NS_IMPL_ISUPPORTS(nsCollationUnix, nsICollation)
 
-nsresult nsCollationUnix::Initialize(nsILocale* locale) 
+nsresult nsCollationUnix::Initialize(const nsACString& locale) 
 {
 #define kPlatformLocaleLength 64
   NS_ASSERTION(!mCollation, "Should only be initialized once");
 
   nsresult res;
 
   mCollation = new nsCollation;
 
-  // default platform locale
-  mLocale.Assign('C');
-
-  nsAutoString localeStr;
-  NS_NAMED_LITERAL_STRING(aCategory, "NSILOCALE_COLLATE##PLATFORM");
-
-  // get locale string, use app default if no locale specified
-  if (locale == nullptr) {
-    nsCOMPtr<nsILocaleService> localeService = 
-             do_GetService(NS_LOCALESERVICE_CONTRACTID, &res);
+  nsCOMPtr <nsIPlatformCharset> platformCharset = do_GetService(NS_PLATFORMCHARSET_CONTRACTID, &res);
+  if (NS_SUCCEEDED(res)) {
+    nsAutoCString mappedCharset;
+    res = platformCharset->GetDefaultCharsetForLocale(NS_ConvertUTF8toUTF16(locale), mappedCharset);
     if (NS_SUCCEEDED(res)) {
-      nsCOMPtr<nsILocale> appLocale;
-      res = localeService->GetApplicationLocale(getter_AddRefs(appLocale));
-      if (NS_SUCCEEDED(res)) {
-        res = appLocale->GetCategory(aCategory, localeStr);
-        NS_ASSERTION(NS_SUCCEEDED(res), "failed to get app locale info");
-      }
-    }
-  }
-  else {
-    res = locale->GetCategory(aCategory, localeStr);
-    NS_ASSERTION(NS_SUCCEEDED(res), "failed to get locale info");
-  }
-
-  // Get platform locale and charset name from locale, if available
-  if (NS_SUCCEEDED(res)) {
-    // keep the same behavior as 4.x as well as avoiding Linux collation key problem
-    if (localeStr.LowerCaseEqualsLiteral("en_us")) { // note: locale is in platform format
-      localeStr.Assign('C');
-    }
-
-    nsPosixLocale::GetPlatformLocale(localeStr, mLocale);
-
-    nsCOMPtr <nsIPlatformCharset> platformCharset = do_GetService(NS_PLATFORMCHARSET_CONTRACTID, &res);
-    if (NS_SUCCEEDED(res)) {
-      nsAutoCString mappedCharset;
-      res = platformCharset->GetDefaultCharsetForLocale(localeStr, mappedCharset);
-      if (NS_SUCCEEDED(res)) {
-        mCollation->SetCharset(mappedCharset.get());
-      }
+      mCollation->SetCharset(mappedCharset.get());
     }
   }
 
   return NS_OK;
 }
 
 
 nsresult nsCollationUnix::CompareString(int32_t strength,
--- a/intl/locale/windows/nsCollationWin.cpp
+++ b/intl/locale/windows/nsCollationWin.cpp
@@ -2,17 +2,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/. */
 
 
 #include "nsCollationWin.h"
 #include "nsIServiceManager.h"
 #include "nsIComponentManager.h"
-#include "nsILocaleService.h"
 #include "nsIPlatformCharset.h"
 #include "nsWin32Locale.h"
 #include "nsCOMPtr.h"
 #include "prmem.h"
 #include "plstr.h"
 #include <windows.h>
 
 #undef CompareString
@@ -25,59 +24,41 @@ nsCollationWin::nsCollationWin() : mColl
 }
 
 nsCollationWin::~nsCollationWin() 
 {
   if (mCollation)
     delete mCollation;
 }
 
-nsresult nsCollationWin::Initialize(nsILocale* locale) 
+nsresult nsCollationWin::Initialize(const nsACString& locale)
 {
   NS_ASSERTION(!mCollation, "Should only be initialized once.");
 
   nsresult res;
 
   mCollation = new nsCollation;
 
+  NS_ConvertASCIItoUTF16 wideLocale(locale);
+
   // default LCID (en-US)
   mLCID = 1033;
 
-  nsAutoString localeStr;
-
-  // get locale string, use app default if no locale specified
-  if (!locale) {
-    nsCOMPtr<nsILocaleService> localeService = 
-             do_GetService(NS_LOCALESERVICE_CONTRACTID);
-    if (localeService) {
-      nsCOMPtr<nsILocale> appLocale;
-      res = localeService->GetApplicationLocale(getter_AddRefs(appLocale));
-      if (NS_SUCCEEDED(res)) {
-        res = appLocale->GetCategory(NS_LITERAL_STRING("NSILOCALE_COLLATE"), 
-                                     localeStr);
-      }
-    }
-  }
-  else {
-    res = locale->GetCategory(NS_LITERAL_STRING("NSILOCALE_COLLATE"), 
-                              localeStr);
-  }
-
   // Get LCID and charset name from locale, if available
   LCID lcid;
-  res = nsWin32Locale::GetPlatformLocale(localeStr, &lcid);
+  res = nsWin32Locale::GetPlatformLocale(wideLocale, &lcid);
   if (NS_SUCCEEDED(res)) {
     mLCID = lcid;
   }
 
   nsCOMPtr <nsIPlatformCharset> platformCharset = 
       do_GetService(NS_PLATFORMCHARSET_CONTRACTID);
   if (platformCharset) {
     nsAutoCString mappedCharset;
-    res = platformCharset->GetDefaultCharsetForLocale(localeStr, mappedCharset);
+    res = platformCharset->GetDefaultCharsetForLocale(wideLocale, mappedCharset);
     if (NS_SUCCEEDED(res)) {
       mCollation->SetCharset(mappedCharset.get());
     }
   }
 
   return NS_OK;
 }
 
--- a/js/xpconnect/src/XPCLocale.cpp
+++ b/js/xpconnect/src/XPCLocale.cpp
@@ -121,31 +121,21 @@ private:
   }
 
   bool
   Compare(JSContext* cx, HandleString src1, HandleString src2, MutableHandleValue rval)
   {
     nsresult rv;
 
     if (!mCollation) {
-      nsCOMPtr<nsILocaleService> localeService =
-        do_GetService(NS_LOCALESERVICE_CONTRACTID, &rv);
+      nsCOMPtr<nsICollationFactory> colFactory =
+        do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID, &rv);
 
       if (NS_SUCCEEDED(rv)) {
-        nsCOMPtr<nsILocale> locale;
-        rv = localeService->GetApplicationLocale(getter_AddRefs(locale));
-
-        if (NS_SUCCEEDED(rv)) {
-          nsCOMPtr<nsICollationFactory> colFactory =
-            do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID, &rv);
-
-          if (NS_SUCCEEDED(rv)) {
-            rv = colFactory->CreateCollation(locale, getter_AddRefs(mCollation));
-          }
-        }
+        rv = colFactory->CreateCollation(getter_AddRefs(mCollation));
       }
 
       if (NS_FAILED(rv)) {
         xpc::Throw(cx, rv);
         return false;
       }
     }
 
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -3389,20 +3389,22 @@ nsLayoutUtils::ExpireDisplayPortOnAsyncS
       break;
     }
     nsIScrollableFrame* scrollAncestor = GetAsyncScrollableAncestorFrame(frame);
     if (!scrollAncestor) {
       break;
     }
     frame = do_QueryFrame(scrollAncestor);
     MOZ_ASSERT(frame);
+    if (!frame) {
+      break;
+    }
     MOZ_ASSERT(scrollAncestor->WantAsyncScroll() ||
       frame->PresContext()->PresShell()->GetRootScrollFrame() == frame);
-    if (nsLayoutUtils::AsyncPanZoomEnabled(frame) &&
-        nsLayoutUtils::HasDisplayPort(frame->GetContent())) {
+    if (nsLayoutUtils::HasDisplayPort(frame->GetContent())) {
       scrollAncestor->TriggerDisplayPortExpiration();
       // Stop after the first trigger. If it failed, there's no point in
       // continuing because all the rest of the frames we encounter are going
       // to be ancestors of |scrollAncestor| which will keep its displayport.
       // If the trigger succeeded, we stop because when the trigger executes
       // it will call this function again to trigger the next ancestor up the
       // chain.
       break;
--- a/layout/style/ServoCSSRuleList.cpp
+++ b/layout/style/ServoCSSRuleList.cpp
@@ -128,16 +128,20 @@ ServoCSSRuleList::InsertRule(const nsASt
   return rv;
 }
 
 nsresult
 ServoCSSRuleList::DeleteRule(uint32_t aIndex)
 {
   nsresult rv = Servo_CssRules_DeleteRule(mRawRules, aIndex);
   if (!NS_FAILED(rv)) {
+    uintptr_t rule = mRules[aIndex];
+    if (rule > kMaxRuleType) {
+      CastToPtr(rule)->Release();
+    }
     mRules.RemoveElementAt(aIndex);
   }
   return rv;
 }
 
 ServoCSSRuleList::~ServoCSSRuleList()
 {
   EnumerateInstantiatedRules([](css::Rule* rule) { rule->Release(); });
--- a/media/libvpx/moz.build
+++ b/media/libvpx/moz.build
@@ -34,17 +34,20 @@ elif CONFIG['CPU_ARCH'] == 'x86':
     elif CONFIG['OS_TARGET'] == 'Darwin':
         ASFLAGS += [ '-I%s/media/libvpx/config/mac/ia32/' % TOPSRCDIR ]
         CFLAGS += [ '-I%s/media/libvpx/config/mac/ia32/' % TOPSRCDIR ]
     else: # Android, Linux, BSDs, etc.
         ASFLAGS += [ '-I%s/media/libvpx/config/linux/ia32/' % TOPSRCDIR ]
         CFLAGS += [ '-I%s/media/libvpx/config/linux/ia32/' % TOPSRCDIR ]
 elif CONFIG['CPU_ARCH'] == 'arm':
     EXPORTS.vpx += files['ARM_EXPORTS']
-    ASFLAGS += [ '-I%s/media/libvpx/config/linux/arm/' % TOPSRCDIR ]
+    ASFLAGS += [
+        '-I%s/media/libvpx/config/linux/arm/' % TOPSRCDIR,
+        '-I%s/libvpx' % OBJDIR,
+    ]
     CFLAGS += [ '-I%s/media/libvpx/config/linux/arm/' % TOPSRCDIR ]
 
     arm_asm_files = files['ARM_SOURCES']
 
     if CONFIG['VPX_AS_CONVERSION']:
         SOURCES += sorted([
             "!%s.S" % f if f.endswith('.asm') else f for f in arm_asm_files
         ])
@@ -83,18 +86,16 @@ if CONFIG['OS_TARGET'] == 'Android':
     # the OS they're on, so do it for them.
     DEFINES['__linux__'] = True
 
     if not CONFIG['MOZ_WEBRTC']:
         SOURCES += [
             '%%%s/sources/android/cpufeatures/cpu-features.c' % CONFIG['ANDROID_NDK'],
         ]
 
-    ASFLAGS += ['-I%s/libvpx' % OBJDIR]
-
 if CONFIG['CLANG_CL'] or not CONFIG['_MSC_VER']:
     for f in SOURCES:
         if f.endswith('.c'):
             if 'sse2.c' in f:
                 SOURCES[f].flags += CONFIG['SSE2_FLAGS']
             if 'ssse3.c' in f:
                 SOURCES[f].flags += ['-mssse3']
             if 'sse4.c' in f:
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -662,17 +662,16 @@ public class BrowserApp extends GeckoApp
                             // shut down (in which case trying to perform UI changes, such as showing
                             // fragments below, will crash).
                             return;
                         }
 
                         final TabHistoryFragment fragment = TabHistoryFragment.newInstance(historyPageList, toIndex);
                         final FragmentManager fragmentManager = getSupportFragmentManager();
                         GeckoAppShell.vibrateOnHapticFeedbackEnabled(getResources().getIntArray(R.array.long_press_vibrate_msec));
-                        if (BrowserApp.this.isForegrounded())
                         fragment.show(R.id.tab_history_panel, fragmentManager.beginTransaction(), TAB_HISTORY_FRAGMENT_TAG);
                     }
                 });
             }
         });
         mBrowserToolbar.setTabHistoryController(tabHistoryController);
 
         final String action = intent.getAction();
--- a/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
@@ -1,23 +1,30 @@
 /* -*- 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.customtabs;
 
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.graphics.Bitmap;
 import android.graphics.Color;
+import android.graphics.drawable.BitmapDrawable;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
+import android.support.annotation.VisibleForTesting;
+import android.support.v4.view.MenuItemCompat;
 import android.support.v7.app.ActionBar;
 import android.support.v7.widget.Toolbar;
 import android.text.TextUtils;
 import android.util.Log;
+import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
 import android.view.Window;
 import android.view.WindowManager;
 import android.widget.TextView;
 
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.GeckoApp;
@@ -30,17 +37,16 @@ import java.lang.reflect.Field;
 
 import static android.support.customtabs.CustomTabsIntent.EXTRA_TOOLBAR_COLOR;
 
 public class CustomTabsActivity extends GeckoApp implements Tabs.OnTabsChangedListener {
     private static final String LOGTAG = "CustomTabsActivity";
     private static final String SAVED_TOOLBAR_COLOR = "SavedToolbarColor";
     private static final String SAVED_TOOLBAR_TITLE = "SavedToolbarTitle";
     private static final int NO_COLOR = -1;
-    private Toolbar toolbar;
 
     private ActionBar actionBar;
     private int tabId = -1;
     private boolean useDomainTitle = true;
 
     private int toolbarColor;
     private String toolbarTitle;
 
@@ -194,25 +200,59 @@ public class CustomTabsActivity extends 
             final Tab tab = tabs.getTab(lastSelectedTabId);
             if (tab == null) {
                 finish();
             }
         }
         super.onResume();
     }
 
+    @Override
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        insertActionButton(menu, getIntent());
+        return super.onPrepareOptionsMenu(menu);
+    }
+
     public boolean onOptionsItemSelected(MenuItem item) {
         switch (item.getItemId()) {
             case android.R.id.home:
                 finish();
                 return true;
+            case R.id.action_button:
+                onActionButtonClicked();
+                return true;
         }
         return super.onOptionsItemSelected(item);
     }
 
+    /**
+     * To insert a MenuItem (as an ActionButton) into Menu.
+     *
+     * @param menu   The options menu in which to place items.
+     * @param intent which to launch this activity
+     * @return the MenuItem which be created and inserted into menu. Otherwise, null.
+     */
+    @VisibleForTesting
+    MenuItem insertActionButton(Menu menu, Intent intent) {
+        if (!IntentUtil.hasActionButton(intent)) {
+            return null;
+        }
+
+        // TODO: Bug 1336373 - Action button icon should support tint
+        MenuItem item = menu.add(Menu.NONE,
+                R.id.action_button,
+                Menu.NONE,
+                IntentUtil.getActionButtonDescription(intent));
+        Bitmap bitmap = IntentUtil.getActionButtonIcon(intent);
+        item.setIcon(new BitmapDrawable(getResources(), bitmap));
+        MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_ALWAYS);
+
+        return item;
+    }
+
     private void updateActionBarWithToolbar(final Toolbar toolbar) {
         setSupportActionBar(toolbar);
         final ActionBar ab = getSupportActionBar();
         if (ab != null) {
             ab.setDisplayHomeAsUpEnabled(true);
         }
     }
 
@@ -224,9 +264,18 @@ public class CustomTabsActivity extends 
         toolbar.setBackgroundColor(toolbarColor);
 
         final Window window = getWindow();
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
             window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
             window.setStatusBarColor(ColorUtil.darken(toolbarColor, 0.25));
         }
     }
+
+    private void onActionButtonClicked() {
+        PendingIntent pendingIntent = IntentUtil.getActionButtonPendingIntent(getIntent());
+        try {
+            pendingIntent.send();
+        } catch (PendingIntent.CanceledException e) {
+            Log.w(LOGTAG, "Action Button clicked, but pending intent was canceled", e);
+        }
+    }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/customtabs/IntentUtil.java
+++ b/mobile/android/base/java/org/mozilla/gecko/customtabs/IntentUtil.java
@@ -1,16 +1,18 @@
 /* -*- 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.customtabs;
 
+import android.app.PendingIntent;
 import android.content.Intent;
+import android.graphics.Bitmap;
 import android.os.Build;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
 import android.support.customtabs.CustomTabsIntent;
 
 /**
  * A utility class for CustomTabsActivity to extract information from intent.
  * For example, this class helps to extract exit-animation resource id.
@@ -23,16 +25,85 @@ class IntentUtil {
     private static final String PREFIX = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
             ? "android:activity."
             : "android:";
     private static final String KEY_PACKAGE_NAME = PREFIX + "packageName";
     private static final String KEY_ANIM_ENTER_RES_ID = PREFIX + "animEnterRes";
     private static final String KEY_ANIM_EXIT_RES_ID = PREFIX + "animExitRes";
 
     /**
+     * To determine whether the intent has necessary information to build an Action-Button.
+     *
+     * @param intent which to launch a Custom-Tabs-Activity
+     * @return true, if intent has all necessary information.
+     */
+    static boolean hasActionButton(@NonNull Intent intent) {
+        return (getActionButtonBundle(intent) != null)
+                && (getActionButtonIcon(intent) != null)
+                && (getActionButtonDescription(intent) != null)
+                && (getActionButtonPendingIntent(intent) != null);
+    }
+
+    /**
+     * To extract bitmap icon from intent for Action-Button.
+     *
+     * @param intent which to launch a Custom-Tabs-Activity
+     * @return bitmap icon, if any. Otherwise, null.
+     */
+    static Bitmap getActionButtonIcon(@NonNull Intent intent) {
+        final Bundle bundle = getActionButtonBundle(intent);
+        return (bundle == null) ? null : (Bitmap) bundle.getParcelable(CustomTabsIntent.KEY_ICON);
+    }
+
+    /**
+     * To extract description from intent for Action-Button. This description is used for
+     * accessibility.
+     *
+     * @param intent which to launch a Custom-Tabs-Activity
+     * @return description, if any. Otherwise, null.
+     */
+    static String getActionButtonDescription(@NonNull Intent intent) {
+        final Bundle bundle = getActionButtonBundle(intent);
+        return (bundle == null) ? null : bundle.getString(CustomTabsIntent.KEY_DESCRIPTION);
+    }
+
+    /**
+     * To extract pending-intent from intent for Action-Button.
+     *
+     * @param intent which to launch a Custom-Tabs-Activity
+     * @return PendingIntent, if any. Otherwise, null.
+     */
+    static PendingIntent getActionButtonPendingIntent(@NonNull Intent intent) {
+        final Bundle bundle = getActionButtonBundle(intent);
+        return (bundle == null)
+                ? null
+                : (PendingIntent) bundle.getParcelable(CustomTabsIntent.KEY_PENDING_INTENT);
+    }
+
+    /**
+     * To know whether the Action-Button should be tinted.
+     *
+     * @param intent which to launch a Custom-Tabs-Activity
+     * @return true, if Action-Button should be tinted. Default value is false.
+     */
+    static boolean isActionButtonTinted(@NonNull Intent intent) {
+        return intent.getBooleanExtra(CustomTabsIntent.EXTRA_TINT_ACTION_BUTTON, false);
+    }
+
+    /**
+     * To extract extra Action-button bundle from an intent.
+     *
+     * @param intent which to launch a Custom-Tabs-Activity
+     * @return bundle for Action-Button, if any. Otherwise, null.
+     */
+    private static Bundle getActionButtonBundle(@NonNull Intent intent) {
+        return intent.getBundleExtra(CustomTabsIntent.EXTRA_ACTION_BUTTON_BUNDLE);
+    }
+
+    /**
      * To get package name of 3rd-party-app from an intent.
      * If the app defined extra exit-animation to use, it should also provide its package name
      * to get correct animation resource.
      *
      * @param intent which to launch a Custom-Tabs-Activity
      * @return package name, if the intent defined extra exit-animation bundle. Otherwise, null.
      */
     static String getAnimationPackageName(@NonNull Intent intent) {
--- a/mobile/android/base/java/org/mozilla/gecko/db/BrowserDatabaseHelper.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/BrowserDatabaseHelper.java
@@ -2073,17 +2073,17 @@ public final class BrowserDatabaseHelper
                 case 25:
                     upgradeDatabaseFrom24to25(db);
                     break;
 
                 case 26:
                     upgradeDatabaseFrom25to26(db);
                     break;
 
-                // case 27 occurs in UrlMetadataTable.onUpgrade
+                // case 27 occurs in URLImageDataTable.onUpgrade
 
                 case 28:
                     upgradeDatabaseFrom27to28(db);
                     break;
 
                 case 29:
                     upgradeDatabaseFrom28to29(db);
                     break;
--- a/mobile/android/base/java/org/mozilla/gecko/db/BrowserProvider.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/BrowserProvider.java
@@ -159,18 +159,18 @@ public class BrowserProvider extends Sha
     static final Map<String, String> THUMBNAILS_PROJECTION_MAP;
     static final Map<String, String> URL_ANNOTATIONS_PROJECTION_MAP;
     static final Map<String, String> VISIT_PROJECTION_MAP;
     static final Map<String, String> PAGE_METADATA_PROJECTION_MAP;
     static final Table[] sTables;
 
     static {
         sTables = new Table[] {
-            // See awful shortcut assumption hack in getURLMetadataTable.
-            new URLMetadataTable()
+            // See awful shortcut assumption hack in getURLImageDataTable.
+            new URLImageDataTable()
         };
         // We will reuse this.
         HashMap<String, String> map;
 
         // Bookmarks
         URI_MATCHER.addURI(BrowserContract.AUTHORITY, "bookmarks", BOOKMARKS);
         URI_MATCHER.addURI(BrowserContract.AUTHORITY, "bookmarks/#", BOOKMARKS_ID);
         URI_MATCHER.addURI(BrowserContract.AUTHORITY, "bookmarks/parents", BOOKMARKS_PARENT);
@@ -365,18 +365,18 @@ public class BrowserProvider extends Sha
     public void shutdown() {
         LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(mShrinkMemoryReceiver);
 
         super.shutdown();
     }
 
     // Convenience accessor.
     // Assumes structure of sTables!
-    private URLMetadataTable getURLMetadataTable() {
-        return (URLMetadataTable) sTables[0];
+    private URLImageDataTable getURLImageDataTable() {
+        return (URLImageDataTable) sTables[0];
     }
 
     private static boolean hasFaviconsInProjection(String[] projection) {
         if (projection == null) return true;
         for (int i = 0; i < projection.length; ++i) {
             if (projection[i].equals(FaviconColumns.FAVICON) ||
                 projection[i].equals(FaviconColumns.FAVICON_URL))
                 return true;
@@ -2199,17 +2199,17 @@ public class BrowserProvider extends Sha
                 + " AND " + History.URL + " IS NOT NULL"
                 + " UNION ALL SELECT " + Bookmarks.URL
                 + " FROM " + TABLE_BOOKMARKS
                 + " WHERE " + Bookmarks.IS_DELETED + " = 0"
                 + " AND " + Bookmarks.URL + " IS NOT NULL)";
 
         return deleteFavicons(uri, faviconSelection, null) +
                deleteThumbnails(uri, thumbnailSelection, null) +
-               getURLMetadataTable().deleteUnused(getWritableDatabase(uri));
+               getURLImageDataTable().deleteUnused(getWritableDatabase(uri));
     }
 
     @Override
     public ContentProviderResult[] applyBatch (ArrayList<ContentProviderOperation> operations)
         throws OperationApplicationException {
         final int numOperations = operations.size();
         final ContentProviderResult[] results = new ContentProviderResult[numOperations];
 
--- a/mobile/android/base/java/org/mozilla/gecko/db/LocalURLMetadata.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/LocalURLMetadata.java
@@ -29,27 +29,27 @@ import android.util.Log;
 import android.util.LruCache;
 
 // Holds metadata info about URLs. Supports some helper functions for getting back a HashMap of key value data.
 public class LocalURLMetadata implements URLMetadata {
     private static final String LOGTAG = "GeckoURLMetadata";
     private final Uri uriWithProfile;
 
     public LocalURLMetadata(String mProfile) {
-        uriWithProfile = DBUtils.appendProfileWithDefault(mProfile, URLMetadataTable.CONTENT_URI);
+        uriWithProfile = DBUtils.appendProfileWithDefault(mProfile, URLImageDataTable.CONTENT_URI);
     }
 
     // A list of columns in the table. It's used to simplify some loops for reading/writing data.
     private static final Set<String> COLUMNS;
     static {
         final HashSet<String> tempModel = new HashSet<>(4);
-        tempModel.add(URLMetadataTable.URL_COLUMN);
-        tempModel.add(URLMetadataTable.TILE_IMAGE_URL_COLUMN);
-        tempModel.add(URLMetadataTable.TILE_COLOR_COLUMN);
-        tempModel.add(URLMetadataTable.TOUCH_ICON_COLUMN);
+        tempModel.add(URLImageDataTable.URL_COLUMN);
+        tempModel.add(URLImageDataTable.TILE_IMAGE_URL_COLUMN);
+        tempModel.add(URLImageDataTable.TILE_COLOR_COLUMN);
+        tempModel.add(URLImageDataTable.TOUCH_ICON_COLUMN);
         COLUMNS = Collections.unmodifiableSet(tempModel);
     }
 
     // Store a cache of recent results. This number is chosen to match the max number of tiles on about:home
     private static final int CACHE_SIZE = 9;
     // Note: Members of this cache are unmodifiable.
     private final LruCache<String, Map<String, Object>> cache = new LruCache<String, Map<String, Object>>(CACHE_SIZE);
 
@@ -83,17 +83,17 @@ public class LocalURLMetadata implements
                 ArrayList<Integer> sizes = new ArrayList<Integer>(icons.length());
                 while (keys.hasNext()) {
                     sizes.add(new Integer(keys.next()));
                 }
 
                 final int bestSize = LoadFaviconResult.selectBestSizeFromList(sizes, preferredSize);
                 final String iconURL = icons.getString(Integer.toString(bestSize));
 
-                data.put(URLMetadataTable.TOUCH_ICON_COLUMN, iconURL);
+                data.put(URLImageDataTable.TOUCH_ICON_COLUMN, iconURL);
             }
         } catch (JSONException e) {
             Log.w(LOGTAG, "Exception processing touchIconList for LocalURLMetadata; ignoring.", e);
         }
 
         return Collections.unmodifiableMap(data);
     }
 
@@ -164,39 +164,39 @@ public class LocalURLMetadata implements
             }
         }
 
         // If everything was in the cache, we're done!
         if (urlsToQuery.size() == 0) {
             return Collections.unmodifiableMap(data);
         }
 
-        final String selection = DBUtils.computeSQLInClause(urlsToQuery.size(), URLMetadataTable.URL_COLUMN);
+        final String selection = DBUtils.computeSQLInClause(urlsToQuery.size(), URLImageDataTable.URL_COLUMN);
         List<String> columns = requestedColumns;
         // We need the url to build our final HashMap, so we force it to be included in the query.
-        if (!columns.contains(URLMetadataTable.URL_COLUMN)) {
+        if (!columns.contains(URLImageDataTable.URL_COLUMN)) {
             // The requestedColumns may be immutable (e.g. if the caller used Collections.singletonList), hence
             // we have to create a copy.
             columns = new ArrayList<String>(columns);
-            columns.add(URLMetadataTable.URL_COLUMN);
+            columns.add(URLImageDataTable.URL_COLUMN);
         }
 
         final Cursor cursor = cr.query(uriWithProfile,
                                        columns.toArray(new String[columns.size()]), // columns,
                                        selection, // selection
                                        urlsToQuery.toArray(new String[urlsToQuery.size()]), // selectionargs
                                        null);
         try {
             if (!cursor.moveToFirst()) {
                 return Collections.unmodifiableMap(data);
             }
 
             do {
                 final Map<String, Object> metadata = fromCursor(cursor);
-                final String url = cursor.getString(cursor.getColumnIndexOrThrow(URLMetadataTable.URL_COLUMN));
+                final String url = cursor.getString(cursor.getColumnIndexOrThrow(URLImageDataTable.URL_COLUMN));
 
                 data.put(url, metadata);
                 cache.put(url, metadata);
             } while (cursor.moveToNext());
 
         } finally {
             cursor.close();
         }
@@ -225,16 +225,16 @@ public class LocalURLMetadata implements
 
             if (values.size() == 0) {
                 return;
             }
 
             Uri uri = uriWithProfile.buildUpon()
                                  .appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true")
                                  .build();
-            cr.update(uri, values, URLMetadataTable.URL_COLUMN + "=?", new String[] {
-                (String) data.get(URLMetadataTable.URL_COLUMN)
+            cr.update(uri, values, URLImageDataTable.URL_COLUMN + "=?", new String[] {
+                (String) data.get(URLImageDataTable.URL_COLUMN)
             });
         } catch (Exception ex) {
             Log.e(LOGTAG, "error saving", ex);
         }
     }
 }
rename from mobile/android/base/java/org/mozilla/gecko/db/URLMetadataTable.java
rename to mobile/android/base/java/org/mozilla/gecko/db/URLImageDataTable.java
--- a/mobile/android/base/java/org/mozilla/gecko/db/URLMetadataTable.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/URLImageDataTable.java
@@ -8,33 +8,33 @@ package org.mozilla.gecko.db;
 
 import org.mozilla.gecko.db.BrowserContract.Bookmarks;
 import org.mozilla.gecko.db.BrowserContract.History;
 
 import android.database.sqlite.SQLiteDatabase;
 import android.net.Uri;
 
 // Holds metadata info about urls. Supports some helper functions for getting back a HashMap of key value data.
-public class URLMetadataTable extends BaseTable {
-    private static final String LOGTAG = "GeckoURLMetadataTable";
+public class URLImageDataTable extends BaseTable {
+    private static final String LOGTAG = "GeckoURLImageDataTable";
 
     private static final String TABLE = "metadata"; // Name of the table in the db
     private static final int TABLE_ID_NUMBER = BrowserProvider.METADATA;
 
     // Uri for querying this table
     public static final Uri CONTENT_URI = Uri.withAppendedPath(BrowserContract.AUTHORITY_URI, "metadata");
 
     // Columns in the table
     public static final String ID_COLUMN = "id";
     public static final String URL_COLUMN = "url";
     public static final String TILE_IMAGE_URL_COLUMN = "tileImage";
     public static final String TILE_COLOR_COLUMN = "tileColor";
     public static final String TOUCH_ICON_COLUMN = "touchIcon";
 
-    URLMetadataTable() { }
+    URLImageDataTable() { }
 
     @Override
     protected String getTable() {
         return TABLE;
     }
 
     @Override
     public void onCreate(SQLiteDatabase db) {
--- a/mobile/android/base/java/org/mozilla/gecko/home/TopSitesPanel.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/TopSitesPanel.java
@@ -1,17 +1,17 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.home;
 
-import static org.mozilla.gecko.db.URLMetadataTable.TILE_COLOR_COLUMN;
-import static org.mozilla.gecko.db.URLMetadataTable.TILE_IMAGE_URL_COLUMN;
+import static org.mozilla.gecko.db.URLImageDataTable.TILE_COLOR_COLUMN;
+import static org.mozilla.gecko.db.URLImageDataTable.TILE_IMAGE_URL_COLUMN;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Future;
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -377,18 +377,18 @@ gbjar.sources += ['java/org/mozilla/geck
     'db/SearchHistoryProvider.java',
     'db/SharedBrowserDatabaseProvider.java',
     'db/SQLiteBridgeContentProvider.java',
     'db/SuggestedSites.java',
     'db/Table.java',
     'db/TabsAccessor.java',
     'db/TabsProvider.java',
     'db/UrlAnnotations.java',
+    'db/URLImageDataTable.java',
     'db/URLMetadata.java',
-    'db/URLMetadataTable.java',
     'delegates/BookmarkStateChangeDelegate.java',
     'delegates/BrowserAppDelegate.java',
     'delegates/BrowserAppDelegateWithReference.java',
     'delegates/OfflineTabStatusDelegate.java',
     'delegates/ScreenshotDelegate.java',
     'delegates/TabsTrayVisibilityAwareDelegate.java',
     'DevToolsAuthHelper.java',
     'distribution/Distribution.java',
--- a/mobile/android/base/resources/values/ids.xml
+++ b/mobile/android/base/resources/values/ids.xml
@@ -14,10 +14,11 @@
     <item type="id" name="recycler_view_click_support" />
     <item type="id" name="range_list"/>
     <item type="id" name="pref_header_general"/>
     <item type="id" name="pref_header_privacy"/>
     <item type="id" name="pref_header_search"/>
     <item type="id" name="updateServicePermissionNotification" />
     <item type="id" name="websiteContentNotification" />
     <item type="id" name="foregroundNotification" />
+    <item type="id" name="action_button"/>
 
 </resources>
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/HardwareUtils.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/HardwareUtils.java
@@ -1,15 +1,16 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.SysInfo;
 
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.os.Build;
 import android.util.Log;
 
@@ -86,16 +87,22 @@ public final class HardwareUtils {
     public static boolean isX86System() {
         return Build.CPU_ABI != null && Build.CPU_ABI.equals("x86");
     }
 
     /**
      * @return false if the current system is not supported (e.g. APK/system ABI mismatch).
      */
     public static boolean isSupportedSystem() {
+        // We've had crash reports from users on API 10 (with minSDK==15). That shouldn't even install,
+        // but since it does we need to protect against it:
+        if (Build.VERSION.SDK_INT < AppConstants.Versions.MIN_SDK_VERSION) {
+            return false;
+        }
+
         // See http://developer.android.com/ndk/guides/abis.html
         final boolean isSystemARM = isARMSystem();
         final boolean isSystemX86 = isX86System();
 
         boolean isAppARM = BuildConfig.ANDROID_CPU_ARCH.startsWith("armeabi-v7a");
         boolean isAppX86 = BuildConfig.ANDROID_CPU_ARCH.startsWith("x86");
 
         // Only reject known incompatible ABIs. Better safe than sorry.
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/customtabs/TestCustomTabsActivity.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/customtabs/TestCustomTabsActivity.java
@@ -1,26 +1,34 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.customtabs;
 
+import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
 import android.support.annotation.AnimRes;
 import android.support.customtabs.CustomTabsIntent;
+import android.view.Menu;
+import android.view.MenuItem;
 
 import junit.framework.Assert;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.internal.util.reflection.Whitebox;
+import org.mozilla.gecko.R;
 import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.robolectric.RuntimeEnvironment;
+import org.robolectric.fakes.RoboMenu;
 
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
@@ -37,16 +45,17 @@ public class TestCustomTabsActivity {
     private final int exitRes = 0x456; // arbitrary number as animation resource id
 
     @Before
     public void setUp() {
         spyContext = spy(RuntimeEnvironment.application);
         doReturn(THIRD_PARTY_PACKAGE_NAME).when(spyContext).getPackageName();
 
         spyActivity = spy(new CustomTabsActivity());
+        doReturn(RuntimeEnvironment.application.getResources()).when(spyActivity).getResources();
 
         final CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
         builder.setExitAnimations(spyContext, enterRes, exitRes);
         final Intent i = builder.build().intent;
     }
 
     /**
      * Activity should not call overridePendingTransition if custom animation does not exist.
@@ -86,9 +95,36 @@ public class TestCustomTabsActivity {
         builder.setExitAnimations(spyContext, enterRes, exitRes);
         final Intent i = builder.build().intent;
 
         doReturn(i).when(spyActivity).getIntent();
         Whitebox.setInternalState(spyActivity, "usingCustomAnimation", true);
 
         Assert.assertEquals(THIRD_PARTY_PACKAGE_NAME, spyActivity.getPackageName());
     }
+
+    @Test
+    public void testInsertActionButton() {
+        // create properties for CustomTabsIntent
+        final String description = "Description";
+        final Intent actionIntent = new Intent(Intent.ACTION_VIEW);
+        final int reqCode = 0x123;
+        final PendingIntent pendingIntent = PendingIntent.getActivities(spyContext,
+                reqCode,
+                new Intent[]{actionIntent},
+                PendingIntent.FLAG_CANCEL_CURRENT);
+        final Bitmap bitmap = BitmapFactory.decodeResource(
+                spyContext.getResources(),
+                R.drawable.ic_action_settings); // arbitrary icon resource
+
+        // To create a CustomTabsIntent which is asking for ActionButton.
+        final CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
+        builder.setActionButton(bitmap, description, pendingIntent, true);
+
+        // CustomTabsActivity should return a MenuItem with corresponding attributes.
+        Menu menu = new RoboMenu(spyContext);
+        MenuItem item = spyActivity.insertActionButton(menu, builder.build().intent);
+        Assert.assertNotNull(item);
+        Assert.assertEquals(item.getTitle(), description);
+        Assert.assertEquals(0, item.getOrder()); // should be the first one
+        Assert.assertTrue(item.isVisible());
+    }
 }
\ No newline at end of file
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/customtabs/TestIntentUtil.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/customtabs/TestIntentUtil.java
@@ -1,41 +1,87 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.customtabs;
 
+import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
 import android.support.annotation.AnimRes;
 import android.support.customtabs.CustomTabsIntent;
 
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mozilla.gecko.R;
 import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.robolectric.RuntimeEnvironment;
 
+import java.util.Objects;
+
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
 
 @RunWith(TestRunner.class)
 public class TestIntentUtil {
 
     private static final String THIRD_PARTY_PACKAGE_NAME = "mozilla.unit.test";
     private Context spyContext;  // 3rd party app context
 
     @Before
     public void setUp() {
         spyContext = spy(RuntimeEnvironment.application);
         doReturn(THIRD_PARTY_PACKAGE_NAME).when(spyContext).getPackageName();
     }
 
     @Test
+    public void testIntentWithActionButton() {
+        // create properties for CustomTabsIntent
+        final String description = "Description";
+        final boolean tinted = true;
+        final Intent actionIntent = new Intent(Intent.ACTION_VIEW);
+        final int reqCode = 0x123;
+        final PendingIntent pendingIntent = PendingIntent.getActivities(spyContext,
+                reqCode,
+                new Intent[]{actionIntent},
+                PendingIntent.FLAG_CANCEL_CURRENT);
+
+        final Bitmap bitmap = BitmapFactory.decodeResource(
+                spyContext.getResources(),
+                R.drawable.ic_action_settings); // arbitrary icon resource
+
+        final CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
+        builder.setActionButton(bitmap, description, pendingIntent, tinted);
+
+        Intent intent = builder.build().intent;
+        Assert.assertTrue(IntentUtil.hasActionButton(intent));
+        Assert.assertEquals(tinted, IntentUtil.isActionButtonTinted(intent));
+        Assert.assertEquals(bitmap, IntentUtil.getActionButtonIcon(intent));
+        Assert.assertEquals(description, IntentUtil.getActionButtonDescription(intent));
+        Assert.assertTrue(
+                Objects.equals(pendingIntent, IntentUtil.getActionButtonPendingIntent(intent)));
+    }
+
+    @Test
+    public void testIntentWithoutActionButton() {
+        final CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
+
+        Intent intent = builder.build().intent;
+        Assert.assertFalse(IntentUtil.hasActionButton(intent));
+        Assert.assertFalse(IntentUtil.isActionButtonTinted(intent));
+        Assert.assertNull(IntentUtil.getActionButtonIcon(intent));
+        Assert.assertNull(IntentUtil.getActionButtonDescription(intent));
+        Assert.assertNull(IntentUtil.getActionButtonPendingIntent(intent));
+    }
+
+    @Test
     public void testIntentWithCustomAnimation() {
         @AnimRes final int enterRes = 0x123; // arbitrary number as animation resource id
         @AnimRes final int exitRes = 0x456; // arbitrary number as animation resource id
 
         final CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
         builder.setExitAnimations(spyContext, enterRes, exitRes);
         final Intent i = builder.build().intent;
 
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testBrowserProvider.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testBrowserProvider.java
@@ -11,17 +11,17 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Random;
 
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.db.BrowserContract.UrlAnnotations.SyncStatus;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.db.URLMetadata;
-import org.mozilla.gecko.db.URLMetadataTable;
+import org.mozilla.gecko.db.URLImageDataTable;
 
 import android.content.ContentProviderOperation;
 import android.content.ContentProviderResult;
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.OperationApplicationException;
 import android.database.Cursor;
 import android.net.Uri;
@@ -193,20 +193,20 @@ public class testBrowserProvider extends
         thumbnailEntry.put(BrowserContract.Thumbnails.DATA, data.getBytes("UTF8"));
 
         return thumbnailEntry;
     }
 
     private ContentValues createUrlMetadataEntry(final String url, final String tileImage, final String tileColor,
                 final String touchIcon) {
         final ContentValues values = new ContentValues();
-        values.put(URLMetadataTable.URL_COLUMN, url);
-        values.put(URLMetadataTable.TILE_IMAGE_URL_COLUMN, tileImage);
-        values.put(URLMetadataTable.TILE_COLOR_COLUMN, tileColor);
-        values.put(URLMetadataTable.TOUCH_ICON_COLUMN, touchIcon);
+        values.put(URLImageDataTable.URL_COLUMN, url);
+        values.put(URLImageDataTable.TILE_IMAGE_URL_COLUMN, tileImage);
+        values.put(URLImageDataTable.TILE_COLOR_COLUMN, tileColor);
+        values.put(URLImageDataTable.TOUCH_ICON_COLUMN, touchIcon);
         return values;
     }
 
     private ContentValues createUrlAnnotationEntry(final String url, final String key, final String value,
                 final long dateCreated) {
         final ContentValues values = new ContentValues();
         values.put(BrowserContract.UrlAnnotations.URL, url);
         values.put(BrowserContract.UrlAnnotations.KEY, key);
@@ -256,18 +256,18 @@ public class testBrowserProvider extends
     private Cursor getUrlAnnotationByUrl(final String url) throws Exception {
         return mProvider.query(BrowserContract.UrlAnnotations.CONTENT_URI, null,
                 BrowserContract.UrlAnnotations.URL + " = ?",
                 new String[] { url },
                 null);
     }
 
     private Cursor getUrlMetadataByUrl(final String url) throws Exception {
-        return mProvider.query(URLMetadataTable.CONTENT_URI, null,
-                URLMetadataTable.URL_COLUMN + " = ?",
+        return mProvider.query(URLImageDataTable.CONTENT_URI, null,
+                URLImageDataTable.URL_COLUMN + " = ?",
                 new String[] { url },
                 null);
     }
 
     @Override
     public void setUp() throws Exception {
         super.setUp(sBrowserProviderCallable, BrowserContract.AUTHORITY, "browser.db");
 
@@ -1530,19 +1530,19 @@ public class testBrowserProvider extends
         final String url2 = "http://hello.org";
 
         private void testInsertionViaContentProvider() throws Exception {
             final String tileImage = "http://mozilla.org/tileImage.png";
             final String tileColor = "#FF0000";
             final String touchIcon = "http://mozilla.org/touchIcon.png";
 
             // We can only use update since the redirection machinery doesn't exist for insert
-            mProvider.update(URLMetadataTable.CONTENT_URI.buildUpon().appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true").build(),
+            mProvider.update(URLImageDataTable.CONTENT_URI.buildUpon().appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true").build(),
                     createUrlMetadataEntry(url1, tileImage, tileColor, touchIcon),
-                    URLMetadataTable.URL_COLUMN + "=?",
+                    URLImageDataTable.URL_COLUMN + "=?",
                     new String[] {url1}
             );
 
             final Cursor c = getUrlMetadataByUrl(url1);
             try {
                 mAsserter.is(c.getCount(), 1, "URL metadata inserted via Content Provider not found");
             } finally {
                 c.close();
@@ -1550,20 +1550,20 @@ public class testBrowserProvider extends
         }
 
         private void testInsertionViaUrlMetadata() throws Exception {
             final String tileImage = "http://hello.org/tileImage.png";
             final String tileColor = "#FF0000";
             final String touchIcon = "http://hello.org/touchIcon.png";
 
             final Map<String, Object> data = new HashMap<>();
-            data.put(URLMetadataTable.URL_COLUMN, url2);
-            data.put(URLMetadataTable.TILE_IMAGE_URL_COLUMN, tileImage);
-            data.put(URLMetadataTable.TILE_COLOR_COLUMN, tileColor);
-            data.put(URLMetadataTable.TOUCH_ICON_COLUMN, touchIcon);
+            data.put(URLImageDataTable.URL_COLUMN, url2);
+            data.put(URLImageDataTable.TILE_IMAGE_URL_COLUMN, tileImage);
+            data.put(URLImageDataTable.TILE_COLOR_COLUMN, tileColor);
+            data.put(URLImageDataTable.TOUCH_ICON_COLUMN, touchIcon);
 
             BrowserDB.from(getTestProfile()).getURLMetadata().save(mResolver, data);
 
             final Cursor c = getUrlMetadataByUrl(url2);
             try {
                 mAsserter.is(c.moveToFirst(), true, "URL metadata inserted via UrlMetadata not found");
             } finally {
                 c.close();
@@ -1581,53 +1581,53 @@ public class testBrowserProvider extends
             URLMetadata metadata = BrowserDB.from(getTestProfile()).getURLMetadata();
 
             Map<String, Map<String, Object>> results;
             Map<String, Object> urlData;
 
             // 1: retrieve just touch Icons for URL 1
             results = metadata.getForURLs(mResolver,
                     Collections.singletonList(url1),
-                    Collections.singletonList(URLMetadataTable.TOUCH_ICON_COLUMN));
+                    Collections.singletonList(URLImageDataTable.TOUCH_ICON_COLUMN));
 
             mAsserter.is(results.containsKey(url1), true, "URL 1 not found in results");
 
             urlData = results.get(url1);
-            mAsserter.is(urlData.containsKey(URLMetadataTable.TOUCH_ICON_COLUMN), true, "touchIcon column missing in UrlMetadata results");
+            mAsserter.is(urlData.containsKey(URLImageDataTable.TOUCH_ICON_COLUMN), true, "touchIcon column missing in UrlMetadata results");
 
             // 2: retrieve just tile color for URL 2
             results = metadata.getForURLs(mResolver,
                     Collections.singletonList(url2),
-                    Collections.singletonList(URLMetadataTable.TILE_COLOR_COLUMN));
+                    Collections.singletonList(URLImageDataTable.TILE_COLOR_COLUMN));
 
             mAsserter.is(results.containsKey(url2), true, "URL 2 not found in results");
 
             urlData = results.get(url2);
-            mAsserter.is(urlData.containsKey(URLMetadataTable.TILE_COLOR_COLUMN), true, "touchIcon column missing in UrlMetadata results");
+            mAsserter.is(urlData.containsKey(URLImageDataTable.TILE_COLOR_COLUMN), true, "touchIcon column missing in UrlMetadata results");
 
 
             // 3: retrieve all columns for both URLs
             final List<String> urls = Arrays.asList(url1, url2);
 
             results = metadata.getForURLs(mResolver,
                     urls,
-                    Arrays.asList(URLMetadataTable.TILE_IMAGE_URL_COLUMN,
-                            URLMetadataTable.TILE_COLOR_COLUMN,
-                            URLMetadataTable.TOUCH_ICON_COLUMN
+                    Arrays.asList(URLImageDataTable.TILE_IMAGE_URL_COLUMN,
+                            URLImageDataTable.TILE_COLOR_COLUMN,
+                            URLImageDataTable.TOUCH_ICON_COLUMN
                     ));
 
             mAsserter.is(results.containsKey(url1), true, "URL 1 not found in results");
             mAsserter.is(results.containsKey(url2), true, "URL 2 not found in results");
 
 
             for (final String url : urls) {
                 urlData = results.get(url);
-                mAsserter.is(urlData.containsKey(URLMetadataTable.TILE_IMAGE_URL_COLUMN), true, "touchIcon column missing in UrlMetadata results");
-                mAsserter.is(urlData.containsKey(URLMetadataTable.TILE_COLOR_COLUMN), true, "touchIcon column missing in UrlMetadata results");
-                mAsserter.is(urlData.containsKey(URLMetadataTable.TOUCH_ICON_COLUMN), true, "touchIcon column missing in UrlMetadata results");
+                mAsserter.is(urlData.containsKey(URLImageDataTable.TILE_IMAGE_URL_COLUMN), true, "touchIcon column missing in UrlMetadata results");
+                mAsserter.is(urlData.containsKey(URLImageDataTable.TILE_COLOR_COLUMN), true, "touchIcon column missing in UrlMetadata results");
+                mAsserter.is(urlData.containsKey(URLImageDataTable.TOUCH_ICON_COLUMN), true, "touchIcon column missing in UrlMetadata results");
             }
         }
     }
 
     private class TestCombinedView extends TestCase {
         @Override
         public void test() throws Exception {
             final String TITLE_1 = "Test Page 1";
--- a/moz.configure
+++ b/moz.configure
@@ -152,16 +152,27 @@ option('--build-backends', nargs='+', de
        choices=build_backends_choices, help='Build backends to generate')
 
 @depends('--build-backends')
 def build_backends(backends):
     return backends
 
 set_config('BUILD_BACKENDS', build_backends)
 
+# Determine whether to build the gtest xul. This happens in automation
+# on Desktop platforms with the exception of Windows PGO, where linking
+# xul-gtest.dll takes too long.
+@depends('MOZ_PGO', build_project, target, 'MOZ_AUTOMATION',
+         when='--enable-compile-environment')
+def build_gtest(pgo, build_project, target, automation):
+    if (automation and build_project == 'browser' and
+        not (pgo and target.os == 'WINNT')):
+        return True
+
+set_config('LINK_GTEST_DURING_COMPILE', build_gtest)
 
 # Awk detection
 # ==============================================================
 awk = check_prog('AWK', ('gawk', 'mawk', 'nawk', 'awk'))
 
 # Until the AWK variable is not necessary in old-configure
 @depends(awk)
 def awk_for_old_configure(value):
--- a/netwerk/base/nsDirectoryIndexStream.cpp
+++ b/netwerk/base/nsDirectoryIndexStream.cpp
@@ -17,18 +17,16 @@
 #include "nsEscape.h"
 #include "nsDirectoryIndexStream.h"
 #include "mozilla/Logging.h"
 #include "prtime.h"
 #include "nsISimpleEnumerator.h"
 #ifdef THREADSAFE_I18N
 #include "nsCollationCID.h"
 #include "nsICollation.h"
-#include "nsILocale.h"
-#include "nsILocaleService.h"
 #endif
 #include "nsIFile.h"
 #include "nsURLHelper.h"
 #include "nsNativeCharsetUtils.h"
 
 // NOTE: This runs on the _file transport_ thread.
 // The problem is that now that we're actually doing something with the data,
 // we want to do stuff like i18n sorting. However, none of the collation stuff
@@ -115,30 +113,22 @@ nsDirectoryIndexStream::Init(nsIFile* aD
         if (NS_SUCCEEDED(rv)) {
             nsCOMPtr<nsIFile> file = do_QueryInterface(elem);
             if (file)
                 mArray.AppendObject(file); // addrefs
         }
     }
 
 #ifdef THREADSAFE_I18N
-    nsCOMPtr<nsILocaleService> ls = do_GetService(NS_LOCALESERVICE_CONTRACTID,
-                                                  &rv);
-    if (NS_FAILED(rv)) return rv;
-
-    nsCOMPtr<nsILocale> locale;
-    rv = ls->GetApplicationLocale(getter_AddRefs(locale));
-    if (NS_FAILED(rv)) return rv;
-    
     nsCOMPtr<nsICollationFactory> cf = do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID,
                                                          &rv);
     if (NS_FAILED(rv)) return rv;
 
     nsCOMPtr<nsICollation> coll;
-    rv = cf->CreateCollation(locale, getter_AddRefs(coll));
+    rv = cf->CreateCollation(getter_AddRefs(coll));
     if (NS_FAILED(rv)) return rv;
 
     mArray.Sort(compare, coll);
 #else
     mArray.Sort(compare, nullptr);
 #endif
 
     mBuf.AppendLiteral("300: ");
--- a/netwerk/protocol/about/nsAboutCache.cpp
+++ b/netwerk/protocol/about/nsAboutCache.cpp
@@ -560,17 +560,17 @@ nsAboutCache::Channel::FlushBuffer()
     }
 
     return rv;
 }
 
 NS_IMETHODIMP
 nsAboutCache::GetURIFlags(nsIURI *aURI, uint32_t *result)
 {
-    *result = 0;
+    *result = nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT;
     return NS_OK;
 }
 
 // static
 nsresult
 nsAboutCache::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult)
 {
     nsAboutCache* about = new nsAboutCache();
--- a/netwerk/protocol/about/nsAboutCacheEntry.cpp
+++ b/netwerk/protocol/about/nsAboutCacheEntry.cpp
@@ -107,17 +107,18 @@ nsAboutCacheEntry::NewChannel(nsIURI* ur
     channel.forget(result);
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsAboutCacheEntry::GetURIFlags(nsIURI *aURI, uint32_t *result)
 {
-    *result = nsIAboutModule::HIDE_FROM_ABOUTABOUT;
+    *result = nsIAboutModule::HIDE_FROM_ABOUTABOUT |
+              nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT;
     return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 // nsAboutCacheEntry::Channel
 
 nsresult
 nsAboutCacheEntry::Channel::Init(nsIURI* uri, nsILoadInfo* aLoadInfo)
--- a/netwerk/test/browser/browser.ini
+++ b/netwerk/test/browser/browser.ini
@@ -1,10 +1,11 @@
 [DEFAULT]
 support-files =
   dummy.html
 
+[browser_about_cache.js]
 [browser_NetUtil.js]
 [browser_child_resource.js]
 skip-if = e10s && debug && os == "linux" && bits == 64
 [browser_post_file.js]
 [browser_nsIFormPOSTActionChannel.js]
 skip-if = e10s # protocol handler and channel does not work in content process
new file mode 100644
--- /dev/null
+++ b/netwerk/test/browser/browser_about_cache.js
@@ -0,0 +1,71 @@
+"use strict";
+
+/**
+ * Open a dummy page, then open about:cache and verify the opened page shows up in the cache.
+ */
+add_task(function*() {
+  const kRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/",
+                                                    "https://example.com/");
+  const kTestPage = kRoot + "dummy.html";
+  // Open the dummy page to get it cached.
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, kTestPage, true);
+  yield BrowserTestUtils.removeTab(tab);
+
+  tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:cache", true);
+  let expectedPageCheck = function(uri) {
+    info("Saw load for " + uri);
+    // Can't easily use searchParms and new URL() because it's an about: URI...
+    return uri.startsWith("about:cache?") && uri.includes("storage=disk");
+  };
+  let diskPageLoaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, expectedPageCheck);
+  yield ContentTask.spawn(tab.linkedBrowser, null, function() {
+    ok(!content.document.nodePrincipal.isSystemPrincipal,
+       "about:cache should not have system principal");
+    let principalURI = content.document.nodePrincipal.URI;
+    let channel = content.document.docShell.currentDocumentChannel;
+    ok(!channel.loadInfo.loadingPrincipal, "Loading principal should be null.");
+    is(principalURI && principalURI.spec, content.document.location.href, "Principal matches location");
+    let links = [... content.document.querySelectorAll("a[href*=disk]")];
+    is(links.length, 1, "Should have 1 link to the disk entries");
+    links[0].click();
+  });
+  yield diskPageLoaded;
+  info("about:cache disk subpage loaded");
+
+  expectedPageCheck = function(uri) {
+    info("Saw load for " + uri);
+    return uri.startsWith("about:cache-entry") && uri.includes("dummy.html");
+  };
+  let triggeringURISpec = tab.linkedBrowser.currentURI.spec;
+  let entryLoaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, expectedPageCheck);
+  yield ContentTask.spawn(tab.linkedBrowser, kTestPage, function(kTestPage) {
+    ok(!content.document.nodePrincipal.isSystemPrincipal,
+       "about:cache with query params should still not have system principal");
+    let principalURI = content.document.nodePrincipal.URI;
+    is(principalURI && principalURI.spec, content.document.location.href, "Principal matches location");
+    let channel = content.document.docShell.currentDocumentChannel;
+    principalURI = channel.loadInfo.triggeringPrincipal.URI;
+    is(principalURI && principalURI.spec, "about:cache", "Triggering principal matches previous location");
+    ok(!channel.loadInfo.loadingPrincipal, "Loading principal should be null.");
+    let links = [... content.document.querySelectorAll("a[href*='" + kTestPage + "']")];
+    is(links.length, 1, "Should have 1 link to the entry for " + kTestPage);
+    links[0].click();
+  });
+  yield entryLoaded;
+  info("about:cache entry loaded");
+
+
+  yield ContentTask.spawn(tab.linkedBrowser, triggeringURISpec, function(triggeringURISpec) {
+    ok(!content.document.nodePrincipal.isSystemPrincipal,
+       "about:cache-entry should also not have system principal");
+    let principalURI = content.document.nodePrincipal.URI;
+    is(principalURI && principalURI.spec, content.document.location.href, "Principal matches location");
+    let channel = content.document.docShell.currentDocumentChannel;
+    principalURI = channel.loadInfo.triggeringPrincipal.URI;
+    is(principalURI && principalURI.spec, triggeringURISpec, "Triggering principal matches previous location");
+    ok(!channel.loadInfo.loadingPrincipal, "Loading principal should be null.");
+    ok(content.document.querySelectorAll("th").length,
+       "Should have several table headers with data.");
+  });
+  yield BrowserTestUtils.removeTab(tab);
+});
--- a/netwerk/test/unit/test_be_conservative.js
+++ b/netwerk/test/unit/test_be_conservative.js
@@ -76,28 +76,37 @@ class InputStreamCallback {
   }
 }
 
 class TLSServerSecurityObserver {
   constructor(input, output) {
     this.input = input;
     this.output = output;
     this.callbacks = [];
+    this.stopped = false;
   }
 
   onHandshakeDone(socket, status) {
     do_print("TLS handshake done");
     do_print(`TLS version used: ${status.tlsVersionUsed}`);
 
+    if (this.stopped) {
+      do_print("handshake done callback stopped - closing streams and bailing");
+      this.input.close();
+      this.output.close();
+      return;
+    }
+
     let callback = new InputStreamCallback(this.output);
     this.callbacks.push(callback);
     this.input.asyncWait(callback, 0, 0, Services.tm.currentThread);
   }
 
   stop() {
+    this.stopped = true;
     this.callbacks.forEach((callback) => {
       callback.stop();
     });
   }
 }
 
 class ServerSocketListener {
   constructor() {
--- a/services/sync/modules/bookmark_validator.js
+++ b/services/sync/modules/bookmark_validator.js
@@ -3,20 +3,21 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/PlacesUtils.jsm");
 Cu.import("resource://gre/modules/PlacesSyncUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/Timer.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
-
 this.EXPORTED_SYMBOLS = ["BookmarkValidator", "BookmarkProblemData"];
 
 const LEFT_PANE_ROOT_ANNO = "PlacesOrganizer/OrganizerFolder";
 const LEFT_PANE_QUERY_ANNO = "PlacesOrganizer/OrganizerQuery";
 
 // Indicates if a local bookmark tree node should be excluded from syncing.
 function isNodeIgnored(treeNode) {
   return treeNode.annos && treeNode.annos.some(anno => anno.name == LEFT_PANE_ROOT_ANNO ||
@@ -209,23 +210,27 @@ class BookmarkValidator {
       let concreteItem = recordMap.get(concreteId);
       if (!concreteItem) {
         continue;
       }
       entry.concrete = concreteItem;
     }
   }
 
-  createClientRecordsFromTree(clientTree) {
+  async createClientRecordsFromTree(clientTree) {
     // Iterate over the treeNode, converting it to something more similar to what
     // the server stores.
     let records = [];
     let recordsByGuid = new Map();
     let syncedRoots = SYNCED_ROOTS;
-    function traverse(treeNode, synced) {
+    let yieldCounter = 0;
+    async function traverse(treeNode, synced) {
+      if (++yieldCounter % 50 === 0) {
+        await new Promise(resolve => setTimeout(resolve, 50));
+      }
       if (!synced) {
         synced = syncedRoots.includes(treeNode.guid);
       } else if (isNodeIgnored(treeNode)) {
         synced = false;
       }
       let guid = PlacesSyncUtils.bookmarks.guidToSyncId(treeNode.guid);
       let itemType = "item";
       treeNode.ignored = !synced;
@@ -275,24 +280,24 @@ class BookmarkValidator {
       // We want to use the "real" guid here.
       recordsByGuid.set(treeNode.guid, treeNode);
       if (treeNode.type === "folder") {
         treeNode.childGUIDs = [];
         if (!treeNode.children) {
           treeNode.children = [];
         }
         for (let child of treeNode.children) {
-          traverse(child, synced);
+          await traverse(child, synced);
           child.parent = treeNode;
           child.parentid = guid;
           treeNode.childGUIDs.push(child.guid);
         }
       }
     }
-    traverse(clientTree, false);
+    await traverse(clientTree, false);
     clientTree.id = "places";
     this._followQueries(recordsByGuid);
     return records;
   }
 
   /**
    * Process the server-side list. Mainly this builds the records into a tree,
    * but it also records information about problems, and produces arrays of the
@@ -313,27 +318,28 @@ class BookmarkValidator {
    * - root: Root of the server-side bookmark tree. Has the same properties as
    *   above.
    * - deletedRecords: As above, but only contains items that the server sent
    *   where it also sent indication that the item should be deleted.
    * - problemData: a BookmarkProblemData object, with the caveat that
    *   the fields describing client/server relationship will not have been filled
    *   out yet.
    */
-  inspectServerRecords(serverRecords) {
+  async inspectServerRecords(serverRecords) {
     let deletedItemIds = new Set();
     let idToRecord = new Map();
     let deletedRecords = [];
 
     let folders = [];
 
     let problemData = new BookmarkProblemData();
 
     let resultRecords = [];
 
+    let yieldCounter = 0;
     for (let record of serverRecords) {
       if (!record.id) {
         ++problemData.missingIDs;
         continue;
       }
       if (record.deleted) {
         deletedItemIds.add(record.id);
       } else if (idToRecord.has(record.id)) {
@@ -362,16 +368,19 @@ class BookmarkValidator {
         // The children array stores special guids as their local guid values,
         // e.g. 'menu________' instead of 'menu', but all other parts of the
         // serverside bookmark info stores it as the special value ('menu').
         record.childGUIDs = record.children;
         record.children = record.children.map(childID => {
           return PlacesSyncUtils.bookmarks.guidToSyncId(childID);
         });
       }
+      if (++yieldCounter % 50 === 0) {
+        await new Promise(resolve => setTimeout(resolve, 50));
+      }
     }
 
     for (let deletedId of deletedItemIds) {
       let record = idToRecord.get(deletedId);
       if (record && !record.isDeleted) {
         deletedRecords.push(record);
         record.isDeleted = true;
       }
@@ -602,20 +611,20 @@ class BookmarkValidator {
    *
    * Returns the same data as described in the inspectServerRecords comment,
    * with the following additional fields.
    * - clientRecords: an array of client records in a similar format to
    *   the .records (ie, server records) entry.
    * - problemData is the same as for inspectServerRecords, except all properties
    *   will be filled out.
    */
-  compareServerWithClient(serverRecords, clientTree) {
+  async compareServerWithClient(serverRecords, clientTree) {
 
-    let clientRecords = this.createClientRecordsFromTree(clientTree);
-    let inspectionInfo = this.inspectServerRecords(serverRecords);
+    let clientRecords = await this.createClientRecordsFromTree(clientTree);
+    let inspectionInfo = await this.inspectServerRecords(serverRecords);
     inspectionInfo.clientRecords = clientRecords;
 
     // Mainly do this to remove deleted items and normalize child guids.
     serverRecords = inspectionInfo.records;
     let problemData = inspectionInfo.problemData;
 
     this._validateClient(problemData, clientRecords);
 
@@ -747,33 +756,30 @@ class BookmarkValidator {
     };
     let resp = collection.getBatched();
     if (!resp.success) {
       throw resp;
     }
     return items;
   }
 
-  validate(engine) {
-    let self = this;
-    return Task.spawn(function*() {
-      let start = Date.now();
-      let clientTree = yield PlacesUtils.promiseBookmarksTree("", {
-        includeItemIds: true
-      });
-      let serverState = self._getServerState(engine);
-      let serverRecordCount = serverState.length;
-      let result = self.compareServerWithClient(serverState, clientTree);
-      let end = Date.now();
-      let duration = end - start;
-      return {
-        duration,
-        version: self.version,
-        problems: result.problemData,
-        recordCount: serverRecordCount
-      };
+  async validate(engine) {
+    let start = Date.now();
+    let clientTree = await PlacesUtils.promiseBookmarksTree("", {
+      includeItemIds: true
     });
+    let serverState = this._getServerState(engine);
+    let serverRecordCount = serverState.length;
+    let result = await this.compareServerWithClient(serverState, clientTree);
+    let end = Date.now();
+    let duration = end - start;
+    return {
+      duration,
+      version: this.version,
+      problems: result.problemData,
+      recordCount: serverRecordCount
+    };
   }
 
 }
 
 BookmarkValidator.prototype.version = BOOKMARK_VALIDATOR_VERSION;
 
--- a/services/sync/modules/engines.js
+++ b/services/sync/modules/engines.js
@@ -728,17 +728,18 @@ Engine.prototype = {
    * must have a `validate(engine)` method that returns a promise to an object
    * with a getSummary method). Otherwise return null.
    */
   getValidator() {
     return null;
   },
 
   finalize() {
-    // Ensure the tracker finishes persisting changed IDs to disk.
+    // Persist all pending tracked changes to disk.
+    this._tracker._saveChangedIDs();
     Async.promiseSpinningly(this._tracker._storage.finalize());
   },
 };
 
 this.SyncEngine = function SyncEngine(name, service) {
   Engine.call(this, name || "SyncEngine", service);
 
   this.loadToFetch();
--- a/services/sync/services-sync.js
+++ b/services/sync/services-sync.js
@@ -72,9 +72,9 @@ pref("services.sync.telemetry.maxPayload
 pref("services.sync.validation.interval", 86400); // 24 hours in seconds
 
 // We only run validation `services.sync.validation.percentageChance` percent of
 // the time, even if it's been the right amount of time since the last validation,
 // and you meet the maxRecord checks.
 pref("services.sync.validation.percentageChance", 10);
 
 // We won't validate an engine if it has more than this many records on the server.
-pref("services.sync.validation.maxRecords", 100);
+pref("services.sync.validation.maxRecords", 1000);
--- a/services/sync/tests/unit/sync_ping_schema.json
+++ b/services/sync/tests/unit/sync_ping_schema.json
@@ -71,17 +71,17 @@
         "version": { "type": "string" }
       }
     },
     "engine": {
       "required": ["name"],
       "additionalProperties": false,
       "properties": {
         "failureReason": { "$ref": "#/definitions/error" },
-        "name": { "enum": ["addons", "bookmarks", "clients", "forms", "history", "passwords", "prefs", "tabs"] },
+        "name": { "enum": ["addons", "bookmarks", "clients", "forms", "history", "passwords", "prefs", "tabs", "extension-storage"] },
         "took": { "type": "integer", "minimum": 1 },
         "status": { "type": "string" },
         "incoming": {
           "type": "object",
           "additionalProperties": false,
           "anyOf": [
             {"required": ["applied"]},
             {"required": ["failed"]},
--- a/services/sync/tests/unit/test_bookmark_duping.js
+++ b/services/sync/tests/unit/test_bookmark_duping.js
@@ -95,17 +95,17 @@ async function promiseNoLocalItem(guid) 
   // and while we are here ensure the places cache doesn't still have it.
   await Assert.rejects(PlacesUtils.promiseItemId(guid));
 }
 
 async function validate(collection, expectedFailures = []) {
   let validator = new BookmarkValidator();
   let records = collection.payloads();
 
-  let problems = validator.inspectServerRecords(records).problemData;
+  let { problemData: problems } = await validator.inspectServerRecords(records);
   // all non-zero problems.
   let summary = problems.getSummary().filter(prob => prob.count != 0);
 
   // split into 2 arrays - expected and unexpected.
   let isInExpectedFailures = elt => {
     for (let i = 0; i < expectedFailures.length; i++) {
       if (elt.name == expectedFailures[i].name && elt.count == expectedFailures[i].count) {
         return true;
--- a/services/sync/tests/unit/test_bookmark_validator.js
+++ b/services/sync/tests/unit/test_bookmark_validator.js
@@ -1,125 +1,121 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 Components.utils.import("resource://services-sync/bookmark_validator.js");
 Components.utils.import("resource://services-sync/util.js");
 
-function inspectServerRecords(data) {
-  return new BookmarkValidator().inspectServerRecords(data);
+async function inspectServerRecords(data) {
+  let validator = new BookmarkValidator();
+  return validator.inspectServerRecords(data);
 }
 
-add_test(function test_isr_rootOnServer() {
-  let c = inspectServerRecords([{
+async function compareServerWithClient(server, client) {
+  let validator = new BookmarkValidator();
+  return validator.compareServerWithClient(server, client);
+}
+
+add_task(async function test_isr_rootOnServer() {
+  let c = await inspectServerRecords([{
     id: "places",
     type: "folder",
     children: [],
   }]);
   ok(c.problemData.rootOnServer);
-  run_next_test();
 });
 
-add_test(function test_isr_empty() {
-  let c = inspectServerRecords([]);
+add_task(async function test_isr_empty() {
+  let c = await inspectServerRecords([]);
   ok(!c.problemData.rootOnServer);
   notEqual(c.root, null);
-  run_next_test();
 });
 
-add_test(function test_isr_cycles() {
-  let c = inspectServerRecords([
+add_task(async function test_isr_cycles() {
+  let c = (await inspectServerRecords([
     {id: "C", type: "folder", children: ["A", "B"], parentid: "places"},
     {id: "A", type: "folder", children: ["B"], parentid: "B"},
     {id: "B", type: "folder", children: ["A"], parentid: "A"},
-  ]).problemData;
+  ])).problemData;
 
   equal(c.cycles.length, 1);
   ok(c.cycles[0].indexOf("A") >= 0);
   ok(c.cycles[0].indexOf("B") >= 0);
-  run_next_test();
 });
 
-add_test(function test_isr_orphansMultiParents() {
-  let c = inspectServerRecords([
+add_task(async function test_isr_orphansMultiParents() {
+  let c = (await inspectServerRecords([
     { id: "A", type: "bookmark", parentid: "D" },
     { id: "B", type: "folder", parentid: "places", children: ["A"]},
     { id: "C", type: "folder", parentid: "places", children: ["A"]},
 
-  ]).problemData;
+  ])).problemData;
   deepEqual(c.orphans, [{ id: "A", parent: "D" }]);
   equal(c.multipleParents.length, 1)
   ok(c.multipleParents[0].parents.indexOf("B") >= 0);
   ok(c.multipleParents[0].parents.indexOf("C") >= 0);
-  run_next_test();
 });
 
-add_test(function test_isr_orphansMultiParents2() {
-  let c = inspectServerRecords([
+add_task(async function test_isr_orphansMultiParents2() {
+  let c = (await inspectServerRecords([
     { id: "A", type: "bookmark", parentid: "D" },
     { id: "B", type: "folder", parentid: "places", children: ["A"]},
-  ]).problemData;
+  ])).problemData;
   equal(c.orphans.length, 1);
   equal(c.orphans[0].id, "A");
   equal(c.multipleParents.length, 0);
-  run_next_test();
 });
 
-add_test(function test_isr_deletedParents() {
-  let c = inspectServerRecords([
+add_task(async function test_isr_deletedParents() {
+  let c = (await inspectServerRecords([
     { id: "A", type: "bookmark", parentid: "B" },
     { id: "B", type: "folder", parentid: "places", children: ["A"]},
     { id: "B", type: "item", deleted: true},
-  ]).problemData;
-  deepEqual(c.deletedParents, ["A"])
-  run_next_test();
+  ])).problemData;
+  deepEqual(c.deletedParents, ["A"]);
 });
 
-add_test(function test_isr_badChildren() {
-  let c = inspectServerRecords([
+add_task(async function test_isr_badChildren() {
+  let c = (await inspectServerRecords([
     { id: "A", type: "bookmark", parentid: "places", children: ["B", "C"] },
     { id: "C", type: "bookmark", parentid: "A" }
-  ]).problemData;
+  ])).problemData;
   deepEqual(c.childrenOnNonFolder, ["A"])
   deepEqual(c.missingChildren, [{parent: "A", child: "B"}]);
   deepEqual(c.parentNotFolder, ["C"]);
-  run_next_test();
 });
 
 
-add_test(function test_isr_parentChildMismatches() {
-  let c = inspectServerRecords([
+add_task(async function test_isr_parentChildMismatches() {
+  let c = (await inspectServerRecords([
     { id: "A", type: "folder", parentid: "places", children: [] },
     { id: "B", type: "bookmark", parentid: "A" }
-  ]).problemData;
+  ])).problemData;
   deepEqual(c.parentChildMismatches, [{parent: "A", child: "B"}]);
-  run_next_test();
 });
 
-add_test(function test_isr_duplicatesAndMissingIDs() {
-  let c = inspectServerRecords([
+add_task(async function test_isr_duplicatesAndMissingIDs() {
+  let c = (await inspectServerRecords([
     {id: "A", type: "folder", parentid: "places", children: []},
     {id: "A", type: "folder", parentid: "places", children: []},
     {type: "folder", parentid: "places", children: []}
-  ]).problemData;
+  ])).problemData;
   equal(c.missingIDs, 1);
   deepEqual(c.duplicates, ["A"]);
-  run_next_test();
 });
 
-add_test(function test_isr_duplicateChildren() {
-  let c = inspectServerRecords([
+add_task(async function test_isr_duplicateChildren() {
+  let c = (await inspectServerRecords([
     {id: "A", type: "folder", parentid: "places", children: ["B", "B"]},
     {id: "B", type: "bookmark", parentid: "A"},
-  ]).problemData;
+  ])).problemData;
   deepEqual(c.duplicateChildren, ["A"]);
-  run_next_test();
 });
 
-// Each compareServerWithClient test mutates these, so we can't just keep them
+// Each compareServerWithClient test mutates these, so we can"t just keep them
 // global
 function getDummyServerAndClient() {
   let server = [
     {
       id: "menu",
       parentid: "places",
       type: "folder",
       parentName: "",
@@ -179,72 +175,68 @@ function getDummyServerAndClient() {
         ]
       }
     ]
   };
   return {server, client};
 }
 
 
-add_test(function test_cswc_valid() {
+add_task(async function test_cswc_valid() {
   let {server, client} = getDummyServerAndClient();
 
-  let c = new BookmarkValidator().compareServerWithClient(server, client).problemData;
+  let c = (await compareServerWithClient(server, client)).problemData;
   equal(c.clientMissing.length, 0);
   equal(c.serverMissing.length, 0);
   equal(c.differences.length, 0);
-  run_next_test();
 });
 
-add_test(function test_cswc_serverMissing() {
+add_task(async function test_cswc_serverMissing() {
   let {server, client} = getDummyServerAndClient();
   // remove c
   server.pop();
   server[0].children.pop();
 
-  let c = new BookmarkValidator().compareServerWithClient(server, client).problemData;
+  let c = (await compareServerWithClient(server, client)).problemData;
   deepEqual(c.serverMissing, ["cccccccccccc"]);
   equal(c.clientMissing.length, 0);
   deepEqual(c.structuralDifferences, [{id: "menu", differences: ["childGUIDs"]}]);
-  run_next_test();
 });
 
-add_test(function test_cswc_clientMissing() {
+add_task(async function test_cswc_clientMissing() {
   let {server, client} = getDummyServerAndClient();
   client.children[0].children.pop();
 
-  let c = new BookmarkValidator().compareServerWithClient(server, client).problemData;
+  let c = (await compareServerWithClient(server, client)).problemData;
   deepEqual(c.clientMissing, ["cccccccccccc"]);
   equal(c.serverMissing.length, 0);
   deepEqual(c.structuralDifferences, [{id: "menu", differences: ["childGUIDs"]}]);
-  run_next_test();
 });
 
-add_test(function test_cswc_differences() {
+add_task(async function test_cswc_differences() {
   {
     let {server, client} = getDummyServerAndClient();
     client.children[0].children[0].title = "asdf";
-    let c = new BookmarkValidator().compareServerWithClient(server, client).problemData;
+    let c = (await compareServerWithClient(server, client)).problemData;
     equal(c.clientMissing.length, 0);
     equal(c.serverMissing.length, 0);
     deepEqual(c.differences, [{id: "bbbbbbbbbbbb", differences: ["title"]}]);
   }
 
   {
     let {server, client} = getDummyServerAndClient();
     server[2].type = "bookmark";
-    let c = new BookmarkValidator().compareServerWithClient(server, client).problemData;
+    let c = (await compareServerWithClient(server, client)).problemData;
     equal(c.clientMissing.length, 0);
     equal(c.serverMissing.length, 0);
     deepEqual(c.differences, [{id: "cccccccccccc", differences: ["type"]}]);
   }
-  run_next_test();
 });
 
-add_test(function test_cswc_serverUnexpected() {
+add_task(async function test_cswc_serverUnexpected() {
   let {server, client} = getDummyServerAndClient();
   client.children.push({
     "guid": "dddddddddddd",
     "title": "",
     "id": 2000,
     "annos": [{
       "name": "places/excludeFromBackup",
       "flags": 0,
@@ -286,41 +278,41 @@ add_test(function test_cswc_serverUnexpe
     id: "eeeeeeeeeeee",
     parentid: "dddddddddddd",
     parentName: "",
     title: "History",
     type: "query",
     bmkUri: "place:type=3&sort=4"
   });
 
-  let c = new BookmarkValidator().compareServerWithClient(server, client).problemData;
+  let c = (await compareServerWithClient(server, client)).problemData;
   equal(c.clientMissing.length, 0);
   equal(c.serverMissing.length, 0);
   equal(c.serverUnexpected.length, 2);
   deepEqual(c.serverUnexpected, ["dddddddddddd", "eeeeeeeeeeee"]);
-  run_next_test();
 });
 
-function validationPing(server, client, duration) {
-  return wait_for_ping(function() {
-    // fake this entirely
-    Svc.Obs.notify("weave:service:sync:start");
-    Svc.Obs.notify("weave:engine:sync:start", null, "bookmarks");
-    Svc.Obs.notify("weave:engine:sync:finish", null, "bookmarks");
-    let validator = new BookmarkValidator();
-    let data = {
-      // We fake duration and version just so that we can verify they're passed through.
-      duration,
-      version: validator.version,
-      recordCount: server.length,
-      problems: validator.compareServerWithClient(server, client).problemData,
-    };
-    Svc.Obs.notify("weave:engine:validate:finish", data, "bookmarks");
-    Svc.Obs.notify("weave:service:sync:finish");
-  }, true); // Allow "failing" pings, since having validation info indicates failure.
+async function validationPing(server, client, duration) {
+  let pingPromise = wait_for_ping(() => {}, true); // Allow "failing" pings, since having validation info indicates failure.
+  // fake this entirely
+  Svc.Obs.notify("weave:service:sync:start");
+  Svc.Obs.notify("weave:engine:sync:start", null, "bookmarks");
+  Svc.Obs.notify("weave:engine:sync:finish", null, "bookmarks");
+  let validator = new BookmarkValidator();
+  let {problemData} = await validator.compareServerWithClient(server, client);
+  let data = {
+    // We fake duration and version just so that we can verify they"re passed through.
+    duration,
+    version: validator.version,
+    recordCount: server.length,
+    problems: problemData,
+  };
+  Svc.Obs.notify("weave:engine:validate:finish", data, "bookmarks");
+  Svc.Obs.notify("weave:service:sync:finish");
+  return pingPromise;
 }
 
 add_task(async function test_telemetry_integration() {
   let {server, client} = getDummyServerAndClient();
   // remove "c"
   server.pop();
   server[0].children.pop();
   const duration = 50;
@@ -336,12 +328,8 @@ add_task(async function test_telemetry_i
   equal(bme.validation.version, new BookmarkValidator().version);
   deepEqual(bme.validation.problems, [
     { name: "badClientRoots", count: 3 },
     { name: "sdiff:childGUIDs", count: 1 },
     { name: "serverMissing", count: 1 },
     { name: "structuralDifferences", count: 1 },
   ]);
 });
-
-function run_test() {
-  run_next_test();
-}
--- a/services/sync/tests/unit/test_engine.js
+++ b/services/sync/tests/unit/test_engine.js
@@ -18,17 +18,18 @@ SteamStore.prototype = {
     this.wasWiped = true;
   }
 };
 
 function SteamTracker(name, engine) {
   Tracker.call(this, name || "Steam", engine);
 }
 SteamTracker.prototype = {
-  __proto__: Tracker.prototype
+  __proto__: Tracker.prototype,
+  persistChangedIDs: false,
 };
 
 function SteamEngine(name, service) {
   Engine.call(this, name, service);
   this.wasReset = false;
   this.wasSynced = false;
 }
 SteamEngine.prototype = {
--- a/services/sync/tests/unit/test_telemetry.js
+++ b/services/sync/tests/unit/test_telemetry.js
@@ -28,17 +28,18 @@ SteamStore.prototype = {
   __proto__: Store.prototype,
 };
 
 function SteamTracker(name, engine) {
   Tracker.call(this, name || "Steam", engine);
 }
 
 SteamTracker.prototype = {
-  __proto__: Tracker.prototype
+  __proto__: Tracker.prototype,
+  persistChangedIDs: false,
 };
 
 function SteamEngine(service) {
   Engine.call(this, "steam", service);
 }
 
 SteamEngine.prototype = {
   __proto__: Engine.prototype,
--- a/services/sync/tps/extensions/tps/resource/tps.jsm
+++ b/services/sync/tps/extensions/tps/resource/tps.jsm
@@ -130,19 +130,16 @@ var TPS = {
 
   _init: function TPS__init() {
     this.delayAutoSync();
 
     OBSERVER_TOPICS.forEach(function(aTopic) {
       Services.obs.addObserver(this, aTopic, true);
     }, this);
 
-    // Configure some logging prefs for Sync itself.
-    Weave.Svc.Prefs.set("log.appender.dump", "Debug");
-
     /* global Authentication */
     Cu.import("resource://tps/auth/fxaccounts.jsm", module);
   },
 
   DumpError(msg, exc = null) {
     this._errors++;
     let errInfo;
     if (exc) {
@@ -619,17 +616,17 @@ var TPS = {
       let clientTree = Async.promiseSpinningly(PlacesUtils.promiseBookmarksTree("", {
         includeItemIds: true
       }));
       let serverRecords = getServerBookmarkState();
       // We can't wait until catch to stringify this, since at that point it will have cycles.
       serverRecordDumpStr = JSON.stringify(serverRecords);
 
       let validator = new BookmarkValidator();
-      let {problemData} = validator.compareServerWithClient(serverRecords, clientTree);
+      let {problemData} = Async.promiseSpinningly(validator.compareServerWithClient(serverRecords, clientTree));
 
       for (let {name, count} of problemData.getSummary()) {
         // Exclude mobile showing up on the server hackily so that we don't
         // report it every time, see bug 1273234 and 1274394 for more information.
         if (name === "serverUnexpected" && problemData.serverUnexpected.indexOf("mobile") >= 0) {
           --count;
         }
         if (count) {
--- a/storage/mozStorageService.cpp
+++ b/storage/mozStorageService.cpp
@@ -9,18 +9,16 @@
 
 #include "mozStorageService.h"
 #include "mozStorageConnection.h"
 #include "nsAutoPtr.h"
 #include "nsCollationCID.h"
 #include "nsEmbedCID.h"
 #include "nsThreadUtils.h"
 #include "mozStoragePrivateHelpers.h"
-#include "nsILocale.h"
-#include "nsILocaleService.h"
 #include "nsIXPConnect.h"
 #include "nsIObserverService.h"
 #include "nsIPropertyBag2.h"
 #include "mozilla/Services.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/LateWriteChecks.h"
 #include "mozIStorageCompletionCallback.h"
 #include "mozIStoragePendingStatement.h"
@@ -603,37 +601,24 @@ Service::localeCompareStrings(const nsAS
 nsICollation *
 Service::getLocaleCollation()
 {
   mMutex.AssertCurrentThreadOwns();
 
   if (mLocaleCollation)
     return mLocaleCollation;
 
-  nsCOMPtr<nsILocaleService> svc(do_GetService(NS_LOCALESERVICE_CONTRACTID));
-  if (!svc) {
-    NS_WARNING("Could not get locale service");
-    return nullptr;
-  }
-
-  nsCOMPtr<nsILocale> appLocale;
-  nsresult rv = svc->GetApplicationLocale(getter_AddRefs(appLocale));
-  if (NS_FAILED(rv)) {
-    NS_WARNING("Could not get application locale");
-    return nullptr;
-  }
-
   nsCOMPtr<nsICollationFactory> collFact =
     do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID);
   if (!collFact) {
     NS_WARNING("Could not create collation factory");
     return nullptr;
   }
 
-  rv = collFact->CreateCollation(appLocale, getter_AddRefs(mLocaleCollation));
+  nsresult rv = collFact->CreateCollation(getter_AddRefs(mLocaleCollation));
   if (NS_FAILED(rv)) {
     NS_WARNING("Could not create collation");
     return nullptr;
   }
 
   return mLocaleCollation;
 }
 
--- a/storage/test/unit/test_locale_collation.js
+++ b/storage/test/unit/test_locale_collation.js
@@ -227,21 +227,19 @@ function setup() {
 
   gStrings = readTestData();
 
   initTableWithStrings(gStrings, getOpenedDatabase());
 
   gUtf16Conn = createUtf16Database();
   initTableWithStrings(gStrings, gUtf16Conn);
 
-  let localeSvc = Cc["@mozilla.org/intl/nslocaleservice;1"].
-                  getService(Ci.nsILocaleService);
   let collFact = Cc["@mozilla.org/intl/collation-factory;1"].
                  createInstance(Ci.nsICollationFactory);
-  gLocaleCollation = collFact.CreateCollation(localeSvc.getApplicationLocale());
+  gLocaleCollation = collFact.CreateCollation();
 }
 
 // Test Runs
 
 var gTests = [
   {
     desc: "Case and accent sensitive UTF-8",
     run:   () => runUtf8Test("locale_case_accent_sensitive")
--- a/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm
+++ b/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm
@@ -37,16 +37,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 // some cases.
 Cu.permitCPOWsInScope(this);
 
 var gSendCharCount = 0;
 var gSynthesizeKeyCount = 0;
 var gSynthesizeCompositionCount = 0;
 var gSynthesizeCompositionChangeCount = 0;
 
+const kAboutPageRegistrationContentScript =
+  "chrome://mochikit/content/tests/BrowserTestUtils/content-about-page-utils.js";
+
 this.BrowserTestUtils = {
   /**
    * Loads a page in a new tab, executes a Task and closes the tab.
    *
    * @param options
    *        An object  or string.
    *        If this is a string it is the url to open and will be opened in the
    *        currently active browser window.
@@ -1263,9 +1266,67 @@ this.BrowserTestUtils = {
       return new Promise((resolve) => {
         addEventListener("MozAfterPaint", function onPaint() {
           removeEventListener("MozAfterPaint", onPaint);
           resolve();
         })
       });
     });
   },
+
+  _knownAboutPages: new Set(),
+  _loadedAboutContentScript: false,
+  /**
+   * Registers an about: page with particular flags in both the parent
+   * and any content processes. Returns a promise that resolves when
+   * registration is complete.
+   *
+   * @param registerCleanupFunction (Function)
+   *        The test framework doesn't keep its cleanup stuff anywhere accessible,
+   *        so the first argument is a reference to your cleanup registration
+   *        function, allowing us to clean up after you if necessary.
+   * @param aboutModule (String)
+   *        The name of the about page.
+   * @param pageURI (String)
+   *        The URI the about: page should point to.
+   * @param flags (Number)
+   *        The nsIAboutModule flags to use for registration.
+   * @returns Promise that resolves when registration has finished.
+   */
+  registerAboutPage(registerCleanupFunction, aboutModule, pageURI, flags) {
+    // Return a promise that resolves when registration finished.
+    const kRegistrationMsgId = "browser-test-utils:about-registration:registered";
+    let rv = this.waitForMessage(Services.ppmm, kRegistrationMsgId, msg => {
+      return msg.data == aboutModule;
+    });
+    // Load a script that registers our page, then send it a message to execute the registration.
+    if (!this._loadedAboutContentScript) {
+      Services.ppmm.loadProcessScript(kAboutPageRegistrationContentScript, true);
+      this._loadedAboutContentScript = true;
+      registerCleanupFunction(this._removeAboutPageRegistrations.bind(this));
+    }
+    Services.ppmm.broadcastAsyncMessage("browser-test-utils:about-registration:register",
+                                        {aboutModule, pageURI, flags});
+    return rv.then(() => {
+      this._knownAboutPages.add(aboutModule);
+    });
+  },
+
+  unregisterAboutPage(aboutModule) {
+    if (!this._knownAboutPages.has(aboutModule)) {
+      return Promise.reject(new Error("We don't think this about page exists!"));
+    }
+    const kUnregistrationMsgId = "browser-test-utils:about-registration:unregistered";
+    let rv = this.waitForMessage(Services.ppmm, kUnregistrationMsgId, msg => {
+      return msg.data == aboutModule;
+    });
+    Services.ppmm.broadcastAsyncMessage("browser-test-utils:about-registration:unregister",
+                                        aboutModule);
+    return rv.then(() => this._knownAboutPages.delete(aboutModule));
+  },
+
+  *_removeAboutPageRegistrations() {
+    for (let aboutModule of this._knownAboutPages) {
+      yield this.unregisterAboutPage(aboutModule);
+    }
+    Services.ppmm.removeDelayedProcessScript(kAboutPageRegistrationContentScript);
+  },
 };
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/BrowserTestUtils/content/content-about-page-utils.js
@@ -0,0 +1,76 @@
+"use strict";
+
+var {classes: Cc, interfaces: Ci, utils: Cu, manager: Cm} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const { generateUUID } = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
+
+function AboutPage(aboutHost, chromeURL, uriFlags) {
+  this.chromeURL = chromeURL;
+  this.aboutHost = aboutHost;
+  this.classID = Components.ID(generateUUID().number);
+  this.description = "BrowserTestUtils: " + aboutHost;
+  this.uriFlags = uriFlags;
+}
+
+AboutPage.prototype = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutModule]),
+  getURIFlags(aURI) { // eslint-disable-line no-unused-vars
+    return this.uriFlags;
+  },
+
+  newChannel(aURI, aLoadInfo) {
+    let newURI = Services.io.newURI(this.chromeURL);
+    let channel = Services.io.newChannelFromURIWithLoadInfo(newURI,
+                                                            aLoadInfo);
+    channel.originalURI = aURI;
+
+    if (this.uriFlags & Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT) {
+      channel.owner = null;
+    }
+    return channel;
+  },
+
+  createInstance(outer, iid) {
+    if (outer !== null) {
+      throw Cr.NS_ERROR_NO_AGGREGATION;
+    }
+    return this.QueryInterface(iid);
+  },
+
+  register() {
+    Cm.QueryInterface(Ci.nsIComponentRegistrar).registerFactory(
+      this.classID, this.description,
+      "@mozilla.org/network/protocol/about;1?what=" + this.aboutHost, this);
+  },
+
+  unregister() {
+    Cm.QueryInterface(Ci.nsIComponentRegistrar).unregisterFactory(
+      this.classID, this);
+  }
+};
+
+const gRegisteredPages = new Map();
+
+addMessageListener("browser-test-utils:about-registration:register", msg => {
+  let {aboutModule, pageURI, flags} = msg.data;
+  if (gRegisteredPages.has(aboutModule)) {
+    gRegisteredPages.get(aboutModule).unregister();
+  }
+  let moduleObj = new AboutPage(aboutModule, pageURI, flags);
+  moduleObj.register();
+  gRegisteredPages.set(aboutModule, moduleObj);
+  sendAsyncMessage("browser-test-utils:about-registration:registered", aboutModule);
+});
+
+addMessageListener("browser-test-utils:about-registration:unregister", msg => {
+  let aboutModule = msg.data;
+  let moduleObj = gRegisteredPages.get(aboutModule);
+  if (moduleObj) {
+    moduleObj.unregister();
+    gRegisteredPages.delete(aboutModule);
+  }
+  sendAsyncMessage("browser-test-utils:about-registration:unregistered", aboutModule);
+});
--- a/testing/mochitest/jar.mn
+++ b/testing/mochitest/jar.mn
@@ -37,10 +37,11 @@ mochikit.jar:
   content/tests/SimpleTest/TestRunner.js (tests/SimpleTest/TestRunner.js)
   content/tests/SimpleTest/iframe-between-tests.html (tests/SimpleTest/iframe-between-tests.html)
   content/tests/SimpleTest/WindowSnapshot.js (tests/SimpleTest/WindowSnapshot.js)
   content/tests/SimpleTest/MockObjects.js (tests/SimpleTest/MockObjects.js)
   content/tests/SimpleTest/NativeKeyCodes.js (tests/SimpleTest/NativeKeyCodes.js)
   content/tests/SimpleTest/paint_listener.js (tests/SimpleTest/paint_listener.js)
   content/tests/SimpleTest/docshell_helpers.js (../../docshell/test/chrome/docshell_helpers.js)
   content/tests/BrowserTestUtils/content-task.js (BrowserTestUtils/content/content-task.js)
+  content/tests/BrowserTestUtils/content-about-page-utils.js (BrowserTestUtils/content/content-about-page-utils.js)
   content/tests/BrowserTestUtils/content-utils.js (BrowserTestUtils/content/content-utils.js)
 
--- a/testing/mochitest/tests/browser/browser.ini
+++ b/testing/mochitest/tests/browser/browser.ini
@@ -1,16 +1,18 @@
 [DEFAULT]
 support-files =
   head.js
 
 [browser_browserLoaded_content_loaded.js]
 [browser_add_task.js]
 [browser_async.js]
 [browser_BrowserTestUtils.js]
+support-files =
+  dummy.html
 [browser_head.js]
 [browser_pass.js]
 [browser_parameters.js]
 [browser_popupNode.js]
 [browser_popupNode_check.js]
 [browser_privileges.js]
 [browser_sanityException.js]
 [browser_sanityException2.js]
--- a/testing/mochitest/tests/browser/browser_BrowserTestUtils.js
+++ b/testing/mochitest/tests/browser/browser_BrowserTestUtils.js
@@ -42,8 +42,29 @@ add_task(function* () {
 
   cancelled = yield BrowserTestUtils.synthesizeMouseAtCenter("body > div", { type: "mouseup" }, browser);
   details = yield getLastEventDetails(browser);
   is(details, "div,40,84", "synthesizeMouseAtCenter mouseup with complex selector");
   ok(!cancelled, "synthesizeMouseAtCenter mouseup with complex selector cancelled");
 
   gBrowser.removeTab(tab);
 });
+
+add_task(function* () {
+  yield BrowserTestUtils.registerAboutPage(
+    registerCleanupFunction, "about-pages-are-cool",
+    getRootDirectory(gTestPath) + "dummy.html", 0);
+  let tab = yield BrowserTestUtils.openNewForegroundTab(
+    gBrowser, "about:about-pages-are-cool", true);
+  ok(tab, "Successfully created an about: page and loaded it.");
+  yield BrowserTestUtils.removeTab(tab);
+  try {
+    yield BrowserTestUtils.unregisterAboutPage("about-pages-are-cool");
+    ok(true, "Successfully unregistered the about page.");
+  } catch (ex) {
+    ok(false, "Should not throw unregistering a known about: page");
+  }
+  yield BrowserTestUtils.unregisterAboutPage("random-other-about-page").then(() => {
+    ok(false, "Should not have succeeded unregistering an unknown about: page.");
+  }, () => {
+    ok(true, "Should have returned a rejected promise trying to unregister an unknown about page");
+  });
+});
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/tests/browser/dummy.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+  <title>This is a dummy page</title>
+  <meta charset="utf-8">
+  <body>This is a dummy page</body>
+</html>
--- a/testing/testsuite-targets.mk
+++ b/testing/testsuite-targets.mk
@@ -7,30 +7,16 @@ TARGET_DEPTH = $(DEPTH)
 include $(topsrcdir)/build/binary-location.mk
 
 SYMBOLS_PATH := --symbols-path=$(DIST)/crashreporter-symbols
 
 ifndef TEST_PACKAGE_NAME
 TEST_PACKAGE_NAME := $(ANDROID_PACKAGE_NAME)
 endif
 
-# Linking xul-gtest.dll takes too long, so we disable GTest on
-# Windows PGO builds (bug 1028035).
-ifneq (1_WINNT,$(MOZ_PGO)_$(OS_ARCH))
-BUILD_GTEST=1
-endif
-
-ifneq (browser,$(MOZ_BUILD_APP))
-BUILD_GTEST=
-endif
-
-ifndef COMPILE_ENVIRONMENT
-BUILD_GTEST=
-endif
-
 ifndef NO_FAIL_ON_TEST_ERRORS
 define check_test_error_internal
   @errors=`grep 'TEST-UNEXPECTED-' $@.log` ;\
   if test "$$errors" ; then \
 	  echo '$@ failed:'; \
 	  echo "$$errors"; \
           $(if $(1),echo $(1);) \
 	  exit 1; \
@@ -147,17 +133,17 @@ TEST_PKGS := \
   cppunittest \
   mochitest \
   reftest \
   talos \
   web-platform \
   xpcshell \
   $(NULL)
 
-ifdef BUILD_GTEST
+ifdef LINK_GTEST_DURING_COMPILE
 stage-all: stage-gtest
 TEST_PKGS += gtest
 endif
 
 PKG_ARG = --$(1) '$(PKG_BASENAME).$(1).tests.zip'
 
 test-packages-manifest:
 	@rm -f $(MOZ_TEST_PACKAGES_FILE)
@@ -213,18 +199,16 @@ stage-mochitest: make-stage-dir
 ifeq ($(MOZ_BUILD_APP),mobile/android)
 	$(MAKE) -C $(DEPTH)/testing/mochitest stage-package
 endif
 
 stage-jstests: make-stage-dir
 	$(MAKE) -C $(DEPTH)/js/src/tests stage-package
 
 stage-gtest: make-stage-dir
-# FIXME: (bug 1200311) We should be generating the gtest xul as part of the build.
-	$(MAKE) -C $(DEPTH)/testing/gtest gtest
 	$(NSINSTALL) -D $(PKG_STAGE)/gtest/gtest_bin
 	cp -RL $(DIST)/bin/gtest $(PKG_STAGE)/gtest/gtest_bin
 	cp -RL $(DEPTH)/_tests/gtest $(PKG_STAGE)
 	cp $(topsrcdir)/testing/gtest/rungtests.py $(PKG_STAGE)/gtest
 	cp $(DIST)/bin/dependentlibs.list.gtest $(PKG_STAGE)/gtest
 	cp $(DEPTH)/mozinfo.json $(PKG_STAGE)/gtest
 
 stage-android: make-stage-dir
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -61491,17 +61491,17 @@
      {}
     ]
    ],
    "web-animations/animation-model/animation-types/property-types.js": [
     [
      {}
     ]
    ],
-   "web-animations/resources/effect-easing-tests.js": [
+   "web-animations/resources/easing-tests.js": [
     [
      {}
     ]
    ],
    "web-animations/resources/keyframe-utils.js": [
     [
      {}
     ]
@@ -120073,28 +120073,40 @@
     ]
    ],
    "web-animations/animation-model/keyframe-effects/effect-value-context.html": [
     [
      "/web-animations/animation-model/keyframe-effects/effect-value-context.html",
      {}
     ]
    ],
+   "web-animations/animation-model/keyframe-effects/effect-value-overlapping-keyframes.html": [
+    [
+     "/web-animations/animation-model/keyframe-effects/effect-value-overlapping-keyframes.html",
+     {}
+    ]
+   ],
+   "web-animations/animation-model/keyframe-effects/effect-value-transformed-distance.html": [
+    [
+     "/web-animations/animation-model/keyframe-effects/effect-value-transformed-distance.html",
+     {}
+    ]
+   ],
+   "web-animations/animation-model/keyframe-effects/effect-value-visibility.html": [
+    [
+     "/web-animations/animation-model/keyframe-effects/effect-value-visibility.html",
+     {}
+    ]
+   ],
    "web-animations/animation-model/keyframe-effects/spacing-keyframes.html": [
     [
      "/web-animations/animation-model/keyframe-effects/spacing-keyframes.html",
      {}
     ]
    ],
-   "web-animations/animation-model/keyframe-effects/the-effect-value-of-a-keyframe-effect.html": [
-    [
-     "/web-animations/animation-model/keyframe-effects/the-effect-value-of-a-keyframe-effect.html",
-     {}
-    ]
-   ],
    "web-animations/interfaces/Animatable/animate.html": [
     [
      "/web-animations/interfaces/Animatable/animate.html",
      {}
     ]
    ],
    "web-animations/interfaces/Animation/cancel.html": [
     [
@@ -120283,22 +120295,16 @@
     ]
    ],
    "web-animations/interfaces/KeyframeEffect/copy-contructor.html": [
     [
      "/web-animations/interfaces/KeyframeEffect/copy-contructor.html",
      {}
     ]
    ],
-   "web-animations/interfaces/KeyframeEffect/effect-easing.html": [
-    [
-     "/web-animations/interfaces/KeyframeEffect/effect-easing.html",
-     {}
-    ]
-   ],
    "web-animations/interfaces/KeyframeEffect/getComputedTiming.html": [
     [
      "/web-animations/interfaces/KeyframeEffect/getComputedTiming.html",
      {}
     ]
    ],
    "web-animations/interfaces/KeyframeEffect/iterationComposite.html": [
     [
@@ -120391,16 +120397,22 @@
     ]
    ],
    "web-animations/timing-model/animations/updating-the-finished-state.html": [
     [
      "/web-animations/timing-model/animations/updating-the-finished-state.html",
      {}
     ]
    ],
+   "web-animations/timing-model/time-transformations/transformed-progress.html": [
+    [
+     "/web-animations/timing-model/time-transformations/transformed-progress.html",
+     {}
+    ]
+   ],
    "webaudio/the-audio-api/the-audiobuffer-interface/idl-test.html": [
     [
      "/webaudio/the-audio-api/the-audiobuffer-interface/idl-test.html",
      {}
     ]
    ],
    "webaudio/the-audio-api/the-audiodestinationnode-interface/idl-test.html": [
     [
@@ -203720,26 +203732,34 @@
   "web-animations/animation-model/combining-effects/effect-composition.html": [
    "8ac06085132d822e908d48de4c1109b66323f19f",
    "testharness"
   ],
   "web-animations/animation-model/keyframe-effects/effect-value-context.html": [
    "10d9ee521240475a1729c2facfcea8b50342614e",
    "testharness"
   ],
+  "web-animations/animation-model/keyframe-effects/effect-value-overlapping-keyframes.html": [
+   "a79db70a385ad767263f285c9401b66611087e42",
+   "testharness"
+  ],
+  "web-animations/animation-model/keyframe-effects/effect-value-transformed-distance.html": [
+   "59e86254c8c118bd30b5c6742cfeaceba783eaee",
+   "testharness"
+  ],
+  "web-animations/animation-model/keyframe-effects/effect-value-visibility.html": [
+   "b01c7c5145c183fdca80dec4ca1966b0f72a7003",
+   "testharness"
+  ],
   "web-animations/animation-model/keyframe-effects/spacing-keyframes.html": [
    "318bc877791852b0829a3e10cb19e2a20a15adab",
    "testharness"
   ],
-  "web-animations/animation-model/keyframe-effects/the-effect-value-of-a-keyframe-effect.html": [
-   "b87824689825406a384d2e8afeac54c790ed16e0",
-   "testharness"
-  ],
   "web-animations/interfaces/Animatable/animate.html": [
-   "d2d57b1fe0bd5b6da4c44c569ff7dcf802298919",
+   "d076cdc3862c8a178d69d44cfe422f4e48b0649a",
    "testharness"
   ],
   "web-animations/interfaces/Animation/cancel.html": [
    "a28589129c6a2665295695f786b7beb2dd887fb3",
    "testharness"
   ],
   "web-animations/interfaces/Animation/constructor.html": [
    "20604949fc295efc398e297b9e4f755a116f0fbb",
@@ -203805,17 +203825,17 @@
    "ab150d71daf36949d4d6804033e19c734a68552d",
    "testharness"
   ],
   "web-animations/interfaces/AnimationEffectTiming/duration.html": [
    "2e0f0a270b8acd3d345732327ee2eabd32bdb2b2",
    "testharness"
   ],
   "web-animations/interfaces/AnimationEffectTiming/easing.html": [
-   "e1055f83a2774e4c406b813cfb19d3ea4db83970",
+   "5d21bb3ae43da1226f9510595b47b452b3b8f223",
    "testharness"
   ],
   "web-animations/interfaces/AnimationEffectTiming/endDelay.html": [
    "644eed9bf43bb0332ee33842ba0ad4423d90fc90",
    "testharness"
   ],
   "web-animations/interfaces/AnimationEffectTiming/fill.html": [
    "bf5b77d3c96e737700e51f8a2c5b8e2b9629902f",
@@ -203853,27 +203873,23 @@
    "3a626b145f4eb77e816b9020f6fc67630088a00b",
    "testharness"
   ],
   "web-animations/interfaces/KeyframeEffect/composite.html": [
    "2580086b2da9b29d1645484c5ad4e59636a370e5",
    "testharness"
   ],
   "web-animations/interfaces/KeyframeEffect/constructor.html": [
-   "577241478fdeca6257711e9f90fec64372bd5637",
+   "1011b4146d1054ee6498cce1905230a10fdb9f96",
    "testharness"
   ],
   "web-animations/interfaces/KeyframeEffect/copy-contructor.html": [
    "e1dfb5c05807a37974ecce98bb8c683cc291bfe4",
    "testharness"
   ],
-  "web-animations/interfaces/KeyframeEffect/effect-easing.html": [
-   "1a8cf09dc40d3a53c0c7f17d6d7da81ab0b11b9e",
-   "testharness"
-  ],
   "web-animations/interfaces/KeyframeEffect/getComputedTiming.html": [
    "c9dcf7c17010e5495007e000b33aeb4dc89f92b7",
    "testharness"
   ],
   "web-animations/interfaces/KeyframeEffect/iterationComposite.html": [
    "5b7dbc28de885751ea952f4fecc2bd07cb3cea11",
    "testharness"
   ],
@@ -203896,18 +203912,18 @@
   "web-animations/interfaces/KeyframeEffectReadOnly/copy-contructor.html": [
    "8ef986f13e7fe7ffeb7403f647b4169ac0d6a138",
    "testharness"
   ],
   "web-animations/interfaces/KeyframeEffectReadOnly/spacing.html": [
    "ffca3a0ad5c7b08242224b80c52ebb8b9b7bfce6",
    "testharness"
   ],
-  "web-animations/resources/effect-easing-tests.js": [
-   "429325e4ff9a4e1dc5277815a8f2a5ba2c9ebc3f",
+  "web-animations/resources/easing-tests.js": [
+   "190134380a0724f470a03ed0aa20c936bfed8d6c",
    "support"
   ],
   "web-animations/resources/keyframe-utils.js": [
    "7a0f21838f4bbda51fe7e0b5d8e55952c6cdcbd4",
    "support"
   ],
   "web-animations/testcommon.js": [
    "001012b71248cdecba02215c827ab437b672e8c6",
@@ -203944,16 +203960,20 @@
   "web-animations/timing-model/animations/set-the-timeline-of-an-animation.html": [
    "6e8e029f813046c3da69b4ff0c9d03d2a56b38a4",
    "testharness"
   ],
   "web-animations/timing-model/animations/updating-the-finished-state.html": [
    "266f1b793aa74a59486081f3ba8f6cbb482e714b",
    "testharness"
   ],
+  "web-animations/timing-model/time-transformations/transformed-progress.html": [
+   "6eebd47c9e60c9590de4d1747f8b8b7866a4a275",
+   "testharness"
+  ],
   "webaudio/.gitignore": [
    "11bc81247643b0a9fc665f1e4b1f592cc1f4c670",
    "support"
   ],
   "webaudio/OWNERS": [
    "d98264a830bdab63db07061e8b25080188e1aeab",
    "support"
   ],
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/009.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/009.html.ini
@@ -1,5 +1,5 @@
 [009.html]
   type: testharness
   [track CORS: No CORS, not same-origin, no headers]
-    expected: FAIL
+    disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1337242
 
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/010.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/010.html.ini
@@ -1,5 +1,5 @@
 [010.html]
   type: testharness
   [track CORS: Anonymous, not same-origin, no headers]
-    expected: FAIL
+    disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1337242
 
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/011.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/011.html.ini
@@ -1,5 +1,5 @@
 [011.html]
   type: testharness
   [track CORS: Anonymous, not same-origin, with headers]
-    expected: FAIL
+    disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1337242
 
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/012.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/012.html.ini
@@ -1,5 +1,5 @@
 [012.html]
   type: testharness
   [track CORS: Use Credentials, not same-origin, no headers]
-    expected: FAIL
+    disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1337242
 
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/013.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/013.html.ini
@@ -1,5 +1,5 @@
 [013.html]
   type: testharness
   [track CORS: Use Credentials, not same-origin, with headers]
-    expected: FAIL
+    disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1337242
 
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/014.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/014.html.ini
@@ -1,6 +1,5 @@
 [014.html]
   type: testharness
-  expected: TIMEOUT
   [track CORS: No CORS, same-origin, no headers, redirects to same-origin, no headers]
-    expected: TIMEOUT
+    disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1337242
 
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/015.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/015.html.ini
@@ -1,6 +1,5 @@
 [015.html]
   type: testharness
-  expected: TIMEOUT
   [track CORS: No CORS, same-origin, with headers, redirects to same-origin, with headers]
-    expected: TIMEOUT
+    disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1337242
 
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/016.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/016.html.ini
@@ -1,6 +1,5 @@
 [016.html]
   type: testharness
-  expected: TIMEOUT
   [track CORS: Anonymous, same-origin, no headers, redirects to same-origin, no headers]
-    expected: TIMEOUT
+    disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1337242
 
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/017.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/017.html.ini
@@ -1,6 +1,5 @@
 [017.html]
   type: testharness
-  expected: TIMEOUT
   [track CORS: Anonymous, same-origin, no headers, redirects to same-origin, with headers]
-    expected: TIMEOUT
+    disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1337242
 
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/018.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/018.html.ini
@@ -1,6 +1,5 @@
 [018.html]
   type: testharness
-  expected: TIMEOUT
   [track CORS: Use Credentials, same-origin, no headers, redirects to same-origin, no headers]
-    expected: TIMEOUT
+    disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1337242
 
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/019.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/019.html.ini
@@ -1,6 +1,5 @@
 [019.html]
   type: testharness
-  expected: TIMEOUT
   [track CORS: Use Credentials, same-origin, with headers, redirects to same-origin, no headers]
-    expected: TIMEOUT
+    disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1337242
 
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/020.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/020.html.ini
@@ -1,6 +1,5 @@
 [020.html]
   type: testharness
-  expected: TIMEOUT
   [track CORS: Anonymous, not same-origin, no headers, redirects to same-origin, no headers]
-    expected: TIMEOUT
+    disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1337242
 
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/021.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/021.html.ini
@@ -1,6 +1,5 @@
 [021.html]
   type: testharness
-  expected: TIMEOUT
   [track CORS: Anonymous, not same-origin, with headers, redirects to same-origin, no headers]
-    expected: TIMEOUT
+    disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1337242
 
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/022.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/022.html.ini
@@ -1,6 +1,5 @@
 [022.html]
   type: testharness
-  expected: TIMEOUT
   [track CORS: Anonymous, not same-origin, with headers, redirects to same-origin, with headers]
-    expected: TIMEOUT
+    disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1337242
 
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/023.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/023.html.ini
@@ -1,6 +1,5 @@
 [023.html]
   type: testharness
-  expected: TIMEOUT
   [track CORS: Use Credentials, not same-origin, no headers, redirects to same-origin, no headers]
-    expected: TIMEOUT
+    disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1337242
 
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/024.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/024.html.ini
@@ -1,6 +1,5 @@
 [024.html]
   type: testharness
-  expected: TIMEOUT
   [track CORS: Use Credentials, not same-origin, with headers, redirects to same-origin, no headers]
-    expected: TIMEOUT
+    disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1337242
 
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/025.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/025.html.ini
@@ -1,6 +1,6 @@
 [025.html]
   type: testharness
-  expected: TIMEOUT
   [track CORS: Use Credentials, not same-origin, with headers, redirects to same-origin, with headers]
-    expected: TIMEOUT
+    disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1337242
 
+
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/026.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/026.html.ini
@@ -1,6 +1,6 @@
 [026.html]
   type: testharness
-  expected: TIMEOUT
   [track CORS: No CORS, same-origin, with headers, redirects to not same-origin, with headers]
-    expected: TIMEOUT
+    disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1337242
 
+
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/027.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/027.html.ini
@@ -1,6 +1,5 @@
 [027.html]
   type: testharness
-  expected: TIMEOUT
   [track CORS: Anonymous, same-origin, no headers, redirects to not same-origin, no headers]
-    expected: TIMEOUT
+    disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1337242
 
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/028.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/028.html.ini
@@ -1,6 +1,5 @@
 [028.html]
   type: testharness
-  expected: TIMEOUT
   [track CORS: Anonymous, same-origin, with headers, redirects to not same-origin, with headers]
-    expected: TIMEOUT
+    disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1337242
 
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/029.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/029.html.ini
@@ -1,6 +1,5 @@
 [029.html]
   type: testharness
-  expected: TIMEOUT
   [track CORS: Use Credentials, same-origin, no headers, redirects to not same-origin, no headers]
-    expected: TIMEOUT
+    disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1337242
 
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/030.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/030.html.ini
@@ -1,6 +1,5 @@
 [030.html]
   type: testharness
-  expected: TIMEOUT
   [track CORS: Use Credentials, same-origin, with headers, redirects to not same-origin, with headers]
-    expected: TIMEOUT
+    disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1337242
 
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/031.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/031.html.ini
@@ -1,6 +1,5 @@
 [031.html]
   type: testharness
-  expected: TIMEOUT
   [track CORS: Anonymous, not same-origin, no headers, redirects to not same-origin, with headers]
-    expected: TIMEOUT
+    disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1337242
 
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/032.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/032.html.ini
@@ -1,6 +1,5 @@
 [032.html]
   type: testharness
-  expected: TIMEOUT
   [track CORS: Anonymous, not same-origin, with headers, redirects to not same-origin, no headers]
-    expected: TIMEOUT
+    disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1337242
 
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/033.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/033.html.ini
@@ -1,6 +1,5 @@
 [033.html]
   type: testharness
-  expected: TIMEOUT
   [track CORS: Anonymous, not same-origin, with headers, redirects to not same-origin, with headers]
-    expected: TIMEOUT
+    disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1337242
 
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/034.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/034.html.ini
@@ -1,6 +1,5 @@
 [034.html]
   type: testharness
-  expected: TIMEOUT
   [track CORS: Use Credentials, not same-origin, no headers, redirects to not same-origin, with headers]
-    expected: TIMEOUT
+    disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1337242
 
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/035.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/035.html.ini
@@ -1,6 +1,5 @@
 [035.html]
   type: testharness
-  expected: TIMEOUT
   [track CORS: Use Credentials, not same-origin, with headers, redirects to not same-origin, no headers]
-    expected: TIMEOUT
+    disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1337242
 
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/036.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/036.html.ini
@@ -1,6 +1,5 @@
 [036.html]
   type: testharness
-  expected: TIMEOUT
   [track CORS: Use Credentials, not same-origin, with headers, redirects to not same-origin, with headers]
-    expected: TIMEOUT
+    disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1337242
 
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/037.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/037.html.ini
@@ -1,6 +1,5 @@
 [037.html]
   type: testharness
-  expected: TIMEOUT
   [track CORS: Anonymous, same-origin, no headers, redirects to not same-origin, no headers, redirects to same-origin, no headers]
-    expected: TIMEOUT
+    disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1337242
 
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/038.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/038.html.ini
@@ -1,6 +1,5 @@
 [038.html]
   type: testharness
-  expected: TIMEOUT
   [track CORS: Anonymous, same-origin, with headers, redirects to not same-origin, with headers, redirects to same-origin, with headers]
-    expected: TIMEOUT
+    disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1337242
 
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/039.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/039.html.ini
@@ -1,6 +1,5 @@
 [039.html]
   type: testharness
-  expected: TIMEOUT
   [track CORS: Anonymous, same-origin, no headers, redirects to not same-origin, with headers, redirects to same-origin, no headers]
-    expected: TIMEOUT
+    disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1337242
 
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/040.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/040.html.ini
@@ -1,6 +1,5 @@
 [040.html]
   type: testharness
-  expected: TIMEOUT
   [track CORS: Use Credentials, same-origin, no headers, redirects to not same-origin, no headers, redirects to same-origin, no headers]
-    expected: TIMEOUT
+    disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1337242
 
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/041.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/041.html.ini
@@ -1,6 +1,5 @@
 [041.html]
   type: testharness
-  expected: TIMEOUT
   [track CORS: Use Credentials, same-origin, with headers, redirects to not same-origin, with headers, redirects to same-origin, with headers]
-    expected: TIMEOUT
+    disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1337242
 
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/042.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/042.html.ini
@@ -1,6 +1,5 @@
 [042.html]
   type: testharness
-  expected: TIMEOUT
   [track CORS: Use Credentials, same-origin, no headers, redirects to not same-origin, with headers, redirects to same-origin, no headers]
-    expected: TIMEOUT
+    disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1337242
 
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/043.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/043.html.ini
@@ -1,6 +1,5 @@
 [043.html]
   type: testharness
-  expected: TIMEOUT
   [track CORS: Anonymous, same-origin, no headers, redirects to same-origin, no headers, redirects to not same-origin, no headers]
-    expected: TIMEOUT
+    disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1337242
 
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/044.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/044.html.ini
@@ -1,6 +1,5 @@
 [044.html]
   type: testharness
-  expected: TIMEOUT
   [track CORS: Anonymous, same-origin, no headers, redirects to same-origin, no headers, redirects to not same-origin, with headers]
-    expected: TIMEOUT
+    disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1337242
 
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/045.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/045.html.ini
@@ -1,6 +1,5 @@
 [045.html]
   type: testharness
-  expected: TIMEOUT
   [track CORS: Use Credentials, same-origin, no headers, redirects to same-origin, no headers, redirects to not same-origin, no headers]
-    expected: TIMEOUT
+    disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1337242
 
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/046.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cors/046.html.ini
@@ -1,6 +1,5 @@
 [046.html]
   type: testharness
-  expected: TIMEOUT
   [track CORS: Use Credentials, same-origin, no headers, redirects to same-origin, no headers, redirects to not same-origin, with headers]
-    expected: TIMEOUT
+    disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1337242
 
rename from testing/web-platform/tests/web-animations/animation-model/keyframe-effects/the-effect-value-of-a-keyframe-effect.html
rename to testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-overlapping-keyframes.html
--- a/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/the-effect-value-of-a-keyframe-effect.html
+++ b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-overlapping-keyframes.html
@@ -1,11 +1,11 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
-<title>Keyframe handling tests</title>
+<title>Effect value computation tests when keyframes overlap</title>
 <link rel="help" href="https://w3c.github.io/web-animations/#the-effect-value-of-a-keyframe-animation-effect">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
 <body>
 <div id="log"></div>
 <div id="target"></div>
 <script>
@@ -64,51 +64,10 @@ test(function(t) {
                 + ' overlap point should be used as interval startpoint');
   anim.currentTime = 750;
   assert_equals(getComputedStyle(div).opacity, '0.85',
                 'After the overlap point, the last keyframe from the'
                 + ' overlap point should be used as interval startpoint');
 }, 'Overlapping keyframes between 0 and 1 use the appropriate value on each'
    + ' side of the overlap point');
 
-test(function(t) {
-  var div = createDiv(t);
-  var anim = div.animate({ visibility: ['hidden','visible'] },
-                         { duration: 100 * MS_PER_SEC, fill: 'both' });
-
-  anim.currentTime = 0;
-  assert_equals(getComputedStyle(div).visibility, 'hidden',
-                'Visibility when progress = 0.');
-
-  anim.currentTime = 10 * MS_PER_SEC + 1;
-  assert_equals(getComputedStyle(div).visibility, 'visible',
-                'Visibility when progress > 0 due to linear easing.');
-
-  anim.finish();
-  assert_equals(getComputedStyle(div).visibility, 'visible',
-                'Visibility when progress = 1.');
-
-}, "Test visibility clamping behavior.");
-
-test(function(t) {
-  var div = createDiv(t);
-  var anim = div.animate({ visibility: ['hidden', 'visible'] },
-                         { duration: 100 * MS_PER_SEC, fill: 'both',
-                           easing: 'cubic-bezier(0.25, -0.6, 0, 0.5)' });
-
-  anim.currentTime = 0;
-  assert_equals(getComputedStyle(div).visibility, 'hidden',
-                'Visibility when progress = 0.');
-
-  // Timing function is below zero. So we expected visibility is hidden.
-  anim.currentTime = 10 * MS_PER_SEC + 1;
-  assert_equals(getComputedStyle(div).visibility, 'hidden',
-                'Visibility when progress < 0 due to cubic-bezier easing.');
-
-  anim.currentTime = 60 * MS_PER_SEC;
-  assert_equals(getComputedStyle(div).visibility, 'visible',
-                'Visibility when progress > 0 due to cubic-bezier easing.');
-
-}, "Test visibility clamping behavior with an easing that has a negative component");
-
-done();
 </script>
 </body>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-transformed-distance.html
@@ -0,0 +1,417 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Tests for calculation of the transformed distance when computing an effect value</title>
+<link rel="help" href="https://w3c.github.io/web-animations/#the-effect-value-of-a-keyframe-animation-effect">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../testcommon.js"></script>
+<script src="../../resources/easing-tests.js"></script>
+<body>
+<div id="log"></div>
+<div id="target"></div>
+<script>
+'use strict';
+
+// Test that applying easing to keyframes is applied as expected
+
+gEasingTests.forEach(params => {
+  test(function(t) {
+    const target = createDiv(t);
+    const anim   = target.animate([ { width: '0px' },
+                                    // We put the easing on the second keyframe
+                                    // so we can test that it is only applied
+                                    // to the specified keyframe.
+                                    { width: '100px', easing: params.easing },
+                                    { width: '200px' } ],
+                                  { duration: 2000,
+                                    fill: 'forwards' });
+
+    [ 0, 999, 1000, 1100, 1500, 2000 ].forEach(sampleTime => {
+      anim.currentTime = sampleTime;
+
+      const portion = (sampleTime - 1000) / 1000;
+      const expectedWidth = sampleTime < 1000
+                            ? sampleTime / 10 // first segment is linear
+                            : 100 + params.easingFunction(portion) * 100;
+      assert_approx_equals(parseFloat(getComputedStyle(target).width),
+                           expectedWidth,
+                           0.01,
+                           'The width should be approximately ' +
+                           `${expectedWidth} at ${sampleTime}ms`);
+    });
+  }, `A ${params.desc} on a keyframe affects the resulting style`);
+});
+
+// Test that a linear-equivalent cubic-bezier easing applied to a keyframe does
+// not alter (including clamping) the result.
+
+gEasingTests.forEach(params => {
+  const linearEquivalentEasings = [ 'cubic-bezier(0, 0, 0, 0)',
+                                    'cubic-bezier(1, 1, 1, 1)' ];
+  test(function(t) {
+    linearEquivalentEasings.forEach(linearEquivalentEasing => {
+      const timing = { duration: 1000,
+                       fill: 'forwards',
+                       easing: params.easing };
+
+      const linearTarget = createDiv(t);
+      const linearAnim = linearTarget.animate([ { width: '0px' },
+                                                { width: '100px' } ],
+                                              timing);
+
+      const equivalentTarget = createDiv(t);
+      const equivalentAnim =
+        equivalentTarget.animate([ { width: '0px',
+                                     easing: linearEquivalentEasing },
+                                   { width: '100px' } ],
+                                 timing);
+
+      [ 0, 250, 500, 750, 1000 ].forEach(sampleTime => {
+        linearAnim.currentTime = sampleTime;
+        equivalentAnim.currentTime = sampleTime;
+
+        assert_equals(getComputedStyle(linearTarget).width,
+                      getComputedStyle(equivalentTarget).width,
+                      `The 'width' of the animated elements should be equal ` +
+                      `at ${sampleTime}ms`);
+      });
+    });
+  }, 'Linear-equivalent cubic-bezier keyframe easing applied to an effect ' +
+     `with a ${params.desc} does not alter the result`);
+});
+
+// Test that different easing functions correctly handle inputs outside the
+// range [0, 1]. This only occurs when we have an easing specified on the
+// effect that produces a value outside [0, 1] which we then pass to an easing
+// on a keyframe.
+
+function assert_style_left_at(animation, time, easingFunction) {
+  animation.currentTime = time;
+  var portion = time / animation.effect.timing.duration;
+  assert_approx_equals(pxToNum(getComputedStyle(animation.effect.target).left),
+                       easingFunction(portion) * 100,
+                       0.01,
+                       'The left of the animation should be approximately ' +
+                       easingFunction(portion) * 100 + ' at ' + time + 'ms');
+}
+
+test(function(t) {
+  var target = createDiv(t);
+  target.style.position = 'absolute';
+  var anim = target.animate([ { left: '0px', easing: 'step-start' },
+                              { left: '100px' } ],
+                            { duration: 1000,
+                              fill: 'forwards',
+                              easing: 'cubic-bezier(0, 1.5, 1, 1.5)' });
+
+  // The bezier function produces values greater than 1 (but always less than 2)
+  // in (0.23368794, 1)
+  anim.currentTime = 0;
+  assert_equals(getComputedStyle(target).left, '100px');
+  anim.currentTime = 230;
+  assert_equals(getComputedStyle(target).left, '100px');
+  anim.currentTime = 250;
+  assert_equals(getComputedStyle(target).left, '200px');
+  anim.currentTime = 1000;
+  assert_equals(getComputedStyle(target).left, '100px');
+}, 'step-start easing with input progress greater than 1');
+
+test(function(t) {
+  var target = createDiv(t);
+  target.style.position = 'absolute';
+  var anim = target.animate([ { left: '0px', easing: 'step-end' },
+                              { left: '100px' } ],
+                            { duration: 1000,
+                              fill: 'forwards',
+                              easing: 'cubic-bezier(0, 1.5, 1, 1.5)' });
+
+  // The bezier function produces values greater than 1 (but always less than 2)
+  // in (0.23368794, 1)
+  anim.currentTime = 0;
+  assert_equals(getComputedStyle(target).left, '0px');
+  anim.currentTime = 230;
+  assert_equals(getComputedStyle(target).left, '0px');
+  anim.currentTime = 250;
+  assert_equals(getComputedStyle(target).left, '100px');
+  anim.currentTime = 1000;
+  assert_equals(getComputedStyle(target).left, '100px');
+}, 'step-end easing with input progress greater than 1');
+
+test(function(t) {
+  var target = createDiv(t);
+  target.style.position = 'absolute';
+  var anim = target.animate([ { left: '0px', easing: 'step-end' },
+                              { left: '100px' } ],
+                            { duration: 1000,
+                              fill: 'forwards',
+                              easing: 'cubic-bezier(0, 3, 1, 3)' });
+
+  // The bezier function produces values greater than 2 (but always less than 3)
+  // in the range (~0.245, ~0.882)
+  anim.currentTime = 0;
+  assert_equals(getComputedStyle(target).left, '0px');
+  anim.currentTime = 500;
+  assert_equals(getComputedStyle(target).left, '200px');
+  anim.currentTime = 900;
+  assert_equals(getComputedStyle(target).left, '100px');
+}, 'step-end easing with input progress greater than 2');
+
+test(function(t) {
+  var target = createDiv(t);
+  target.style.position = 'absolute';
+  var anim = target.animate([ { left: '0px', easing: 'step-start' },
+                              { left: '100px' } ],
+                            { duration: 1000,
+                              fill: 'forwards',
+                              easing: 'cubic-bezier(0, -0.5, 1, -0.5)' });
+
+  // The bezier function produces negative values (but always greater than -1)
+  // in (0, 0.766312060)
+  anim.currentTime = 0;
+  assert_equals(getComputedStyle(target).left, '100px');
+  anim.currentTime = 750;
+  assert_equals(getComputedStyle(target).left, '0px');
+  anim.currentTime = 800;
+  assert_equals(getComputedStyle(target).left, '100px');
+  anim.currentTime = 1000;
+  assert_equals(getComputedStyle(target).left, '100px');
+}, 'step-start easing with input progress less than 0');
+
+test(function(t) {
+  var target = createDiv(t);
+  target.style.position = 'absolute';
+  var anim = target.animate([ { left: '0px', easing: 'step-start' },
+                              { left: '100px' } ],
+                            { duration: 1000,
+                              fill: 'forwards',
+                              easing: 'cubic-bezier(0, -2, 1, -2)' });
+
+  // The bezier function produces values less than -1 (but always greater than
+  // -2) in the range (~0.118, ~0.755)
+  anim.currentTime = 0;
+  assert_equals(getComputedStyle(target).left, '100px');
+  anim.currentTime = 100;
+  assert_equals(getComputedStyle(target).left, '0px');
+  anim.currentTime = 500;
+  assert_equals(getComputedStyle(target).left, '-100px');
+  anim.currentTime = 1000;
+  assert_equals(getComputedStyle(target).left, '100px');
+}, 'step-start easing with input progress less than -1');
+
+test(function(t) {
+  var target = createDiv(t);
+  target.style.position = 'absolute';
+  var anim = target.animate([ { left: '0px', easing: 'step-end' },
+                              { left: '100px' } ],
+                            { duration: 1000,
+                              fill: 'forwards',
+                              easing: 'cubic-bezier(0, -0.5, 1, -0.5)' });
+
+  // The bezier function produces negative values (but always greater than -1)
+  // in (0, 0.766312060)
+  anim.currentTime = 0;
+  assert_equals(getComputedStyle(target).left, '0px');
+  anim.currentTime = 750;
+  assert_equals(getComputedStyle(target).left, '-100px');
+  anim.currentTime = 800;
+  assert_equals(getComputedStyle(target).left, '0px');
+  anim.currentTime = 1000;
+  assert_equals(getComputedStyle(target).left, '100px');
+}, 'step-end easing with input progress less than 0');
+
+test(function(t) {
+  var target = createDiv(t);
+  target.style.position = 'absolute';
+  var anim = target.animate(
+    // http://cubic-bezier.com/#.5,1,.5,0
+    [ { left: '0px', easing: 'cubic-bezier(0.5, 1, 0.5, 0)' },
+      { left: '100px' } ],
+      { duration: 1000,
+        fill: 'forwards',
+        easing: 'cubic-bezier(0, 1.5, 1, 1.5)' });
+  var keyframeEasing = function(x) {
+    assert_greater_than_equal(x, 0.0,
+      'This function should be called in [0, 1.0] range');
+    assert_less_than_equal(x, 1.0,
+      'This function should be called in [0, 1.0] range');
+    return cubicBezier(0.5, 1, 0.5, 0)(x);
+  }
+  var keyframeEasingExtrapolated = function(x) {
+    assert_greater_than(x, 1.0,
+      'This function should be called in (1.0, infinity) range');
+    // p3x + (p2y - p3y) / (p2x - p3x) * (x - p3x)
+    return 1.0 + (0 - 1) / (0.5 - 1) * (x - 1.0);
+  }
+  var effectEasing = function(x) {
+    return cubicBezier(0, 1.5, 1, 1.5)(x);
+  }
+
+  // The effect-easing produces values greater than 1 in (0.23368794, 1)
+  assert_style_left_at(anim, 0, function(x) {
+    return keyframeEasing(effectEasing(x));
+  });
+  assert_style_left_at(anim, 230, function(x) {
+    return keyframeEasing(effectEasing(x));
+  });
+  assert_style_left_at(anim, 240, function(x) {
+    return keyframeEasingExtrapolated(effectEasing(x));
+  });
+  // Near the extreme point of the effect-easing function
+  assert_style_left_at(anim, 700, function(x) {
+    return keyframeEasingExtrapolated(effectEasing(x));
+  });
+  assert_style_left_at(anim, 990, function(x) {
+    return keyframeEasingExtrapolated(effectEasing(x));
+  });
+  assert_style_left_at(anim, 1000, function(x) {
+    return keyframeEasing(effectEasing(x));
+  });
+}, 'cubic-bezier easing with input progress greater than 1');
+
+test(function(t) {
+  var target = createDiv(t);
+  target.style.position = 'absolute';
+  var anim = target.animate(
+    // http://cubic-bezier.com/#0,1.5,1,1.5
+    [ { left: '0px', easing: 'cubic-bezier(0, 1.5, 1, 1.5)' },
+      { left: '100px' } ],
+      { duration: 1000,
+        fill: 'forwards',
+        easing: 'cubic-bezier(0, 1.5, 1, 1.5)' });
+  var easing = function(x) {
+    assert_greater_than_equal(x, 0.0,
+      'This function should be called in [0, 1.0] range');
+    assert_less_than_equal(x, 1.0,
+      'This function should be called in [0, 1.0] range');
+    return cubicBezier(0, 1.5, 1, 1.5)(x);
+  }
+  var easingExtrapolated = function(x) {
+    assert_greater_than(x, 1.0,
+      'This function should be called in negative range');
+    // For cubic-bezier(0, 1.5, 1, 1.5), the tangent at the
+    // endpoint (x = 1.0) is infinity so we should just return 1.0.
+    return 1.0;
+  }
+
+  // The effect-easing produces values greater than 1 in (0.23368794, 1)
+  assert_style_left_at(anim, 0, function(x) {
+    return easing(easing(x))
+  });
+  assert_style_left_at(anim, 230, function(x) {
+    return easing(easing(x))
+  });
+  assert_style_left_at(anim, 240, function(x) {
+    return easingExtrapolated(easing(x));
+  });
+  // Near the extreme point of the effect-easing function
+  assert_style_left_at(anim, 700, function(x) {
+    return easingExtrapolated(easing(x));
+  });
+  assert_style_left_at(anim, 990, function(x) {
+    return easingExtrapolated(easing(x));
+  });
+  assert_style_left_at(anim, 1000, function(x) {
+    return easing(easing(x))
+  });
+}, 'cubic-bezier easing with input progress greater than 1 and where the ' +
+   'tangent on the upper boundary is infinity');
+
+test(function(t) {
+  var target = createDiv(t);
+  target.style.position = 'absolute';
+  var anim = target.animate(
+    // http://cubic-bezier.com/#.5,1,.5,0
+    [ { left: '0px', easing: 'cubic-bezier(0.5, 1, 0.5, 0)' },
+      { left: '100px' } ],
+      { duration: 1000,
+        fill: 'forwards',
+        easing: 'cubic-bezier(0, -0.5, 1, -0.5)' });
+  var keyframeEasing = function(x) {
+    assert_greater_than_equal(x, 0.0,
+      'This function should be called in [0, 1.0] range');
+    assert_less_than_equal(x, 1.0,
+      'This function should be called in [0, 1.0] range');
+    return cubicBezier(0.5, 1, 0.5, 0)(x);
+  }
+  var keyframeEasingExtrapolated = function(x) {
+    assert_less_than(x, 0.0,
+      'This function should be called in negative range');
+    // p0x + (p1y - p0y) / (p1x - p0x) * (x - p0x)
+    return (1 / 0.5) * x;
+  }
+  var effectEasing = function(x) {
+    return cubicBezier(0, -0.5, 1, -0.5)(x);
+  }
+
+  // The effect-easing produces negative values in (0, 0.766312060)
+  assert_style_left_at(anim, 0, function(x) {
+    return keyframeEasing(effectEasing(x));
+  });
+  assert_style_left_at(anim, 10, function(x) {
+    return keyframeEasingExtrapolated(effectEasing(x));
+  });
+  // Near the extreme point of the effect-easing function
+  assert_style_left_at(anim, 300, function(x) {
+    return keyframeEasingExtrapolated(effectEasing(x));
+  });
+  assert_style_left_at(anim, 750, function(x) {
+    return keyframeEasingExtrapolated(effectEasing(x));
+  });
+  assert_style_left_at(anim, 770, function(x) {
+    return keyframeEasing(effectEasing(x));
+  });
+  assert_style_left_at(anim, 1000, function(x) {
+    return keyframeEasing(effectEasing(x));
+  });
+}, 'cubic-bezier easing with input progress less than 0');
+
+test(function(t) {
+  var target = createDiv(t);
+  target.style.position = 'absolute';
+  var anim = target.animate(
+    // http://cubic-bezier.com/#0,-0.5,1,-0.5
+    [ { left: '0px', easing: 'cubic-bezier(0, -0.5, 1, -0.5)' },
+      { left: '100px' } ],
+      { duration: 1000,
+        fill: 'forwards',
+        easing: 'cubic-bezier(0, -0.5, 1, -0.5)' });
+  var easing = function(x) {
+    assert_greater_than_equal(x, 0.0,
+      'This function should be called in [0, 1.0] range');
+    assert_less_than_equal(x, 1.0,
+      'This function should be called in [0, 1.0] range');
+    return cubicBezier(0, -0.5, 1, -0.5)(x);
+  }
+  var easingExtrapolated = function(x) {
+    assert_less_than(x, 0.0,
+      'This function should be called in negative range');
+    // For cubic-bezier(0, -0.5, 1, -0.5), the tangent at the
+    // endpoint (x = 0.0) is infinity so we should just return 0.0.
+    return 0.0;
+  }
+
+  // The effect-easing produces negative values in (0, 0.766312060)
+  assert_style_left_at(anim, 0, function(x) {
+    return easing(easing(x))
+  });
+  assert_style_left_at(anim, 10, function(x) {
+    return easingExtrapolated(easing(x));
+  });
+  // Near the extreme point of the effect-easing function
+  assert_style_left_at(anim, 300, function(x) {
+    return easingExtrapolated(easing(x));
+  });
+  assert_style_left_at(anim, 750, function(x) {
+    return easingExtrapolated(easing(x));
+  });
+  assert_style_left_at(anim, 770, function(x) {
+    return easing(easing(x))
+  });
+  assert_style_left_at(anim, 1000, function(x) {
+    return easing(easing(x))
+  });
+}, 'cubic-bezier easing with input progress less than 0 and where the ' +
+   'tangent on the lower boundary is infinity');
+
+</script>
+</body>
copy from testing/web-platform/tests/web-animations/animation-model/keyframe-effects/the-effect-value-of-a-keyframe-effect.html
copy to testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-visibility.html
--- a/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/the-effect-value-of-a-keyframe-effect.html
+++ b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-visibility.html
@@ -1,114 +1,55 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
-<title>Keyframe handling tests</title>
+<title>Effect value computation tests for 'visibility' property</title>
 <link rel="help" href="https://w3c.github.io/web-animations/#the-effect-value-of-a-keyframe-animation-effect">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
 <body>
 <div id="log"></div>
 <div id="target"></div>
 <script>
 'use strict';
 
 test(function(t) {
   var div = createDiv(t);
-  var anim = div.animate([ { offset: 0, opacity: 0 },
-                           { offset: 0, opacity: 0.1 },
-                           { offset: 0, opacity: 0.2 },
-                           { offset: 1, opacity: 0.8 },
-                           { offset: 1, opacity: 0.9 },
-                           { offset: 1, opacity: 1 } ],
-                         { duration: 1000,
-                           easing: 'cubic-bezier(0.5, -0.5, 0.5, 1.5)' });
-  assert_equals(getComputedStyle(div).opacity, '0.2',
-                'When progress is zero the last keyframe with offset 0 should'
-                + ' be used');
-  // http://cubic-bezier.com/#.5,-0.5,.5,1.5
-  // At t=0.15, the progress should be negative
-  anim.currentTime = 150;
-  assert_equals(getComputedStyle(div).opacity, '0',
-                'When progress is negative, the first keyframe with a 0 offset'
-                + ' should be used');
-  // At t=0.71, the progress should be just less than 1
-  anim.currentTime = 710;
-  assert_approx_equals(parseFloat(getComputedStyle(div).opacity), 0.8, 0.01,
-                'When progress is just less than 1, the first keyframe with an'
-                + ' offset of 1 should be used as the interval endpoint');
-  // At t=0.85, the progress should be > 1
-  anim.currentTime = 850;
-  assert_equals(getComputedStyle(div).opacity, '1',
-                'When progress is greater than 1.0, the last keyframe with a 1'
-                + ' offset should be used');
-  anim.finish();
-  assert_equals(getComputedStyle(div).opacity, '1',
-                'When progress is equal to 1.0, the last keyframe with a 1'
-                + ' offset should be used');
-}, 'Overlapping keyframes at 0 and 1 use the appropriate value when the'
-   + ' progress is outside the range [0, 1]');
-
-test(function(t) {
-  var div = createDiv(t);
-  var anim = div.animate([ { offset: 0, opacity: 0 },
-                           { offset: 0.5, opacity: 0.3 },
-                           { offset: 0.5, opacity: 0.5 },
-                           { offset: 0.5, opacity: 0.7 },
-                           { offset: 1, opacity: 1 } ], 1000);
-  anim.currentTime = 250;
-  assert_equals(getComputedStyle(div).opacity, '0.15',
-                'Before the overlap point, the first keyframe from the'
-                + ' overlap point should be used as interval endpoint');
-  anim.currentTime = 500;
-  assert_equals(getComputedStyle(div).opacity, '0.7',
-                'At the overlap point, the last keyframe from the'
-                + ' overlap point should be used as interval startpoint');
-  anim.currentTime = 750;
-  assert_equals(getComputedStyle(div).opacity, '0.85',
-                'After the overlap point, the last keyframe from the'
-                + ' overlap point should be used as interval startpoint');
-}, 'Overlapping keyframes between 0 and 1 use the appropriate value on each'
-   + ' side of the overlap point');
-
-test(function(t) {
-  var div = createDiv(t);
   var anim = div.animate({ visibility: ['hidden','visible'] },
                          { duration: 100 * MS_PER_SEC, fill: 'both' });
 
   anim.currentTime = 0;
   assert_equals(getComputedStyle(div).visibility, 'hidden',
-                'Visibility when progress = 0.');
+                'Visibility when progress = 0');
 
   anim.currentTime = 10 * MS_PER_SEC + 1;
   assert_equals(getComputedStyle(div).visibility, 'visible',
-                'Visibility when progress > 0 due to linear easing.');
+                'Visibility when progress > 0 due to linear easing');
 
   anim.finish();
   assert_equals(getComputedStyle(div).visibility, 'visible',
-                'Visibility when progress = 1.');
+                'Visibility when progress = 1');
 
-}, "Test visibility clamping behavior.");
+}, 'Visibility clamping behavior');
 
 test(function(t) {
   var div = createDiv(t);
   var anim = div.animate({ visibility: ['hidden', 'visible'] },
                          { duration: 100 * MS_PER_SEC, fill: 'both',
                            easing: 'cubic-bezier(0.25, -0.6, 0, 0.5)' });
 
   anim.currentTime = 0;
   assert_equals(getComputedStyle(div).visibility, 'hidden',
-                'Visibility when progress = 0.');
+                'Visibility when progress = 0');
 
   // Timing function is below zero. So we expected visibility is hidden.
   anim.currentTime = 10 * MS_PER_SEC + 1;
   assert_equals(getComputedStyle(div).visibility, 'hidden',
-                'Visibility when progress < 0 due to cubic-bezier easing.');
+                'Visibility when progress < 0 due to cubic-bezier easing');
 
   anim.currentTime = 60 * MS_PER_SEC;
   assert_equals(getComputedStyle(div).visibility, 'visible',
-                'Visibility when progress > 0 due to cubic-bezier easing.');
+                'Visibility when progress > 0 due to cubic-bezier easing');
 
-}, "Test visibility clamping behavior with an easing that has a negative component");
+}, 'Visibility clamping behavior with an easing that has a negative component');
 
-done();
 </script>
 </body>
--- a/testing/web-platform/tests/web-animations/interfaces/Animatable/animate.html
+++ b/testing/web-platform/tests/web-animations/interfaces/Animatable/animate.html
@@ -1,15 +1,16 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <title>Animatable.animate tests</title>
 <link rel="help" href="https://w3c.github.io/web-animations/#dom-animatable-animate">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
+<script src="../../resources/easing-tests.js"></script>
 <script src="../../resources/keyframe-utils.js"></script>
 <body>
 <div id="log"></div>
 <iframe width="10" height="10" id="iframe"></iframe>
 <script>
 'use strict';
 
 // Tests on Element
@@ -91,16 +92,25 @@ gInvalidKeyframesTests.forEach(function(
   test(function(t) {
     var div = createDiv(t);
     assert_throws(subtest.expected, function() {
       div.animate(subtest.input, 2000);
     });
   }, 'Element.animate() does not accept ' + subtest.desc);
 });
 
+gInvalidEasings.forEach(invalidEasing => {
+  test(function(t) {
+    var div = createDiv(t);
+    assert_throws(new TypeError, () => {
+      div.animate({ easing: invalidEasing }, 2000);
+    });
+  }, `Element.animate() does not accept invalid easing: '${invalidEasing}'`);
+});
+
 test(function(t) {
   var div = createDiv(t);
   var anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
   assert_equals(anim.effect.timing.duration, 2000);
   // Also check that unspecified parameters receive their default values
   assert_equals(anim.effect.timing.fill, 'auto');
 }, 'Element.animate() accepts a double as an options argument');
 
--- a/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/easing.html
+++ b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/easing.html
@@ -1,32 +1,32 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <title>easing tests</title>
 <link rel="help" href="https://w3c.github.io/web-animations/#dom-animationeffecttiming-easing">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
-<script src="../../resources/effect-easing-tests.js"></script>
+<script src="../../resources/easing-tests.js"></script>
 <body>
 <div id="log"></div>
 <script>
 'use strict';
 
 function assert_progress(animation, currentTime, easingFunction) {
   animation.currentTime = currentTime;
   var portion = currentTime / animation.effect.timing.duration;
   assert_approx_equals(animation.effect.getComputedTiming().progress,
                        easingFunction(portion),
                        0.01,
                        'The progress of the animation should be approximately ' +
                        easingFunction(portion) + ' at ' + currentTime + 'ms');
 }
 
-gEffectEasingTests.forEach(function(options) {
+gEasingTests.forEach(function(options) {
   test(function(t) {
     var target = createDiv(t);
     var anim = target.animate([ { opacity: 0 }, { opacity: 1 } ],
                               { duration: 1000 * MS_PER_SEC,
                                 fill: 'forwards' });
     anim.effect.timing.easing = options.easing;
     assert_equals(anim.effect.timing.easing,
                   options.serialization || options.easing);
@@ -35,25 +35,25 @@ gEffectEasingTests.forEach(function(opti
     assert_progress(anim, 0, easing);
     assert_progress(anim, 250 * MS_PER_SEC, easing);
     assert_progress(anim, 500 * MS_PER_SEC, easing);
     assert_progress(anim, 750 * MS_PER_SEC, easing);
     assert_progress(anim, 1000 * MS_PER_SEC, easing);
   }, options.desc);
 });
 
-gInvalidEasingTests.forEach(function(options) {
+gInvalidEasings.forEach(function(invalidEasing) {
   test(function(t) {
     var div = createDiv(t);
     var anim = div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
     assert_throws({ name: 'TypeError' },
                   function() {
-                    anim.effect.timing.easing = options.easing;
+                    anim.effect.timing.easing = invalidEasing;
                   });
-  }, 'Invalid effect easing value test: \'' + options.easing + '\'');
+  }, 'Invalid effect easing value test: \'' + invalidEasing + '\'');
 });
 
 test(function(t) {
   var delay = 1000 * MS_PER_SEC;
 
   var target = createDiv(t);
   var anim = target.animate([ { opacity: 0 }, { opacity: 1 } ],
                             { duration: 1000 * MS_PER_SEC,
--- a/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/constructor.html
+++ b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/constructor.html
@@ -1,15 +1,16 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <title>KeyframeEffectReadOnly constructor tests</title>
 <link rel="help" href="https://w3c.github.io/web-animations/#the-keyframeeffect-interfaces">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
+<script src="../../resources/easing-tests.js"></script>
 <script src="../../resources/keyframe-utils.js"></script>
 <body>
 <div id="log"></div>
 <div id="target"></div>
 <style>
 #target {
   border-style: solid;  /* so border-*-width values don't compute to 0 */
 }
@@ -61,17 +62,35 @@ test(function(t) {
     var expected = subtest[1];
     var effect = new KeyframeEffectReadOnly(target, {
       left: ["10px", "20px"]
     }, { easing: easing });
     assert_equals(effect.timing.easing, expected,
                   "resulting easing for '" + easing + "'");
   });
 }, "easing values are parsed correctly when passed to the " +
-   "KeyframeEffectReadOnly constructor in KeyframeTimingOptions");
+   "KeyframeEffectReadOnly constructor in KeyframeEffectOptions");
+
+test(function(t) {
+  gInvalidEasings.forEach(invalidEasing => {
+    assert_throws(new TypeError, () => {
+      new KeyframeEffectReadOnly(target, { easing: invalidEasing });
+    }, `TypeError is thrown for easing '${invalidEasing}'`);
+  });
+}, 'invalid easing values are correctly rejected when passed to the ' +
+   'KeyframeEffectReadOnly constructor in regular keyframes');
+
+test(function(t) {
+  gInvalidEasings.forEach(invalidEasing => {
+    assert_throws(new TypeError, () => {
+      new KeyframeEffectReadOnly(target, null, { easing: invalidEasing });
+    }, `TypeError is thrown for easing '${invalidEasing}'`);
+  });
+}, 'invalid easing values are correctly rejected when passed to the ' +
+   'KeyframeEffectReadOnly constructor in KeyframeEffectOptions');
 
 test(function(t) {
   var getKeyframe = function(composite) {
     return { left: [ "10px", "20px" ], composite: composite };
   };
   gGoodKeyframeCompositeValueTests.forEach(function(composite) {
     var effect = new KeyframeEffectReadOnly(target, getKeyframe(composite));
     assert_equals(effect.getKeyframes()[0].composite, composite,
deleted file mode 100644
--- a/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/effect-easing.html
+++ /dev/null
@@ -1,683 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>Effect-level easing tests</title>
-<link rel="help" href="http://w3c.github.io/web-animations/#calculating-the-transformed-time">
-<link rel="author" title="Hiroyuki Ikezoe" href="mailto:hiikezoe@mozilla-japan.org">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="../../testcommon.js"></script>
-<script src="../../resources/effect-easing-tests.js"></script>
-<body>
-<div id="log"></div>
-<div id="target"></div>
-<script>
-"use strict";
-
-function assert_style_left_at(animation, time, easingFunction) {
-  animation.currentTime = time;
-  var portion = time / animation.effect.timing.duration;
-  assert_approx_equals(pxToNum(getComputedStyle(animation.effect.target).left),
-                       easingFunction(portion) * 100,
-                       0.01,
-                       'The left of the animation should be approximately ' +
-                       easingFunction(portion) * 100 + ' at ' + time + 'ms');
-}
-
-gEffectEasingTests.forEach(function(options) {
-  test(function(t) {
-    var target = createDiv(t);
-    target.style.position = 'absolute';
-    var anim = target.animate([ { left: '0px' }, { left: '100px' } ],
-                              { duration: 1000,
-                                fill: 'forwards',
-                                easing: options.easing });
-    var easing = options.easingFunction;
-
-    anim.pause();
-
-    assert_style_left_at(anim, 0, easing);
-    assert_style_left_at(anim, 250, easing);
-    assert_style_left_at(anim, 500, easing);
-    assert_style_left_at(anim, 750, easing);
-    assert_style_left_at(anim, 1000, easing);
-  }, options.desc);
-});
-
-var gEffectEasingTestsWithKeyframeEasing = [
-  {
-    desc: 'effect easing produces values greater than 1 with keyframe ' +
-          'easing cubic-bezier(0, 0, 0, 0)',
-    easing: 'cubic-bezier(0, 1.5, 1, 1.5)',
-    keyframeEasing: 'cubic-bezier(0, 0, 0, 0)', // linear
-    easingFunction: cubicBezier(0, 1.5, 1, 1.5)
-  },
-  {
-    desc: 'effect easing produces values greater than 1 with keyframe ' +
-          'easing cubic-bezier(1, 1, 1, 1)',
-    easing: 'cubic-bezier(0, 1.5, 1, 1.5)',
-    keyframeEasing: 'cubic-bezier(1, 1, 1, 1)', // linear
-    easingFunction: cubicBezier(0, 1.5, 1, 1.5)
-  },
-  {
-    desc: 'effect easing produces negative values 1 with keyframe ' +
-          'easing cubic-bezier(0, 0, 0, 0)',
-    easing: 'cubic-bezier(0, -0.5, 1, -0.5)',
-    keyframeEasing: 'cubic-bezier(0, 0, 0, 0)', // linear
-    easingFunction: cubicBezier(0, -0.5, 1, -0.5)
-  },
-  {
-    desc: 'effect easing produces negative values 1 with keyframe ' +
-          'easing cubic-bezier(1, 1, 1, 1)',
-    easing: 'cubic-bezier(0, -0.5, 1, -0.5)',
-    keyframeEasing: 'cubic-bezier(1, 1, 1, 1)', // linear
-    easingFunction: cubicBezier(0, -0.5, 1, -0.5)
-  },
-];
-
-gEffectEasingTestsWithKeyframeEasing.forEach(function(options) {
-  test(function(t) {
-    var target = createDiv(t);
-    target.style.position = 'absolute';
-    var anim = target.animate(
-      [ { left: '0px', easing: options.keyframeEasing },
-        { left: '100px' } ],
-        { duration: 1000,
-          fill: 'forwards',
-          easing: options.easing });
-    var easing = options.easingFunction;
-
-    anim.pause();
-
-    assert_style_left_at(anim, 0, easing);
-    assert_style_left_at(anim, 250, easing);
-    assert_style_left_at(anim, 500, easing);
-    assert_style_left_at(anim, 750, easing);
-    assert_style_left_at(anim, 1000, easing);
-  }, options.desc);
-});
-
-// Other test cases that effect easing produces values outside of [0,1].
-test(function(t) {
-  var target = createDiv(t);
-  target.style.position = 'absolute';
-  var anim = target.animate([ { left: '0px', easing: 'step-start' },
-                              { left: '100px' } ],
-                            { duration: 1000,
-                              fill: 'forwards',
-                              easing: 'cubic-bezier(0, 1.5, 1, 1.5)' });
-  anim.pause();
-
-  // The bezier function produces values greater than 1 in (0.23368794, 1)
-  anim.currentTime = 0;
-  assert_equals(getComputedStyle(target).left, '100px');
-  anim.currentTime = 230;
-  assert_equals(getComputedStyle(target).left, '100px');
-  anim.currentTime = 250;
-  assert_equals(getComputedStyle(target).left, '100px');
-  anim.currentTime = 1000;
-  assert_equals(getComputedStyle(target).left, '100px');
-}, 'effect easing produces values greater than 1 with step-start keyframe');
-
-test(function(t) {
-  var target = createDiv(t);
-  target.style.position = 'absolute';
-  var anim = target.animate([ { left: '0px', easing: 'step-end' },
-                              { left: '100px' } ],
-                            { duration: 1000,
-                              fill: 'forwards',
-                              easing: 'cubic-bezier(0, 1.5, 1, 1.5)' });
-  anim.pause();
-
-  // The bezier function produces values greater than 1 in (0.23368794, 1)
-  anim.currentTime = 0;
-  assert_equals(getComputedStyle(target).left, '0px');
-  anim.currentTime = 230;
-  assert_equals(getComputedStyle(target).left, '0px');
-  anim.currentTime = 250;
-  assert_equals(getComputedStyle(target).left, '100px');
-  anim.currentTime = 1000;
-  assert_equals(getComputedStyle(target).left, '100px');
-}, 'effect easing produces values greater than 1 with step-end keyframe');
-
-test(function(t) {
-  var target = createDiv(t);
-  target.style.position = 'absolute';
-  var anim = target.animate([ { left: '0px', easing: 'step-start' },
-                              { left: '100px' } ],
-                            { duration: 1000,
-                              fill: 'forwards',
-                              easing: 'cubic-bezier(0, -0.5, 1, -0.5)' });
-  anim.pause();
-
-  // The bezier function produces negative values in (0, 0.766312060)
-  anim.currentTime = 0;
-  assert_equals(getComputedStyle(target).left, '100px');
-  anim.currentTime = 750;
-  assert_equals(getComputedStyle(target).left, '0px');
-  anim.currentTime = 800;
-  assert_equals(getComputedStyle(target).left, '100px');
-  anim.currentTime = 1000;
-  assert_equals(getComputedStyle(target).left, '100px');
-}, 'effect easing produces negative values with step-start keyframe');
-
-test(function(t) {
-  var target = createDiv(t);
-  target.style.position = 'absolute';
-  var anim = target.animate([ { left: '0px', easing: 'step-end' },
-                              { left: '100px' } ],
-                            { duration: 1000,
-                              fill: 'forwards',
-                              easing: 'cubic-bezier(0, -0.5, 1, -0.5)' });
-  anim.pause();
-
-  // The bezier function produces negative values in (0, 0.766312060)
-  anim.currentTime = 0;
-  assert_equals(getComputedStyle(target).left, '0px');
-  anim.currentTime = 750;
-  assert_equals(getComputedStyle(target).left, '0px');
-  anim.currentTime = 800;
-  assert_equals(getComputedStyle(target).left, '0px');
-  anim.currentTime = 1000;
-  assert_equals(getComputedStyle(target).left, '100px');
-}, 'effect easing produces negative values with step-end keyframe');
-
-test(function(t) {
-  var target = createDiv(t);
-  target.style.position = 'absolute';
-  var anim = target.animate(
-    // http://cubic-bezier.com/#.5,1,.5,0
-    [ { left: '0px', easing: 'cubic-bezier(0.5, 1, 0.5, 0)' },
-      { left: '100px' } ],
-      { duration: 1000,
-        fill: 'forwards',
-        easing: 'cubic-bezier(0, 1.5, 1, 1.5)' });
-  var keyframeEasing = function(x) {
-    assert_greater_than_equal(x, 0.0,
-      'This function should be called in [0, 1.0] range');
-    assert_less_than_equal(x, 1.0,
-      'This function should be called in [0, 1.0] range');
-    return cubicBezier(0.5, 1, 0.5, 0)(x);
-  }
-  var keyframeEasingExtrapolated = function(x) {
-    assert_greater_than(x, 1.0,
-      'This function should be called in (1.0, infinity) range');
-    // p3x + (p2y - p3y) / (p2x - p3x) * (x - p3x)
-    return 1.0 + (0 - 1) / (0.5 - 1) * (x - 1.0);
-  }
-  var effectEasing = function(x) {
-    return cubicBezier(0, 1.5, 1, 1.5)(x);
-  }
-
-  anim.pause();
-
-  // The effect-easing produces values greater than 1 in (0.23368794, 1)
-  assert_style_left_at(anim, 0, function(x) {
-    return keyframeEasing(effectEasing(x));
-  });
-  assert_style_left_at(anim, 230, function(x) {
-    return keyframeEasing(effectEasing(x));
-  });
-  assert_style_left_at(anim, 240, function(x) {
-    return keyframeEasingExtrapolated(effectEasing(x));
-  });
-  // Near the extreme point of the effect-easing function
-  assert_style_left_at(anim, 700, function(x) {
-    return keyframeEasingExtrapolated(effectEasing(x));
-  });
-  assert_style_left_at(anim, 990, function(x) {
-    return keyframeEasingExtrapolated(effectEasing(x));
-  });
-  assert_style_left_at(anim, 1000, function(x) {
-    return keyframeEasing(effectEasing(x));
-  });
-}, 'effect easing produces values greater than 1 with keyframe easing ' +
-   'producing values greater than 1');
-
-test(function(t) {
-  var target = createDiv(t);
-  target.style.position = 'absolute';
-  var anim = target.animate(
-    // http://cubic-bezier.com/#0,1.5,1,1.5
-    [ { left: '0px', easing: 'cubic-bezier(0, 1.5, 1, 1.5)' },
-      { left: '100px' } ],
-      { duration: 1000,
-        fill: 'forwards',
-        easing: 'cubic-bezier(0, 1.5, 1, 1.5)' });
-  var easing = function(x) {
-    assert_greater_than_equal(x, 0.0,
-      'This function should be called in [0, 1.0] range');
-    assert_less_than_equal(x, 1.0,
-      'This function should be called in [0, 1.0] range');
-    return cubicBezier(0, 1.5, 1, 1.5)(x);
-  }
-  var easingExtrapolated = function(x) {
-    assert_greater_than(x, 1.0,
-      'This function should be called in negative range');
-    // For cubic-bezier(0, 1.5, 1, 1.5), the tangent at the
-    // endpoint (x = 1.0) is infinity so we should just return 1.0.
-    return 1.0;
-  }
-
-  anim.pause();
-
-  // The effect-easing produces values greater than 1 in (0.23368794, 1)
-  assert_style_left_at(anim, 0, function(x) {
-    return easing(easing(x))
-  });
-  assert_style_left_at(anim, 230, function(x) {
-    return easing(easing(x))
-  });
-  assert_style_left_at(anim, 240, function(x) {
-    return easingExtrapolated(easing(x));
-  });
-  // Near the extreme point of the effect-easing function
-  assert_style_left_at(anim, 700, function(x) {
-    return easingExtrapolated(easing(x));
-  });
-  assert_style_left_at(anim, 990, function(x) {
-    return easingExtrapolated(easing(x));
-  });
-  assert_style_left_at(anim, 1000, function(x) {
-    return easing(easing(x))
-  });
-}, 'effect easing which produces values greater than 1 and the tangent on ' +
-   'the upper boundary is infinity with keyframe easing producing values ' +
-   'greater than 1');
-
-test(function(t) {
-  var target = createDiv(t);
-  target.style.position = 'absolute';
-  var anim = target.animate(
-    // http://cubic-bezier.com/#.5,1,.5,0
-    [ { left: '0px', easing: 'cubic-bezier(0.5, 1, 0.5, 0)' },
-      { left: '100px' } ],
-      { duration: 1000,
-        fill: 'forwards',
-        easing: 'cubic-bezier(0, -0.5, 1, -0.5)' });
-  var keyframeEasing = function(x) {
-    assert_greater_than_equal(x, 0.0,
-      'This function should be called in [0, 1.0] range');
-    assert_less_than_equal(x, 1.0,
-      'This function should be called in [0, 1.0] range');
-    return cubicBezier(0.5, 1, 0.5, 0)(x);
-  }
-  var keyframeEasingExtrapolated = function(x) {
-    assert_less_than(x, 0.0,
-      'This function should be called in negative range');
-    // p0x + (p1y - p0y) / (p1x - p0x) * (x - p0x)
-    return (1 / 0.5) * x;
-  }
-  var effectEasing = function(x) {
-    return cubicBezier(0, -0.5, 1, -0.5)(x);
-  }
-
-  anim.pause();
-
-  // The effect-easing produces negative values in (0, 0.766312060)
-  assert_style_left_at(anim, 0, function(x) {
-    return keyframeEasing(effectEasing(x));
-  });
-  assert_style_left_at(anim, 10, function(x) {
-    return keyframeEasingExtrapolated(effectEasing(x));
-  });
-  // Near the extreme point of the effect-easing function
-  assert_style_left_at(anim, 300, function(x) {
-    return keyframeEasingExtrapolated(effectEasing(x));
-  });
-  assert_style_left_at(anim, 750, function(x) {
-    return keyframeEasingExtrapolated(effectEasing(x));
-  });
-  assert_style_left_at(anim, 770, function(x) {
-    return keyframeEasing(effectEasing(x));
-  });
-  assert_style_left_at(anim, 1000, function(x) {
-    return keyframeEasing(effectEasing(x));
-  });
-}, 'effect easing produces negative values with keyframe easing ' +
-   'producing negative values');
-
-test(function(t) {
-  var target = createDiv(t);
-  target.style.position = 'absolute';
-  var anim = target.animate(
-    // http://cubic-bezier.com/#0,-0.5,1,-0.5
-    [ { left: '0px', easing: 'cubic-bezier(0, -0.5, 1, -0.5)' },
-      { left: '100px' } ],
-      { duration: 1000,
-        fill: 'forwards',
-        easing: 'cubic-bezier(0, -0.5, 1, -0.5)' });
-  var easing = function(x) {
-    assert_greater_than_equal(x, 0.0,
-      'This function should be called in [0, 1.0] range');
-    assert_less_than_equal(x, 1.0,
-      'This function should be called in [0, 1.0] range');
-    return cubicBezier(0, -0.5, 1, -0.5)(x);
-  }
-  var easingExtrapolated = function(x) {
-    assert_less_than(x, 0.0,
-      'This function should be called in negative range');
-    // For cubic-bezier(0, -0.5, 1, -0.5), the tangent at the
-    // endpoint (x = 0.0) is infinity so we should just return 0.0.
-    return 0.0;
-  }
-
-  anim.pause();
-
-  // The effect-easing produces negative values in (0, 0.766312060)
-  assert_style_left_at(anim, 0, function(x) {
-    return easing(easing(x))
-  });
-  assert_style_left_at(anim, 10, function(x) {
-    return easingExtrapolated(easing(x));
-  });
-  // Near the extreme point of the effect-easing function
-  assert_style_left_at(anim, 300, function(x) {
-    return easingExtrapolated(easing(x));
-  });
-  assert_style_left_at(anim, 750, function(x) {
-    return easingExtrapolated(easing(x));
-  });
-  assert_style_left_at(anim, 770, function(x) {
-    return easing(easing(x))
-  });
-  assert_style_left_at(anim, 1000, function(x) {
-    return easing(easing(x))
-  });
-}, 'effect easing which produces negative values and the tangent on ' +
-   'the lower boundary is infinity with keyframe easing producing ' +
-   'negative values');
-
-var gStepTimingFunctionTests = [
-  {
-    description: 'Test bounds point of step-start easing',
-    keyframe:   [ { width: '0px' },
-                  { width: '100px' } ],
-    effect:     {
-                  delay: 1000,
-                  duration: 1000,
-                  fill: 'both',
-                  easing: 'steps(2, start)'
-                },
-    conditions: [
-                  { currentTime: 0,    progress: 0 },
-                  { currentTime: 999,  progress: 0 },
-                  { currentTime: 1000, progress: 0.5 },
-                  { currentTime: 1499, progress: 0.5 },
-                  { currentTime: 1500, progress: 1 },
-                  { currentTime: 2000, progress: 1 }
-                ]
-  },
-  {
-    description: 'Test bounds point of step-start easing with compositor',
-    keyframe:   [ { opacity: 0 },
-                  { opacity: 1 } ],
-    effect:     {
-                  delay: 1000,
-                  duration: 1000,
-                  fill: 'both',
-                  easing: 'steps(2, start)'
-                },
-    conditions: [
-                  { currentTime: 0,    progress: 0 },
-                  { currentTime: 999,  progress: 0 },
-                  { currentTime: 1000, progress: 0.5 },
-                  { currentTime: 1499, progress: 0.5 },
-                  { currentTime: 1500, progress: 1 },
-                  { currentTime: 2000, progress: 1 }
-                ]
-  },
-  {
-    description: 'Test bounds point of step-start easing with reverse direction',
-    keyframe:   [ { width: '0px' },
-                  { width: '100px' } ],
-    effect:     {
-                  delay: 1000,
-                  duration: 1000,
-                  fill: 'both',
-                  direction: 'reverse',
-                  easing: 'steps(2, start)'
-                },
-    conditions: [
-                  { currentTime: 0,    progress: 1 },
-                  { currentTime: 1001, progress: 1 },
-                  { currentTime: 1500, progress: 1 },
-                  { currentTime: 1501, progress: 0.5 },
-                  { currentTime: 2000, progress: 0 },
-                  { currentTime: 2500, progress: 0 }
-                ]
-  },
-  {
-    description: 'Test bounds point of step-start easing ' +
-                 'with iterationStart not at a transition point',
-    keyframe:   [ { width: '0px' },
-                  { width: '100px' } ],
-    effect:     {
-                  delay: 1000,
-                  duration: 1000,
-                  fill: 'both',
-                  iterationStart: 0.25,
-                  easing: 'steps(2, start)'
-                },
-    conditions: [
-                  { currentTime: 0,    progress: 0.5 },
-                  { currentTime: 999,  progress: 0.5 },
-                  { currentTime: 1000, progress: 0.5 },
-                  { currentTime: 1249, progress: 0.5 },
-                  { currentTime: 1250, progress: 1 },
-                  { currentTime: 1749, progress: 1 },
-                  { currentTime: 1750, progress: 0.5 },
-                  { currentTime: 2000, progress: 0.5 },
-                  { currentTime: 2500, progress: 0.5 },
-                ]
-  },
-  {
-    description: 'Test bounds point of step-start easing ' +
-                 'with iterationStart and delay',
-    keyframe:   [ { width: '0px' },
-                  { width: '100px' } ],
-    effect:     {
-                  delay: 1000,
-                  duration: 1000,
-                  fill: 'both',
-                  iterationStart: 0.5,
-                  easing: 'steps(2, start)'
-                },
-    conditions: [
-                  { currentTime: 0,    progress: 0.5 },
-                  { currentTime: 999,  progress: 0.5 },
-                  { currentTime: 1000, progress: 1 },
-                  { currentTime: 1499, progress: 1 },
-                  { currentTime: 1500, progress: 0.5 },
-                  { currentTime: 2000, progress: 1 }
-                ]
-  },
-  {
-    description: 'Test bounds point of step-start easing ' +
-                 'with iterationStart and reverse direction',
-    keyframe:   [ { width: '0px' },
-                  { width: '100px' } ],
-    effect:     {
-                  delay: 1000,
-                  duration: 1000,
-                  fill: 'both',
-                  iterationStart: 0.5,
-                  direction: 'reverse',
-                  easing: 'steps(2, start)'
-                },
-    conditions: [
-                  { currentTime: 0,    progress: 1 },
-                  { currentTime: 1000, progress: 1 },
-                  { currentTime: 1001, progress: 0.5 },
-                  { currentTime: 1499, progress: 0.5 },
-                  { currentTime: 1500, progress: 1 },
-                  { currentTime: 1999, progress: 1 },
-                  { currentTime: 2000, progress: 0.5 },
-                  { currentTime: 2500, progress: 0.5 }
-                ]
-  },
-  {
-    description: 'Test bounds point of step(4, start) easing ' +
-                 'with iterationStart 0.75 and delay',
-    keyframe:   [ { width: '0px' },
-                  { width: '100px' } ],
-    effect:     {
-                  duration: 1000,
-                  fill: 'both',
-                  delay: 1000,
-                  iterationStart: 0.75,
-                  easing: 'steps(4, start)'
-                },
-    conditions: [
-                  { currentTime: 0,    progress: 0.75 },
-                  { currentTime: 999,  progress: 0.75 },
-                  { currentTime: 1000, progress: 1 },
-                  { currentTime: 2000, progress: 1 },
-                  { currentTime: 2500, progress: 1 }
-                ]
-  },
-  {
-    description: 'Test bounds point of step-start easing ' +
-                 'with alternate direction',
-    keyframe:   [ { width: '0px' },
-                  { width: '100px' } ],
-    effect:     {
-                  duration: 1000,
-                  fill: 'both',
-                  delay: 1000,
-                  iterations: 2,
-                  iterationStart: 1.5,
-                  direction: 'alternate',
-                  easing: 'steps(2, start)'
-                },
-    conditions: [
-                  { currentTime: 0,    progress: 1 },
-                  { currentTime: 1000, progress: 1 },
-                  { currentTime: 1001, progress: 0.5 },
-                  { currentTime: 2999, progress: 1 },
-                  { currentTime: 3000, progress: 0.5 },
-                  { currentTime: 3500, progress: 0.5 }
-                ]
-  },
-  {
-    description: 'Test bounds point of step-start easing ' +
-                 'with alternate-reverse direction',
-    keyframe:   [ { width: '0px' },
-                  { width: '100px' } ],
-    effect:     {
-                  duration: 1000,
-                  fill: 'both',
-                  delay: 1000,
-                  iterations: 2,
-                  iterationStart: 0.5,
-                  direction: 'alternate-reverse',
-                  easing: 'steps(2, start)'
-                },
-    conditions: [
-                  { currentTime: 0,    progress: 1 },
-                  { currentTime: 1000, progress: 1 },
-                  { currentTime: 1001, progress: 0.5 },
-                  { currentTime: 2999, progress: 1 },
-                  { currentTime: 3000, progress: 0.5 },
-                  { currentTime: 3500, progress: 0.5 }
-                ]
-  },
-  {
-    description: 'Test bounds point of step-start easing in keyframe',
-    keyframe:   [ { width: '0px', easing: 'steps(2, start)' },
-                  { width: '100px' } ],
-    effect:     {
-                  delay: 1000,
-                  duration: 1000,
-                  fill: 'both',
-                },
-    conditions: [
-                  { currentTime: 0,    progress: 0,     width: '0px' },
-                  { currentTime: 999,  progress: 0,     width: '0px' },
-                  { currentTime: 1000, progress: 0,     width: '50px' },
-                  { currentTime: 1499, progress: 0.499, width: '50px' },
-                  { currentTime: 1500, progress: 0.5,   width: '100px' },
-                  { currentTime: 2000, progress: 1,     width: '100px' },
-                  { currentTime: 2500, progress: 1,     width: '100px' }
-                ]
-  },
-  {
-    description: 'Test bounds point of step-end easing ' +
-                 'with iterationStart and delay',
-    keyframe:   [ { width: '0px' },
-                  { width: '100px' } ],
-    effect:     {
-                  duration: 1000,
-                  fill: 'both',
-                  delay: 1000,
-                  iterationStart: 0.5,
-                  easing: 'steps(2, end)'
-                },
-    conditions: [
-                  { currentTime: 0,    progress: 0 },
-                  { currentTime: 999,  progress: 0 },
-                  { currentTime: 1000, progress: 0.5 },
-                  { currentTime: 1499, progress: 0.5 },
-                  { currentTime: 1500, progress: 0 },
-                  { currentTime: 1999, progress: 0 },
-                  { currentTime: 2000, progress: 0.5 },
-                  { currentTime: 2500, progress: 0.5 }
-                ]
-  },
-  {
-    description: 'Test bounds point of step-end easing ' +
-                 'with iterationStart not at a transition point',
-    keyframe:   [ { width: '0px' },
-                  { width: '100px' } ],
-    effect:     {
-                  delay: 1000,
-                  duration: 1000,
-                  fill: 'both',
-                  iterationStart: 0.75,
-                  easing: 'steps(2, end)'
-                },
-    conditions: [
-                  { currentTime: 0,    progress: 0.5 },
-                  { currentTime: 999,  progress: 0.5 },
-                  { currentTime: 1000, progress: 0.5 },
-                  { currentTime: 1249, progress: 0.5 },
-                  { currentTime: 1250, progress: 0 },
-                  { currentTime: 1749, progress: 0 },
-                  { currentTime: 1750, progress: 0.5 },
-                  { currentTime: 2000, progress: 0.5 },
-                  { currentTime: 2500, progress: 0.5 },
-                ]
-  }
-];
-
-gStepTimingFunctionTests.forEach(function(options) {
-  test(function(t) {
-    var target = createDiv(t);
-    var animation = target.animate(options.keyframe, options.effect);
-    options.conditions.forEach(function(condition) {
-      animation.currentTime = condition.currentTime;
-      if (typeof condition.progress !== 'undefined') {
-        assert_equals(animation.effect.getComputedTiming().progress,
-                      condition.progress,
-                      'Progress at ' + animation.currentTime + 'ms');
-      }
-      if (typeof condition.width !== 'undefined') {
-        assert_equals(getComputedStyle(target).width,
-                      condition.width,
-                      'Progress at ' + animation.currentTime + 'ms');
-      }
-    });
-  }, options.description);
-});
-
-gInvalidEasingTests.forEach(function(options) {
-  test(function(t) {
-    var div = createDiv(t);
-    assert_throws({ name: 'TypeError' },
-                  function() {
-                    div.animate({ easing: options.easing }, 100 * MS_PER_SEC);
-                  });
-  }, 'Invalid keyframe easing value: \'' + options.easing + '\'');
-});
-
-</script>
-</body>
rename from testing/web-platform/tests/web-animations/resources/effect-easing-tests.js
rename to testing/web-platform/tests/web-animations/resources/easing-tests.js
--- a/testing/web-platform/tests/web-animations/resources/effect-easing-tests.js
+++ b/testing/web-platform/tests/web-animations/resources/easing-tests.js
@@ -1,9 +1,9 @@
-var gEffectEasingTests = [
+var gEasingTests = [
   {
     desc: 'step-start function',
     easing: 'step-start',
     easingFunction: stepStart(1),
     serialization: 'steps(1, start)'
   },
   {
     desc: 'steps(1, start) function',
@@ -62,37 +62,26 @@ var gEffectEasingTests = [
     desc: 'ease-out function',
     easing: 'ease-out', // cubic-bezier(0, 0, 0.58, 1.0)
     easingFunction: cubicBezier(0, 0, 0.58, 1.0)
   },
   {
     desc: 'easing function which produces values greater than 1',
     easing: 'cubic-bezier(0, 1.5, 1, 1.5)',
     easingFunction: cubicBezier(0, 1.5, 1, 1.5)
+  },
+  {
+    desc: 'easing function which produces values less than 1',
+    easing: 'cubic-bezier(0, -0.5, 1, -0.5)',
+    easingFunction: cubicBezier(0, -0.5, 1, -0.5)
   }
 ];
 
-var gInvalidEasingTests = [
-  {
-    easing: ''
-  },
-  {
-    easing: 'test'
-  },
-  {
-    easing: 'cubic-bezier(1.1, 0, 1, 1)'
-  },
-  {
-    easing: 'cubic-bezier(0, 0, 1.1, 1)'
-  },
-  {
-    easing: 'cubic-bezier(-0.1, 0, 1, 1)'
-  },
-  {
-    easing: 'cubic-bezier(0, 0, -0.1, 1)'
-  },
-  {
-    easing: 'steps(-1, start)'
-  },
-  {
-    easing: 'steps(0.1, start)'
-  },
+var gInvalidEasings = [
+  '',
+  'test',
+  'cubic-bezier(1.1, 0, 1, 1)',
+  'cubic-bezier(0, 0, 1.1, 1)',
+  'cubic-bezier(-0.1, 0, 1, 1)',
+  'cubic-bezier(0, 0, -0.1, 1)',
+  'steps(-1, start)',
+  'steps(0.1, start)'
 ];
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/web-animations/timing-model/time-transformations/transformed-progress.html
@@ -0,0 +1,272 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Tests for the transformed progress</title>
+<link rel="help" href="https://w3c.github.io/web-animations/#calculating-the-transformed-progress">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../testcommon.js"></script>
+<script src="../../resources/easing-tests.js"></script>
+<body>
+<div id="log"></div>
+<div id="target"></div>
+<script>
+'use strict';
+
+gEasingTests.forEach(params => {
+  test(function(t) {
+    const target = createDiv(t);
+    const anim   = target.animate(null, { duration: 1000,
+                                          fill: 'forwards',
+                                          easing: params.easing });
+
+    [ 0, 250, 500, 750, 1000 ].forEach(sampleTime => {
+      anim.currentTime = sampleTime;
+      const portion = sampleTime / anim.effect.getComputedTiming().duration;
+      const expectedProgress = params.easingFunction(portion);
+      assert_approx_equals(anim.effect.getComputedTiming().progress,
+                           expectedProgress,
+                           0.01,
+                           'The progress should be approximately ' +
+                           expectedProgress + ` at ${sampleTime}ms`);
+    });
+  }, 'Transformed progress for ' + params.desc);
+});
+
+// Additional tests for various boundary conditions of step timing functions
+
+var gStepTimingFunctionTests = [
+  {
+    description: 'Test bounds point of step-start easing',
+    effect:     {
+                  delay: 1000,
+                  duration: 1000,
+                  fill: 'both',
+                  easing: 'steps(2, start)'
+                },
+    conditions: [
+                  { currentTime: 0,    progress: 0 },
+                  { currentTime: 999,  progress: 0 },
+                  { currentTime: 1000, progress: 0.5 },
+                  { currentTime: 1499, progress: 0.5 },
+                  { currentTime: 1500, progress: 1 },
+                  { currentTime: 2000, progress: 1 }
+                ]
+  },
+  {
+    description: 'Test bounds point of step-start easing with reverse direction',
+    effect:     {
+                  delay: 1000,
+                  duration: 1000,
+                  fill: 'both',
+                  direction: 'reverse',
+                  easing: 'steps(2, start)'
+                },
+    conditions: [
+                  { currentTime: 0,    progress: 1 },
+                  { currentTime: 1001, progress: 1 },
+                  { currentTime: 1500, progress: 1 },
+                  { currentTime: 1501, progress: 0.5 },
+                  { currentTime: 2000, progress: 0 },
+                  { currentTime: 2500, progress: 0 }
+                ]
+  },
+  {
+    description: 'Test bounds point of step-start easing ' +
+                 'with iterationStart not at a transition point',
+    effect:     {
+                  delay: 1000,
+                  duration: 1000,
+                  fill: 'both',
+                  iterationStart: 0.25,
+                  easing: 'steps(2, start)'
+                },
+    conditions: [
+                  { currentTime: 0,    progress: 0.5 },
+                  { currentTime: 999,  progress: 0.5 },
+                  { currentTime: 1000, progress: 0.5 },
+                  { currentTime: 1249, progress: 0.5 },
+                  { currentTime: 1250, progress: 1 },
+                  { currentTime: 1749, progress: 1 },
+                  { currentTime: 1750, progress: 0.5 },
+                  { currentTime: 2000, progress: 0.5 },
+                  { currentTime: 2500, progress: 0.5 },
+                ]
+  },
+  {
+    description: 'Test bounds point of step-start easing ' +
+                 'with iterationStart and delay',
+    effect:     {
+                  delay: 1000,
+                  duration: 1000,
+                  fill: 'both',
+                  iterationStart: 0.5,
+                  easing: 'steps(2, start)'
+                },
+    conditions: [
+                  { currentTime: 0,    progress: 0.5 },
+                  { currentTime: 999,  progress: 0.5 },
+                  { currentTime: 1000, progress: 1 },
+                  { currentTime: 1499, progress: 1 },
+                  { currentTime: 1500, progress: 0.5 },
+                  { currentTime: 2000, progress: 1 }
+                ]
+  },
+  {
+    description: 'Test bounds point of step-start easing ' +
+                 'with iterationStart and reverse direction',
+    effect:     {
+                  delay: 1000,
+                  duration: 1000,
+                  fill: 'both',
+                  iterationStart: 0.5,
+                  direction: 'reverse',
+                  easing: 'steps(2, start)'
+                },
+    conditions: [
+                  { currentTime: 0,    progress: 1 },
+                  { currentTime: 1000, progress: 1 },
+                  { currentTime: 1001, progress: 0.5 },
+                  { currentTime: 1499, progress: 0.5 },
+                  { currentTime: 1500, progress: 1 },
+                  { currentTime: 1999, progress: 1 },
+                  { currentTime: 2000, progress: 0.5 },
+                  { currentTime: 2500, progress: 0.5 }
+                ]
+  },
+  {
+    description: 'Test bounds point of step(4, start) easing ' +
+                 'with iterationStart 0.75 and delay',
+    effect:     {
+                  duration: 1000,
+                  fill: 'both',
+                  delay: 1000,
+                  iterationStart: 0.75,
+                  easing: 'steps(4, start)'
+                },
+    conditions: [
+                  { currentTime: 0,    progress: 0.75 },
+                  { currentTime: 999,  progress: 0.75 },
+                  { currentTime: 1000, progress: 1 },
+                  { currentTime: 2000, progress: 1 },
+                  { currentTime: 2500, progress: 1 }
+                ]
+  },
+  {
+    description: 'Test bounds point of step-start easing ' +
+                 'with alternate direction',
+    effect:     {
+                  duration: 1000,
+                  fill: 'both',
+                  delay: 1000,
+                  iterations: 2,
+                  iterationStart: 1.5,
+                  direction: 'alternate',
+                  easing: 'steps(2, start)'
+                },
+    conditions: [
+                  { currentTime: 0,    progress: 1 },
+                  { currentTime: 1000, progress: 1 },
+                  { currentTime: 1001, progress: 0.5 },
+                  { currentTime: 2999, progress: 1 },
+                  { currentTime: 3000, progress: 0.5 },
+                  { currentTime: 3500, progress: 0.5 }
+                ]
+  },
+  {
+    description: 'Test bounds point of step-start easing ' +
+                 'with alternate-reverse direction',
+    effect:     {
+                  duration: 1000,
+                  fill: 'both',
+                  delay: 1000,
+                  iterations: 2,
+                  iterationStart: 0.5,
+                  direction: 'alternate-reverse',
+                  easing: 'steps(2, start)'
+                },
+    conditions: [
+                  { currentTime: 0,    progress: 1 },
+                  { currentTime: 1000, progress: 1 },
+                  { currentTime: 1001, progress: 0.5 },
+                  { currentTime: 2999, progress: 1 },
+                  { currentTime: 3000, progress: 0.5 },
+                  { currentTime: 3500, progress: 0.5 }
+                ]
+  },
+  {
+    description: 'Test bounds point of step-end easing',
+    effect:     {
+                  delay: 1000,
+                  duration: 1000,
+                  fill: 'both',
+                  easing: 'steps(2, end)'
+                },
+    conditions: [
+                  { currentTime: 0,    progress: 0 },
+                  { currentTime: 999,  progress: 0 },
+                  { currentTime: 1000, progress: 0 },
+                  { currentTime: 1499, progress: 0 },
+                  { currentTime: 1500, progress: 0.5 },
+                  { currentTime: 2000, progress: 1 }
+                ]
+  },
+  {
+    description: 'Test bounds point of step-end easing ' +
+                 'with iterationStart and delay',
+    effect:     {
+                  duration: 1000,
+                  fill: 'both',
+                  delay: 1000,
+                  iterationStart: 0.5,
+                  easing: 'steps(2, end)'
+                },
+    conditions: [
+                  { currentTime: 0,    progress: 0 },
+                  { currentTime: 999,  progress: 0 },
+                  { currentTime: 1000, progress: 0.5 },
+                  { currentTime: 1499, progress: 0.5 },
+                  { currentTime: 1500, progress: 0 },
+                  { currentTime: 1999, progress: 0 },
+                  { currentTime: 2000, progress: 0.5 },
+                  { currentTime: 2500, progress: 0.5 }
+                ]
+  },
+  {
+    description: 'Test bounds point of step-end easing ' +
+                 'with iterationStart not at a transition point',
+    effect:     {
+                  delay: 1000,
+                  duration: 1000,
+                  fill: 'both',
+                  iterationStart: 0.75,
+                  easing: 'steps(2, end)'
+                },
+    conditions: [
+                  { currentTime: 0,    progress: 0.5 },
+                  { currentTime: 999,  progress: 0.5 },
+                  { currentTime: 1000, progress: 0.5 },
+                  { currentTime: 1249, progress: 0.5 },
+                  { currentTime: 1250, progress: 0 },
+                  { currentTime: 1749, progress: 0 },
+                  { currentTime: 1750, progress: 0.5 },
+                  { currentTime: 2000, progress: 0.5 },
+                  { currentTime: 2500, progress: 0.5 },
+                ]
+  }
+];
+
+gStepTimingFunctionTests.forEach(function(options) {
+  test(function(t) {
+    var target = createDiv(t);
+    var animation = target.animate(null, options.effect);
+    options.conditions.forEach(function(condition) {
+      animation.currentTime = condition.currentTime;
+      assert_equals(animation.effect.getComputedTiming().progress,
+                    condition.progress,
+                    'Progress at ' + animation.currentTime + 'ms');
+    });
+  }, options.description);
+});
+
+</script>
+</body>
--- a/toolkit/components/places/nsNavHistory.cpp
+++ b/toolkit/components/places/nsNavHistory.cpp
@@ -15,17 +15,16 @@
 #include "nsAnnotationService.h"
 #include "nsFaviconService.h"
 #include "nsPlacesMacros.h"
 #include "History.h"
 #include "Helpers.h"
 
 #include "nsTArray.h"
 #include "nsCollationCID.h"
-#include "nsILocaleService.h"
 #include "nsNetUtil.h"
 #include "nsPrintfCString.h"
 #include "nsPromiseFlatString.h"
 #include "nsString.h"
 #include "nsUnicharUtils.h"
 #include "prsystem.h"
 #include "prtime.h"
 #include "nsEscape.h"
@@ -4474,28 +4473,21 @@ nsNavHistory::AutoCompleteFeedback(int32
 
 
 nsICollation *
 nsNavHistory::GetCollation()
 {
   if (mCollation)
     return mCollation;
 
-  // locale
-  nsCOMPtr<nsILocale> locale;
-  nsCOMPtr<nsILocaleService> ls(do_GetService(NS_LOCALESERVICE_CONTRACTID));
-  NS_ENSURE_TRUE(ls, nullptr);
-  nsresult rv = ls->GetApplicationLocale(getter_AddRefs(locale));
-  NS_ENSURE_SUCCESS(rv, nullptr);
-
   // collation
   nsCOMPtr<nsICollationFactory> cfact =
     do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID);
   NS_ENSURE_TRUE(cfact, nullptr);
-  rv = cfact->CreateCollation(locale, getter_AddRefs(mCollation));
+  nsresult rv = cfact->CreateCollation(getter_AddRefs(mCollation));
   NS_ENSURE_SUCCESS(rv, nullptr);
 
   return mCollation;
 }
 
 nsIStringBundle *
 nsNavHistory::GetBundle()
 {
--- a/toolkit/components/search/nsSearchService.js
+++ b/toolkit/components/search/nsSearchService.js
@@ -3863,22 +3863,19 @@ SearchService.prototype = {
     let alphaEngines = [];
 
     for (let name in this._engines) {
       let engine = this._engines[name];
       if (!(engine.name in addedEngines))
         alphaEngines.push(this._engines[engine.name]);
     }
 
-    let locale = Cc["@mozilla.org/intl/nslocaleservice;1"]
-                   .getService(Ci.nsILocaleService)
-                   .newLocale(getLocale());
     let collation = Cc["@mozilla.org/intl/collation-factory;1"]
                       .createInstance(Ci.nsICollationFactory)
-                      .CreateCollation(locale);
+                      .CreateCollation();
     const strength = Ci.nsICollation.kCollationCaseInsensitiveAscii;
     let comparator = (a, b) => collation.compareString(strength, a.name, b.name);
     alphaEngines.sort(comparator);
     return this.__sortedEngines = this.__sortedEngines.concat(alphaEngines);
   },
 
   /**
    * Get a sorted array of engines.
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -9226,22 +9226,30 @@
     "keyed": true,
     "kind": "exponential",
     "high": 10000,
     "n_buckets": 100,
     "bug_numbers": [1294349]
   },
   "VIDEO_AS_CONTENT_SOURCE" : {
     "alert_emails": ["ajones@mozilla.com", "kaku@mozilla.com"],
-    "expires_in_version": "56",
+    "expires_in_version": "58",
     "description": "Usage of a {visible / invisible} video element as the source of {drawImage(), createPattern(), createImageBitmap() and captureStream()} APIs. (0 = ALL_VISIBLE, 1 = ALL_INVISIBLE, 2 = drawImage_VISIBLE, 3 = drawImage_INVISIBLE, 4 = createPattern_VISIBLE, 5 = createPattern_INVISIBLE, 6 = createImageBitmap_VISIBLE, 7 = createImageBitmap_INVISIBLE, 8 = captureStream_VISIBLE, 9 = captureStream_INVISIBLE)",
     "kind": "enumerated",
     "n_values": 12,
     "bug_numbers": [1299718]
   },
+  "VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT" : {
+    "alert_emails": ["ajones@mozilla.com", "kaku@mozilla.com"],
+    "expires_in_version": "58",
+    "description": "Usage of an invisible {in tree / not in tree} video element as the source of {drawImage(), createPattern(), createImageBitmap() and captureStream()} APIs. (0 = ALL_IN_TREE, 1 = ALL_NOT_IN_TREE, 2 = drawImage_IN_TREE, 3 = drawImage_NOT_IN_TREE, 4 = createPattern_IN_TREE, 5 = createPattern_NOT_IN_TREE, 6 = createImageBitmap_IN_TREE, 7 = createImageBitmap_NOT_IN_TREE, 8 = captureStream_IN_TREE, 9 = captureStream_NOT_IN_TREE)",
+    "kind": "enumerated",
+    "n_values": 12,
+    "bug_numbers": [1337301]
+  },
   "VIDEO_UNLOAD_STATE": {
     "alert_emails": ["ajones@mozilla.com"],
     "expires_in_version": "55",
     "kind": "enumerated",
     "n_values": 5,
     "description": "HTML Media Element state when unloading. ended = 0, paused = 1, stalled = 2, seeking = 3, other = 4",
     "bug_numbers": [1261955, 1261955]
   },
--- a/toolkit/components/windowwatcher/test/browser_new_content_window_chromeflags.js
+++ b/toolkit/components/windowwatcher/test/browser_new_content_window_chromeflags.js
@@ -84,17 +84,17 @@ const DISALLOWED = {
     flag: Ci.nsIWebBrowserChrome.CHROME_WINDOW_LOWERED, // Renamed to alwaysLowered
     defaults_to: false,
   },
   "alwaysRaised": {
     flag: Ci.nsIWebBrowserChrome.CHROME_WINDOW_RAISED,
     defaults_to: false,
   },
   "suppressanimation": {
-    flag: Ci.nsIWebBrowserChrome.CHROME_MAC_SUPPRESS_ANIMATION,
+    flag: Ci.nsIWebBrowserChrome.CHROME_SUPPRESS_ANIMATION,
     defaults_to: false,
   },
   "extrachrome": {
     flag: Ci.nsIWebBrowserChrome.CHROME_EXTRA,
     defaults_to: false,
   },
   "centerscreen": {
     flag: Ci.nsIWebBrowserChrome.CHROME_CENTER_SCREEN,
@@ -187,16 +187,17 @@ function assertContentFlags(chromeFlags)
       // have been able to flip it on.
       Assert.ok((chromeFlags & flag),
                 `Expected feature ${feature} to be enabled`);
     }
   }
 
   for (let feature in DISALLOWED) {
     let flag = DISALLOWED[feature].flag;
+    Assert.ok(flag, "Expected flag to be a non-zeroish value");
     if (DISALLOWED[feature].defaults_to) {
       // The feature is supposed to default to true, so it should
       // stay true.
       Assert.ok((chromeFlags & flag),
                 `Expected feature ${feature} to be unchanged`);
     } else {
       // The feature is supposed to default to false, so it should
       // stay false.
--- a/toolkit/library/Makefile.in
+++ b/toolkit/library/Makefile.in
@@ -9,9 +9,9 @@ include $(topsrcdir)/config/config.mk
 # Wrap linker to print linking status periodically to prevent the linking
 # process from getting killed
 EXPAND_LIBS_EXEC := $(PYTHON) $(topsrcdir)/config/link.py
 
 include $(topsrcdir)/config/rules.mk
 
 .PHONY: gtestxul
 gtestxul:
-	$(MAKE) -C $(DEPTH) toolkit/library/gtest/target LINK_GTEST=1
+	$(MAKE) -C $(DEPTH) toolkit/library/gtest/target LINK_GTEST_DURING_COMPILE=1
--- a/toolkit/library/dependentlibs.py
+++ b/toolkit/library/dependentlibs.py
@@ -121,15 +121,21 @@ def gen_list(output, lib):
     else:
         ext = os.path.splitext(lib)[1]
         assert(ext == '.dll')
         func = dependentlibs_dumpbin
 
     deps = dependentlibs(lib, libpaths, func)
     deps[lib] = mozpath.join(libpaths[0], lib)
     output.write('\n'.join(deps.keys()) + '\n')
+
+    with open(output.name + ".gtest", 'w') as gtest_out:
+        libs = deps.keys()
+        libs[-1] = 'gtest/' + libs[-1]
+        gtest_out.write('\n'.join(libs) + '\n')
+
     return set(deps.values())
 
 def main():
     gen_list(sys.stdout, sys.argv[1])
 
 if __name__ == '__main__':
     main()
--- a/toolkit/library/gtest/Makefile.in
+++ b/toolkit/library/gtest/Makefile.in
@@ -1,35 +1,28 @@
 # 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/.
 
 # Enforce that the clean/distclean rules removes everything that needs
 # to be removed from this directory.
 ifneq (,$(filter clean distclean,$(MAKECMDGOALS)))
-LINK_GTEST = 1
+LINK_GTEST_DURING_COMPILE = 1
 endif
 
-ifndef LINK_GTEST
-# Force to not include backend.mk unless LINK_GTEST is defined.
+ifndef LINK_GTEST_DURING_COMPILE
+# Force to not include backend.mk unless LINK_GTEST_DURING_COMPILE is set.
 # Not including backend.mk makes traversing this directory do nothing.
 STANDALONE_MAKEFILE = 1
 
 else
 
 include $(topsrcdir)/toolkit/library/libxul.mk
 
 include $(topsrcdir)/config/config.mk
 
 # Wrap linker to print linking status periodically to prevent the linking
 # process from getting killed
 EXPAND_LIBS_EXEC := $(PYTHON) $(topsrcdir)/config/link.py
 
-ifdef COMPILE_ENVIRONMENT
-target:: $(DIST)/bin/dependentlibs.list.gtest
-endif
-
-$(DIST)/bin/dependentlibs.list.gtest: $(DIST)/bin/dependentlibs.list
-	sed -e 's|$(SHARED_LIBRARY)|gtest/$(SHARED_LIBRARY)|' $< > $@
-
 LINK_PDBFILE = xul-gtest.pdb
 
 endif
--- a/toolkit/library/moz.build
+++ b/toolkit/library/moz.build
@@ -373,22 +373,23 @@ if CONFIG['COMPILE_ENVIRONMENT']:
     if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('cocoa', 'uikit'):
         full_libname = SHARED_LIBRARY_NAME
     else:
         full_libname = '%s%s%s' % (
             CONFIG['DLL_PREFIX'],
             LIBRARY_NAME,
             CONFIG['DLL_SUFFIX']
         )
-    GENERATED_FILES += ['dependentlibs.list']
-    GENERATED_FILES['dependentlibs.list'].script = 'dependentlibs.py:gen_list'
-    GENERATED_FILES['dependentlibs.list'].inputs = [
+    GENERATED_FILES += [('dependentlibs.list', 'dependentlibs.list.gtest')]
+    dep_libs_list = GENERATED_FILES[('dependentlibs.list', 'dependentlibs.list.gtest')]
+    dep_libs_list.script = 'dependentlibs.py:gen_list'
+    dep_libs_list.inputs = [
         '!%s' % full_libname,
     ]
-    FINAL_TARGET_FILES += ['!dependentlibs.list']
+    FINAL_TARGET_FILES += ['!dependentlibs.list', '!dependentlibs.list.gtest']
 
 # WebRender dependencies
 if CONFIG['MOZ_ENABLE_WEBRENDER']:
     if CONFIG['OS_ARCH'] == 'Linux':
         OS_LIBS += [
             'GL',
         ]
     elif CONFIG['OS_ARCH'] == 'WINNT':
--- a/widget/cocoa/TextInputHandler.mm
+++ b/widget/cocoa/TextInputHandler.mm
@@ -3752,17 +3752,34 @@ IMEInputHandler::SendCommittedText(NSStr
   NS_ENSURE_TRUE(mWidget, );
   // XXX We should send the string without mView.
   if (!mView) {
     return;
   }
 
   NSAttributedString* attrStr =
     [[NSAttributedString alloc] initWithString:aString];
-  [mView insertText:attrStr];
+  if ([mView conformsToProtocol:@protocol(NSTextInputClient)]) {
+    NSObject<NSTextInputClient>* textInputClient =
+      static_cast<NSObject<NSTextInputClient>*>(mView);
+    [textInputClient insertText:attrStr
+               replacementRange:NSMakeRange(NSNotFound, 0)];
+  }
+
+  // Last resort.  If we cannot retrieve NSTextInputProtocol from mView
+  // or blocking to call our InsertText(), we should call InsertText()
+  // directly to commit composition forcibly.
+  if (mIsIMEComposing) {
+    MOZ_LOG(gLog, LogLevel::Info,
+      ("%p IMEInputHandler::SendCommittedText, trying to insert text directly "
+       "due to IME not calling our InsertText()", this));
+    static_cast<TextInputHandler*>(this)->InsertText(attrStr);
+    MOZ_ASSERT(!mIsIMEComposing);
+  }
+
   [attrStr release];
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 void
 IMEInputHandler::KillIMEComposition()
 {