Merge mozilla-central to mozilla-inbound. a=merge
authorDaniel Varga <dvarga@mozilla.com>
Thu, 16 May 2019 07:19:28 +0300
changeset 532987 c1b13e664eb4bf1e49d4dab83a9cf93a5277e4c3
parent 532986 0e8d68920793aca5c383e4977aa3899a2bc5876b (current diff)
parent 532857 79ca43bd514a81906b8ac0ec4c8fcbcdc80f4870 (diff)
child 532988 a0464187dbfa6b296752aeb9e34779ac1b85be77
push id11276
push userrgurzau@mozilla.com
push dateMon, 20 May 2019 13:11:24 +0000
treeherdermozilla-beta@847755a7c325 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone68.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. a=merge
devtools/client/debugger/panel.js
mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ActivityResultHandler.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ActivityResultHandlerMap.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/InputOptionsUtils.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/JSONUtils.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/MapUtils.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/MenuUtils.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/NonEvictingLruCache.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/PrefUtils.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/WindowUtils.java
testing/web-platform/meta/svg/embedded/image-embedding-svg-with-viewport-units-inline-style.svg.ini
testing/web-platform/meta/svg/embedded/image-embedding-svg-with-viewport-units.svg.ini
testing/web-platform/meta/svg/geometry/parsing/cx-valid.svg.ini
testing/web-platform/meta/svg/geometry/parsing/cy-valid.svg.ini
testing/web-platform/meta/svg/geometry/parsing/r-valid.svg.ini
testing/web-platform/meta/svg/geometry/parsing/rx-valid.svg.ini
testing/web-platform/meta/svg/geometry/parsing/ry-valid.svg.ini
testing/web-platform/meta/svg/geometry/parsing/x-valid.svg.ini
testing/web-platform/meta/svg/geometry/parsing/y-valid.svg.ini
testing/web-platform/meta/svg/geometry/reftests/percentage.svg.ini
testing/web-platform/meta/svg/shapes/ellipse-01.svg.ini
testing/web-platform/meta/svg/shapes/ellipse-02.svg.ini
testing/web-platform/meta/svg/shapes/ellipse-03.svg.ini
testing/web-platform/meta/svg/shapes/ellipse-05.svg.ini
testing/web-platform/meta/svg/shapes/ellipse-06.svg.ini
testing/web-platform/meta/svg/shapes/ellipse-07.svg.ini
testing/web-platform/meta/svg/shapes/ellipse-08.svg.ini
testing/web-platform/meta/svg/shapes/rx-ry-not-inherited.svg.ini
--- a/.taskcluster.yml
+++ b/.taskcluster.yml
@@ -40,42 +40,48 @@
 #     push: {owner, pushlog_id, revision},
 #     repository: {url, project, level},
 #     input,
 #     parameters,
 #     taskId,      // targetted taskId
 #     taskGroupId, // targetted taskGroupId
 #     action: {name, title, description, taskGroupId, symbol, repo_scope, cb_name}
 #     ownTaskId:   // taskId of the task that will be created
+#     clientId:    // clientId that triggered this hook
 #   }
 
 version: 1
 tasks:
   # NOTE: support for actions in ci-admin requires that the `tasks` property be an array *before* JSON-e rendering
   # takes place.
   - $if: 'tasks_for in ["hg-push", "action", "cron"]'
     then:
       $let:
         # sometimes the push user is just `ffxbld` or the like, but we want an email-like field..
         ownerEmail: {$if: '"@" in push.owner', then: '${push.owner}', else: '${push.owner}@noreply.mozilla.org'}
         # ensure there's no trailing `/` on the repo URL
         repoUrl: {$if: 'repository.url[-1] == "/"', then: {$eval: 'repository.url[:-1]'}, else: {$eval: 'repository.url'}}
+        # expire try earlier than other branches
+        expires:
+          $if: 'repository.project == "try"'
+          then: {$fromNow: '28 days'}
+          else: {$fromNow: '1 year'}
       in:
         taskId: {$if: 'tasks_for != "action"', then: '${ownTaskId}'}
         taskGroupId:
           $if: 'tasks_for == "action"'
           then:
             '${action.taskGroupId}'
           else:
             '${ownTaskId}' # same as taskId; this is how automation identifies a decision tsak
         schedulerId: 'gecko-level-${repository.level}'
 
         created: {$fromNow: ''}
         deadline: {$fromNow: '1 day'}
-        expires: {$fromNow: '1 year 1 second'} # 1 second so artifacts expire first, despite rounding errors
+        expires: {$eval: 'expires'}
         metadata:
           $merge:
             - owner: "${ownerEmail}"
               source: "${repoUrl}/raw-file/${push.revision}/.taskcluster.yml"
             - $if: 'tasks_for == "hg-push"'
               then:
                 name: "Gecko Decision Task"
                 description: 'The task that creates all of the other tasks in the task graph'
@@ -249,17 +255,17 @@ tasks:
                   --head-ref="$GECKO_HEAD_REF"
                   --head-rev="$GECKO_HEAD_REV"
                   ${extraArgs}
 
           artifacts:
             'public':
               type: 'directory'
               path: '/builds/worker/artifacts'
-              expires: {$fromNow: '1 year'}
+              expires: {$eval: expires}
 
         extra:
           $merge:
             - treeherder:
                 $merge:
                   - machine:
                       platform: gecko-decision
                   - $if: 'tasks_for == "hg-push"'
--- a/browser/actors/NetErrorChild.jsm
+++ b/browser/actors/NetErrorChild.jsm
@@ -478,26 +478,16 @@ class NetErrorChild extends ActorChild {
         let certRange = this._getCertValidityRange(docShell);
 
         let approximateDate = now - difference * 1000;
         // If the difference is more than a day, we last fetched the date in the last 5 days,
         // and adjusting the date per the interval would make the cert valid, warn the user:
         if (Math.abs(difference) > 60 * 60 * 24 && (now - lastFetched) <= 60 * 60 * 24 * 5 &&
             certRange.notBefore < approximateDate && certRange.notAfter > approximateDate) {
           clockSkew = true;
-          let systemDate = formatter.format(new Date());
-          // negative difference means local time is behind server time
-          approximateDate = formatter.format(new Date(approximateDate));
-
-          doc.getElementById("wrongSystemTime_URL").textContent = doc.location.hostname;
-          doc.getElementById("wrongSystemTime_systemDate").textContent = systemDate;
-          doc.getElementById("wrongSystemTime_actualDate").textContent = approximateDate;
-
-          doc.getElementById("errorShortDesc").style.display = "none";
-          doc.getElementById("wrongSystemTimePanel").style.display = "block";
 
         // If there is no clock skew with Kinto servers, check against the build date.
         // (The Kinto ping could have happened when the time was still right, or not at all)
         } else {
           let appBuildID = Services.appinfo.appBuildID;
 
           let year = parseInt(appBuildID.substr(0, 4), 10);
           let month = parseInt(appBuildID.substr(4, 2), 10) - 1;
@@ -507,35 +497,29 @@ class NetErrorChild extends ActorChild {
           let systemDate = new Date();
 
           // We don't check the notBefore of the cert with the build date,
           // as it is of course almost certain that it is now later than the build date,
           // so we shouldn't exclude the possibility that the cert has become valid
           // since the build date.
           if (buildDate > systemDate && new Date(certRange.notAfter) > buildDate) {
             clockSkew = true;
-
-            doc.getElementById("wrongSystemTimeWithoutReference_URL")
-              .textContent = doc.location.hostname;
-            doc.getElementById("wrongSystemTimeWithoutReference_systemDate")
-              .textContent = formatter.format(systemDate);
           }
         }
 
         let systemDate = formatter.format(new Date());
         doc.getElementById("wrongSystemTime_systemDate1").textContent = systemDate;
         if (clockSkew) {
           doc.body.classList.add("illustrated", "clockSkewError");
           let clockErrTitle = doc.getElementById("et_clockSkewError");
           let clockErrDesc = doc.getElementById("ed_clockSkewError");
           // eslint-disable-next-line no-unsanitized/property
           doc.querySelector(".title-text").textContent = clockErrTitle.textContent;
           let desc = doc.getElementById("errorShortDescText");
           doc.getElementById("errorShortDesc").style.display = "block";
-          doc.getElementById("wrongSystemTimePanel").style.display = "none";
           doc.getElementById("certificateErrorReporting").style.display = "none";
           if (desc) {
             // eslint-disable-next-line no-unsanitized/property
             desc.innerHTML = clockErrDesc.innerHTML;
           }
           let errorPageContainer = doc.getElementById("errorPageContainer");
           let textContainer = doc.getElementById("text-container");
           errorPageContainer.style.backgroundPosition = `left top calc(50vh - ${textContainer.clientHeight / 2}px)`;
--- a/browser/base/content/aboutNetError.xhtml
+++ b/browser/base/content/aboutNetError.xhtml
@@ -165,24 +165,16 @@
               <p id="errorWhatToDoText" />
           </div>
 
           <div id="errorWhatToDo2">
               <p id="errorWhatToDoText2" />
               <p id="badStsCertExplanation" hidden="true">&certerror.whatShouldIDo.badStsCertExplanation1;</p>
           </div>
 
-          <div id="wrongSystemTimePanel">
-            &certerror.wrongSystemTime2;
-          </div>
-
-          <div id="wrongSystemTimeWithoutReferencePanel">
-            &certerror.wrongSystemTimeWithoutReference;
-          </div>
-
           <!-- Long Description (Note: See netError.dtd for used XHTML tags) -->
           <div id="errorLongDesc" />
 
           <div id="learnMoreContainer">
             <p><a id="learnMoreLink" target="new" data-telemetry-id="learn_more_link">&errorReporting.learnMore;</a></p>
           </div>
         </div>
 
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -3721,20 +3721,18 @@ var browserDragAndDrop = {
   getTriggeringPrincipal(aEvent) {
     return Services.droppedLinkHandler.getTriggeringPrincipal(aEvent);
   },
 
   getCSP(aEvent) {
     return Services.droppedLinkHandler.getCSP(aEvent);
   },
 
-  validateURIsForDrop(aEvent, aURIsCount, aURIs) {
-    return Services.droppedLinkHandler.validateURIsForDrop(aEvent,
-                                                           aURIsCount,
-                                                           aURIs);
+  validateURIsForDrop(aEvent, aURIs) {
+    return Services.droppedLinkHandler.validateURIsForDrop(aEvent, aURIs);
   },
 
   dropLinks(aEvent, aDisallowInherit) {
     return Services.droppedLinkHandler.dropLinks(aEvent, aDisallowInherit);
   },
 };
 
 var homeButtonObserver = {
@@ -3747,17 +3745,17 @@ var homeButtonObserver = {
           if (link.url.includes("|")) {
             urls.push(...link.url.split("|"));
           } else {
             urls.push(link.url);
           }
         }
 
         try {
-          browserDragAndDrop.validateURIsForDrop(aEvent, urls.length, urls);
+          browserDragAndDrop.validateURIsForDrop(aEvent, urls);
         } catch (e) {
           return;
         }
 
         setTimeout(openHomeDialog, 0, urls.join("|"));
       }
     },
 
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -776,17 +776,20 @@ nsContextMenu.prototype = {
     popup.insertBefore(fragment, insertBeforeElement);
   },
 
   initSyncItems() {
     gSync.updateContentContextMenu(this);
   },
 
   openPasswordManager() {
-    LoginHelper.openPasswordManager(window, gContextMenuContentData.documentURIObject.host);
+    LoginHelper.openPasswordManager(window, {
+      filterString: gContextMenuContentData.documentURIObject.host,
+      entryPoint: "contextmenu",
+    });
   },
 
   inspectNode() {
     return DevToolsShim.inspectNode(gBrowser.selectedTab, this.targetSelectors);
   },
 
   inspectA11Y() {
     return DevToolsShim.inspectA11Y(gBrowser.selectedTab, this.targetSelectors);
--- a/browser/base/content/pageinfo/security.js
+++ b/browser/base/content/pageinfo/security.js
@@ -175,17 +175,20 @@ var security = {
       }
     }
   },
 
   /**
    * Open the login manager window
    */
   viewPasswords() {
-    LoginHelper.openPasswordManager(window, this._getSecurityInfo().hostName);
+    LoginHelper.openPasswordManager(window, {
+      filterString: this._getSecurityInfo().hostName,
+      entryPoint: "pageinfo",
+    });
   },
 
   _cert: null,
 };
 
 function securityOnLoad(uri, windowInfo) {
   security.init(uri, windowInfo);
 
--- a/browser/base/content/test/siteIdentity/browser.ini
+++ b/browser/base/content/test/siteIdentity/browser.ini
@@ -109,8 +109,9 @@ support-files =
   iframe_navigation.html
 [browser_navigation_failures.js]
 [browser_secure_transport_insecure_scheme.js]
 [browser_ignore_same_page_navigation.js]
 [browser_mixed_content_with_navigation.js]
 support-files =
   file_mixedPassiveContent.html
   file_bug1045809_1.html
+[browser_deprecatedTLSVersions.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/siteIdentity/browser_deprecatedTLSVersions.js
@@ -0,0 +1,61 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Tests for Bug 1535210 - Set SSL STATE_IS_BROKEN flag for TLS1.0 and TLS 1.1 connections
+ */
+
+const HTTPS_TLS1_0 = "https://tls1.example.com";
+const HTTPS_TLS1_1 = "https://tls11.example.com";
+const HTTPS_TLS1_2 = "https://tls12.example.com";
+const HTTPS_TLS1_3 = "https://tls13.example.com";
+
+
+function getIdentityMode(aWindow = window) {
+  return aWindow.document.getElementById("identity-box").className;
+}
+
+function getConnectionState() {
+  // Prevents items that are being lazy loaded causing issues
+  document.getElementById("identity-box").click();
+  gIdentityHandler.refreshIdentityPopup();
+  return document.getElementById("identity-popup").getAttribute("connection");
+}
+
+add_task(async function() {
+  await BrowserTestUtils.withNewTab("about:blank", async function(browser) {
+    // Try deprecated versions
+    await BrowserTestUtils.loadURI(browser, HTTPS_TLS1_0);
+    await BrowserTestUtils.browserLoaded(browser);
+    isSecurityState(browser, "broken");
+    is(getIdentityMode(), "unknownIdentity weakCipher", "Identity should be unknownIdentity");
+    is(getConnectionState(), "not-secure", "connectionState should be not-secure");
+
+    await BrowserTestUtils.loadURI(browser, HTTPS_TLS1_1);
+    await BrowserTestUtils.browserLoaded(browser);
+    isSecurityState(browser, "broken");
+    is(getIdentityMode(), "unknownIdentity weakCipher", "Identity should be unknownIdentity");
+    is(getConnectionState(), "not-secure", "connectionState should be not-secure");
+
+    // Transition to secure
+    await BrowserTestUtils.loadURI(browser, HTTPS_TLS1_2);
+    await BrowserTestUtils.browserLoaded(browser);
+    isSecurityState(browser, "secure");
+    is(getIdentityMode(), "verifiedDomain", "Identity should be verified");
+    is(getConnectionState(), "secure", "connectionState should be secure");
+
+    // Transition back to broken
+    await BrowserTestUtils.loadURI(browser, HTTPS_TLS1_1);
+    await BrowserTestUtils.browserLoaded(browser);
+    isSecurityState(browser, "broken");
+    is(getIdentityMode(), "unknownIdentity weakCipher", "Identity should be unknownIdentity");
+    is(getConnectionState(), "not-secure", "connectionState should be not-secure");
+
+    // TLS1.3 for completeness
+    await BrowserTestUtils.loadURI(browser, HTTPS_TLS1_3);
+    await BrowserTestUtils.browserLoaded(browser);
+    isSecurityState(browser, "secure");
+    is(getIdentityMode(), "verifiedDomain", "Identity should be verified");
+    is(getConnectionState(), "secure", "connectionState should be secure");
+  });
+});
--- a/browser/base/content/test/static/browser_misused_characters_in_strings.js
+++ b/browser/base/content/test/static/browser_misused_characters_in_strings.js
@@ -29,24 +29,16 @@ let gWhitelist = [{
     key: "certerror.whatShouldIDo.badStsCertExplanation1",
     type: "single-quote",
   }, {
     file: "netError.dtd",
     key: "inadequateSecurityError.longDesc",
     type: "single-quote",
   }, {
     file: "netError.dtd",
-    key: "certerror.wrongSystemTime2",
-    type: "single-quote",
-  }, {
-    file: "netError.dtd",
-    key: "certerror.wrongSystemTimeWithoutReference",
-    type: "single-quote",
-  }, {
-    file: "netError.dtd",
     key: "clockSkewError.longDesc",
     type: "single-quote",
   }, {
     file: "netError.dtd",
     key: "certerror.mitm.longDesc",
     type: "single-quote",
   }, {
     file: "netError.dtd",
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -331,17 +331,17 @@
         <toolbarbutton id="appMenu-library-button"
                        class="subviewbutton subviewbutton-iconic subviewbutton-nav"
                        label="&places.library.title;"
                        closemenu="none"
                        oncommand="PanelUI.showSubView('appMenu-libraryView', this)"/>
         <toolbarbutton id="appMenu-logins-button"
                        class="subviewbutton subviewbutton-iconic"
                        label="&logins.label;"
-                       oncommand="LoginHelper.openPasswordManager(window)"
+                       oncommand="LoginHelper.openPasswordManager(window, { entryPoint: 'mainmenu' })"
                        />
         <toolbarbutton id="appMenu-addons-button"
                        class="subviewbutton subviewbutton-iconic"
                        label="&addons.label;"
                        key="key_openAddons"
                        command="Tools:Addons"
                        />
         <toolbarbutton id="appMenu-preferences-button"
@@ -420,17 +420,17 @@
 #endif
       </vbox>
     </panelview>
     <panelview id="PanelUI-history" flex="1">
       <vbox class="panel-subview-body">
         <toolbarbutton id="appMenuViewHistorySidebar"
                        label="&appMenuHistory.viewSidebar.label;"
                        label-checked="&appMenuHistory.hideSidebar.label;"
-                       label-unchecked="&appMenuHistory.viewSidebar.label;"                      
+                       label-unchecked="&appMenuHistory.viewSidebar.label;"
                        type="checkbox"
                        class="subviewbutton subviewbutton-iconic"
                        key="key_gotoHistory"
                        oncommand="SidebarUI.toggle('viewHistorySidebar');">
                        <observes element="sidebar-box" attribute="positionend"/>
         </toolbarbutton>
         <toolbarbutton id="appMenuClearRecentHistory"
                        label="&appMenuHistory.clearRecent.label;"
--- a/browser/components/preferences/in-content/privacy.js
+++ b/browser/components/preferences/in-content/privacy.js
@@ -289,16 +289,18 @@ var gPrivacyPane = {
     /* Initialize Content Blocking */
     this.initContentBlocking();
 
     this.blockAutoplayReadPrefs();
     this.trackingProtectionReadPrefs();
     this.networkCookieBehaviorReadPrefs();
     this._initTrackingProtectionExtensionControl();
 
+    Services.telemetry.setEventRecordingEnabled("pwmgr", true);
+
     Preferences.get("media.autoplay.default").on("change",
       gPrivacyPane.blockAutoplayReadPrefs.bind(gPrivacyPane));
 
     Preferences.get("privacy.trackingprotection.enabled").on("change",
       gPrivacyPane.trackingProtectionReadPrefs.bind(gPrivacyPane));
     Preferences.get("privacy.trackingprotection.pbmode.enabled").on("change",
       gPrivacyPane.trackingProtectionReadPrefs.bind(gPrivacyPane));
 
@@ -1417,19 +1419,22 @@ var gPrivacyPane = {
   },
 
   /**
  * Shows the sites where the user has saved passwords and the associated login
  * information.
  */
   showPasswords() {
     if (LoginHelper.managementURI) {
-      window.docShell.messageManager.sendAsyncMessage("PasswordManager:OpenPreferences", {});
+      window.docShell.messageManager.sendAsyncMessage("PasswordManager:OpenPreferences", {
+        entryPoint: "preferences",
+      });
       return;
     }
+    Services.telemetry.recordEvent("pwmgr", "open_management", "preferences");
     gSubDialog.open("chrome://passwordmgr/content/passwordManager.xul");
   },
 
   /**
    * Enables/disables the Exceptions button used to configure sites where
    * passwords are never saved. When browser is set to start in Private
    * Browsing mode, the "Remember passwords" UI is useless, so we disable it.
    */
--- a/browser/components/preferences/in-content/tests/browser_password_management.js
+++ b/browser/components/preferences/in-content/tests/browser_password_management.js
@@ -1,9 +1,12 @@
 "use strict";
+
+ChromeUtils.import("resource://testing-common/TelemetryTestUtils.jsm", this);
+
 const PM_URL = "chrome://passwordmgr/content/passwordManager.xul";
 const PREF_MANAGEMENT_URI = "signon.management.overrideURI";
 
 var passwordsDialog;
 
 add_task(async function test_setup() {
   Services.logins.removeAllLogins();
 
@@ -16,32 +19,38 @@ add_task(async function test_setup() {
 
   registerCleanupFunction(async function() {
     Services.logins.removeAllLogins();
     Services.prefs.clearUserPref(PREF_MANAGEMENT_URI);
   });
 });
 
 add_task(async function test_openPasswordSubDialog() {
+  Services.telemetry.clearEvents();
   await openPreferencesViaOpenPreferencesAPI("privacy", {leaveOpen: true});
 
   let dialogOpened = promiseLoadSubDialog(PM_URL);
 
   await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
     let doc = content.document;
     let savePasswordCheckBox = doc.getElementById("savePasswords");
     Assert.ok(!savePasswordCheckBox.checked,
               "Save Password CheckBox should be unchecked by default");
     savePasswordCheckBox.click();
 
     let showPasswordsButton = doc.getElementById("showPasswords");
     showPasswordsButton.click();
   });
 
   passwordsDialog = await dialogOpened;
+
+  // check telemetry events while we are in here
+  TelemetryTestUtils.assertEvents(
+    [["pwmgr", "open_management", "preferences"]],
+    {category: "pwmgr", method: "open_management"});
 });
 
 add_task(async function test_deletePasswordWithKey() {
   let doc = passwordsDialog.document;
 
   let tree = doc.getElementById("signonsTree");
   Assert.equal(tree.view.rowCount, 1, "Row count should initially be 1");
   tree.focus();
@@ -55,16 +64,17 @@ add_task(async function test_deletePassw
 
   await TestUtils.waitForCondition(() => tree.view.rowCount == 0);
 
   is_element_visible(content.gSubDialog._dialogs[0]._box,
     "Subdialog is visible after deleting an element");
 });
 
 add_task(async function subdialog_cleanup() {
+  Services.telemetry.clearEvents();
   // Undo the save password change.
   await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
     let doc = content.document;
     let savePasswordCheckBox = doc.getElementById("savePasswords");
     if (savePasswordCheckBox.checked) {
       savePasswordCheckBox.click();
     }
   });
--- a/browser/locales/en-US/browser/newtab/onboarding.ftl
+++ b/browser/locales/en-US/browser/newtab/onboarding.ftl
@@ -1,40 +1,48 @@
 # 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/.
 
-## UI strings for the simplified onboarding modal
+### UI strings for the simplified onboarding modal / about:welcome
+### Various strings use a non-breaking space to avoid a single dangling /
+### widowed word, so test on various window sizes if you also want this.
+
+## These button action text can be split onto multiple lines, so use explicit
+## newlines in translations to control where the line break appears (e.g., to
+## avoid breaking quoted text).
 
 onboarding-button-label-learn-more = Learn More
 onboarding-button-label-try-now = Try It Now
 onboarding-button-label-get-started = Get Started
 
+## Welcome modal dialog strings
+
 onboarding-welcome-header = Welcome to { -brand-short-name }
 onboarding-welcome-body = You’ve got the browser.<br/>Meet the rest of { -brand-product-name }.
 onboarding-welcome-learn-more = Learn more about the benefits.
 
 onboarding-join-form-header = Join { -brand-product-name }
-onboarding-join-form-body = Enter your email address to get started.
+onboarding-join-form-body = Enter your email address to get started.
 onboarding-join-form-email =
     .placeholder = Enter email
 onboarding-join-form-email-error = Valid email required
-onboarding-join-form-legal = By proceeding, you agree to the <a data-l10n-name="terms">Terms of Service</a> and <a data-l10n-name="privacy">Privacy Notice</a>.
+onboarding-join-form-legal = By proceeding, you agree to the <a data-l10n-name="terms">Terms of Service</a> and <a data-l10n-name="privacy">Privacy Notice</a>.
 onboarding-join-form-continue = Continue
 
 onboarding-start-browsing-button-label = Start Browsing
 
 ## These are individual benefit messages shown with an image, title and
 ## description.
 
 onboarding-benefit-products-title = Useful Products
 onboarding-benefit-products-text = Get things done with a family of tools that respects your privacy across your devices.
 
 onboarding-benefit-knowledge-title = Practical Knowledge
-onboarding-benefit-knowledge-text = Learn everything you need to know to stay smarter and safer online.
+onboarding-benefit-knowledge-text = Learn everything you need to know to stay smarter and safer online.
 
 onboarding-benefit-privacy-title = True Privacy
 # "Personal Data Promise" is a concept that should be translated consistently
 # across the product. It refers to a concept shown elsewhere to the user: "The
 # Firefox Personal Data Promise is the way we honor your data in everything we
 # make and do. We take less data. We keep it safe. And we make sure that we are
 # transparent about how we use it."
 onboarding-benefit-privacy-text = Everything we do honors our Personal Data Promise: Take less. Keep it safe. No secrets.
@@ -60,54 +68,54 @@ onboarding-ghostery-text = Browse faster
 # Note: "Sync" in this case is a generic verb, as in "to synchronize"
 onboarding-fxa-title = Sync
 onboarding-fxa-text = Sign up for a { -fxaccount-brand-name } and sync your bookmarks, passwords, and open tabs everywhere you use { -brand-short-name }.
 
 onboarding-tracking-protection-title2 = Protection From Tracking
 onboarding-tracking-protection-text2 = { -brand-short-name } helps stop websites from tracking you online, making it harder for ads to follow you around the web.
 onboarding-tracking-protection-button2 = How it Works
 
-onboarding-data-sync-title = Take Your Settings with You
+onboarding-data-sync-title = Take Your Settings with You
 # "Sync" is short for synchronize.
 onboarding-data-sync-text2 = Sync your bookmarks, passwords, and more everywhere you use { -brand-product-name }.
 onboarding-data-sync-button2 = Sign in to { -sync-brand-short-name }
 
-onboarding-firefox-monitor-title = Stay Alert to Data Breaches
+onboarding-firefox-monitor-title = Stay Alert to Data Breaches
 onboarding-firefox-monitor-text = { -monitor-brand-name } monitors if your email has appeared in a data breach and alerts you if it appears in a new breach.
 onboarding-firefox-monitor-button = Sign up for Alerts
 
 onboarding-browse-privately-title = Browse Privately
 onboarding-browse-privately-text = Private Browsing clears your search and browsing history to keep it secret from anyone who uses your computer.
 onboarding-browse-privately-button = Open a Private Window
 
-onboarding-firefox-send-title = Keep Your Shared Files Private
+onboarding-firefox-send-title = Keep Your Shared Files Private
 onboarding-firefox-send-text2 = Upload your files to { -send-brand-name } to share them with end-to-end encryption and a link that automatically expires.
 onboarding-firefox-send-button = Try { -send-brand-name }
 
 onboarding-mobile-phone-title = Get { -brand-product-name } on Your Phone
 onboarding-mobile-phone-text = Download { -brand-product-name } for iOS or Android and sync your data across devices.
 # "Mobile" is short for mobile/cellular phone, "Browser" is short for web
 # browser.
 onboarding-mobile-phone-button = Download Mobile Browser
 
-onboarding-send-tabs-title = Instantly Send Yourself Tabs
+onboarding-send-tabs-title = Instantly Send Yourself Tabs
 # "Send Tabs" refers to "Send Tab to Device" feature that appears when opening a
 # tab's context menu.
 onboarding-send-tabs-text = Send Tabs instantly shares pages between your devices without having to copy, paste, or leave the browser.
 onboarding-send-tabs-button = Start Using Send Tabs
 
 onboarding-pocket-anywhere-title = Read and Listen Anywhere
-onboarding-pocket-anywhere-text2 = Save your favorite content offline with the { -pocket-brand-name } App and read, listen, and watch whenever it’s convenient for you.
+onboarding-pocket-anywhere-text2 = Save your favorite content offline with the { -pocket-brand-name } App and read, listen, and watch whenever it’s convenient for you.
 onboarding-pocket-anywhere-button = Try { -pocket-brand-name }
 
-onboarding-lockwise-passwords-title = Take Your Passwords Everywhere
+onboarding-lockwise-passwords-title = Take Your Passwords Everywhere
 onboarding-lockwise-passwords-text2 = Keep the passwords you save secure and easily log in to your accounts with { -lockwise-brand-name }.
 onboarding-lockwise-passwords-button2 = Get the App
 
-onboarding-facebook-container-title = Set Boundaries with Facebook
+onboarding-facebook-container-title = Set Boundaries with Facebook
 onboarding-facebook-container-text2 = { -facebook-container-brand-name } keeps your profile separate from everything else, making it harder for Facebook to target you with ads.
 onboarding-facebook-container-button = Add the Extension
 
 
 ## Message strings belonging to the Return to AMO flow
 return-to-amo-sub-header = Great, you’ve got { -brand-short-name }
 
 # <icon></icon> will be replaced with the icon belonging to the extension
--- a/browser/locales/en-US/chrome/overrides/netError.dtd
+++ b/browser/locales/en-US/chrome/overrides/netError.dtd
@@ -200,22 +200,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">
 
-<!-- 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.pagetitle2  "Warning: Potential Security Risk Ahead">
 <!ENTITY certerror.sts.pagetitle  "Did Not Connect: Potential Security Issue">
 <!ENTITY certerror.whatShouldIDo.badStsCertExplanation1 "<span class='hostname'></span> has a security policy called HTTP Strict Transport Security (HSTS), which means that &brandShortName; can only connect to it securely. You can’t add an exception to visit this site.">
 <!ENTITY certerror.copyToClipboard.label "Copy text to clipboard">
 
 <!ENTITY inadequateSecurityError.title "Your connection is not secure">
 <!-- LOCALIZATION NOTE (inadequateSecurityError.longDesc) - Do not translate
      "NS_ERROR_NET_INADEQUATE_SECURITY". -->
--- a/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/Tabs.jsm
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/Tabs.jsm
@@ -144,26 +144,27 @@ async function allTabTitlesDisplayed(bro
     "about:privatebrowsing": "about:privatebrowsing",
   };
   specToTitleMap[PREFS_TAB] = "browser/skin/settings.svg";
   specToTitleMap[CUST_TAB] = "browser/skin/customize.svg";
   specToTitleMap[DEFAULT_FAVICON_TAB] = "No favicon";
 
   let tabTitlePromises = [];
   for (let tab of browserWindow.gBrowser.tabs) {
-    function tabTitleLoaded(spec) {
-      return () => {
-        return spec ? tab.label == specToTitleMap[spec] : false;
-      };
+    function getSpec() {
+      return tab.linkedBrowser &&
+             tab.linkedBrowser.documentURI &&
+             tab.linkedBrowser.documentURI.spec;
     }
-    let spec = tab.linkedBrowser &&
-               tab.linkedBrowser.documentURI &&
-               tab.linkedBrowser.documentURI.spec;
+    function tabTitleLoaded() {
+      let spec = getSpec();
+      return spec ? tab.label == specToTitleMap[spec] : false;
+    }
     let promise =
-      TestUtils.waitForCondition(tabTitleLoaded(spec), `Tab (${spec}) should be showing "${specToTitleMap[spec]}". Got "${tab.label}"`);
+      TestUtils.waitForCondition(tabTitleLoaded, `Tab (${getSpec()}) should be showing "${specToTitleMap[getSpec()]}". Got "${tab.label}"`);
     tabTitlePromises.push(promise);
   }
 
   return Promise.all(tabTitlePromises);
 }
 
 function fiveTabsHelper() {
   // some with no favicon and some with. Selected tab in middle.
index 449e713dd95de0a52c62ecdd45c0788cc60931e1..5a3baaf54bbb34ecfd85a47dfc5f03cf4fd18177
GIT binary patch
literal 229376
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
z000000000000000000000000000000000000000000000000000002^|A8Dq6Tbu(
zZ72C@EBV<)^3z6zV2bCnB!6(;S~Ay4i(rrECQ?W-bQTSLV1I$$Qj%w(M{sQ`L1EG8
znW+p8on|h{Cu$QM@mwB-$DoEXSS$uBp%;Z_Cdo8aAn4*#6WCOWiDZtk4#62u%VJSd
zzFyu)l540$FvLqXU?7>NuSy`|xpZbcmyy7t)AS^>b+OreRx*p7!HS_$bR@I2vGt=(
zarGd7)fg>FuBHaT22Ww~=o}UWov_(F4M~nVmJ`QMa3{HWBi>Xxho>gVQN_9pZ3CCi
zNvCsEBy*Lq<>_21C6!L$@hH?}l9D7xQIlYcr>8Sm=mt{gGzPjM1xc2?E&=(j@qO5A
znw%tC7TZz^o0`br(YZVs$y{jz0vXRpNnx<iwlK3|Xbi4oXAmSyNNEuq6<F+Weq0ir
z%JXCL22fJyis->$mzRqZ-V*K?Rtt-T$-@3Z7ompWx!|l|i(r-@M=(OrLtri-<lV_T
zD0nT16WHXf7B&fw2v-Wrg@c6dLS4Zn!FIuX!6-q1KtAt5UVYv)VJf;x0000000000
z00000000000000000000000000000000000000000000000000000000000000000
z000000000000000000000QhP81b6(D;Q1%@Rg{-aOTJL8x@P+AZecQ~?XZm8obBC5
zPq%K_UM+O$op>YBW5rqrTP%Z|P_big6XE^Kd;|RB@)GMS`6{tpqzO2@O-IG7v0ST;
zaxJl3i;i;5v0P$D+cm>-O*<-Ug5?@_RMrT~HSDOY0hX)ZQLY}AtJ_ho4wkFkQLYx2
ztJzVlG_YLtj>@WGxvCxIs$jXw9px%vxr!a_a|J9{zN4~oSgveGxiVO;bVs=aELTd8
K;I7bK?*9Xkx|p{B
index 33da46fa1c9d223d0dcc95d05555632eb48408fe..c0de19d70781a569d2f7930056f5c6125d23a6e8
GIT binary patch
literal 294912
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
zqjl|6`Ce4h0l#veI8Nf<ezuLoP=<O#v2yK|uGu@L;3{sX*e1X5CP}82f-sk!fwc88
z9krY6l<!?lbZ92GQK3{V+eaRYK1hF|B=@7-kzS`k(yhBBc495U0DIk}mfX1RSqOsZ
z9N%E7$33wg^^Mw-65G=0DGH7*cjz5`ui3S4iMb;Agw8{mZi`7$7Ok(+*zm1`|Nr#P
zzIo_v`aeB#0{{R3;QtAl%`eiJ*F-VjViq?q000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z004mhk5Q455mQQX+mmk1AojPAP?C~SQ}U-3&}=Rxjrm9vvl_F!c>w?b0000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000{0~7!N<>Whb3#H&N<>W)^Mr^r+K?#PFvdOl$p7nX00000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000Px?O3TZGkQeIvTDMLYOPew{7it+dG4f90HgoL?!
z2Kb>c{von~7=M3^zqc&<Fv>5`7cJ`%;D_}ji#H=iVqeJ9BB_6FNx?#5Z}-O)Di#ub
zU3oSn>(48cEcBKEVg8|;LkaA`pbyClAq9WlPQ$`w84!u_^+gHD2pH}W@I<?Zc?*Q1
zLqY{MH|9lh|6GZdh1Cx25f+RIjSw*O_s006(ZQQh<W-PLTX|VzHn-fpxy|N{0^*zZ
z5*!v1dMF?`)F(n>a~L^)lpk6X9WL{8SQ!tLyfjjhf?D#J$+2a6VrpVGUJoBsurDeU
z<*yU^Ymk5cCP{<Hu%5)o1M=cX(O>t*zZ>lr7!ZsKju6mC2Zv(3FdnE-w193{s87Jp
zjPe{vwqM^H;~(GKzrx82BN4y8A#Us7056O$TEI9A<B9eZ*cBLx@xw%-LNNjUB%6Cg
zn$TTG6SGJ}8gq{*W*}x}^8x?>0000000000000000000000000000000000000000
z0000000000000000000000000000000000000000000000000000000_-}}XRGK;@
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
UkY=fYzHY(ZTHatVY4pl}0l1aA_W%F@
index c7a408b7a6cbc0d3e2a77fbdbf4abaffb578aa8d..43c8c200fb5a07b4f9785f8252552eb52ef17cd1
GIT binary patch
literal 2448
zc$`&~c{~(~7RSe|rm{9;A415uF}AC*m5FSPWxDnfhK7(Ok*To^Wx1Aa*&~x}2t~GN
zD6(%2NywIcgtCwQbw8i?-hJ<n^EsdMJ?H%S{b8u^I3NcILxmHdaP&i?hud5nFb*0O
zei2NCpZ|rmFjUaTe^yW$74-fWVgNY+zgpG*JYmG3$NoK>;DBPd!JO%=gXCU|TlPR8
zCm@Upik|GAqcLS%VZ;X6tNGsrx1+X7_ZRx6-?}%zw!y*ovOPFh;_ZkR>qV-9g%{6H
z%P<Ml8qRpjtdix-A@vsygc0|p1lFWm?OIm0vZR##SJ~jh$&H?;=0g28je?ffl%x;4
zCDV3YDZ}FXv!KHm73)UiQVE%cWex~-$kA+`AB2tE%|g<Avs;$_dQqk!m(eQIf5Uqq
zcFfe}l~+z@f3uqPA*R(i*z@60!TU-Rri%WcL`b1-G!i^cH~ZAyyWd%=Eix5Ht-$Ks
zeRF0?aw9C45MlRnOtD~ABHu?>kTE}JgX(#7O40pW%(ohk)#6In_U|1(hmhn@vThu<
zSI_OfJXWC2AxJH_*62ZNAR;1F@bQ`>q%32T_;H86&)w_n70t)M9pq?}H198<pNSq|
zCcvUR-bJ=`uqxv{zr3iw$WNTtzlf<V{)D2K_pzsZ(iwD0{vf0=E-*Ab|7#$flzV2A
zPDk-eZQdm2oh;$QKYKMQdyVkhDexWA(l|MM-Px8!Q(?(anRu><!j>YV@t6G3gi7P^
zA6!~E&7%==RN(~rO_O;?sj7YW%-%Nl{fbF+l(xNmMWfOOs3QwA@I5&wC@5tQr!nhF
z7*6v_92hTOB~4+5Gw?M5-p!Hh*acOL=}E1H#`puzs}t2AY%YFDIi=X0aiC7!a`d<-
zetE<WP51vm5-3XB4V5VFArw^)WvvM#Ei<2LF!=TOV#Z<$jx@}yWET=DNhRT5bq#u~
zxikBko_Z_b;wO(e*UR_)m^cq%%r5Mv-aakfj#6;^zTtT$dXwvUl2`dl+hXHhop|-)
z8(cEGBFRlZn`f9lVur5!0v@sn?q%d7WW>%4PAkbjYux^L8GQ4yp9YZ1_4K!k$;LhE
zQkSlw_W9qBJ1tx|u)C8#8&mLZ=VN4pw3aS&uV%HTLmq|g0Tk;-_-AhC{#aHx2%5o*
z^sA%9PrURm;rnAnQ6$eabYRipz$iRtOTW&Zqv07@FY=kZXtCd#?Yy6*xl}8={v*yL
zKjf`WXcbZDO+%6Cu2yx%K6d3}jkmDbT<m3Nv+pI8StObBl9^3Ff%^lx_KUlGIlP7d
z`?uDvrJsHmNPI9pOmw@h_FdKmKMCdCGr{wj68(FV);H{Q@|L12la!^->zgpuR1aA*
zlm4xyx;i?FYH}TguLW$hSih4V7u+FXs1VoxvmIgura~-#Vf?Q$h93Wq#>Y5-G%9!n
zLj^DXpWUFp?RKgQFTKHzJpZ@dU@Ex%$+0xKD)-pW3L~^bf)7u8yr`Kr9rZa#pndbZ
zlA5OlRZUDK$3vki#Hu@yaL`K`vg!S%s1m4VQY(vu+)Z`7UPdBb@Be&j`Jja)J#QZT
zHR@p^m)?cbLtS%hWeuF~8vR{mPfCc3(_7Y4p5oD_KNZd?Bzg;yLNhk8(FGOR+u+Wx
z!%=QOgN+e2a;r)F(lzX#4kPR4U!}2K`LYaOvAQ$th^gEb@_L^HUX<xYGWu<J6ux!!
zv+g(j(+252?64~a#DLVk@w)>=i$qjs!M;m+V1)tR|E*kOO5u|-N>jv&BJXBsp0N}<
zf&p%qS(EC||8XRtNYmoDsqe}ML7#dPf6ynUbW>Z#8?`&o-x0i**;QhL2Iq#gXd6pT
z%4iripOqO^TdHb<&A;}{4oteaB}NMvGl)a?vHW8K30<Nl=-2{iKu;Tvqcfjs5g5+b
zwBvDvzj(~fI1!tyhqjq7u+>h72|*8EURzZ^F>fnFm1^1Xp?n`w&g&pK)|73Sf@zYH
zY40&-?A*(5QKAUqn&sBjz<ZxbqOc32LCuOeD_;=Q=TVkh-}-3Rw*bomzGk5na?GqI
zV!_Uu9(P-)`kP2}HSKmZFe9#1$u@FcELBiMv`GS)XRJ8-*k{Y4XTc@Auw2i{m`wJj
zJkIQ`9Y3e>2R+0mD55-}UF$FSM*Rd7+xmexlQLS&Q|;ryBaR<XKf+~jDM@|t9hhE@
zu-14hurd1aQO5}|h<?t=Fc$_mS~B=UynytD8C{y|xXTxyusMA^Yd?oG(CU@N>peaS
zHzD3<2X|{y!OZ~Gt3J2_yf=lBp|}MJ+O!dbEJ3Vka|}m(`JJRG%Pt`k+-1C5J~45J
z)6OnTv591hdww=8q0r2$I0U3*Jykj~A;uWLq-H-M42b6}@n(Ehm7t9WzYA^~ynz@x
zCi2cq=SQ5QZ=OBA^$ByX8t>0;xr&^tni%8(d`RoL{Yb{^&U4YOG?<#?ru_O9_1A=y
z(Cx!*<gM;Zc#UcKjj3r@e3g}7$OA;}ySau-&=RXso>2Bnh}4UeyFhGX+QoWlW;Czb
zvSXg-@*~~`6;wI@VM-swPLyJ1?xl+owwOkLx>?AnUyb6mY%@KVEGk{cj?$j(aW88m
zTu7*mmiBg3O+|qf(>)BG1whi~bB0{mbUjT&j<U=@W6i{tNILEILg&_s13GyI9rhle
z*7P^0J;2GaF=rQ@>hn*m4&GjD>r-@dtr{qVBaEJYlW*>Zfqc*49khokU)9FM^*3Md
zel*>oF1%^hz<HX)8WR}fJe(2!(+(H*$@lb_r}coA!&+B)MRs?R8!bp1qMX#=@J>=N
zMjj}g!6$O=N|9!aV)NO>&VecWM(ZnqI<v^`2#r1k=U2m)XuuevDDQ<;>isI0Tcbbp
zYF#yvi3vp(+-+J<kmf5Y7^}zi5{WLcC{-tqtdisPes#eLUnmVsIMZ*}^lC;gD;4%y
zwi$aF{Zn`FEPK16Z)K6cQ+&WGp5>9osgl2n4g|61C~HhwMiYwHFTh}>dx-%H>4iNf
zk3I;`i%LRp=op3uL-SNm+%Gq*X;t1}4C!{B<OS-n<NQ8!b#<{%_Zr1Ju9U|r8(}2>
zn~rdVGD4gm;0^Er5CQH04?qCG4R9L}fRX;^1gR_lMht2RnS99ig>>|tL{xsVQHGDY
zN@J8Uk{CD?qQDITia-EhM51sTrO6!jM010^Fh8MkN;DYr7|fY+!$KdGVNvsUkp2rg
CSbppP
--- a/build/pgo/server-locations.txt
+++ b/build/pgo/server-locations.txt
@@ -281,21 +281,24 @@ https://fail-handshake.example.com:443  
 
 # Hosts for sha1 console warning tests
 https://sha1ee.example.com:443                                    privileged,cert=sha1_end_entity
 https://sha256ee.example.com:443                                  privileged,cert=sha256_end_entity
 
 # Hosts for imminent distrust warning tests
 https://imminently-distrusted.example.com:443                     privileged,cert=imminently_distrusted
 
-# Hosts for ssl3/rc4 console warning tests
-https://ssl3.example.com:443        privileged,ssl3
-https://rc4.example.com:443         privileged,rc4
-https://ssl3rc4.example.com:443     privileged,ssl3,rc4
-https://tls1.example.com:443        privileged,tls1
+# Hosts for ssl3/rc4/tls1 warning tests
+https://ssl3.example.com:443         privileged,ssl3
+https://rc4.example.com:443          privileged,rc4
+https://ssl3rc4.example.com:443      privileged,ssl3,rc4
+https://tls1.example.com:443         privileged,tls1
+https://tls11.example.com:443        privileged,tls1_1
+https://tls12.example.com:443        privileged,tls1_2
+https://tls13.example.com:443        privileged,tls1,tls1_3
 
 # Hosts for youtube rewrite tests
 https://mochitest.youtube.com:443
 
 # Host for U2F localhost tests
 https://localhost:443
 
 # Bug 1402530
--- a/devtools/client/debugger/dist/parser-worker.js
+++ b/devtools/client/debugger/dist/parser-worker.js
@@ -17940,28 +17940,32 @@ var _mapExpression2 = _interopRequireDef
 var _devtoolsUtils = __webpack_require__(7);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 const { workerHandler } = _devtoolsUtils.workerUtils; /* 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/>. */
 
+function clearState() {
+  (0, _ast.clearASTs)();
+  (0, _getScopes.clearScopes)();
+  (0, _sources.clearSources)();
+  (0, _getSymbols.clearSymbols)();
+}
+
 self.onmessage = workerHandler({
   findOutOfScopeLocations: _findOutOfScopeLocations2.default,
   getSymbols: _getSymbols.getSymbols,
   getScopes: _getScopes2.default,
-  clearSymbols: _getSymbols.clearSymbols,
-  clearScopes: _getScopes.clearScopes,
-  clearASTs: _ast.clearASTs,
-  setSource: _sources.setSource,
-  clearSources: _sources.clearSources,
+  clearState,
   getNextStep: _steps.getNextStep,
   hasSyntaxError: _validate.hasSyntaxError,
-  mapExpression: _mapExpression2.default
+  mapExpression: _mapExpression2.default,
+  setSource: _sources.setSource
 });
 
 /***/ }),
 /* 200 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
--- a/devtools/client/debugger/panel.js
+++ b/devtools/client/debugger/panel.js
@@ -28,17 +28,20 @@ DebuggerPanel.prototype = {
       actions,
       store,
       selectors,
       client
     } = await this.panelWin.Debugger.bootstrap({
       threadClient: this.toolbox.threadClient,
       tabTarget: this.toolbox.target,
       debuggerClient: this.toolbox.target.client,
-      sourceMaps: this.toolbox.sourceMapService,
+      workers: {
+        sourceMaps: this.toolbox.sourceMapService,
+        evaluationsParser: this.toolbox.parserService
+      },
       panel: this
     });
 
     this._actions = actions;
     this._store = store;
     this._selectors = selectors;
     this._client = client;
     this.isReady = true;
--- a/devtools/client/debugger/src/actions/ast.js
+++ b/devtools/client/debugger/src/actions/ast.js
@@ -3,23 +3,21 @@
  * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
 
 // @flow
 
 import { getSourceWithContent, getSelectedLocation } from "../selectors";
 
 import { setInScopeLines } from "./ast/setInScopeLines";
 
-import * as parser from "../workers/parser";
-
 import type { Context } from "../types";
 import type { ThunkArgs, Action } from "./types";
 
 export function setOutOfScopeLocations(cx: Context) {
-  return async ({ dispatch, getState }: ThunkArgs) => {
+  return async ({ dispatch, getState, parser }: ThunkArgs) => {
     const location = getSelectedLocation(getState());
     if (!location) {
       return;
     }
 
     const { source, content } = getSourceWithContent(
       getState(),
       location.sourceId
--- a/devtools/client/debugger/src/actions/breakpoints/breakpointPositions.js
+++ b/devtools/client/debugger/src/actions/breakpoints/breakpointPositions.js
@@ -39,16 +39,17 @@ async function mapLocations(
   generatedLocations: SourceLocation[],
   { sourceMaps }: ThunkArgs
 ) {
   if (generatedLocations.length == 0) {
     return [];
   }
 
   const { sourceId } = generatedLocations[0];
+
   const originalLocations = await sourceMaps.getOriginalLocations(
     sourceId,
     generatedLocations
   );
 
   return zip(originalLocations, generatedLocations).map(
     ([location, generatedLocation]) => ({ location, generatedLocation })
   );
--- a/devtools/client/debugger/src/actions/expressions.js
+++ b/devtools/client/debugger/src/actions/expressions.js
@@ -17,35 +17,34 @@ import {
   getIsPaused,
   isMapScopesEnabled
 } from "../selectors";
 import { PROMISE } from "./utils/middleware/promise";
 import { wrapExpression } from "../utils/expressions";
 import { features } from "../utils/prefs";
 import { isOriginal } from "../utils/source";
 
-import * as parser from "../workers/parser";
 import type { Expression, ThreadContext } from "../types";
 import type { ThunkArgs } from "./types";
 
 /**
  * Add expression for debugger to watch
  *
  * @param {object} expression
  * @param {number} expression.id
  * @memberof actions/pause
  * @static
  */
 export function addExpression(cx: ThreadContext, input: string) {
-  return async ({ dispatch, getState }: ThunkArgs) => {
+  return async ({ dispatch, getState, evaluationsParser }: ThunkArgs) => {
     if (!input) {
       return;
     }
 
-    const expressionError = await parser.hasSyntaxError(input);
+    const expressionError = await evaluationsParser.hasSyntaxError(input);
 
     const expression = getExpression(getState(), input);
     if (expression) {
       return dispatch(evaluateExpression(cx, expression));
     }
 
     dispatch({ type: "ADD_EXPRESSION", cx, input, expressionError });
 
@@ -75,17 +74,17 @@ export function clearExpressionError() {
   return { type: "CLEAR_EXPRESSION_ERROR" };
 }
 
 export function updateExpression(
   cx: ThreadContext,
   input: string,
   expression: Expression
 ) {
-  return async ({ dispatch, getState }: ThunkArgs) => {
+  return async ({ dispatch, getState, parser }: ThunkArgs) => {
     if (!input) {
       return;
     }
 
     const expressionError = await parser.hasSyntaxError(input);
     dispatch({
       type: "UPDATE_EXPRESSION",
       cx,
@@ -172,34 +171,40 @@ function evaluateExpression(cx: ThreadCo
   };
 }
 
 /**
  * Gets information about original variable names from the source map
  * and replaces all posible generated names.
  */
 export function getMappedExpression(expression: string) {
-  return async function({ dispatch, getState, client, sourceMaps }: ThunkArgs) {
+  return async function({
+    dispatch,
+    getState,
+    client,
+    sourceMaps,
+    evaluationsParser
+  }: ThunkArgs) {
     const thread = getCurrentThread(getState());
     const mappings = getSelectedScopeMappings(getState(), thread);
     const bindings = getSelectedFrameBindings(getState(), thread);
 
     // We bail early if we do not need to map the expression. This is important
-    // because mapping an expression can be slow if the parser worker is
-    // busy doing other work.
+    // because mapping an expression can be slow if the evaluationsParser
+    // worker is busy doing other work.
     //
     // 1. there are no mappings - we do not need to map original expressions
     // 2. does not contain `await` - we do not need to map top level awaits
     // 3. does not contain `=` - we do not need to map assignments
     const shouldMapScopes = isMapScopesEnabled(getState()) && mappings;
     if (!shouldMapScopes && !expression.match(/(await|=)/)) {
       return null;
     }
 
-    return parser.mapExpression(
+    return evaluationsParser.mapExpression(
       expression,
       mappings,
       bindings || [],
       features.mapExpressionBindings && getIsPaused(getState(), thread),
       features.mapAwaitExpression
     );
   };
 }
--- a/devtools/client/debugger/src/actions/navigation.js
+++ b/devtools/client/debugger/src/actions/navigation.js
@@ -7,46 +7,42 @@
 import { clearDocuments } from "../utils/editor";
 import sourceQueue from "../utils/source-queue";
 import { getSourceList } from "../reducers/sources";
 import { waitForMs } from "../utils/utils";
 
 import { newGeneratedSources } from "./sources";
 import { updateWorkers } from "./debuggee";
 
-import {
-  clearASTs,
-  clearSymbols,
-  clearScopes,
-  clearSources
-} from "../workers/parser";
-
 import { clearWasmStates } from "../utils/wasm";
 import { getMainThread } from "../selectors";
 import type { Action, ThunkArgs } from "./types";
 
 /**
  * Redux actions for the navigation state
  * @module actions/navigation
  */
 
 /**
  * @memberof actions/navigation
  * @static
  */
 export function willNavigate(event: Object) {
-  return function({ dispatch, getState, client, sourceMaps }: ThunkArgs) {
+  return async function({
+    dispatch,
+    getState,
+    client,
+    sourceMaps,
+    parser
+  }: ThunkArgs) {
     sourceQueue.clear();
     sourceMaps.clearSourceMaps();
     clearWasmStates();
     clearDocuments();
-    clearSymbols();
-    clearASTs();
-    clearScopes();
-    clearSources();
+    parser.clear();
     client.detachWorkers();
     const thread = getMainThread(getState());
 
     dispatch({
       type: "NAVIGATE",
       mainThread: { ...thread, url: event.url }
     });
   };
--- a/devtools/client/debugger/src/actions/pause/commands.js
+++ b/devtools/client/debugger/src/actions/pause/commands.js
@@ -8,17 +8,16 @@
 import {
   getSource,
   getSourceContent,
   getTopFrame,
   getSelectedFrame,
   getThreadContext
 } from "../../selectors";
 import { PROMISE } from "../utils/middleware/promise";
-import { getNextStep } from "../../workers/parser";
 import { addHiddenBreakpoint } from "../breakpoints";
 import { evaluateExpressions } from "../expressions";
 import { selectLocation } from "../sources";
 import { fetchScopes } from "./fetchScopes";
 import { features } from "../../utils/prefs";
 import { recordEvent } from "../../utils/telemetry";
 import assert from "../../utils/assert";
 import { isFulfilled, type AsyncValue } from "../../utils/async-value";
@@ -208,29 +207,32 @@ function hasAwait(content: AsyncValue<So
 
 /**
  * @memberOf actions/pause
  * @static
  * @param stepType
  * @returns {function(ThunkArgs)}
  */
 export function astCommand(cx: ThreadContext, stepType: Command) {
-  return async ({ dispatch, getState, sourceMaps }: ThunkArgs) => {
+  return async ({ dispatch, getState, sourceMaps, parser }: ThunkArgs) => {
     if (!features.asyncStepping) {
       return dispatch(command(cx, stepType));
     }
 
     if (stepType == "stepOver") {
       // This type definition is ambiguous:
       const frame: any = getTopFrame(getState(), cx.thread);
       const source = getSource(getState(), frame.location.sourceId);
       const content = source ? getSourceContent(getState(), source.id) : null;
 
       if (source && hasAwait(content, frame.location)) {
-        const nextLocation = await getNextStep(source.id, frame.location);
+        const nextLocation = await parser.getNextStep(
+          source.id,
+          frame.location
+        );
         if (nextLocation) {
           await dispatch(addHiddenBreakpoint(cx, nextLocation));
           return dispatch(command(cx, "resume"));
         }
       }
     }
 
     return dispatch(command(cx, stepType));
--- a/devtools/client/debugger/src/actions/pause/mapScopes.js
+++ b/devtools/client/debugger/src/actions/pause/mapScopes.js
@@ -50,17 +50,18 @@ export function toggleMapScopes() {
   };
 }
 
 export function mapScopes(
   cx: ThreadContext,
   scopes: Promise<Scope>,
   frame: Frame
 ) {
-  return async function({ dispatch, getState, client, sourceMaps }: ThunkArgs) {
+  return async function(thunkArgs: ThunkArgs) {
+    const { dispatch, getState } = thunkArgs;
     assert(cx.thread == frame.thread, "Thread mismatch");
 
     const generatedSource = getSource(
       getState(),
       frame.generatedLocation.sourceId
     );
 
     const source = getSource(getState(), frame.location.sourceId);
@@ -94,18 +95,17 @@ export function mapScopes(
 
           return await buildMappedScopes(
             source,
             content && isFulfilled(content)
               ? content.value
               : { type: "text", value: "", contentType: undefined },
             frame,
             await scopes,
-            sourceMaps,
-            client
+            thunkArgs
           );
         } catch (e) {
           log(e);
           return null;
         }
       })()
     });
   };
--- a/devtools/client/debugger/src/actions/sources/loadSourceText.js
+++ b/devtools/client/debugger/src/actions/sources/loadSourceText.js
@@ -16,17 +16,16 @@ import {
   getSourceActorsForSource
 } from "../../selectors";
 import { addBreakpoint } from "../breakpoints";
 
 import { prettyPrintSource } from "./prettyPrint";
 import { setBreakableLines } from "./breakableLines";
 import { isFulfilled } from "../../utils/async-value";
 
-import * as parser from "../../workers/parser";
 import { isOriginal, isPretty } from "../../utils/source";
 import {
   memoizeableAction,
   type MemoizedAction
 } from "../../utils/memoizableAction";
 
 import { Telemetry } from "devtools-modules";
 
@@ -88,17 +87,17 @@ async function loadSource(
     text: response.source,
     contentType: response.contentType || "text/javascript"
   };
 }
 
 async function loadSourceTextPromise(
   cx: Context,
   source: Source,
-  { dispatch, getState, client, sourceMaps }: ThunkArgs
+  { dispatch, getState, client, sourceMaps, parser }: ThunkArgs
 ): Promise<?Source> {
   const epoch = getSourcesEpoch(getState());
   await dispatch({
     type: "LOAD_SOURCE_TEXT",
     sourceId: source.id,
     epoch,
     [PROMISE]: loadSource(getState(), source, { sourceMaps, client, getState })
   });
--- a/devtools/client/debugger/src/actions/sources/symbols.js
+++ b/devtools/client/debugger/src/actions/sources/symbols.js
@@ -5,27 +5,25 @@
 // @flow
 
 import { hasSymbols, getSymbols } from "../../selectors";
 
 import { PROMISE } from "../utils/middleware/promise";
 import { updateTab } from "../tabs";
 import { loadSourceText } from "./loadSourceText";
 
-import * as parser from "../../workers/parser";
-
 import {
   memoizeableAction,
   type MemoizedAction
 } from "../../utils/memoizableAction";
 
 import type { Source, Context } from "../../types";
 import type { Symbols } from "../../reducers/types";
 
-async function doSetSymbols(cx, source, { dispatch, getState }) {
+async function doSetSymbols(cx, source, { dispatch, getState, parser }) {
   const sourceId = source.id;
 
   await dispatch(loadSourceText({ cx, source }));
 
   await dispatch({
     type: "SET_SYMBOLS",
     cx,
     sourceId,
--- a/devtools/client/debugger/src/actions/tests/__snapshots__/project-text-search.spec.js.snap
+++ b/devtools/client/debugger/src/actions/tests/__snapshots__/project-text-search.spec.js.snap
@@ -119,16 +119,37 @@ Array [
       },
     ],
     "sourceId": "bar",
     "type": "RESULT",
   },
 ]
 `;
 
+exports[`project text search should search a specific source 2`] = `
+Array [
+  Object {
+    "filepath": "http://localhost:8000/examples/bar",
+    "matches": Array [
+      Object {
+        "column": 9,
+        "line": 1,
+        "match": "bla",
+        "matchIndex": 9,
+        "sourceId": "bar",
+        "type": "MATCH",
+        "value": "function bla(x, y) {",
+      },
+    ],
+    "sourceId": "bar",
+    "type": "RESULT",
+  },
+]
+`;
+
 exports[`project text search should search all the loaded sources based on the query 1`] = `
 Array [
   Object {
     "filepath": "http://localhost:8000/examples/foo1",
     "matches": Array [
       Object {
         "column": 9,
         "line": 1,
--- a/devtools/client/debugger/src/actions/types/index.js
+++ b/devtools/client/debugger/src/actions/types/index.js
@@ -14,16 +14,17 @@ import type { SearchOperation } from "..
 import type { BreakpointAction } from "./BreakpointAction";
 import type { SourceAction } from "./SourceAction";
 import type { SourceActorAction } from "./SourceActorAction";
 import type { UIAction } from "./UIAction";
 import type { PauseAction } from "./PauseAction";
 import type { ASTAction } from "./ASTAction";
 import { clientCommands } from "../../client/firefox";
 import type { Panel } from "../../client/firefox/types";
+import type { ParserDispatcher } from "../../workers/parser";
 
 /**
  * Flow types
  * @module actions/types
  */
 
 /**
  * Argument parameters via Thunk middleware for {@link https://github.com/gaearon/redux-thunk|Redux Thunk}
@@ -33,16 +34,18 @@ import type { Panel } from "../../client
  * @typedef {Object} ThunkArgs
  */
 export type ThunkArgs = {
   dispatch: (action: any) => Promise<any>,
   forkedDispatch: (action: any) => Promise<any>,
   getState: () => State,
   client: typeof clientCommands,
   sourceMaps: SourceMaps,
+  parser: ParserDispatcher,
+  evaluationsParser: ParserDispatcher,
   panel: Panel
 };
 
 export type Thunk = ThunkArgs => any;
 
 export type ActionType = Object | Function;
 
 type ProjectTextSearchResult = {
--- a/devtools/client/debugger/src/actions/utils/middleware/log.js
+++ b/devtools/client/debugger/src/actions/utils/middleware/log.js
@@ -15,17 +15,18 @@ const blacklist = [
   "OUT_OF_SCOPE_LOCATIONS",
   "MAP_SCOPES",
   "MAP_FRAMES",
   "ADD_SCOPES",
   "IN_SCOPE_LINES",
   "REMOVE_BREAKPOINT",
   "NODE_PROPERTIES_LOADED",
   "SET_FOCUSED_SOURCE_ITEM",
-  "NODE_EXPAND"
+  "NODE_EXPAND",
+  "IN_SCOPE_LINES"
 ];
 
 function cloneAction(action: any) {
   action = action || {};
   action = { ...action };
 
   // ADD_TAB, ...
   if (action.source && action.source.text) {
--- a/devtools/client/debugger/src/client/index.js
+++ b/devtools/client/debugger/src/client/index.js
@@ -54,47 +54,47 @@ function getClient(connection: any) {
   const {
     tab: { clientType }
   } = connection;
   return clientType == "firefox" ? firefox : chrome;
 }
 
 export async function onConnect(
   connection: Object,
-  sourceMaps: Object,
+  panelWorkers: Object,
   panel: Panel
 ) {
   // NOTE: the landing page does not connect to a JS process
   if (!connection) {
     return;
   }
 
   verifyPrefSchema();
 
   const client = getClient(connection);
   const commands = client.clientCommands;
 
   const initialState = await loadInitialState();
+  const workers = bootstrapWorkers(panelWorkers);
 
   const { store, actions, selectors } = bootstrapStore(
     commands,
-    sourceMaps,
+    workers,
     panel,
     initialState
   );
 
-  const workers = bootstrapWorkers();
   await client.onConnect(connection, actions);
 
   await syncBreakpoints();
   syncXHRBreakpoints();
   setupHelper({
     store,
     actions,
     selectors,
-    workers: { ...workers, sourceMaps },
+    workers,
     connection,
     client: client.clientCommands
   });
 
   bootstrapApp(store);
   return { store, actions, selectors, client: commands };
 }
--- a/devtools/client/debugger/src/main.js
+++ b/devtools/client/debugger/src/main.js
@@ -14,29 +14,29 @@ function unmountRoot() {
   ReactDOM.unmountComponentAtNode(mount);
 }
 
 module.exports = {
   bootstrap: ({
     threadClient,
     tabTarget,
     debuggerClient,
-    sourceMaps,
+    workers,
     panel
   }: any) =>
     onConnect(
       {
         tab: { clientType: "firefox" },
         tabConnection: {
           tabTarget,
           threadClient,
           debuggerClient
         }
       },
-      sourceMaps,
+      workers,
       panel
     ),
   destroy: () => {
     unmountRoot();
     sourceQueue.clear();
     teardownWorkers();
   }
 };
--- a/devtools/client/debugger/src/test/tests-setup.js
+++ b/devtools/client/debugger/src/test/tests-setup.js
@@ -18,22 +18,17 @@ import { prefs } from "../utils/prefs";
 
 import { startSourceMapWorker, stopSourceMapWorker } from "devtools-source-map";
 
 import {
   start as startPrettyPrintWorker,
   stop as stopPrettyPrintWorker
 } from "../workers/pretty-print";
 
-import {
-  start as startParserWorker,
-  stop as stopParserWorker,
-  clearSymbols,
-  clearASTs
-} from "../workers/parser";
+import { ParserDispatcher } from "../workers/parser";
 import {
   start as startSearchWorker,
   stop as stopSearchWorker
 } from "../workers/search";
 import { clearDocuments } from "../utils/editor";
 import { clearHistory } from "./utils/history";
 
 import env from "devtools-environment/test-flag";
@@ -63,42 +58,43 @@ global.URL = URL;
 global.indexedDB = mockIndexeddDB();
 
 Enzyme.configure({ adapter: new Adapter() });
 
 function formatException(reason, p) {
   console && console.log("Unhandled Rejection at:", p, "reason:", reason);
 }
 
+export const parserWorker = new ParserDispatcher();
+
 beforeAll(() => {
   startSourceMapWorker(
     path.join(rootPath, "node_modules/devtools-source-map/src/worker.js"),
     ""
   );
   startPrettyPrintWorker(
     path.join(rootPath, "src/workers/pretty-print/worker.js")
   );
-  startParserWorker(path.join(rootPath, "src/workers/parser/worker.js"));
+  parserWorker.start(path.join(rootPath, "src/workers/parser/worker.js"));
   startSearchWorker(path.join(rootPath, "src/workers/search/worker.js"));
   process.on("unhandledRejection", formatException);
 });
 
 afterAll(() => {
   stopSourceMapWorker();
   stopPrettyPrintWorker();
-  stopParserWorker();
+  parserWorker.stop();
   stopSearchWorker();
   process.removeListener("unhandledRejection", formatException);
 });
 
 afterEach(() => {});
 
 beforeEach(async () => {
-  clearASTs();
-  await clearSymbols();
+  parserWorker.clear();
   clearHistory();
   clearDocuments();
   prefs.projectDirectoryRoot = "";
 
   // Ensures window.dbg is there to track telemetry
   setupHelper({ selectors: {} });
 });
 
--- a/devtools/client/debugger/src/utils/bootstrap.js
+++ b/devtools/client/debugger/src/utils/bootstrap.js
@@ -11,85 +11,94 @@ const { Provider } = require("react-redu
 
 import { isFirefoxPanel, isDevelopment, isTesting } from "devtools-environment";
 import SourceMaps, {
   startSourceMapWorker,
   stopSourceMapWorker
 } from "devtools-source-map";
 import * as search from "../workers/search";
 import * as prettyPrint from "../workers/pretty-print";
-import * as parser from "../workers/parser";
+import { ParserDispatcher } from "../workers/parser";
 
 import configureStore from "../actions/utils/create-store";
 import reducers from "../reducers";
 import * as selectors from "../selectors";
 import App from "../components/App";
 import { asyncStore, prefs } from "./prefs";
 
 import type { Panel } from "../client/firefox/types";
 
+let parser;
+
 function renderPanel(component, store) {
   const root = document.createElement("div");
   root.className = "launchpad-root theme-body";
   root.style.setProperty("flex", "1");
   const mount = document.querySelector("#mount");
   if (!mount) {
     return;
   }
   mount.appendChild(root);
 
   ReactDOM.render(
     React.createElement(Provider, { store }, React.createElement(component)),
     root
   );
 }
 
+type Workers = {
+  sourceMaps: typeof SourceMaps,
+  evaluationsParser: typeof ParserDispatcher
+};
+
 export function bootstrapStore(
   client: any,
-  sourceMaps: typeof SourceMaps,
+  workers: Workers,
   panel: Panel,
   initialState: Object
 ) {
   const createStore = configureStore({
     log: prefs.logging || isTesting(),
     timing: isDevelopment(),
     makeThunkArgs: (args, state) => {
-      return { ...args, client, sourceMaps, panel };
+      return { ...args, client, ...workers, panel };
     }
   });
 
   const store = createStore(combineReducers(reducers), initialState);
   store.subscribe(() => updatePrefs(store.getState()));
 
   const actions = bindActionCreators(
     require("../actions").default,
     store.dispatch
   );
 
   return { store, actions, selectors };
 }
 
-export function bootstrapWorkers() {
+export function bootstrapWorkers(panelWorkers: Workers) {
   const workerPath = isDevelopment()
     ? "assets/build"
     : "resource://devtools/client/debugger/dist";
 
   if (isDevelopment()) {
     // When used in Firefox, the toolbox manages the source map worker.
     startSourceMapWorker(
       `${workerPath}/source-map-worker.js`,
       // This is relative to the worker itself.
       "./source-map-worker-assets/"
     );
   }
 
   prettyPrint.start(`${workerPath}/pretty-print-worker.js`);
+  parser = new ParserDispatcher();
+
   parser.start(`${workerPath}/parser-worker.js`);
   search.start(`${workerPath}/search-worker.js`);
-  return { prettyPrint, parser, search };
+  return { ...panelWorkers, prettyPrint, parser, search };
 }
 
 export function teardownWorkers() {
   if (!isFirefoxPanel()) {
     // When used in Firefox, the toolbox manages the source map worker.
     stopSourceMapWorker();
   }
   prettyPrint.stop();
--- a/devtools/client/debugger/src/utils/pause/mapScopes/index.js
+++ b/devtools/client/debugger/src/utils/pause/mapScopes/index.js
@@ -2,17 +2,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/>. */
 
 // @flow
 
 import typeof SourceMaps from "devtools-source-map";
 
 import {
-  getScopes,
   type SourceScope,
   type BindingData,
   type BindingLocation
 } from "../../../workers/parser";
 import type { RenderableScope } from "../scopes/getScope";
 import { locColumn } from "./locColumn";
 import {
   loadRangeMetadata,
@@ -32,16 +31,18 @@ import {
   type GeneratedBindingLocation
 } from "./buildGeneratedBindingList";
 import {
   originalRangeStartsInside,
   getApplicableBindingsForOriginalPosition
 } from "./getApplicableBindingsForOriginalPosition";
 
 import { log } from "../../log";
+import type { ThunkArgs } from "../../../actions/types";
+
 import type {
   PartialPosition,
   Frame,
   Scope,
   Source,
   SourceContent,
   BindingContents,
   ScopeBindings
@@ -49,26 +50,25 @@ import type {
 
 export type OriginalScope = RenderableScope;
 
 export async function buildMappedScopes(
   source: Source,
   content: SourceContent,
   frame: Frame,
   scopes: Scope,
-  sourceMaps: any,
-  client: any
+  { client, parser, sourceMaps }: ThunkArgs
 ): Promise<?{
   mappings: {
     [string]: string
   },
   scope: OriginalScope
 }> {
-  const originalAstScopes = await getScopes(frame.location);
-  const generatedAstScopes = await getScopes(frame.generatedLocation);
+  const originalAstScopes = await parser.getScopes(frame.location);
+  const generatedAstScopes = await parser.getScopes(frame.generatedLocation);
 
   if (!originalAstScopes || !generatedAstScopes) {
     return null;
   }
 
   const originalRanges = await loadRangeMetadata(
     source,
     frame,
--- a/devtools/client/debugger/src/utils/test-head.js
+++ b/devtools/client/debugger/src/utils/test-head.js
@@ -10,16 +10,17 @@
  */
 
 import { combineReducers } from "redux";
 import sourceMaps from "devtools-source-map";
 import reducers from "../reducers";
 import actions from "../actions";
 import * as selectors from "../selectors";
 import { getHistory } from "../test/utils/history";
+import { parserWorker } from "../test/tests-setup";
 import configureStore from "../actions/utils/create-store";
 import sourceQueue from "../utils/source-queue";
 import type { Source, OriginalSourceData, GeneratedSourceData } from "../types";
 
 /**
  * This file contains older interfaces used by tests that have not been
  * converted to use test-mockup.js
  */
@@ -36,17 +37,18 @@ function createStore(client: any, initia
 
   const store = configureStore({
     log: false,
     history: getHistory(),
     makeThunkArgs: args => {
       return {
         ...args,
         client,
-        sourceMaps: sourceMapsMock !== undefined ? sourceMapsMock : sourceMaps
+        sourceMaps: sourceMapsMock !== undefined ? sourceMapsMock : sourceMaps,
+        parser: parserWorker
       };
     }
   })(combineReducers(reducers), initialState);
   sourceQueue.clear();
   sourceQueue.initialize({
     newQueuedSources: sources =>
       store.dispatch(actions.newQueuedSources(sources))
   });
--- a/devtools/client/debugger/src/workers/parser/index.js
+++ b/devtools/client/debugger/src/workers/parser/index.js
@@ -7,87 +7,81 @@
 import { workerUtils } from "devtools-utils";
 const { WorkerDispatcher } = workerUtils;
 
 import type { AstSource, AstLocation, AstPosition } from "./types";
 import type { SourceLocation, SourceId, SourceContent } from "../../types";
 import type { SourceScope } from "./getScopes/visitor";
 import type { SymbolDeclarations } from "./getSymbols";
 
-const dispatcher = new WorkerDispatcher();
-export const start = (url: string, win: any = window) =>
-  dispatcher.start(url, win);
-export const stop = () => dispatcher.stop();
-
-export const findOutOfScopeLocations = async (
-  sourceId: string,
-  position: AstPosition
-): Promise<AstLocation[]> =>
-  dispatcher.invoke("findOutOfScopeLocations", sourceId, position);
-
-export const getNextStep = async (
-  sourceId: SourceId,
-  pausedPosition: AstPosition
-): Promise<?SourceLocation> =>
-  dispatcher.invoke("getNextStep", sourceId, pausedPosition);
+export class ParserDispatcher extends WorkerDispatcher {
+  async findOutOfScopeLocations(
+    sourceId: string,
+    position: AstPosition
+  ): Promise<AstLocation[]> {
+    return this.invoke("findOutOfScopeLocations", sourceId, position);
+  }
 
-export const clearASTs = async (): Promise<void> =>
-  dispatcher.invoke("clearASTs");
-
-export const getScopes = async (
-  location: SourceLocation
-): Promise<SourceScope[]> => dispatcher.invoke("getScopes", location);
+  async getNextStep(
+    sourceId: SourceId,
+    pausedPosition: AstPosition
+  ): Promise<?SourceLocation> {
+    return this.invoke("getNextStep", sourceId, pausedPosition);
+  }
 
-export const clearScopes = async (): Promise<void> =>
-  dispatcher.invoke("clearScopes");
+  async clearState(): Promise<void> {
+    return this.invoke("clearState");
+  }
 
-export const clearSymbols = async (): Promise<void> =>
-  dispatcher.invoke("clearSymbols");
+  async getScopes(location: SourceLocation): Promise<SourceScope[]> {
+    return this.invoke("getScopes", location);
+  }
 
-export const getSymbols = async (
-  sourceId: string
-): Promise<SymbolDeclarations> => dispatcher.invoke("getSymbols", sourceId);
+  async getSymbols(sourceId: string): Promise<SymbolDeclarations> {
+    return this.invoke("getSymbols", sourceId);
+  }
 
-export const setSource = async (
-  sourceId: SourceId,
-  content: SourceContent
-): Promise<void> => {
-  const astSource: AstSource = {
-    id: sourceId,
-    text: content.type === "wasm" ? "" : content.value,
-    contentType: content.contentType || null,
-    isWasm: content.type === "wasm"
-  };
+  async setSource(sourceId: SourceId, content: SourceContent): Promise<void> {
+    const astSource: AstSource = {
+      id: sourceId,
+      text: content.type === "wasm" ? "" : content.value,
+      contentType: content.contentType || null,
+      isWasm: content.type === "wasm"
+    };
 
-  await dispatcher.invoke("setSource", astSource);
-};
+    return this.invoke("setSource", astSource);
+  }
 
-export const clearSources = async (): Promise<void> =>
-  dispatcher.invoke("clearSources");
+  async hasSyntaxError(input: string): Promise<string | false> {
+    return this.invoke("hasSyntaxError", input);
+  }
 
-export const hasSyntaxError = async (input: string): Promise<string | false> =>
-  dispatcher.invoke("hasSyntaxError", input);
+  async mapExpression(
+    expression: string,
+    mappings: {
+      [string]: string | null
+    } | null,
+    bindings: string[],
+    shouldMapBindings?: boolean,
+    shouldMapAwait?: boolean
+  ): Promise<{ expression: string }> {
+    return this.invoke(
+      "mapExpression",
+      expression,
+      mappings,
+      bindings,
+      shouldMapBindings,
+      shouldMapAwait
+    );
+  }
 
-export const mapExpression = async (
-  expression: string,
-  mappings: {
-    [string]: string | null
-  } | null,
-  bindings: string[],
-  shouldMapBindings?: boolean,
-  shouldMapAwait?: boolean
-): Promise<{ expression: string }> =>
-  dispatcher.invoke(
-    "mapExpression",
-    expression,
-    mappings,
-    bindings,
-    shouldMapBindings,
-    shouldMapAwait
-  );
+  async clear() {
+    await this.clearState();
+  }
+}
 
 export type {
   SourceScope,
   BindingData,
   BindingLocation,
   BindingLocationType,
   BindingDeclarationLocation,
   BindingMetaValue,
--- a/devtools/client/debugger/src/workers/parser/worker.js
+++ b/devtools/client/debugger/src/workers/parser/worker.js
@@ -11,21 +11,25 @@ import { setSource, clearSources } from 
 import findOutOfScopeLocations from "./findOutOfScopeLocations";
 import { getNextStep } from "./steps";
 import { hasSyntaxError } from "./validate";
 import mapExpression from "./mapExpression";
 
 import { workerUtils } from "devtools-utils";
 const { workerHandler } = workerUtils;
 
+function clearState() {
+  clearASTs();
+  clearScopes();
+  clearSources();
+  clearSymbols();
+}
+
 self.onmessage = workerHandler({
   findOutOfScopeLocations,
   getSymbols,
   getScopes,
-  clearSymbols,
-  clearScopes,
-  clearASTs,
-  setSource,
-  clearSources,
+  clearState,
   getNextStep,
   hasSyntaxError,
-  mapExpression
+  mapExpression,
+  setSource
 });
--- a/devtools/client/debugger/test/mochitest/helpers.js
+++ b/devtools/client/debugger/test/mochitest/helpers.js
@@ -1055,18 +1055,18 @@ const keyMappings = {
   quickOpen: { code: "p", modifiers: cmdOrCtrl },
   quickOpenFunc: { code: "o", modifiers: cmdShift },
   quickOpenLine: { code: ":", modifiers: cmdOrCtrl },
   fileSearch: { code: "f", modifiers: cmdOrCtrl },
   fileSearchNext: { code: "g", modifiers: cmdOrCtrl },
   fileSearchPrev: { code: "g", modifiers: cmdShift },
   Enter: { code: "VK_RETURN" },
   ShiftEnter: { code: "VK_RETURN", modifiers: shiftOrAlt },
-  AltEnter: { 
-    code: "VK_RETURN", 
+  AltEnter: {
+    code: "VK_RETURN",
     modifiers: { altKey: true }
   },
   Up: { code: "VK_UP" },
   Down: { code: "VK_DOWN" },
   Right: { code: "VK_RIGHT" },
   Left: { code: "VK_LEFT" },
   End: endKey,
   Start: startKey,
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -932,16 +932,34 @@ Toolbox.prototype = {
    * A common access point for the client-side mapping service for source maps that
    * any panel can use.  This is a "low-level" API that connects to
    * the source map worker.
    */
   get sourceMapService() {
     return this._createSourceMapService();
   },
 
+    /**
+   * A common access point for the client-side parser service that any panel can use.
+   */
+  get parserService() {
+    if (this._parserService) {
+      return this._parserService;
+    }
+
+    const { ParserDispatcher } =
+      require("devtools/client/debugger/src/workers/parser/index");
+
+    this._parserService = new ParserDispatcher();
+    this._parserService.start(
+      "resource://devtools/client/debugger/dist/parser-worker.js",
+      this.win);
+    return this._parserService;
+  },
+
   /**
    * Clients wishing to use source maps but that want the toolbox to
    * track the source and style sheet actor mapping can use this
    * source map service.  This is a higher-level service than the one
    * returned by |sourceMapService|, in that it automatically tracks
    * source and style sheet actor IDs.
    */
   get sourceMapURLService() {
@@ -3069,21 +3087,27 @@ Toolbox.prototype = {
     this.telemetry.toolClosed(this.currentToolId, this.sessionId, this);
 
     this._lastFocusedElement = null;
 
     if (this._sourceMapURLService) {
       this._sourceMapURLService.destroy();
       this._sourceMapURLService = null;
     }
+
     if (this._sourceMapService) {
       this._sourceMapService.stopSourceMapWorker();
       this._sourceMapService = null;
     }
 
+    if (this._parserService) {
+      this._parserService.stop();
+      this._parserService = null;
+    }
+
     if (this.webconsolePanel) {
       this._saveSplitConsoleHeight();
       this.webconsolePanel.removeEventListener("resize",
         this._saveSplitConsoleHeight);
       this.webconsolePanel = null;
     }
     if (this._componentMount) {
       this._componentMount.removeEventListener("keypress", this._onToolbarArrowKeypress);
--- a/devtools/client/webconsole/webconsole.js
+++ b/devtools/client/webconsole/webconsole.js
@@ -296,27 +296,25 @@ class WebConsole {
       const res = this.parserService.mapExpression(
         expression, null, null, shouldMapBindings, shouldMapAwait);
       return res;
     }
 
     return null;
   }
 
-  /**
-   * A common access point for the client-side parser service that any panel can use.
-   */
   get parserService() {
     if (this._parserService) {
       return this._parserService;
     }
 
-    this._parserService =
+    const { ParserDispatcher } =
       require("devtools/client/debugger/src/workers/parser/index");
 
+    this._parserService = new ParserDispatcher();
     this._parserService.start(
       "resource://devtools/client/debugger/dist/parser-worker.js",
       this.chromeUtilsWindow);
     return this._parserService;
   }
 
   /**
    * Retrieves the current selection from the Inspector, if such a selection
--- a/devtools/server/actors/animation-type-longhand.js
+++ b/devtools/server/actors/animation-type-longhand.js
@@ -288,16 +288,18 @@ exports.ANIMATION_TYPE_FOR_LONGHANDS = [
     "border-top-right-radius",
     "border-start-start-radius",
     "border-start-end-radius",
     "border-end-start-radius",
     "border-end-end-radius",
     "bottom",
     "column-gap",
     "column-width",
+    "cx",
+    "cy",
     "flex-basis",
     "height",
     "left",
     "letter-spacing",
     "line-height",
     "margin-bottom",
     "margin-left",
     "margin-right",
@@ -310,16 +312,19 @@ exports.ANIMATION_TYPE_FOR_LONGHANDS = [
     "-moz-outline-radius-bottomright",
     "-moz-outline-radius-topleft",
     "-moz-outline-radius-topright",
     "padding-bottom",
     "padding-left",
     "padding-right",
     "padding-top",
     "perspective",
+    "r",
+    "rx",
+    "ry",
     "right",
     "row-gap",
     "scroll-padding-block-start",
     "scroll-padding-block-end",
     "scroll-padding-inline-start",
     "scroll-padding-inline-end",
     "scroll-padding-top",
     "scroll-padding-right",
@@ -337,16 +342,18 @@ exports.ANIMATION_TYPE_FOR_LONGHANDS = [
     "stroke-dashoffset",
     "stroke-width",
     "-moz-tab-size",
     "text-indent",
     "top",
     "vertical-align",
     "width",
     "word-spacing",
+    "x",
+    "y",
     "z-index",
   ])],
   ["float", new Set([
     "-moz-box-flex",
     "fill-opacity",
     "flex-grow",
     "flex-shrink",
     "flood-opacity",
--- a/devtools/shared/css/generated/properties-db.js
+++ b/devtools/shared/css/generated/properties-db.js
@@ -3310,16 +3310,23 @@ exports.CSS_PROPERTIES = {
       "mask-repeat",
       "mask-position-x",
       "mask-position-y",
       "mask-clip",
       "mask-origin",
       "mask-size",
       "mask-composite",
       "mask-image",
+      "x",
+      "y",
+      "cx",
+      "cy",
+      "rx",
+      "ry",
+      "r",
       "-moz-box-align",
       "-moz-box-direction",
       "-moz-box-flex",
       "-moz-box-orient",
       "-moz-box-pack",
       "-moz-stack-sizing",
       "-moz-box-ordinal-group"
     ],
@@ -5827,16 +5834,42 @@ exports.CSS_PROPERTIES = {
       "url",
       "vertical-text",
       "w-resize",
       "wait",
       "zoom-in",
       "zoom-out"
     ]
   },
+  "cx": {
+    "isInherited": false,
+    "subproperties": [
+      "cx"
+    ],
+    "supports": [],
+    "values": [
+      "inherit",
+      "initial",
+      "revert",
+      "unset"
+    ]
+  },
+  "cy": {
+    "isInherited": false,
+    "subproperties": [
+      "cy"
+    ],
+    "supports": [],
+    "values": [
+      "inherit",
+      "initial",
+      "revert",
+      "unset"
+    ]
+  },
   "direction": {
     "isInherited": true,
     "subproperties": [
       "direction"
     ],
     "supports": [],
     "values": [
       "inherit",
@@ -8910,16 +8943,29 @@ exports.CSS_PROPERTIES = {
     "values": [
       "inherit",
       "initial",
       "none",
       "revert",
       "unset"
     ]
   },
+  "r": {
+    "isInherited": false,
+    "subproperties": [
+      "r"
+    ],
+    "supports": [],
+    "values": [
+      "inherit",
+      "initial",
+      "revert",
+      "unset"
+    ]
+  },
   "resize": {
     "isInherited": false,
     "subproperties": [
       "resize"
     ],
     "supports": [],
     "values": [
       "block",
@@ -8989,16 +9035,44 @@ exports.CSS_PROPERTIES = {
       "inherit",
       "initial",
       "over",
       "revert",
       "under",
       "unset"
     ]
   },
+  "rx": {
+    "isInherited": false,
+    "subproperties": [
+      "rx"
+    ],
+    "supports": [],
+    "values": [
+      "auto",
+      "inherit",
+      "initial",
+      "revert",
+      "unset"
+    ]
+  },
+  "ry": {
+    "isInherited": false,
+    "subproperties": [
+      "ry"
+    ],
+    "supports": [],
+    "values": [
+      "auto",
+      "inherit",
+      "initial",
+      "revert",
+      "unset"
+    ]
+  },
   "scroll-behavior": {
     "isInherited": false,
     "subproperties": [
       "scroll-behavior"
     ],
     "supports": [],
     "values": [
       "auto",
@@ -10439,16 +10513,42 @@ exports.CSS_PROPERTIES = {
       "sideways-rl",
       "tb",
       "tb-rl",
       "unset",
       "vertical-lr",
       "vertical-rl"
     ]
   },
+  "x": {
+    "isInherited": false,
+    "subproperties": [
+      "x"
+    ],
+    "supports": [],
+    "values": [
+      "inherit",
+      "initial",
+      "revert",
+      "unset"
+    ]
+  },
+  "y": {
+    "isInherited": false,
+    "subproperties": [
+      "y"
+    ],
+    "supports": [],
+    "values": [
+      "inherit",
+      "initial",
+      "revert",
+      "unset"
+    ]
+  },
   "z-index": {
     "isInherited": false,
     "subproperties": [
       "z-index"
     ],
     "supports": [],
     "values": [
       "auto",
@@ -10592,16 +10692,48 @@ exports.PREFERENCES = [
     "scroll-snap-destination",
     "layout.css.scroll-snap.enabled"
   ],
   [
     "-moz-binding",
     "layout.css.moz-binding.content.enabled"
   ],
   [
+    "scroll-margin-block-end",
+    "layout.css.scroll-snap-v1.enabled"
+  ],
+  [
+    "scroll-margin-block-start",
+    "layout.css.scroll-snap-v1.enabled"
+  ],
+  [
+    "scroll-margin-bottom",
+    "layout.css.scroll-snap-v1.enabled"
+  ],
+  [
+    "scroll-margin-inline-end",
+    "layout.css.scroll-snap-v1.enabled"
+  ],
+  [
+    "scroll-margin-inline-start",
+    "layout.css.scroll-snap-v1.enabled"
+  ],
+  [
+    "scroll-margin-left",
+    "layout.css.scroll-snap-v1.enabled"
+  ],
+  [
+    "scroll-margin-right",
+    "layout.css.scroll-snap-v1.enabled"
+  ],
+  [
+    "scroll-margin-top",
+    "layout.css.scroll-snap-v1.enabled"
+  ],
+  [
     "scroll-padding-block-end",
     "layout.css.scroll-snap-v1.enabled"
   ],
   [
     "scroll-padding-block-start",
     "layout.css.scroll-snap-v1.enabled"
   ],
   [
@@ -10624,48 +10756,16 @@ exports.PREFERENCES = [
     "scroll-padding-right",
     "layout.css.scroll-snap-v1.enabled"
   ],
   [
     "scroll-padding-top",
     "layout.css.scroll-snap-v1.enabled"
   ],
   [
-    "scroll-margin-block-end",
-    "layout.css.scroll-snap-v1.enabled"
-  ],
-  [
-    "scroll-margin-block-start",
-    "layout.css.scroll-snap-v1.enabled"
-  ],
-  [
-    "scroll-margin-bottom",
-    "layout.css.scroll-snap-v1.enabled"
-  ],
-  [
-    "scroll-margin-inline-end",
-    "layout.css.scroll-snap-v1.enabled"
-  ],
-  [
-    "scroll-margin-inline-start",
-    "layout.css.scroll-snap-v1.enabled"
-  ],
-  [
-    "scroll-margin-left",
-    "layout.css.scroll-snap-v1.enabled"
-  ],
-  [
-    "scroll-margin-right",
-    "layout.css.scroll-snap-v1.enabled"
-  ],
-  [
-    "scroll-margin-top",
-    "layout.css.scroll-snap-v1.enabled"
-  ],
-  [
     "-webkit-text-stroke-width",
     "layout.css.prefixes.webkit"
   ],
   [
     "-webkit-text-fill-color",
     "layout.css.prefixes.webkit"
   ],
   [
--- a/docshell/base/nsDocShellTreeOwner.cpp
+++ b/docshell/base/nsDocShellTreeOwner.cpp
@@ -868,36 +868,30 @@ nsDocShellTreeOwner::HandleEvent(Event* 
     bool canDropLink = false;
     handler->CanDropLink(dragEvent, false, &canDropLink);
     if (canDropLink) {
       aEvent->PreventDefault();
     }
   } else if (eventType.EqualsLiteral("drop")) {
     nsIWebNavigation* webnav = static_cast<nsIWebNavigation*>(mWebBrowser);
 
-    uint32_t linksCount;
-    nsIDroppedLinkItem** links;
-    if (webnav && NS_SUCCEEDED(handler->DropLinks(dragEvent, true, &linksCount,
-                                                  &links))) {
-      if (linksCount >= 1) {
+    nsTArray<RefPtr<nsIDroppedLinkItem>> links;
+    if (webnav && NS_SUCCEEDED(handler->DropLinks(dragEvent, true, links))) {
+      if (links.Length() >= 1) {
         nsCOMPtr<nsIPrincipal> triggeringPrincipal;
         handler->GetTriggeringPrincipal(dragEvent,
                                         getter_AddRefs(triggeringPrincipal));
         if (triggeringPrincipal) {
           nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome =
               GetWebBrowserChrome();
           if (webBrowserChrome) {
             nsCOMPtr<nsIBrowserChild> browserChild =
                 do_QueryInterface(webBrowserChrome);
             if (browserChild) {
-              nsresult rv = browserChild->RemoteDropLinks(linksCount, links);
-              for (uint32_t i = 0; i < linksCount; i++) {
-                NS_RELEASE(links[i]);
-              }
-              free(links);
+              nsresult rv = browserChild->RemoteDropLinks(links);
               return rv;
             }
           }
           nsAutoString url;
           if (NS_SUCCEEDED(links[0]->GetUrl(url))) {
             if (!url.IsEmpty()) {
 #ifndef ANDROID
               MOZ_ASSERT(triggeringPrincipal,
@@ -907,21 +901,16 @@ nsDocShellTreeOwner::HandleEvent(Event* 
               LoadURIOptions loadURIOptions;
               loadURIOptions.mTriggeringPrincipal = triggeringPrincipal;
               nsCOMPtr<nsIContentSecurityPolicy> csp;
               handler->GetCSP(dragEvent, getter_AddRefs(csp));
               loadURIOptions.mCsp = csp;
               webnav->LoadURI(url, loadURIOptions);
             }
           }
-
-          for (uint32_t i = 0; i < linksCount; i++) {
-            NS_RELEASE(links[i]);
-          }
-          free(links);
         }
       }
     } else {
       aEvent->StopPropagation();
       aEvent->PreventDefault();
     }
   }
 
--- a/dom/base/ContentAreaDropListener.jsm
+++ b/dom/base/ContentAreaDropListener.jsm
@@ -271,17 +271,17 @@ ContentAreaDropListener.prototype =
       let name = links[0].name;
       if (name)
         aName.value = name;
     }
 
     return url;
   },
 
-  dropLinks: function(aEvent, aDisallowInherit, aCount)
+  dropLinks: function(aEvent, aDisallowInherit)
   {
     if (aEvent && this._eventTargetIsDisabled(aEvent))
       return [];
 
     let dataTransfer = aEvent.dataTransfer;
     let links = this._getDropLinks(dataTransfer);
     let triggeringPrincipal = this._getTriggeringPrincipalFromDataTransfer(dataTransfer, false);
 
@@ -292,40 +292,34 @@ ContentAreaDropListener.prototype =
       } catch (ex) {
         // Prevent the drop entirely if any of the links are invalid even if
         // one of them is valid.
         aEvent.stopPropagation();
         aEvent.preventDefault();
         throw ex;
       }
     }
-    if (aCount)
-      aCount.value = links.length;
 
     return links;
   },
 
-  validateURIsForDrop: function(aEvent, aURIsCount, aURIs, aDisallowInherit)
+  validateURIsForDrop: function(aEvent, aURIs, aDisallowInherit)
   {
     let dataTransfer = aEvent.dataTransfer;
     let triggeringPrincipal = this._getTriggeringPrincipalFromDataTransfer(dataTransfer, false);
 
     for (let uri of aURIs) {
       this._validateURI(dataTransfer, uri, aDisallowInherit,
                         triggeringPrincipal);
     }
   },
 
-  queryLinks: function(aDataTransfer, aCount)
+  queryLinks: function(aDataTransfer)
   {
-    let links = this._getDropLinks(aDataTransfer);
-    if (aCount) {
-      aCount.value = links.length;
-    }
-    return links;
+    return this._getDropLinks(aDataTransfer);
   },
 
   _eventTargetIsDisabled: function(aEvent)
   {
     let ownerDoc = aEvent.originalTarget.ownerDocument;
     if (!ownerDoc || !ownerDoc.defaultView)
       return false;
 
--- a/dom/base/DOMIntersectionObserver.cpp
+++ b/dom/base/DOMIntersectionObserver.cpp
@@ -288,16 +288,17 @@ void DOMIntersectionObserver::Update(Doc
                                                             : rootRect.Width();
     rootMargin.Side(side) =
         nsLayoutUtils::ComputeCBDependentValue(basis, mRootMargin.Get(side));
   }
 
   for (size_t i = 0; i < mObservationTargets.Length(); ++i) {
     Element* target = mObservationTargets.ElementAt(i);
     nsIFrame* targetFrame = target->GetPrimaryFrame();
+    nsIFrame* originalTargetFrame = targetFrame;
     nsRect targetRect;
     Maybe<nsRect> intersectionRect;
     bool isSameDoc = root && root->GetComposedDoc() == target->GetComposedDoc();
 
     if (rootFrame && targetFrame) {
       // If mRoot is set we are testing intersection with a container element
       // instead of the implicit root.
       if (mRoot) {
@@ -369,17 +370,17 @@ void DOMIntersectionObserver::Update(Doc
       nsRect intersectionRectRelativeToRoot =
           nsLayoutUtils::TransformFrameRectToAncestor(
               targetFrame, intersectionRect.value(),
               nsLayoutUtils::GetContainingBlockForClientRect(rootFrame));
       intersectionRect = EdgeInclusiveIntersection(
           intersectionRectRelativeToRoot, rootIntersectionRect);
       if (intersectionRect.isSome() && !isSameDoc) {
         nsRect rect = intersectionRect.value();
-        nsPresContext* presContext = targetFrame->PresContext();
+        nsPresContext* presContext = originalTargetFrame->PresContext();
         nsIFrame* rootScrollFrame =
             presContext->PresShell()->GetRootScrollFrame();
         if (rootScrollFrame) {
           nsLayoutUtils::TransformRect(rootFrame, rootScrollFrame, rect);
         }
         intersectionRect = Some(rect);
       }
     }
--- a/dom/base/nsIDroppedLinkHandler.idl
+++ b/dom/base/nsIDroppedLinkHandler.idl
@@ -60,51 +60,46 @@ interface nsIDroppedLinkHandler : nsISup
    * otherwise.
    */
   AString dropLink(in DragEvent aEvent, out AString aName,
                    [optional] in boolean aDisallowInherit);
 
   /**
    * Given a drop event aEvent, determines links being dragged and returns
    * them. If links are returned the caller can, for instance, load them. If
-   * the count of links is 0, there is no valid link to be dropped.
+   * the returned array is empty, there is no valid link to be dropped.
    *
    * A NS_ERROR_DOM_SECURITY_ERR error will be thrown and the event cancelled if
    * the receiving target should not load the uri for security reasons. This
    * will occur if any of the following conditions are true:
    *  - the source of the drag initiated a link for dragging that
    *    it itself cannot access. This prevents a source document from tricking
    *    the user into a dragging a chrome url, for example.
    *  - aDisallowInherit is true, and the URI being dropped would inherit the
    *    current document's security context (URI_INHERITS_SECURITY_CONTEXT).
    */
-  void dropLinks(in DragEvent aEvent,
-                 [optional] in boolean aDisallowInherit,
-                 [optional] out unsigned long aCount,
-                 [retval, array, size_is(aCount)] out nsIDroppedLinkItem aLinks);
+  Array<nsIDroppedLinkItem> dropLinks(in DragEvent aEvent,
+                                      [optional] in boolean aDisallowInherit);
 
   /**
    * Given a drop event aEvent, validate the extra URIs for the event,
    * this is used when the caller extracts yet another URIs from the dropped
    * text, like home button that splits the text with "|".
    */
   void validateURIsForDrop(in DragEvent aEvent,
-                           in unsigned long aURIsCount,
-                           [array, size_is(aURIsCount)] in wstring aURIs,
+                           in Array<AString> aURIs,
                            [optional] in boolean aDisallowInherit);
 
   /**
    * Given a dataTransfer, allows caller to determine and verify links being
    * dragged. Since drag/drop performs a roundtrip of parent, child, parent,
    * it allows the parent to verify that the child did not modify links
    * being dropped.
    */
-  void queryLinks(in DataTransfer aDataTransfer,
-                  [optional] out unsigned long aCount,
-                  [retval, array, size_is(aCount)] out nsIDroppedLinkItem aLinks);
+  Array<nsIDroppedLinkItem> queryLinks(in DataTransfer aDataTransfer);
 
   /**
    * Given a drop event aEvent, determines the triggering principal for the
    * event and returns it.
    */
   nsIPrincipal getTriggeringPrincipal(in DragEvent aEvent);
 
   /**
--- a/dom/credentialmanagement/tests/mochitest/test_credman_iframes.html
+++ b/dom/credentialmanagement/tests/mochitest/test_credman_iframes.html
@@ -40,17 +40,18 @@ function handleEventMessage(event) {
   } else {
     ok(false, "Unexpected message in the test harness: " + event.data);
   }
 }
 
 window.addEventListener("message", handleEventMessage);
 SpecialPowers.pushPrefEnv({"set": [["security.webauth.webauthn", true],
                                    ["security.webauth.webauthn_enable_softtoken", true],
-                                   ["security.webauth.webauthn_enable_usbtoken", false]]},
+                                   ["security.webauth.webauthn_enable_usbtoken", false],
+                                   ["security.webauth.webauthn_enable_android_fido2", false]]},
 function() {
   document.getElementById("frame_top").src = "https://example.com/tests/dom/credentialmanagement/tests/mochitest/frame_credman_iframes.html";
 
   document.getElementById("frame_bottom").src = "https://test1.example.com/tests/dom/credentialmanagement/tests/mochitest/frame_credman_iframes.html";
 });
 </script>
 </body>
 </html>
--- a/dom/interfaces/base/nsIBrowser.idl
+++ b/dom/interfaces/base/nsIBrowser.idl
@@ -20,24 +20,22 @@ interface nsIBrowser : nsISupports
    * they are loaded in the same process as the content.
    */
   readonly attribute FrameLoader sameProcessAsFrameLoader;
 
   /*
    * Called by the child to inform the parent that links are dropped into
    * content area.
    *
-   * @param linksCount length of links
    * @param links a flat array of url, name, and type for each link
    * @param triggeringPrincipal a principal that initiated loading
    *                            of the dropped links
    */
-  void dropLinks(in unsigned long linksCount,
-                 [array, size_is(linksCount)] in wstring links,
-                 in nsIPrincipal aTriggeringPrincipal);
+  void dropLinks(in Array<AString> links,
+                 in nsIPrincipal triggeringPrincipal);
 
   /**
    * Swapping of frameloaders are usually initiated from a frameloader owner
    * or other components operating on frameloader owners. This is done by calling
    * swapFrameLoaders at MozFrameLoaderOwner webidl interface.
    *
    * This function aimed to provide the other way around -
    * if the swapping is initiated from frameloader itself or other platform level
@@ -68,22 +66,18 @@ interface nsIBrowser : nsISupports
    */
   readonly attribute nsIWebProgress remoteWebProgressManager;
 
   /**
    * Called by the child to inform the parent that a command update has occurred
    * and the supplied set of commands are now enabled and disabled.
    *
    * @param action command updater action
-   * @param enabledLength length of enabledCommands array
    * @param enabledCommands commands to enable
-   * @param disabledLength length of disabledCommands array
    * @param disabledCommand commands to disable
    */
   void enableDisableCommandsRemoteOnly(in AString action,
-                                       in unsigned long enabledLength,
-                                       [array, size_is(enabledLength)] in string enabledCommands,
-                                       in unsigned long disabledLength,
-                                       [array, size_is(disabledLength)] in string disabledCommands);
+                                       in Array<ACString> enabledCommands,
+                                       in Array<ACString> disabledCommands);
 
   readonly attribute nsIPrincipal contentPrincipal;
   readonly attribute nsIContentSecurityPolicy csp;
 };
--- a/dom/interfaces/base/nsIBrowserChild.idl
+++ b/dom/interfaces/base/nsIBrowserChild.idl
@@ -24,18 +24,17 @@ interface nsIBrowserChild : nsISupports
 
   [noscript, notxpcom] void enableDisableCommands(in AString action,
                                                   in CommandsArrayRef enabledCommands,
                                                   in CommandsArrayRef disabledCommands);
 
   [noscript] void remoteSizeShellTo(in int32_t width, in int32_t height,
                                     in int32_t shellItemWidth, in int32_t shellItemHeight);
 
-  [noscript] void remoteDropLinks(in unsigned long linksCount,
-                                  [array, size_is(linksCount)] in nsIDroppedLinkItem links);
+  void remoteDropLinks(in Array<nsIDroppedLinkItem> links);
 
   readonly attribute uint64_t tabId;
 
   /*
    * Indicates whether or not there are other tabs in this tab's window.
    */
   attribute boolean hasSiblings;
 
--- a/dom/ipc/BrowserChild.cpp
+++ b/dom/ipc/BrowserChild.cpp
@@ -730,35 +730,35 @@ BrowserChild::RemoteSizeShellTo(int32_t 
 
   bool sent = SendSizeShellTo(flags, aWidth, aHeight, aShellItemWidth,
                               aShellItemHeight);
 
   return sent ? NS_OK : NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
-BrowserChild::RemoteDropLinks(uint32_t aLinksCount,
-                              nsIDroppedLinkItem** aLinks) {
+BrowserChild::RemoteDropLinks(
+    const nsTArray<RefPtr<nsIDroppedLinkItem>>& aLinks) {
   nsTArray<nsString> linksArray;
   nsresult rv = NS_OK;
-  for (uint32_t i = 0; i < aLinksCount; i++) {
+  for (nsIDroppedLinkItem* link : aLinks) {
     nsString tmp;
-    rv = aLinks[i]->GetUrl(tmp);
+    rv = link->GetUrl(tmp);
     if (NS_FAILED(rv)) {
       return rv;
     }
     linksArray.AppendElement(tmp);
 
-    rv = aLinks[i]->GetName(tmp);
+    rv = link->GetName(tmp);
     if (NS_FAILED(rv)) {
       return rv;
     }
     linksArray.AppendElement(tmp);
 
-    rv = aLinks[i]->GetType(tmp);
+    rv = link->GetType(tmp);
     if (NS_FAILED(rv)) {
       return rv;
     }
     linksArray.AppendElement(tmp);
   }
   bool sent = SendDropLinks(linksArray);
 
   return sent ? NS_OK : NS_ERROR_FAILURE;
@@ -1811,17 +1811,18 @@ mozilla::ipc::IPCResult BrowserChild::Re
     nsCOMPtr<Document> document = GetTopLevelDocument();
     if (gfxPrefs::TouchActionEnabled()) {
       APZCCallbackHelper::SendSetAllowedTouchBehaviorNotification(
           mPuppetWidget, document, localEvent, aInputBlockId,
           mSetAllowedTouchBehaviorCallback);
     }
     UniquePtr<DisplayportSetListener> postLayerization =
         APZCCallbackHelper::SendSetTargetAPZCNotification(
-            mPuppetWidget, document, localEvent, aGuid.mLayersId, aInputBlockId);
+            mPuppetWidget, document, localEvent, aGuid.mLayersId,
+            aInputBlockId);
     if (postLayerization && postLayerization->Register()) {
       Unused << postLayerization.release();
     }
   }
 
   // Dispatch event to content (potentially a long-running operation)
   nsEventStatus status = DispatchWidgetEventViaAPZ(localEvent);
 
--- a/dom/ipc/BrowserParent.cpp
+++ b/dom/ipc/BrowserParent.cpp
@@ -804,34 +804,31 @@ mozilla::ipc::IPCResult BrowserParent::R
     // Verify that links have not been modified by the child. If links have
     // not been modified then it's safe to load those links using the
     // SystemPrincipal. If they have been modified by web content, then
     // we use a NullPrincipal which still allows to load web links.
     bool loadUsingSystemPrincipal = true;
     if (aLinks.Length() != mVerifyDropLinks.Length()) {
       loadUsingSystemPrincipal = false;
     }
-    UniquePtr<const char16_t*[]> links;
-    links = MakeUnique<const char16_t*[]>(aLinks.Length());
     for (uint32_t i = 0; i < aLinks.Length(); i++) {
       if (loadUsingSystemPrincipal) {
         if (!aLinks[i].Equals(mVerifyDropLinks[i])) {
           loadUsingSystemPrincipal = false;
         }
       }
-      links[i] = aLinks[i].get();
     }
     mVerifyDropLinks.Clear();
     nsCOMPtr<nsIPrincipal> triggeringPrincipal;
     if (loadUsingSystemPrincipal) {
       triggeringPrincipal = nsContentUtils::GetSystemPrincipal();
     } else {
       triggeringPrincipal = NullPrincipal::CreateWithoutOriginAttributes();
     }
-    browser->DropLinks(aLinks.Length(), links.get(), triggeringPrincipal);
+    browser->DropLinks(aLinks, triggeringPrincipal);
   }
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult BrowserParent::RecvEvent(const RemoteDOMEvent& aEvent) {
   RefPtr<Event> event = aEvent.mEvent;
   NS_ENSURE_TRUE(event, IPC_OK());
 
@@ -1389,52 +1386,46 @@ bool BrowserParent::QueryDropLinksForVer
     NS_WARNING("No dropHandler to query links for verification");
     return false;
   }
 
   // No more than one drop event can happen simultaneously; reset the link
   // verification array and store all links that are being dragged.
   mVerifyDropLinks.Clear();
 
-  uint32_t linksCount = 0;
-  nsIDroppedLinkItem** droppedLinkedItems = nullptr;
-  dropHandler->QueryLinks(initialDataTransfer, &linksCount,
-                          &droppedLinkedItems);
+  nsTArray<RefPtr<nsIDroppedLinkItem>> droppedLinkItems;
+  dropHandler->QueryLinks(initialDataTransfer, droppedLinkItems);
 
   // Since the entire event is cancelled if one of the links is invalid,
   // we can store all links on the parent side without any prior
   // validation checks.
   nsresult rv = NS_OK;
-  for (uint32_t i = 0; i < linksCount; i++) {
+  for (nsIDroppedLinkItem* item : droppedLinkItems) {
     nsString tmp;
-    rv = droppedLinkedItems[i]->GetUrl(tmp);
+    rv = item->GetUrl(tmp);
     if (NS_FAILED(rv)) {
       NS_WARNING("Failed to query url for verification");
       break;
     }
     mVerifyDropLinks.AppendElement(tmp);
 
-    rv = droppedLinkedItems[i]->GetName(tmp);
+    rv = item->GetName(tmp);
     if (NS_FAILED(rv)) {
       NS_WARNING("Failed to query name for verification");
       break;
     }
     mVerifyDropLinks.AppendElement(tmp);
 
-    rv = droppedLinkedItems[i]->GetType(tmp);
+    rv = item->GetType(tmp);
     if (NS_FAILED(rv)) {
       NS_WARNING("Failed to query type for verification");
       break;
     }
     mVerifyDropLinks.AppendElement(tmp);
   }
-  for (uint32_t i = 0; i < linksCount; i++) {
-    NS_IF_RELEASE(droppedLinkedItems[i]);
-  }
-  free(droppedLinkedItems);
   if (NS_FAILED(rv)) {
     mVerifyDropLinks.Clear();
     return false;
   }
   return true;
 }
 
 void BrowserParent::SendRealDragEvent(WidgetDragEvent& aEvent,
@@ -2158,35 +2149,18 @@ mozilla::ipc::IPCResult BrowserParent::R
     nsTArray<nsCString>&& aDisabledCommands) {
   nsCOMPtr<nsIBrowser> browser =
       mFrameElement ? mFrameElement->AsBrowser() : nullptr;
   bool isRemoteBrowser = false;
   if (browser) {
     browser->GetIsRemoteBrowser(&isRemoteBrowser);
   }
   if (isRemoteBrowser) {
-    UniquePtr<const char*[]> enabledCommands, disabledCommands;
-
-    if (aEnabledCommands.Length()) {
-      enabledCommands = MakeUnique<const char*[]>(aEnabledCommands.Length());
-      for (uint32_t c = 0; c < aEnabledCommands.Length(); c++) {
-        enabledCommands[c] = aEnabledCommands[c].get();
-      }
-    }
-
-    if (aDisabledCommands.Length()) {
-      disabledCommands = MakeUnique<const char*[]>(aDisabledCommands.Length());
-      for (uint32_t c = 0; c < aDisabledCommands.Length(); c++) {
-        disabledCommands[c] = aDisabledCommands[c].get();
-      }
-    }
-
-    browser->EnableDisableCommandsRemoteOnly(
-        aAction, aEnabledCommands.Length(), enabledCommands.get(),
-        aDisabledCommands.Length(), disabledCommands.get());
+    browser->EnableDisableCommandsRemoteOnly(aAction, aEnabledCommands,
+                                             aDisabledCommands);
   }
 
   return IPC_OK();
 }
 
 LayoutDeviceIntPoint BrowserParent::TransformPoint(
     const LayoutDeviceIntPoint& aPoint,
     const LayoutDeviceToLayoutDeviceMatrix4x4& aMatrix) {
--- a/dom/smil/SMILCompositor.cpp
+++ b/dom/smil/SMILCompositor.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "SMILCompositor.h"
 
+#include "mozilla/dom/SVGSVGElement.h"
 #include "nsComputedDOMStyle.h"
 #include "nsCSSProps.h"
 #include "nsHashKeys.h"
 #include "SMILCSSProperty.h"
 
 namespace mozilla {
 
 // PLDHashEntryHdr methods
@@ -142,29 +143,31 @@ nsCSSPropertyID SMILCompositor::GetCSSPr
       nsCSSProps::LookupProperty(nsDependentAtomString(mKey.mAttributeName));
 
   if (!SMILCSSProperty::IsPropertyAnimatable(propID)) {
     return eCSSProperty_UNKNOWN;
   }
 
   // If we are animating the 'width' or 'height' of an outer SVG
   // element we should animate it as a CSS property, but for other elements
-  // (e.g. <rect>) we should animate it as a length attribute.
-  // The easiest way to test for an outer SVG element, is to see if it is an
-  // SVG-namespace element mapping its width/height attribute to style.
-  //
-  // If we have animation of 'width' or 'height' on an SVG element that is
-  // NOT mapping that attributes to style then it must not be an outermost SVG
-  // element so we should return eCSSProperty_UNKNOWN to indicate that we
-  // should animate as an attribute instead.
+  // in SVG namespace (e.g. <rect>) we should animate it as a length attribute.
   if ((mKey.mAttributeName == nsGkAtoms::width ||
        mKey.mAttributeName == nsGkAtoms::height) &&
-      mKey.mElement->GetNameSpaceID() == kNameSpaceID_SVG &&
-      !mKey.mElement->IsAttributeMapped(mKey.mAttributeName)) {
-    return eCSSProperty_UNKNOWN;
+      mKey.mElement->GetNameSpaceID() == kNameSpaceID_SVG) {
+    // Not an <svg> element.
+    if (!mKey.mElement->IsSVGElement(nsGkAtoms::svg)) {
+      return eCSSProperty_UNKNOWN;
+    }
+
+    // An inner <svg> element
+    if (static_cast<dom::SVGSVGElement const&>(*mKey.mElement).IsInner()) {
+      return eCSSProperty_UNKNOWN;
+    }
+
+    // Indeed an outer <svg> element, fall through.
   }
 
   return propID;
 }
 
 bool SMILCompositor::MightNeedBaseStyle() const {
   if (GetCSSPropertyToAnimate() == eCSSProperty_UNKNOWN) {
     return false;
--- a/dom/svg/SVGCircleElement.cpp
+++ b/dom/svg/SVGCircleElement.cpp
@@ -1,19 +1,21 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#include "ComputedStyle.h"
 #include "mozilla/dom/SVGCircleElement.h"
 #include "mozilla/gfx/2D.h"
 #include "nsGkAtoms.h"
 #include "mozilla/dom/SVGCircleElementBinding.h"
 #include "mozilla/dom/SVGLengthBinding.h"
+#include "SVGGeometryProperty.h"
 
 NS_IMPL_NS_NEW_SVG_ELEMENT(Circle)
 
 using namespace mozilla::gfx;
 
 namespace mozilla {
 namespace dom {
 
@@ -32,16 +34,23 @@ SVGElement::LengthInfo SVGCircleElement:
 
 //----------------------------------------------------------------------
 // Implementation
 
 SVGCircleElement::SVGCircleElement(
     already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
     : SVGCircleElementBase(std::move(aNodeInfo)) {}
 
+bool SVGCircleElement::IsAttributeMapped(const nsAtom* aAttribute) const {
+  return IsInLengthInfo(aAttribute, sLengthInfo) ||
+         SVGCircleElementBase::IsAttributeMapped(aAttribute);
+}
+
+namespace SVGT = SVGGeometryProperty::Tags;
+
 //----------------------------------------------------------------------
 // nsINode methods
 
 NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGCircleElement)
 
 //----------------------------------------------------------------------
 
 already_AddRefed<DOMSVGAnimatedLength> SVGCircleElement::Cx() {
@@ -56,33 +65,39 @@ already_AddRefed<DOMSVGAnimatedLength> S
   return mLengthAttributes[ATTR_R].ToDOMAnimatedLength(this);
 }
 
 //----------------------------------------------------------------------
 // SVGElement methods
 
 /* virtual */
 bool SVGCircleElement::HasValidDimensions() const {
-  return mLengthAttributes[ATTR_R].IsExplicitlySet() &&
-         mLengthAttributes[ATTR_R].GetAnimValInSpecifiedUnits() > 0;
+  float r;
+
+  MOZ_ASSERT(GetPrimaryFrame());
+  SVGGeometryProperty::ResolveAll<SVGT::R>(this, &r);
+  return r > 0;
 }
 
 SVGElement::LengthAttributesInfo SVGCircleElement::GetLengthInfo() {
   return LengthAttributesInfo(mLengthAttributes, sLengthInfo,
                               ArrayLength(sLengthInfo));
 }
 
 //----------------------------------------------------------------------
 // SVGGeometryElement methods
 
 bool SVGCircleElement::GetGeometryBounds(
     Rect* aBounds, const StrokeOptions& aStrokeOptions,
     const Matrix& aToBoundsSpace, const Matrix* aToNonScalingStrokeSpace) {
   float x, y, r;
-  GetAnimatedLengthValues(&x, &y, &r, nullptr);
+
+  MOZ_ASSERT(GetPrimaryFrame());
+  SVGGeometryProperty::ResolveAll<SVGT::Cx, SVGT::Cy, SVGT::R>(this, &x, &y,
+                                                               &r);
 
   if (r <= 0.f) {
     // Rendering of the element is disabled
     *aBounds = Rect(aToBoundsSpace.TransformPoint(Point(x, y)), Size());
     return true;
   }
 
   if (aToBoundsSpace.IsRectilinear()) {
@@ -107,21 +122,48 @@ bool SVGCircleElement::GetGeometryBounds
     return true;
   }
 
   return false;
 }
 
 already_AddRefed<Path> SVGCircleElement::BuildPath(PathBuilder* aBuilder) {
   float x, y, r;
-  GetAnimatedLengthValues(&x, &y, &r, nullptr);
+  MOZ_ASSERT(GetPrimaryFrame());
+  SVGGeometryProperty::ResolveAll<SVGT::Cx, SVGT::Cy, SVGT::R>(this, &x, &y,
+                                                               &r);
 
   if (r <= 0.0f) {
     return nullptr;
   }
 
   aBuilder->Arc(Point(x, y), r, 0, Float(2 * M_PI));
 
   return aBuilder->Finish();
 }
 
+bool SVGCircleElement::IsLengthChangedViaCSS(const ComputedStyle& aNewStyle,
+                                             const ComputedStyle& aOldStyle) {
+  auto *newSVGReset = aNewStyle.StyleSVGReset(),
+       *oldSVGReset = aOldStyle.StyleSVGReset();
+
+  return newSVGReset->mCx != oldSVGReset->mCx ||
+         newSVGReset->mCy != oldSVGReset->mCy ||
+         newSVGReset->mR != oldSVGReset->mR;
+}
+
+nsCSSPropertyID SVGCircleElement::GetCSSPropertyIdForAttrEnum(
+    uint8_t aAttrEnum) {
+  switch (aAttrEnum) {
+    case ATTR_CX:
+      return eCSSProperty_cx;
+    case ATTR_CY:
+      return eCSSProperty_cy;
+    case ATTR_R:
+      return eCSSProperty_r;
+    default:
+      MOZ_ASSERT_UNREACHABLE("Unknown attr enum");
+      return eCSSProperty_UNKNOWN;
+  }
+}
+
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/svg/SVGCircleElement.h
+++ b/dom/svg/SVGCircleElement.h
@@ -2,50 +2,59 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_SVGCircleElement_h
 #define mozilla_dom_SVGCircleElement_h
 
+#include "nsCSSPropertyID.h"
 #include "SVGGeometryElement.h"
 #include "SVGAnimatedLength.h"
 
 nsresult NS_NewSVGCircleElement(
     nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
 
 namespace mozilla {
+class ComputedStyle;
+
 namespace dom {
 
 typedef SVGGeometryElement SVGCircleElementBase;
 
 class SVGCircleElement final : public SVGCircleElementBase {
  protected:
   explicit SVGCircleElement(
       already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
   virtual JSObject* WrapNode(JSContext* cx,
                              JS::Handle<JSObject*> aGivenProto) override;
   friend nsresult(::NS_NewSVGCircleElement(
       nsIContent** aResult,
       already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo));
 
  public:
+  NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override;
+
   // nsSVGSVGElement methods:
   virtual bool HasValidDimensions() const override;
 
   // SVGGeometryElement methods:
   virtual bool GetGeometryBounds(
       Rect* aBounds, const StrokeOptions& aStrokeOptions,
       const Matrix& aToBoundsSpace,
       const Matrix* aToNonScalingStrokeSpace = nullptr) override;
   virtual already_AddRefed<Path> BuildPath(PathBuilder* aBuilder) override;
 
   virtual nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override;
 
+  static bool IsLengthChangedViaCSS(const ComputedStyle& aNewStyle,
+                                    const ComputedStyle& aOldStyle);
+  static nsCSSPropertyID GetCSSPropertyIdForAttrEnum(uint8_t aAttrEnum);
+
   // WebIDL
   already_AddRefed<DOMSVGAnimatedLength> Cx();
   already_AddRefed<DOMSVGAnimatedLength> Cy();
   already_AddRefed<DOMSVGAnimatedLength> R();
 
  protected:
   virtual LengthAttributesInfo GetLengthInfo() override;
 
--- a/dom/svg/SVGElement.cpp
+++ b/dom/svg/SVGElement.cpp
@@ -25,16 +25,17 @@
 #include "mozilla/SVGContentUtils.h"
 #include "mozilla/Unused.h"
 
 #include "DOMSVGAnimatedEnumeration.h"
 #include "mozAutoDocUpdate.h"
 #include "nsAttrValueOrString.h"
 #include "nsCSSProps.h"
 #include "nsContentUtils.h"
+#include "nsDOMCSSAttrDeclaration.h"
 #include "nsICSSDeclaration.h"
 #include "nsIContentInlines.h"
 #include "mozilla/dom/Document.h"
 #include "nsError.h"
 #include "nsGkAtoms.h"
 #include "nsIFrame.h"
 #include "nsQueryObject.h"
 #include "nsLayoutUtils.h"
@@ -48,16 +49,17 @@
 #include "SVGAnimatedInteger.h"
 #include "SVGAnimatedIntegerPair.h"
 #include "SVGAnimatedLength.h"
 #include "SVGAnimatedNumber.h"
 #include "SVGAnimatedNumberPair.h"
 #include "SVGAnimatedOrient.h"
 #include "SVGAnimatedString.h"
 #include "SVGAnimatedViewBox.h"
+#include "SVGGeometryProperty.h"
 #include "SVGMotionSMILAttr.h"
 #include <stdarg.h>
 
 // This is needed to ensure correct handling of calls to the
 // vararg-list methods in this file:
 //   SVGElement::GetAnimated{Length,Number,Integer}Values
 // See bug 547964 for details:
 static_assert(sizeof(void*) == sizeof(nullptr),
@@ -1015,31 +1017,69 @@ SVGSVGElement* SVGElement::GetOwnerSVGEl
 SVGElement* SVGElement::GetViewportElement() {
   return SVGContentUtils::GetNearestViewportElement(this);
 }
 
 already_AddRefed<DOMSVGAnimatedString> SVGElement::ClassName() {
   return mClassAttribute.ToDOMAnimatedString(this);
 }
 
+/* static */
+bool SVGElement::UpdateDeclarationBlockFromLength(
+    DeclarationBlock& aBlock, nsCSSPropertyID aPropId,
+    const SVGAnimatedLength& aLength, ValToUse aValToUse) {
+  aBlock.AssertMutable();
+
+  float value;
+  if (aValToUse == ValToUse::Anim) {
+    value = aLength.GetAnimValInSpecifiedUnits();
+  } else {
+    MOZ_ASSERT(aValToUse == ValToUse::Base);
+    value = aLength.GetBaseValInSpecifiedUnits();
+  }
+
+  // SVG parser doesn't check non-negativity of some parsed value,
+  // we should not pass those to CSS side.
+  if (value < 0 &&
+      SVGGeometryProperty::IsNonNegativeGeometryProperty(aPropId)) {
+    return false;
+  }
+
+  nsCSSUnit cssUnit = SVGGeometryProperty::SpecifiedUnitTypeToCSSUnit(
+      aLength.GetSpecifiedUnitType());
+
+  if (cssUnit == eCSSUnit_Percent) {
+    Servo_DeclarationBlock_SetPercentValue(aBlock.Raw(), aPropId,
+                                           value / 100.f);
+  } else {
+    Servo_DeclarationBlock_SetLengthValue(aBlock.Raw(), aPropId, value,
+                                          cssUnit);
+  }
+
+  return true;
+}
+
 //------------------------------------------------------------------------
 // Helper class: MappedAttrParser, for parsing values of mapped attributes
 
 namespace {
 
 class MOZ_STACK_CLASS MappedAttrParser {
  public:
   MappedAttrParser(css::Loader* aLoader, nsIURI* aDocURI,
                    already_AddRefed<nsIURI> aBaseURI, SVGElement* aElement);
   ~MappedAttrParser();
 
   // Parses a mapped attribute value.
   void ParseMappedAttrValue(nsAtom* aMappedAttrName,
                             const nsAString& aMappedAttrValue);
 
+  void TellStyleAlreadyParsedResult(nsAtom const* aAtom,
+                                    SVGAnimatedLength const& aLength);
+
   // If we've parsed any values for mapped attributes, this method returns the
   // already_AddRefed css::Declaration that incorporates the parsed
   // values. Otherwise, this method returns null.
   already_AddRefed<DeclarationBlock> GetDeclarationBlock();
 
  private:
   // MEMBER DATA
   // -----------
@@ -1117,16 +1157,28 @@ void MappedAttrParser::ParseMappedAttrVa
   // nsCSSParser doesn't know about 'lang', so we need to handle it specially.
   if (aMappedAttrName == nsGkAtoms::lang) {
     propertyID = eCSSProperty__x_lang;
     RefPtr<nsAtom> atom = NS_Atomize(aMappedAttrValue);
     Servo_DeclarationBlock_SetIdentStringValue(mDecl->Raw(), propertyID, atom);
   }
 }
 
+void MappedAttrParser::TellStyleAlreadyParsedResult(
+    nsAtom const* aAtom, SVGAnimatedLength const& aLength) {
+  if (!mDecl) {
+    mDecl = new DeclarationBlock();
+  }
+  nsCSSPropertyID propertyID =
+      nsCSSProps::LookupProperty(nsDependentAtomString(aAtom));
+
+  SVGElement::UpdateDeclarationBlockFromLength(*mDecl, propertyID, aLength,
+                                               SVGElement::ValToUse::Base);
+}
+
 already_AddRefed<DeclarationBlock> MappedAttrParser::GetDeclarationBlock() {
   return mDecl.forget();
 }
 
 }  // namespace
 
 //----------------------------------------------------------------------
 // Implementation Helpers:
@@ -1140,16 +1192,19 @@ void SVGElement::UpdateContentDeclaratio
     // nothing to do
     return;
   }
 
   Document* doc = OwnerDoc();
   MappedAttrParser mappedAttrParser(doc->CSSLoader(), doc->GetDocumentURI(),
                                     GetBaseURI(), this);
 
+  bool lengthAffectsStyle =
+      SVGGeometryProperty::ElementMapsLengthsToStyle(this);
+
   for (uint32_t i = 0; i < attrCount; ++i) {
     const nsAttrName* attrName = mAttrs.AttrNameAt(i);
     if (!attrName->IsAtom() || !IsAttributeMapped(attrName->Atom())) continue;
 
     if (attrName->NamespaceID() != kNameSpaceID_None &&
         !attrName->Equals(nsGkAtoms::lang, kNameSpaceID_XML)) {
       continue;
     }
@@ -1172,16 +1227,30 @@ void SVGElement::UpdateContentDeclaratio
         continue;
       }
       if (attrName->Atom() == nsGkAtoms::height &&
           !GetAnimatedLength(nsGkAtoms::height)->HasBaseVal()) {
         continue;
       }
     }
 
+    if (lengthAffectsStyle) {
+      auto const* length = GetAnimatedLength(attrName->Atom());
+
+      if (length && length->HasBaseVal()) {
+        // This is an element with geometry property set via SVG attribute,
+        // and the attribute is already successfully parsed. We want to go
+        // through the optimized path to tell the style system the result
+        // directly, rather than let it parse the same thing again.
+        mappedAttrParser.TellStyleAlreadyParsedResult(attrName->Atom(),
+                                                      *length);
+        continue;
+      }
+    }
+
     nsAutoString value;
     mAttrs.AttrAt(i)->ToString(value);
     mappedAttrParser.ParseMappedAttrValue(attrName->Atom(), value);
   }
   mContentDeclarationBlock = mappedAttrParser.GetDeclarationBlock();
 }
 
 const DeclarationBlock* SVGElement::GetContentDeclarationBlock() const {
@@ -1386,16 +1455,25 @@ void SVGElement::DidChangeLength(uint8_t
 
   nsAttrValue newValue;
   newValue.SetTo(info.mLengths[aAttrEnum], nullptr);
 
   DidChangeValue(info.mLengthInfo[aAttrEnum].mName, aEmptyOrOldValue, newValue);
 }
 
 void SVGElement::DidAnimateLength(uint8_t aAttrEnum) {
+  if (SVGGeometryProperty::ElementMapsLengthsToStyle(this)) {
+    nsCSSPropertyID propId =
+        SVGGeometryProperty::AttrEnumToCSSPropId(this, aAttrEnum);
+
+    SMILOverrideStyle()->SetSMILValue(propId,
+                                      GetLengthInfo().mLengths[aAttrEnum]);
+    return;
+  }
+
   ClearAnyCachedPath();
 
   nsIFrame* frame = GetPrimaryFrame();
 
   if (frame) {
     LengthAttributesInfo info = GetLengthInfo();
     frame->AttributeChanged(kNameSpaceID_None,
                             info.mLengthInfo[aAttrEnum].mName,
@@ -1406,17 +1484,16 @@ void SVGElement::DidAnimateLength(uint8_
 SVGAnimatedLength* SVGElement::GetAnimatedLength(const nsAtom* aAttrName) {
   LengthAttributesInfo lengthInfo = GetLengthInfo();
 
   for (uint32_t i = 0; i < lengthInfo.mLengthCount; i++) {
     if (aAttrName == lengthInfo.mLengthInfo[i].mName) {
       return &lengthInfo.mLengths[i];
     }
   }
-  MOZ_ASSERT(false, "no matching length found");
   return nullptr;
 }
 
 void SVGElement::GetAnimatedLengthValues(float* aFirst, ...) {
   LengthAttributesInfo info = GetLengthInfo();
 
   NS_ASSERTION(info.mLengthCount > 0,
                "GetAnimatedLengthValues on element with no length attribs");
--- a/dom/svg/SVGElement.h
+++ b/dom/svg/SVGElement.h
@@ -160,16 +160,22 @@ class SVGElement : public SVGElementBase
     return GetStringInfo().mStringInfo[aAttrEnum].mIsAnimatable;
   }
   bool NumberAttrAllowsPercentage(uint8_t aAttrEnum) {
     return GetNumberInfo().mNumberInfo[aAttrEnum].mPercentagesAllowed;
   }
   virtual bool HasValidDimensions() const { return true; }
   void SetLength(nsAtom* aName, const SVGAnimatedLength& aLength);
 
+  enum class ValToUse { Base, Anim };
+  static bool UpdateDeclarationBlockFromLength(DeclarationBlock& aBlock,
+                                               nsCSSPropertyID aPropId,
+                                               const SVGAnimatedLength& aLength,
+                                               ValToUse aValToUse);
+
   nsAttrValue WillChangeLength(uint8_t aAttrEnum);
   nsAttrValue WillChangeNumberPair(uint8_t aAttrEnum);
   nsAttrValue WillChangeIntegerPair(uint8_t aAttrEnum);
   nsAttrValue WillChangeOrient();
   nsAttrValue WillChangeViewBox();
   nsAttrValue WillChangePreserveAspectRatio();
   nsAttrValue WillChangeNumberList(uint8_t aAttrEnum);
   nsAttrValue WillChangeLengthList(uint8_t aAttrEnum);
--- a/dom/svg/SVGEllipseElement.cpp
+++ b/dom/svg/SVGEllipseElement.cpp
@@ -1,20 +1,22 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#include "ComputedStyle.h"
 #include "mozilla/dom/SVGEllipseElement.h"
 #include "mozilla/dom/SVGEllipseElementBinding.h"
 #include "mozilla/dom/SVGLengthBinding.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/PathHelpers.h"
 #include "mozilla/RefPtr.h"
+#include "SVGGeometryProperty.h"
 
 NS_IMPL_NS_NEW_SVG_ELEMENT(Ellipse)
 
 using namespace mozilla::gfx;
 
 namespace mozilla {
 namespace dom {
 
@@ -36,16 +38,23 @@ SVGElement::LengthInfo SVGEllipseElement
 
 //----------------------------------------------------------------------
 // Implementation
 
 SVGEllipseElement::SVGEllipseElement(
     already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
     : SVGEllipseElementBase(std::move(aNodeInfo)) {}
 
+bool SVGEllipseElement::IsAttributeMapped(const nsAtom* aAttribute) const {
+  return IsInLengthInfo(aAttribute, sLengthInfo) ||
+         SVGEllipseElementBase::IsAttributeMapped(aAttribute);
+}
+
+namespace SVGT = SVGGeometryProperty::Tags;
+
 //----------------------------------------------------------------------
 // nsINode methods
 
 NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGEllipseElement)
 
 //----------------------------------------------------------------------
 // nsIDOMSVGEllipseElement methods
 
@@ -65,35 +74,40 @@ already_AddRefed<DOMSVGAnimatedLength> S
   return mLengthAttributes[RY].ToDOMAnimatedLength(this);
 }
 
 //----------------------------------------------------------------------
 // SVGElement methods
 
 /* virtual */
 bool SVGEllipseElement::HasValidDimensions() const {
-  return mLengthAttributes[RX].IsExplicitlySet() &&
-         mLengthAttributes[RX].GetAnimValInSpecifiedUnits() > 0 &&
-         mLengthAttributes[RY].IsExplicitlySet() &&
-         mLengthAttributes[RY].GetAnimValInSpecifiedUnits() > 0;
+  float rx, ry;
+
+  MOZ_ASSERT(GetPrimaryFrame());
+  SVGGeometryProperty::ResolveAll<SVGT::Rx, SVGT::Ry>(this, &rx, &ry);
+
+  return rx > 0 && ry > 0;
 }
 
 SVGElement::LengthAttributesInfo SVGEllipseElement::GetLengthInfo() {
   return LengthAttributesInfo(mLengthAttributes, sLengthInfo,
                               ArrayLength(sLengthInfo));
 }
 
 //----------------------------------------------------------------------
 // SVGGeometryElement methods
 
 bool SVGEllipseElement::GetGeometryBounds(
     Rect* aBounds, const StrokeOptions& aStrokeOptions,
     const Matrix& aToBoundsSpace, const Matrix* aToNonScalingStrokeSpace) {
   float x, y, rx, ry;
-  GetAnimatedLengthValues(&x, &y, &rx, &ry, nullptr);
+
+  MOZ_ASSERT(GetPrimaryFrame());
+  SVGGeometryProperty::ResolveAll<SVGT::Cx, SVGT::Cy, SVGT::Rx, SVGT::Ry>(
+      this, &x, &y, &rx, &ry);
 
   if (rx <= 0.f || ry <= 0.f) {
     // Rendering of the element is disabled
     *aBounds = Rect(aToBoundsSpace.TransformPoint(Point(x, y)), Size());
     return true;
   }
 
   if (aToBoundsSpace.IsRectilinear()) {
@@ -119,21 +133,52 @@ bool SVGEllipseElement::GetGeometryBound
     return true;
   }
 
   return false;
 }
 
 already_AddRefed<Path> SVGEllipseElement::BuildPath(PathBuilder* aBuilder) {
   float x, y, rx, ry;
-  GetAnimatedLengthValues(&x, &y, &rx, &ry, nullptr);
+
+  MOZ_ASSERT(GetPrimaryFrame());
+  SVGGeometryProperty::ResolveAll<SVGT::Cx, SVGT::Cy, SVGT::Rx, SVGT::Ry>(
+      this, &x, &y, &rx, &ry);
 
   if (rx <= 0.0f || ry <= 0.0f) {
     return nullptr;
   }
 
   EllipseToBezier(aBuilder, Point(x, y), Size(rx, ry));
 
   return aBuilder->Finish();
 }
 
+bool SVGEllipseElement::IsLengthChangedViaCSS(const ComputedStyle& aNewStyle,
+                                              const ComputedStyle& aOldStyle) {
+  auto *newSVGReset = aNewStyle.StyleSVGReset(),
+       *oldSVGReset = aOldStyle.StyleSVGReset();
+
+  return newSVGReset->mCx != oldSVGReset->mCx ||
+         newSVGReset->mCy != oldSVGReset->mCy ||
+         newSVGReset->mRx != oldSVGReset->mRx ||
+         newSVGReset->mRy != oldSVGReset->mRy;
+}
+
+nsCSSPropertyID SVGEllipseElement::GetCSSPropertyIdForAttrEnum(
+    uint8_t aAttrEnum) {
+  switch (aAttrEnum) {
+    case CX:
+      return eCSSProperty_cx;
+    case CY:
+      return eCSSProperty_cy;
+    case RX:
+      return eCSSProperty_rx;
+    case RY:
+      return eCSSProperty_ry;
+    default:
+      MOZ_ASSERT_UNREACHABLE("Unknown attr enum");
+      return eCSSProperty_UNKNOWN;
+  }
+}
+
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/svg/SVGEllipseElement.h
+++ b/dom/svg/SVGEllipseElement.h
@@ -2,50 +2,59 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_SVGEllipseElement_h
 #define mozilla_dom_SVGEllipseElement_h
 
+#include "nsCSSPropertyID.h"
 #include "SVGAnimatedLength.h"
 #include "SVGGeometryElement.h"
 
 nsresult NS_NewSVGEllipseElement(
     nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
 
 namespace mozilla {
+class ComputedStyle;
+
 namespace dom {
 
 typedef SVGGeometryElement SVGEllipseElementBase;
 
 class SVGEllipseElement final : public SVGEllipseElementBase {
  protected:
   explicit SVGEllipseElement(
       already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
   virtual JSObject* WrapNode(JSContext* cx,
                              JS::Handle<JSObject*> aGivenProto) override;
   friend nsresult(::NS_NewSVGEllipseElement(
       nsIContent** aResult,
       already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo));
 
  public:
+  NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override;
+
   // nsSVGSVGElement methods:
   virtual bool HasValidDimensions() const override;
 
   // SVGGeometryElement methods:
   virtual bool GetGeometryBounds(
       Rect* aBounds, const StrokeOptions& aStrokeOptions,
       const Matrix& aToBoundsSpace,
       const Matrix* aToNonScalingStrokeSpace = nullptr) override;
   virtual already_AddRefed<Path> BuildPath(PathBuilder* aBuilder) override;
 
   virtual nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override;
 
+  static bool IsLengthChangedViaCSS(const ComputedStyle& aNewStyle,
+                                    const ComputedStyle& aOldStyle);
+  static nsCSSPropertyID GetCSSPropertyIdForAttrEnum(uint8_t aAttrEnum);
+
   // WebIDL
   already_AddRefed<DOMSVGAnimatedLength> Cx();
   already_AddRefed<DOMSVGAnimatedLength> Cy();
   already_AddRefed<DOMSVGAnimatedLength> Rx();
   already_AddRefed<DOMSVGAnimatedLength> Ry();
 
  protected:
   virtual LengthAttributesInfo GetLengthInfo() override;
--- a/dom/svg/SVGForeignObjectElement.cpp
+++ b/dom/svg/SVGForeignObjectElement.cpp
@@ -6,16 +6,17 @@
 
 #include "mozilla/dom/SVGForeignObjectElement.h"
 
 #include "mozilla/AlreadyAddRefed.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/dom/SVGDocument.h"
 #include "mozilla/dom/SVGForeignObjectElementBinding.h"
 #include "mozilla/dom/SVGLengthBinding.h"
+#include "SVGGeometryProperty.h"
 
 NS_IMPL_NS_NEW_SVG_ELEMENT(ForeignObject)
 
 namespace mozilla {
 namespace dom {
 
 JSObject* SVGForeignObjectElement::WrapNode(JSContext* aCx,
                                             JS::Handle<JSObject*> aGivenProto) {
@@ -35,16 +36,18 @@ SVGElement::LengthInfo SVGForeignObjectE
 
 //----------------------------------------------------------------------
 // Implementation
 
 SVGForeignObjectElement::SVGForeignObjectElement(
     already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
     : SVGGraphicsElement(std::move(aNodeInfo)) {}
 
+namespace SVGT = SVGGeometryProperty::Tags;
+
 //----------------------------------------------------------------------
 // nsINode methods
 
 NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGForeignObjectElement)
 
 //----------------------------------------------------------------------
 
 already_AddRefed<DOMSVGAnimatedLength> SVGForeignObjectElement::X() {
@@ -72,54 +75,82 @@ gfxMatrix SVGForeignObjectElement::Prepe
   // 'transform' attribute:
   gfxMatrix fromUserSpace =
       SVGGraphicsElement::PrependLocalTransformsTo(aMatrix, aWhich);
   if (aWhich == eUserSpaceToParent) {
     return fromUserSpace;
   }
   // our 'x' and 'y' attributes:
   float x, y;
-  const_cast<SVGForeignObjectElement*>(this)->GetAnimatedLengthValues(&x, &y,
-                                                                      nullptr);
+
+  if (GetPrimaryFrame()) {
+    SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y>(this, &x, &y);
+  } else {
+    // This function might be called for element in display:none subtree
+    // (e.g. getScreenCTM), we fall back to use SVG attributes.
+    const_cast<SVGForeignObjectElement*>(this)->GetAnimatedLengthValues(
+        &x, &y, nullptr);
+  }
+
   gfxMatrix toUserSpace = gfxMatrix::Translation(x, y);
   if (aWhich == eChildToUserSpace) {
     return toUserSpace * aMatrix;
   }
   MOZ_ASSERT(aWhich == eAllTransforms, "Unknown TransformTypes");
   return toUserSpace * fromUserSpace;
 }
 
 /* virtual */
 bool SVGForeignObjectElement::HasValidDimensions() const {
-  return mLengthAttributes[ATTR_WIDTH].IsExplicitlySet() &&
-         mLengthAttributes[ATTR_WIDTH].GetAnimValInSpecifiedUnits() > 0 &&
-         mLengthAttributes[ATTR_HEIGHT].IsExplicitlySet() &&
-         mLengthAttributes[ATTR_HEIGHT].GetAnimValInSpecifiedUnits() > 0;
+  float width, height;
+
+  MOZ_ASSERT(GetPrimaryFrame());
+  SVGGeometryProperty::ResolveAll<SVGT::Width, SVGT::Height>(
+      const_cast<SVGForeignObjectElement*>(this), &width, &height);
+  return width > 0 && height > 0;
 }
 
 //----------------------------------------------------------------------
 // nsIContent methods
 
 NS_IMETHODIMP_(bool)
 SVGForeignObjectElement::IsAttributeMapped(const nsAtom* name) const {
   static const MappedAttributeEntry* const map[] = {sFEFloodMap,
                                                     sFiltersMap,
                                                     sFontSpecificationMap,
                                                     sGradientStopMap,
                                                     sLightingEffectsMap,
                                                     sMarkersMap,
                                                     sTextContentElementsMap,
                                                     sViewportsMap};
 
-  return FindAttributeDependence(name, map) ||
+  return IsInLengthInfo(name, sLengthInfo) ||
+         FindAttributeDependence(name, map) ||
          SVGGraphicsElement::IsAttributeMapped(name);
 }
 
 //----------------------------------------------------------------------
 // SVGElement methods
 
 SVGElement::LengthAttributesInfo SVGForeignObjectElement::GetLengthInfo() {
   return LengthAttributesInfo(mLengthAttributes, sLengthInfo,
                               ArrayLength(sLengthInfo));
 }
 
+nsCSSPropertyID SVGForeignObjectElement::GetCSSPropertyIdForAttrEnum(
+    uint8_t aAttrEnum) {
+  switch (aAttrEnum) {
+    case ATTR_X:
+      return eCSSProperty_x;
+    case ATTR_Y:
+      return eCSSProperty_y;
+    case ATTR_WIDTH:
+      return eCSSProperty_width;
+    case ATTR_HEIGHT:
+      return eCSSProperty_height;
+    default:
+      MOZ_ASSERT_UNREACHABLE("Unknown attr enum");
+      return eCSSProperty_UNKNOWN;
+  }
+}
+
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/svg/SVGForeignObjectElement.h
+++ b/dom/svg/SVGForeignObjectElement.h
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_SVGForeignObjectElement_h
 #define mozilla_dom_SVGForeignObjectElement_h
 
 #include "mozilla/dom/SVGGraphicsElement.h"
+#include "nsCSSPropertyID.h"
 #include "SVGAnimatedLength.h"
 
 nsresult NS_NewSVGForeignObjectElement(
     nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
 
 class nsSVGForeignObjectFrame;
 
 namespace mozilla {
@@ -37,16 +38,18 @@ class SVGForeignObjectElement final : pu
       SVGTransformTypes aWhich = eAllTransforms) const override;
   virtual bool HasValidDimensions() const override;
 
   // nsIContent interface
   NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* name) const override;
 
   virtual nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override;
 
+  static nsCSSPropertyID GetCSSPropertyIdForAttrEnum(uint8_t aAttrEnum);
+
   // WebIDL
   already_AddRefed<DOMSVGAnimatedLength> X();
   already_AddRefed<DOMSVGAnimatedLength> Y();
   already_AddRefed<DOMSVGAnimatedLength> Width();
   already_AddRefed<DOMSVGAnimatedLength> Height();
 
  protected:
   virtual LengthAttributesInfo GetLengthInfo() override;
--- a/dom/svg/SVGGeometryElement.cpp
+++ b/dom/svg/SVGGeometryElement.cpp
@@ -5,18 +5,21 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "SVGGeometryElement.h"
 
 #include "DOMSVGPoint.h"
 #include "gfxPlatform.h"
 #include "nsCOMPtr.h"
 #include "nsComputedDOMStyle.h"
+#include "nsSVGUtils.h"
 #include "SVGAnimatedLength.h"
-#include "nsSVGUtils.h"
+#include "SVGCircleElement.h"
+#include "SVGEllipseElement.h"
+#include "SVGRectElement.h"
 #include "mozilla/dom/SVGLengthBinding.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/SVGContentUtils.h"
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 using namespace mozilla::dom;
@@ -106,16 +109,32 @@ already_AddRefed<Path> SVGGeometryElemen
 
 already_AddRefed<Path> SVGGeometryElement::GetOrBuildPathForMeasuring() {
   RefPtr<DrawTarget> drawTarget =
       gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
   FillRule fillRule = mCachedPath ? mCachedPath->GetFillRule() : GetFillRule();
   return GetOrBuildPath(drawTarget, fillRule);
 }
 
+bool SVGGeometryElement::IsGeometryChangedViaCSS(
+    ComputedStyle const& aNewStyle, ComputedStyle const& aOldStyle) const {
+  if (IsSVGElement(nsGkAtoms::rect)) {
+    return SVGRectElement::IsLengthChangedViaCSS(aNewStyle, aOldStyle);
+  }
+
+  if (IsSVGElement(nsGkAtoms::circle)) {
+    return SVGCircleElement::IsLengthChangedViaCSS(aNewStyle, aOldStyle);
+  }
+
+  if (IsSVGElement(nsGkAtoms::ellipse)) {
+    return SVGEllipseElement::IsLengthChangedViaCSS(aNewStyle, aOldStyle);
+  }
+  return false;
+}
+
 FillRule SVGGeometryElement::GetFillRule() {
   FillRule fillRule =
       FillRule::FILL_WINDING;  // Equivalent to StyleFillRule::Nonzero
 
   RefPtr<ComputedStyle> computedStyle =
       nsComputedDOMStyle::GetComputedStyleNoFlush(this, nullptr);
 
   if (computedStyle) {
--- a/dom/svg/SVGGeometryElement.h
+++ b/dom/svg/SVGGeometryElement.h
@@ -187,16 +187,23 @@ class SVGGeometryElement : public SVGGeo
    * In principle these inserted lines could interfere with path measurement,
    * so we keep callers that are looking to do measurement separate in case we
    * run into problems with the inserted lines negatively affecting measuring
    * for content.
    */
   virtual already_AddRefed<Path> GetOrBuildPathForMeasuring();
 
   /**
+   * Return |true| if some geometry properties (|x|, |y|, etc) are changed
+   * because of CSS change.
+   */
+  bool IsGeometryChangedViaCSS(ComputedStyle const& aNewStyle,
+                               ComputedStyle const& aOldStyle) const;
+
+  /**
    * Returns the current computed value of the CSS property 'fill-rule' for
    * this element.
    */
   FillRule GetFillRule();
 
   enum PathLengthScaleForType { eForTextPath, eForStroking };
 
   /**
new file mode 100644
--- /dev/null
+++ b/dom/svg/SVGGeometryProperty.cpp
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SVGGeometryProperty.h"
+#include "SVGCircleElement.h"
+#include "SVGEllipseElement.h"
+#include "SVGForeignObjectElement.h"
+#include "SVGRectElement.h"
+
+namespace mozilla {
+namespace dom {
+namespace SVGGeometryProperty {
+
+nsCSSUnit SpecifiedUnitTypeToCSSUnit(uint8_t aSpecifiedUnit) {
+  switch (aSpecifiedUnit) {
+    case SVGLength_Binding::SVG_LENGTHTYPE_NUMBER:
+    case SVGLength_Binding::SVG_LENGTHTYPE_PX:
+      return nsCSSUnit::eCSSUnit_Pixel;
+
+    case SVGLength_Binding::SVG_LENGTHTYPE_MM:
+      return nsCSSUnit::eCSSUnit_Millimeter;
+
+    case SVGLength_Binding::SVG_LENGTHTYPE_CM:
+      return nsCSSUnit::eCSSUnit_Centimeter;
+
+    case SVGLength_Binding::SVG_LENGTHTYPE_IN:
+      return nsCSSUnit::eCSSUnit_Inch;
+
+    case SVGLength_Binding::SVG_LENGTHTYPE_PT:
+      return nsCSSUnit::eCSSUnit_Point;
+
+    case SVGLength_Binding::SVG_LENGTHTYPE_PC:
+      return nsCSSUnit::eCSSUnit_Pica;
+
+    case SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE:
+      return nsCSSUnit::eCSSUnit_Percent;
+
+    case SVGLength_Binding::SVG_LENGTHTYPE_EMS:
+      return nsCSSUnit::eCSSUnit_EM;
+
+    case SVGLength_Binding::SVG_LENGTHTYPE_EXS:
+      return nsCSSUnit::eCSSUnit_XHeight;
+
+    default:
+      MOZ_ASSERT_UNREACHABLE("Unknown unit type");
+      return nsCSSUnit::eCSSUnit_Pixel;
+  }
+}
+
+nsCSSPropertyID AttrEnumToCSSPropId(const SVGElement* aElement,
+                                    uint8_t aAttrEnum) {
+  // This is a very trivial function only applied to a few elements,
+  // so we want to avoid making it virtual.
+  if (aElement->IsSVGElement(nsGkAtoms::rect)) {
+    return SVGRectElement::GetCSSPropertyIdForAttrEnum(aAttrEnum);
+  }
+  if (aElement->IsSVGElement(nsGkAtoms::circle)) {
+    return SVGCircleElement::GetCSSPropertyIdForAttrEnum(aAttrEnum);
+  }
+  if (aElement->IsSVGElement(nsGkAtoms::ellipse)) {
+    return SVGEllipseElement::GetCSSPropertyIdForAttrEnum(aAttrEnum);
+  }
+  if (aElement->IsSVGElement(nsGkAtoms::foreignObject)) {
+    return SVGForeignObjectElement::GetCSSPropertyIdForAttrEnum(aAttrEnum);
+  }
+  return eCSSProperty_UNKNOWN;
+}
+
+bool IsNonNegativeGeometryProperty(nsCSSPropertyID aProp) {
+  return aProp == eCSSProperty_r || aProp == eCSSProperty_rx ||
+         aProp == eCSSProperty_ry || aProp == eCSSProperty_width ||
+         aProp == eCSSProperty_height;
+}
+
+bool ElementMapsLengthsToStyle(SVGElement const* aElement) {
+  return aElement->IsSVGElement(nsGkAtoms::rect) ||
+         aElement->IsSVGElement(nsGkAtoms::circle) ||
+         aElement->IsSVGElement(nsGkAtoms::ellipse) ||
+         aElement->IsSVGElement(nsGkAtoms::foreignObject);
+}
+
+}  // namespace SVGGeometryProperty
+}  // namespace dom
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/svg/SVGGeometryProperty.h
@@ -0,0 +1,162 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_SVGGeometryProperty_SVGGeometryProperty_h
+#define mozilla_dom_SVGGeometryProperty_SVGGeometryProperty_h
+
+#include "mozilla/dom/SVGElement.h"
+#include "SVGAnimatedLength.h"
+#include "ComputedStyle.h"
+#include "nsIFrame.h"
+#include <type_traits>
+
+namespace mozilla {
+namespace dom {
+
+namespace SVGGeometryProperty {
+namespace ResolverTypes {
+struct LengthPercentNoAuto {};
+struct LengthPercentRXY {};
+struct LengthPercentWidthHeight {};
+}  // namespace ResolverTypes
+
+namespace Tags {
+
+#define SVGGEOMETRYPROPERTY_GENERATETAG(tagName, resolver, direction, \
+                                        styleStruct)                  \
+  struct tagName {                                                    \
+    using ResolverType = ResolverTypes::resolver;                     \
+    constexpr static auto CtxDirection = SVGContentUtils::direction;  \
+    constexpr static auto Getter = &styleStruct::m##tagName;          \
+  }
+
+SVGGEOMETRYPROPERTY_GENERATETAG(X, LengthPercentNoAuto, X, nsStyleSVGReset);
+SVGGEOMETRYPROPERTY_GENERATETAG(Y, LengthPercentNoAuto, Y, nsStyleSVGReset);
+SVGGEOMETRYPROPERTY_GENERATETAG(Cx, LengthPercentNoAuto, X, nsStyleSVGReset);
+SVGGEOMETRYPROPERTY_GENERATETAG(Cy, LengthPercentNoAuto, Y, nsStyleSVGReset);
+SVGGEOMETRYPROPERTY_GENERATETAG(R, LengthPercentNoAuto, XY, nsStyleSVGReset);
+SVGGEOMETRYPROPERTY_GENERATETAG(Width, LengthPercentWidthHeight, X,
+                                nsStylePosition);
+SVGGEOMETRYPROPERTY_GENERATETAG(Height, LengthPercentWidthHeight, Y,
+                                nsStylePosition);
+
+#undef SVGGEOMETRYPROPERTY_GENERATETAG
+
+struct Ry;
+struct Rx {
+  using ResolverType = ResolverTypes::LengthPercentRXY;
+  constexpr static auto CtxDirection = SVGContentUtils::X;
+  constexpr static auto Getter = &nsStyleSVGReset::mRx;
+  using CounterPart = Ry;
+};
+struct Ry {
+  using ResolverType = ResolverTypes::LengthPercentRXY;
+  constexpr static auto CtxDirection = SVGContentUtils::Y;
+  constexpr static auto Getter = &nsStyleSVGReset::mRy;
+  using CounterPart = Rx;
+};
+
+}  // namespace Tags
+
+namespace details {
+template <class T>
+using AlwaysFloat = float;
+
+using CtxDirectionType = decltype(SVGContentUtils::X);
+
+template <CtxDirectionType CTD>
+float ResolvePureLengthPercentage(SVGElement* aElement,
+                                  const LengthPercentage& aLP) {
+  return aLP.ResolveToCSSPixelsWith(
+      [&] { return CSSCoord{SVGElementMetrics(aElement).GetAxisLength(CTD)}; });
+}
+
+template <class Tag>
+float ResolveImpl(ComputedStyle const& aStyle, SVGElement* aElement,
+                  ResolverTypes::LengthPercentNoAuto) {
+  auto const& value = aStyle.StyleSVGReset()->*Tag::Getter;
+  return ResolvePureLengthPercentage<Tag::CtxDirection>(aElement, value);
+}
+
+template <class Tag>
+float ResolveImpl(ComputedStyle const& aStyle, SVGElement* aElement,
+                  ResolverTypes::LengthPercentWidthHeight) {
+  static_assert(
+      std::is_same<Tag, Tags::Width>{} || std::is_same<Tag, Tags::Height>{},
+      "Wrong tag");
+
+  auto const& value = aStyle.StylePosition()->*Tag::Getter;
+  if (value.IsLengthPercentage()) {
+    return ResolvePureLengthPercentage<Tag::CtxDirection>(
+        aElement, value.AsLengthPercentage());
+  }
+
+  // |auto| and |max-content| etc. are treated as 0.
+  return 0.f;
+}
+
+template <class Tag>
+float ResolveImpl(ComputedStyle const& aStyle, SVGElement* aElement,
+                  ResolverTypes::LengthPercentRXY) {
+  static_assert(std::is_same<Tag, Tags::Rx>{} || std::is_same<Tag, Tags::Ry>{},
+                "Wrong tag");
+
+  auto const& value = aStyle.StyleSVGReset()->*Tag::Getter;
+  if (value.IsLengthPercentage()) {
+    return ResolvePureLengthPercentage<Tag::CtxDirection>(
+        aElement, value.AsLengthPercentage());
+  }
+
+  MOZ_ASSERT(value.IsAuto());
+  using Rother = typename Tag::CounterPart;
+  auto const& valueOther = aStyle.StyleSVGReset()->*Rother::Getter;
+
+  if (valueOther.IsAuto()) {
+    // Per SVG2, |Rx|, |Ry| resolve to 0 if both are |auto|
+    return 0.f;
+  }
+
+  // If |Rx| is auto while |Ry| not, |Rx| gets the value of |Ry|.
+  return ResolvePureLengthPercentage<Rother::CtxDirection>(
+      aElement, valueOther.AsLengthPercentage());
+}
+
+}  // namespace details
+
+template <class Tag>
+float ResolveWith(const ComputedStyle& aStyle, const SVGElement* aElement) {
+  // TODO: There are a lot of utilities lacking const-ness in dom/svg.
+  // We should fix that problem and remove this `const_cast`.
+  return details::ResolveImpl<Tag>(aStyle, const_cast<SVGElement*>(aElement),
+                                   typename Tag::ResolverType{});
+}
+
+// To add support for new properties, or to handle special cases for
+// existing properties, you can add a new tag in |Tags| and |ResolverTypes|
+// namespace, then implement the behavior in |details::ResolveImpl|.
+template <class... Tags>
+bool ResolveAll(const SVGElement* aElement,
+                details::AlwaysFloat<Tags>*... aRes) {
+  if (nsIFrame const* f = aElement->GetPrimaryFrame()) {
+    using dummy = int[];
+    (void)dummy{0, (*aRes = ResolveWith<Tags>(*f->Style(), aElement), 0)...};
+    return true;
+  }
+  return false;
+}
+
+nsCSSUnit SpecifiedUnitTypeToCSSUnit(uint8_t aSpecifiedUnit);
+nsCSSPropertyID AttrEnumToCSSPropId(const SVGElement* aElement,
+                                    uint8_t aAttrEnum);
+
+bool IsNonNegativeGeometryProperty(nsCSSPropertyID aProp);
+bool ElementMapsLengthsToStyle(SVGElement const* aElement);
+
+}  // namespace SVGGeometryProperty
+}  // namespace dom
+}  // namespace mozilla
+
+#endif
--- a/dom/svg/SVGGraphicsElement.h
+++ b/dom/svg/SVGGraphicsElement.h
@@ -27,14 +27,24 @@ class SVGGraphicsElement : public SVGGra
 
   bool IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse) override;
   SVGElement* AsSVGElement() final { return this; }
 
  protected:
   // returns true if focusability has been definitively determined otherwise
   // false
   bool IsSVGFocusable(bool* aIsFocusable, int32_t* aTabIndex);
+
+  template <typename T>
+  bool IsInLengthInfo(const nsAtom* aAttribute, const T& aLengthInfos) const {
+    for (auto const& e : aLengthInfos) {
+      if (e.mName == aAttribute) {
+        return true;
+      }
+    }
+    return false;
+  }
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // mozilla_dom_SVGGraphicsElement_h
--- a/dom/svg/SVGImageElement.cpp
+++ b/dom/svg/SVGImageElement.cpp
@@ -213,17 +213,18 @@ EventStates SVGImageElement::IntrinsicSt
 }
 
 NS_IMETHODIMP_(bool)
 SVGImageElement::IsAttributeMapped(const nsAtom* name) const {
   static const MappedAttributeEntry* const map[] = {
       sViewportsMap,
   };
 
-  return FindAttributeDependence(name, map) ||
+  return IsInLengthInfo(name, sLengthInfo) ||
+         FindAttributeDependence(name, map) ||
          SVGImageElementBase::IsAttributeMapped(name);
 }
 
 //----------------------------------------------------------------------
 // SVGGeometryElement methods
 
 /* For the purposes of the update/invalidation logic pretend to
    be a rectangle. */
--- a/dom/svg/SVGRectElement.cpp
+++ b/dom/svg/SVGRectElement.cpp
@@ -1,22 +1,23 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/SVGRectElement.h"
-#include "nsGkAtoms.h"
 #include "mozilla/dom/SVGLengthBinding.h"
 #include "mozilla/dom/SVGRectElementBinding.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/Matrix.h"
 #include "mozilla/gfx/Rect.h"
 #include "mozilla/gfx/PathHelpers.h"
+#include "nsGkAtoms.h"
+#include "SVGGeometryProperty.h"
 #include <algorithm>
 
 NS_IMPL_NS_NEW_SVG_ELEMENT(Rect)
 
 using namespace mozilla::gfx;
 
 namespace mozilla {
 namespace dom {
@@ -44,16 +45,23 @@ SVGElement::LengthInfo SVGRectElement::s
 
 //----------------------------------------------------------------------
 // Implementation
 
 SVGRectElement::SVGRectElement(
     already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
     : SVGRectElementBase(std::move(aNodeInfo)) {}
 
+bool SVGRectElement::IsAttributeMapped(const nsAtom* aAttribute) const {
+  return IsInLengthInfo(aAttribute, sLengthInfo) ||
+         SVGRectElementBase::IsAttributeMapped(aAttribute);
+}
+
+namespace SVGT = SVGGeometryProperty::Tags;
+
 //----------------------------------------------------------------------
 // nsINode methods
 
 NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGRectElement)
 
 //----------------------------------------------------------------------
 
 already_AddRefed<DOMSVGAnimatedLength> SVGRectElement::X() {
@@ -80,38 +88,44 @@ already_AddRefed<DOMSVGAnimatedLength> S
   return mLengthAttributes[ATTR_RY].ToDOMAnimatedLength(this);
 }
 
 //----------------------------------------------------------------------
 // SVGElement methods
 
 /* virtual */
 bool SVGRectElement::HasValidDimensions() const {
-  return mLengthAttributes[ATTR_WIDTH].IsExplicitlySet() &&
-         mLengthAttributes[ATTR_WIDTH].GetAnimValInSpecifiedUnits() > 0 &&
-         mLengthAttributes[ATTR_HEIGHT].IsExplicitlySet() &&
-         mLengthAttributes[ATTR_HEIGHT].GetAnimValInSpecifiedUnits() > 0;
+  float width, height;
+
+  MOZ_ASSERT(GetPrimaryFrame());
+  SVGGeometryProperty::ResolveAll<SVGT::Width, SVGT::Height>(this, &width,
+                                                             &height);
+
+  return width > 0 && height > 0;
 }
 
 SVGElement::LengthAttributesInfo SVGRectElement::GetLengthInfo() {
   return LengthAttributesInfo(mLengthAttributes, sLengthInfo,
                               ArrayLength(sLengthInfo));
 }
 
 //----------------------------------------------------------------------
 // SVGGeometryElement methods
 
 bool SVGRectElement::GetGeometryBounds(Rect* aBounds,
                                        const StrokeOptions& aStrokeOptions,
                                        const Matrix& aToBoundsSpace,
                                        const Matrix* aToNonScalingStrokeSpace) {
   Rect rect;
   Float rx, ry;
-  GetAnimatedLengthValues(&rect.x, &rect.y, &rect.width, &rect.height, &rx, &ry,
-                          nullptr);
+
+  MOZ_ASSERT(GetPrimaryFrame());
+  SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height,
+                                  SVGT::Rx, SVGT::Ry>(
+      this, &rect.x, &rect.y, &rect.width, &rect.height, &rx, &ry);
 
   if (rect.IsEmpty()) {
     // Rendering of the element disabled
     rect.SetEmpty();  // Make sure width/height are zero and not negative
     // We still want the x/y position from 'rect'
     *aBounds = aToBoundsSpace.TransformBounds(rect);
     return true;
   }
@@ -150,17 +164,21 @@ bool SVGRectElement::GetGeometryBounds(R
   }
 
   *aBounds = aToBoundsSpace.TransformBounds(rect);
   return true;
 }
 
 void SVGRectElement::GetAsSimplePath(SimplePath* aSimplePath) {
   float x, y, width, height, rx, ry;
-  GetAnimatedLengthValues(&x, &y, &width, &height, &rx, &ry, nullptr);
+
+  MOZ_ASSERT(GetPrimaryFrame());
+  SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height,
+                                  SVGT::Rx, SVGT::Ry>(this, &x, &y, &width,
+                                                      &height, &rx, &ry);
 
   if (width <= 0 || height <= 0) {
     aSimplePath->Reset();
     return;
   }
 
   rx = std::max(rx, 0.0f);
   ry = std::max(ry, 0.0f);
@@ -170,17 +188,21 @@ void SVGRectElement::GetAsSimplePath(Sim
     return;
   }
 
   aSimplePath->SetRect(x, y, width, height);
 }
 
 already_AddRefed<Path> SVGRectElement::BuildPath(PathBuilder* aBuilder) {
   float x, y, width, height, rx, ry;
-  GetAnimatedLengthValues(&x, &y, &width, &height, &rx, &ry, nullptr);
+
+  MOZ_ASSERT(GetPrimaryFrame());
+  SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height,
+                                  SVGT::Rx, SVGT::Ry>(this, &x, &y, &width,
+                                                      &height, &rx, &ry);
 
   if (width <= 0 || height <= 0) {
     return nullptr;
   }
 
   rx = std::max(rx, 0.0f);
   ry = std::max(ry, 0.0f);
 
@@ -188,33 +210,56 @@ already_AddRefed<Path> SVGRectElement::B
     // Optimization for the no rounded corners case.
     Rect r(x, y, width, height);
     aBuilder->MoveTo(r.TopLeft());
     aBuilder->LineTo(r.TopRight());
     aBuilder->LineTo(r.BottomRight());
     aBuilder->LineTo(r.BottomLeft());
     aBuilder->Close();
   } else {
-    // If either the 'rx' or the 'ry' attribute isn't set, then we have to
-    // set it to the value of the other:
-    bool hasRx = mLengthAttributes[ATTR_RX].IsExplicitlySet();
-    bool hasRy = mLengthAttributes[ATTR_RY].IsExplicitlySet();
-    MOZ_ASSERT(hasRx || hasRy);
-
-    if (hasRx && !hasRy) {
-      ry = rx;
-    } else if (hasRy && !hasRx) {
-      rx = ry;
-    }
-
     // Clamp rx and ry to half the rect's width and height respectively:
     rx = std::min(rx, width / 2);
     ry = std::min(ry, height / 2);
 
     RectCornerRadii radii(rx, ry);
     AppendRoundedRectToPath(aBuilder, Rect(x, y, width, height), radii);
   }
 
   return aBuilder->Finish();
 }
 
+bool SVGRectElement::IsLengthChangedViaCSS(const ComputedStyle& aNewStyle,
+                                           const ComputedStyle& aOldStyle) {
+  auto *newSVGReset = aNewStyle.StyleSVGReset(),
+       *oldSVGReset = aOldStyle.StyleSVGReset();
+  auto *newPosition = aNewStyle.StylePosition(),
+       *oldPosition = aOldStyle.StylePosition();
+
+  return newSVGReset->mX != oldSVGReset->mX ||
+         newSVGReset->mY != oldSVGReset->mY ||
+         newPosition->mWidth != oldPosition->mWidth ||
+         newPosition->mHeight != oldPosition->mHeight ||
+         newSVGReset->mRx != oldSVGReset->mRx ||
+         newSVGReset->mRy != oldSVGReset->mRy;
+}
+
+nsCSSPropertyID SVGRectElement::GetCSSPropertyIdForAttrEnum(uint8_t aAttrEnum) {
+  switch (aAttrEnum) {
+    case ATTR_X:
+      return eCSSProperty_x;
+    case ATTR_Y:
+      return eCSSProperty_y;
+    case ATTR_WIDTH:
+      return eCSSProperty_width;
+    case ATTR_HEIGHT:
+      return eCSSProperty_height;
+    case ATTR_RX:
+      return eCSSProperty_rx;
+    case ATTR_RY:
+      return eCSSProperty_ry;
+    default:
+      MOZ_ASSERT_UNREACHABLE("Unknown attr enum");
+      return eCSSProperty_UNKNOWN;
+  }
+}
+
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/svg/SVGRectElement.h
+++ b/dom/svg/SVGRectElement.h
@@ -2,51 +2,60 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_SVGRectElement_h
 #define mozilla_dom_SVGRectElement_h
 
+#include "nsCSSPropertyID.h"
 #include "SVGAnimatedLength.h"
 #include "SVGGeometryElement.h"
 
 nsresult NS_NewSVGRectElement(
     nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
 
 namespace mozilla {
+class ComputedStyle;
+
 namespace dom {
 
 typedef SVGGeometryElement SVGRectElementBase;
 
 class SVGRectElement final : public SVGRectElementBase {
  protected:
   explicit SVGRectElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
   virtual JSObject* WrapNode(JSContext* cx,
                              JS::Handle<JSObject*> aGivenProto) override;
   friend nsresult(::NS_NewSVGRectElement(
       nsIContent** aResult,
       already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo));
 
  public:
+  NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override;
+
   // nsSVGSVGElement methods:
   virtual bool HasValidDimensions() const override;
 
   // SVGGeometryElement methods:
   virtual bool GetGeometryBounds(
       Rect* aBounds, const StrokeOptions& aStrokeOptions,
       const Matrix& aToBoundsSpace,
       const Matrix* aToNonScalingStrokeSpace = nullptr) override;
   virtual void GetAsSimplePath(SimplePath* aSimplePath) override;
   virtual already_AddRefed<Path> BuildPath(
       PathBuilder* aBuilder = nullptr) override;
 
   virtual nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override;
 
+  static bool IsLengthChangedViaCSS(const ComputedStyle& aNewStyle,
+                                    const ComputedStyle& aOldStyle);
+  static nsCSSPropertyID GetCSSPropertyIdForAttrEnum(uint8_t aAttrEnum);
+
   // WebIDL
   already_AddRefed<DOMSVGAnimatedLength> X();
   already_AddRefed<DOMSVGAnimatedLength> Y();
   already_AddRefed<DOMSVGAnimatedLength> Height();
   already_AddRefed<DOMSVGAnimatedLength> Width();
   already_AddRefed<DOMSVGAnimatedLength> Rx();
   already_AddRefed<DOMSVGAnimatedLength> Ry();
 
--- a/dom/svg/SVGViewportElement.h
+++ b/dom/svg/SVGViewportElement.h
@@ -119,43 +119,43 @@ class SVGViewportElement : public SVGGra
     return svgFloatSize(mViewportWidth, mViewportHeight);
   }
 
   void SetViewportSize(const svgFloatSize& aSize) {
     mViewportWidth = aSize.width;
     mViewportHeight = aSize.height;
   }
 
+  /**
+   * Returns true if either this is an SVG <svg> element that is the child of
+   * another non-foreignObject SVG element, or this is a SVG <symbol> element
+   * this is the root of a use-element shadow tree.
+   */
+  bool IsInner() const {
+    const nsIContent* parent = GetFlattenedTreeParent();
+    return parent && parent->IsSVGElement() &&
+           !parent->IsSVGElement(nsGkAtoms::foreignObject);
+  }
+
   // WebIDL
   already_AddRefed<SVGAnimatedRect> ViewBox();
   already_AddRefed<DOMSVGAnimatedPreserveAspectRatio> PreserveAspectRatio();
   virtual SVGAnimatedViewBox* GetAnimatedViewBox() override;
 
  protected:
   // implementation helpers:
 
   bool IsRoot() const {
     NS_ASSERTION((IsInUncomposedDoc() && !GetParent()) ==
                      (OwnerDoc()->GetRootElement() == this),
                  "Can't determine if we're root");
     return IsInUncomposedDoc() && !GetParent();
   }
 
   /**
-   * Returns true if either this is an SVG <svg> element that is the child of
-   * another non-foreignObject SVG element, or this is a SVG <symbol> element
-   * this is the root of a use-element shadow tree.
-   */
-  bool IsInner() const {
-    const nsIContent* parent = GetFlattenedTreeParent();
-    return parent && parent->IsSVGElement() &&
-           !parent->IsSVGElement(nsGkAtoms::foreignObject);
-  }
-
-  /**
    * Returns the explicit or default preserveAspectRatio, unless we're
    * synthesizing a viewBox, in which case it returns the "none" value.
    */
   virtual SVGPreserveAspectRatio GetPreserveAspectRatioWithOverride() const {
     return mPreserveAspectRatio.GetAnimValue();
   }
 
   /**
--- a/dom/svg/moz.build
+++ b/dom/svg/moz.build
@@ -176,16 +176,17 @@ UNIFIED_SOURCES += [
     'SVGFETileElement.cpp',
     'SVGFETurbulenceElement.cpp',
     'SVGFilterElement.cpp',
     'SVGFilters.cpp',
     'SVGForeignObjectElement.cpp',
     'SVGFragmentIdentifier.cpp',
     'SVGGElement.cpp',
     'SVGGeometryElement.cpp',
+    'SVGGeometryProperty.cpp',
     'SVGGradientElement.cpp',
     'SVGGraphicsElement.cpp',
     'SVGImageElement.cpp',
     'SVGIntegerPairSMILType.cpp',
     'SVGLength.cpp',
     'SVGLengthList.cpp',
     'SVGLengthListSMILType.cpp',
     'SVGLineElement.cpp',
--- a/dom/u2f/tests/browser/browser_abort_visibility.js
+++ b/dom/u2f/tests/browser/browser_abort_visibility.js
@@ -65,16 +65,17 @@ function startGetAssertionRequest(tab) {
 
 // Test that MakeCredential() and GetAssertion() requests
 // are aborted when the current tab loses its focus.
 add_task(async function test_abort() {
   // Enable the USB token.
   Services.prefs.setBoolPref("security.webauth.u2f", true);
   Services.prefs.setBoolPref("security.webauth.webauthn_enable_softtoken", false);
   Services.prefs.setBoolPref("security.webauth.webauthn_enable_usbtoken", true);
+  Services.prefs.setBoolPref("security.webauth.webauthn_enable_android_fido2", false);
 
   // Create a new tab for the MakeCredential() request.
   let tab_create = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
 
   // Start the request.
   await startMakeCredentialRequest(tab_create);
   await assertStatus(tab_create, "pending");
 
--- a/dom/u2f/tests/browser/browser_appid_localhost.js
+++ b/dom/u2f/tests/browser/browser_appid_localhost.js
@@ -20,16 +20,17 @@ function promiseU2FRegister(tab, app_id)
 
 add_task(async function () {
   // By default, proxies don't apply to localhost. We need them to for this test, though:
   await SpecialPowers.pushPrefEnv({set: [["network.proxy.allow_hijacking_localhost", true],]});
   // Enable the soft token.
   Services.prefs.setBoolPref("security.webauth.u2f", true);
   Services.prefs.setBoolPref("security.webauth.webauthn_enable_softtoken", true);
   Services.prefs.setBoolPref("security.webauth.webauthn_enable_usbtoken", false);
+  Services.prefs.setBoolPref("security.webauth.webauthn_enable_android_fido2", false);
 
   // Open a new tab.
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
 
   // Check that we have the right origin, and U2F is available.
   let ready = await ContentTask.spawn(tab.linkedBrowser, null, async () => {
     return content.location.origin == "https://localhost" && content.u2f;
   });
--- a/dom/u2f/tests/test_appid_facet.html
+++ b/dom/u2f/tests/test_appid_facet.html
@@ -28,16 +28,17 @@
 "use strict";
 
 SimpleTest.waitForExplicitFinish();
 
 // listen for messages from the test harness
 window.addEventListener("message", handleEventMessage);
 SpecialPowers.pushPrefEnv({"set": [["security.webauth.u2f", true],
                                    ["security.webauth.webauthn_enable_softtoken", true],
+                                   ["security.webauth.webauthn_enable_android_fido2", false],
                                    ["security.webauth.webauthn_enable_usbtoken", false]]},
   function(){
     document.getElementById('testing_frame').src = "https://example.com/tests/dom/u2f/tests/frame_appid_facet.html";
   });
 
 </script>
 </body>
 </html>
--- a/dom/u2f/tests/test_appid_facet_insecure.html
+++ b/dom/u2f/tests/test_appid_facet_insecure.html
@@ -28,16 +28,17 @@
 "use strict";
 
 SimpleTest.waitForExplicitFinish();
 
 // listen for messages from the test harness
 window.addEventListener("message", handleEventMessage);
 SpecialPowers.pushPrefEnv({"set": [["security.webauth.u2f", true],
                                    ["security.webauth.webauthn_enable_softtoken", true],
+                                   ["security.webauth.webauthn_enable_android_fido2", false],
                                    ["security.webauth.webauthn_enable_usbtoken", false]]},
   function(){
     document.getElementById('testing_frame').src = "http://test2.example.com/tests/dom/u2f/tests/frame_appid_facet_insecure.html";
   });
 
 </script>
 </body>
 </html>
--- a/dom/u2f/tests/test_appid_facet_subdomain.html
+++ b/dom/u2f/tests/test_appid_facet_subdomain.html
@@ -28,16 +28,17 @@
 "use strict";
 
 SimpleTest.waitForExplicitFinish();
 
 // listen for messages from the test harness
 window.addEventListener("message", handleEventMessage);
 SpecialPowers.pushPrefEnv({"set": [["security.webauth.u2f", true],
                                    ["security.webauth.webauthn_enable_softtoken", true],
+                                   ["security.webauth.webauthn_enable_android_fido2", false],
                                    ["security.webauth.webauthn_enable_usbtoken", false]]},
   function(){
     document.getElementById('testing_frame').src = "https://test1.example.com/tests/dom/u2f/tests/frame_appid_facet_subdomain.html";
   });
 
 </script>
 
 </body>
--- a/dom/u2f/tests/test_multiple_keys.html
+++ b/dom/u2f/tests/test_multiple_keys.html
@@ -28,16 +28,17 @@
 "use strict";
 
 SimpleTest.waitForExplicitFinish();
 
 // listen for messages from the test harness
 window.addEventListener("message", handleEventMessage);
 SpecialPowers.pushPrefEnv({"set": [["security.webauth.u2f", true],
                                    ["security.webauth.webauthn_enable_softtoken", true],
+                                   ["security.webauth.webauthn_enable_android_fido2", false],
                                    ["security.webauth.webauthn_enable_usbtoken", false]]},
   function(){
     document.getElementById('testing_frame').src = "https://test2.example.com/tests/dom/u2f/tests/frame_multiple_keys.html";
   });
 
 
 </script>
 
--- a/dom/u2f/tests/test_no_token.html
+++ b/dom/u2f/tests/test_no_token.html
@@ -26,16 +26,17 @@
 
 <script class="testbody" type="text/javascript">
 "use strict";
 
 SimpleTest.waitForExplicitFinish();
 
 SpecialPowers.pushPrefEnv({"set": [["security.webauth.u2f", true],
                                    ["security.webauth.webauthn_enable_softtoken", false],
+                                   ["security.webauth.webauthn_enable_android_fido2", false],
                                    ["security.webauth.webauthn_enable_usbtoken", false]]},
 function() {
   // listen for messages from the test harness
   window.addEventListener("message", handleEventMessage);
   document.getElementById('testing_frame').src = "https://test1.example.com/tests/dom/u2f/tests/frame_no_token.html";
 });
 
 </script>
--- a/dom/u2f/tests/test_override_request.html
+++ b/dom/u2f/tests/test_override_request.html
@@ -18,16 +18,17 @@
     "use strict";
 
     SimpleTest.waitForExplicitFinish();
 
     // Enable USB tokens.
     SpecialPowers.pushPrefEnv({"set": [
       ["security.webauth.u2f", true],
       ["security.webauth.webauthn_enable_softtoken", false],
+      ["security.webauth.webauthn_enable_android_fido2", false],
       ["security.webauth.webauthn_enable_usbtoken", true],
     ]}, () => {
       addEventListener("message", handleEventMessage);
       document.getElementById("testing_frame").src = "https://example.com/tests/dom/u2f/tests/frame_override_request.html";
     });
   </script>
 
 </body>
--- a/dom/u2f/tests/test_register.html
+++ b/dom/u2f/tests/test_register.html
@@ -26,16 +26,17 @@
 
 <script class="testbody" type="text/javascript">
 "use strict";
 
 SimpleTest.waitForExplicitFinish();
 
 SpecialPowers.pushPrefEnv({"set": [["security.webauth.u2f", true],
                                    ["security.webauth.webauthn_enable_softtoken", true],
+                                   ["security.webauth.webauthn_enable_android_fido2", false],
                                    ["security.webauth.webauthn_enable_usbtoken", false]]},
 function() {
   // listen for messages from the test harness
   window.addEventListener("message", handleEventMessage);
   document.getElementById('testing_frame').src = "https://example.com/tests/dom/u2f/tests/frame_register.html";
 });
 </script>
 
--- a/dom/u2f/tests/test_register_sign.html
+++ b/dom/u2f/tests/test_register_sign.html
@@ -22,16 +22,17 @@
 
 <script class="testbody" type="text/javascript">
 "use strict";
 
 SimpleTest.waitForExplicitFinish();
 
 SpecialPowers.pushPrefEnv({"set": [["security.webauth.u2f", true],
                                    ["security.webauth.webauthn_enable_softtoken", true],
+                                   ["security.webauth.webauthn_enable_android_fido2", false],
                                    ["security.webauth.webauthn_enable_usbtoken", false]]},
 function() {
   // listen for messages from the test harness
   window.addEventListener("message", handleEventMessage);
   document.getElementById('testing_frame').src = "https://example.com/tests/dom/u2f/tests/frame_register_sign.html";
 });
 </script>
 
--- a/dom/webauthn/tests/browser/browser_abort_visibility.js
+++ b/dom/webauthn/tests/browser/browser_abort_visibility.js
@@ -81,16 +81,17 @@ function startGetAssertionRequest(tab) {
   });
 }
 
 add_task(async function test_setup() {
   await SpecialPowers.pushPrefEnv({
     "set": [
       ["security.webauth.webauthn", true],
       ["security.webauth.webauthn_enable_softtoken", false],
+      ["security.webauth.webauthn_enable_android_fido2", false],
       ["security.webauth.webauthn_enable_usbtoken", true]
     ]
   });
 });
 
 // Test that MakeCredential() and GetAssertion() requests
 // are aborted when the current tab loses its focus.
 add_task(async function test_switch_tab() {
--- a/dom/webauthn/tests/browser/browser_fido_appid_extension.js
+++ b/dom/webauthn/tests/browser/browser_fido_appid_extension.js
@@ -88,16 +88,17 @@ function promiseWebAuthnSign(tab, key_ha
           };
         })
     });
 }
 
 add_task(function test_setup() {
   Services.prefs.setBoolPref("security.webauth.u2f", true);
   Services.prefs.setBoolPref("security.webauth.webauthn", true);
+  Services.prefs.setBoolPref("security.webauth.webauthn_enable_android_fido2", false);
   Services.prefs.setBoolPref("security.webauth.webauthn_enable_softtoken", true);
   Services.prefs.setBoolPref("security.webauth.webauthn_enable_usbtoken", false);
 });
 
 add_task(async function test_appid() {
   // Open a new tab.
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
 
--- a/dom/webauthn/tests/browser/browser_webauthn_prompts.js
+++ b/dom/webauthn/tests/browser/browser_webauthn_prompts.js
@@ -94,16 +94,17 @@ function promiseWebAuthnSign(tab) {
 }
 
 add_task(async function test_setup_usbtoken() {
   await SpecialPowers.pushPrefEnv({
     "set": [
       ["security.webauth.u2f", false],
       ["security.webauth.webauthn", true],
       ["security.webauth.webauthn_enable_softtoken", false],
+      ["security.webauth.webauthn_enable_android_fido2", false],
       ["security.webauth.webauthn_enable_usbtoken", true]
     ]
   });
 });
 
 add_task(async function test_register() {
   // Open a new tab.
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
--- a/dom/webauthn/tests/browser/browser_webauthn_telemetry.js
+++ b/dom/webauthn/tests/browser/browser_webauthn_telemetry.js
@@ -93,16 +93,17 @@ function checkRpIdHash(rpIdHash) {
 add_task(async function test_setup() {
   cleanupTelemetry();
 
   await SpecialPowers.pushPrefEnv({
     "set": [
       ["security.webauth.webauthn", true],
       ["security.webauth.webauthn_enable_softtoken", true],
       ["security.webauth.webauthn_enable_usbtoken", false],
+      ["security.webauth.webauthn_enable_android_fido2", false],
       ["security.webauth.webauthn_testing_allow_direct_attestation", true]
     ]
   });
 });
 
 add_task(async function test() {
   // These tests can't run simultaneously as the preference changes will race.
   // So let's run them sequentially here, but in an async function so we can
--- a/dom/webauthn/tests/test_webauthn_abort_signal.html
+++ b/dom/webauthn/tests/test_webauthn_abort_signal.html
@@ -23,16 +23,17 @@
     }
 
     add_task(() => {
       // Enable USB tokens.
       return SpecialPowers.pushPrefEnv({"set": [
         ["security.webauth.webauthn", true],
         ["security.webauth.webauthn_enable_softtoken", false],
         ["security.webauth.webauthn_enable_usbtoken", true],
+        ["security.webauth.webauthn_enable_android_fido2", false],
       ]});
     });
 
     // Start a new MakeCredential() request.
     function requestMakeCredential(signal) {
       let publicKey = {
         rp: {id: document.domain, name: "none", icon: "none"},
         user: {id: new Uint8Array(), name: "none", icon: "none", displayName: "none"},
--- a/dom/webauthn/tests/test_webauthn_attestation_conveyance.html
+++ b/dom/webauthn/tests/test_webauthn_attestation_conveyance.html
@@ -57,16 +57,17 @@
     }
 
     add_task(() => {
       // Enable the softtoken.
       return SpecialPowers.pushPrefEnv({"set": [
         ["security.webauth.webauthn", true],
         ["security.webauth.webauthn_enable_softtoken", true],
         ["security.webauth.webauthn_enable_usbtoken", false],
+        ["security.webauth.webauthn_enable_android_fido2", false],
         ["security.webauth.webauthn_testing_allow_direct_attestation", true],
       ]});
     });
 
     // Start a new MakeCredential() request.
     function requestMakeCredential(attestation) {
       let publicKey = {
         rp: {id: document.domain, name: "none", icon: "none"},
--- a/dom/webauthn/tests/test_webauthn_authenticator_selection.html
+++ b/dom/webauthn/tests/test_webauthn_authenticator_selection.html
@@ -32,16 +32,17 @@
     let gCredential;
 
     add_task(() => {
       // Enable the softtoken.
       return SpecialPowers.pushPrefEnv({"set": [
         ["security.webauth.webauthn", true],
         ["security.webauth.webauthn_enable_softtoken", true],
         ["security.webauth.webauthn_enable_usbtoken", false],
+        ["security.webauth.webauthn_enable_android_fido2", false],
       ]});
     });
 
     // Start a new MakeCredential() request.
     function requestMakeCredential(authenticatorSelection) {
       let publicKey = {
         rp: {id: document.domain, name: "none", icon: "none"},
         user: {id: new Uint8Array(), name: "none", icon: "none", displayName: "none"},
--- a/dom/webauthn/tests/test_webauthn_authenticator_transports.html
+++ b/dom/webauthn/tests/test_webauthn_authenticator_transports.html
@@ -35,16 +35,17 @@
     let gCredential;
 
     add_task(() => {
       // Enable the softtoken.
       return SpecialPowers.pushPrefEnv({"set": [
         ["security.webauth.webauthn", true],
         ["security.webauth.webauthn_enable_softtoken", true],
         ["security.webauth.webauthn_enable_usbtoken", false],
+        ["security.webauth.webauthn_enable_android_fido2", false],
       ]});
     });
 
     // Start a new MakeCredential() request.
     function requestMakeCredential(excludeCredentials) {
       let publicKey = {
         rp: {id: document.domain, name: "none", icon: "none"},
         user: {id: new Uint8Array(), name: "none", icon: "none", displayName: "none"},
--- a/dom/webauthn/tests/test_webauthn_get_assertion.html
+++ b/dom/webauthn/tests/test_webauthn_get_assertion.html
@@ -57,17 +57,18 @@
     function expectAbortError(aResult) {
       is(aResult.code, DOMException.ABORT_ERR, "Expecting an AbortError");
     }
 
     add_task(() => {
       return SpecialPowers.pushPrefEnv({"set": [
         ["security.webauth.webauthn", true],
         ["security.webauth.webauthn_enable_softtoken", true],
-        ["security.webauth.webauthn_enable_usbtoken", false]
+        ["security.webauth.webauthn_enable_usbtoken", false],
+        ["security.webauth.webauthn_enable_android_fido2", false],
       ]});
     });
 
     // Set up a valid credential
     add_task(async () => {
       let publicKey = {
         rp: {id: document.domain, name: "none", icon: "none"},
         user: {id: new Uint8Array(), name: "none", icon: "none", displayName: "none"},
--- a/dom/webauthn/tests/test_webauthn_isexternalctap2securitykeysupported.html
+++ b/dom/webauthn/tests/test_webauthn_isexternalctap2securitykeysupported.html
@@ -18,17 +18,19 @@
 <script class="testbody" type="text/javascript">
 "use strict";
 
 // Execute the full-scope test
 SimpleTest.waitForExplicitFinish();
 
 SpecialPowers.pushPrefEnv({"set": [["security.webauth.webauthn", true],
                                    ["security.webauth.webauthn_enable_softtoken", true],
-                                   ["security.webauth.webauthn_enable_usbtoken", false]]},
+                                   ["security.webauth.webauthn_enable_usbtoken", false],
+                                   ["security.webauth.webauthn_enable_android_fido2", false],
+                                  ]},
 function() {
   PublicKeyCredential.isExternalCTAP2SecurityKeySupported()
   .then(aResult => ok(true, `Should always return either true or false: ${aResult}`))
   .catch(aProblem => ok(false, `We shouldn't get here: ${aProblem}`))
   .then(() => SimpleTest.finish());
 });
 
 </script>
--- a/dom/webauthn/tests/test_webauthn_isplatformauthenticatoravailable.html
+++ b/dom/webauthn/tests/test_webauthn_isplatformauthenticatoravailable.html
@@ -18,17 +18,19 @@
 <script class="testbody" type="text/javascript">
 "use strict";
 
 // Execute the full-scope test
 SimpleTest.waitForExplicitFinish();
 
 SpecialPowers.pushPrefEnv({"set": [["security.webauth.webauthn", true],
                                    ["security.webauth.webauthn_enable_softtoken", true],
-                                   ["security.webauth.webauthn_enable_usbtoken", false]]},
+                                   ["security.webauth.webauthn_enable_usbtoken", false],
+                                   ["security.webauth.webauthn_enable_android_fido2", false],
+                                  ]},
 async function() {
   // This test ensures that isUserVerifyingPlatformAuthenticatorAvailable()
   // is a callable method, but with the softtoken enabled, it's not useful to
   // figure out what it actually returns, so we'll just make sure it runs.
   await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
   .then(function(aResult) {
     ok(true, "Resolved: " + aResult);
   })
--- a/dom/webauthn/tests/test_webauthn_loopback.html
+++ b/dom/webauthn/tests/test_webauthn_loopback.html
@@ -20,16 +20,17 @@
 "use strict";
 
 // Execute the full-scope test
 SimpleTest.waitForExplicitFinish();
 
 SpecialPowers.pushPrefEnv({"set": [["security.webauth.webauthn", true],
                                    ["security.webauth.webauthn_enable_softtoken", true],
                                    ["security.webauth.webauthn_enable_usbtoken", false],
+                                   ["security.webauth.webauthn_enable_android_fido2", false],
                                    ["security.webauth.webauthn_testing_allow_direct_attestation", true]]},
 function() {
   is(navigator.authentication, undefined, "navigator.authentication does not exist any longer");
   isnot(navigator.credentials, undefined, "Credential Management API endpoint must exist");
   isnot(navigator.credentials.create, undefined, "CredentialManagement create API endpoint must exist");
   isnot(navigator.credentials.get, undefined, "CredentialManagement get API endpoint must exist");
 
   let credm = navigator.credentials;
--- a/dom/webauthn/tests/test_webauthn_make_credential.html
+++ b/dom/webauthn/tests/test_webauthn_make_credential.html
@@ -43,16 +43,17 @@
 
     function expectNotSupportedError(aResult) {
       ok(aResult.toString().startsWith("NotSupportedError"), "Expecting a NotSupportedError");
       return Promise.resolve();
     }
 
     SpecialPowers.pushPrefEnv({"set": [["security.webauth.webauthn", true],
                                        ["security.webauth.webauthn_enable_softtoken", true],
+                                       ["security.webauth.webauthn_enable_android_fido2", false],
                                        ["security.webauth.webauthn_enable_usbtoken", false]]}, runTests);
     function runTests() {
       is(navigator.authentication, undefined, "navigator.authentication does not exist any longer");
       isnot(navigator.credentials, undefined, "Credential Management API endpoint must exist");
       isnot(navigator.credentials.create, undefined, "CredentialManagement create API endpoint must exist");
       isnot(navigator.credentials.get, undefined, "CredentialManagement get API endpoint must exist");
 
       let credm = navigator.credentials;
--- a/dom/webauthn/tests/test_webauthn_no_token.html
+++ b/dom/webauthn/tests/test_webauthn_no_token.html
@@ -19,16 +19,17 @@
 "use strict";
 
 // Execute the full-scope test
 SimpleTest.waitForExplicitFinish();
 
 // Turn off all tokens. This should result in "not allowed" failures
 SpecialPowers.pushPrefEnv({"set": [["security.webauth.webauthn", true],
                                    ["security.webauth.webauthn_enable_softtoken", false],
+                                   ["security.webauth.webauthn_enable_android_fido2", false],
                                    ["security.webauth.webauthn_enable_usbtoken", false]]},
 function() {
   is(navigator.authentication, undefined, "navigator.authentication does not exist any longer");
   isnot(navigator.credentials, undefined, "Credential Management API endpoint must exist");
   isnot(navigator.credentials.create, undefined, "CredentialManagement create API endpoint must exist");
   isnot(navigator.credentials.get, undefined, "CredentialManagement get API endpoint must exist");
 
   let credm = navigator.credentials;
--- a/dom/webauthn/tests/test_webauthn_override_request.html
+++ b/dom/webauthn/tests/test_webauthn_override_request.html
@@ -17,16 +17,17 @@
     // Last request status.
     let status = "";
 
     add_task(() => {
       // Enable USB tokens.
       return SpecialPowers.pushPrefEnv({"set": [
         ["security.webauth.webauthn", true],
         ["security.webauth.webauthn_enable_softtoken", false],
+        ["security.webauth.webauthn_enable_android_fido2", false],
         ["security.webauth.webauthn_enable_usbtoken", true],
       ]});
     });
 
     // Start a new MakeCredential() request.
     async function requestMakeCredential(status_value) {
       let publicKey = {
         rp: {id: document.domain, name: "none", icon: "none"},
--- a/dom/webauthn/tests/test_webauthn_sameorigin.html
+++ b/dom/webauthn/tests/test_webauthn_sameorigin.html
@@ -294,15 +294,16 @@
         console.log(i, testFuncs[i], testFuncs.length);
         testFuncs[i]().then(() => { runNextTest(); });
         i = i + 1;
       };
       runNextTest();
     };
     SpecialPowers.pushPrefEnv({"set": [["security.webauth.webauthn", true],
                                        ["security.webauth.webauthn_enable_softtoken", true],
+                                       ["security.webauth.webauthn_enable_android_fido2", false],
                                        ["security.webauth.webauthn_enable_usbtoken", false]]},
                               runTests);
 
   </script>
 
 </body>
 </html>
--- a/dom/webauthn/tests/test_webauthn_store_credential.html
+++ b/dom/webauthn/tests/test_webauthn_store_credential.html
@@ -21,16 +21,17 @@
     function expectNotSupportedError(aResult) {
       ok(aResult.toString().startsWith("NotSupportedError"), "Expecting a NotSupportedError, received: " + aResult);
       return Promise.resolve();
     }
 
     add_task(async function(){
       await SpecialPowers.pushPrefEnv({"set": [["security.webauth.webauthn", true],
                                    ["security.webauth.webauthn_enable_softtoken", true],
+                                   ["security.webauth.webauthn_enable_android_fido2", false],
                                    ["security.webauth.webauthn_enable_usbtoken", false]]});
 
       isnot(navigator.credentials, undefined, "Credential Management API endpoint must exist");
       isnot(navigator.credentials.create, undefined, "CredentialManagement create API endpoint must exist");
       isnot(navigator.credentials.get, undefined, "CredentialManagement get API endpoint must exist");
       isnot(navigator.credentials.store, undefined, "CredentialManagement store API endpoint must exist");
 
       let credentialChallenge = new Uint8Array(16);
--- a/gfx/skia/skia/src/ports/SkScalerContext_win_dw.cpp
+++ b/gfx/skia/skia/src/ports/SkScalerContext_win_dw.cpp
@@ -37,27 +37,56 @@
 
 #include <dwrite.h>
 #include <dwrite_1.h>
 #include <dwrite_3.h>
 
 /* Note:
  * In versions 8 and 8.1 of Windows, some calls in DWrite are not thread safe.
  * The DWriteFactoryMutex protects the calls that are problematic.
+ *
+ * On DWrite 3 or above, which is only available on Windows 10, we don't enable
+ * the locking to avoid thread contention.
  */
 static SkSharedMutex DWriteFactoryMutex;
 
-typedef SkAutoSharedMutexShared Shared;
+struct MaybeExclusive {
+    MaybeExclusive(SkScalerContext_DW* ctx) : fEnabled(!ctx->isDWrite3()) {
+        if (fEnabled) {
+            DWriteFactoryMutex.acquire();
+        }
+    }
+    ~MaybeExclusive() {
+        if (fEnabled) {
+            DWriteFactoryMutex.release();
+        }
+    }
+    bool fEnabled;
+};
+
+struct MaybeShared {
+    MaybeShared(SkScalerContext_DW* ctx) : fEnabled(!ctx->isDWrite3()) {
+        if (fEnabled) {
+            DWriteFactoryMutex.acquireShared();
+        }
+    }
+    ~MaybeShared() {
+        if (fEnabled) {
+            DWriteFactoryMutex.releaseShared();
+        }
+    }
+    bool fEnabled;
+};
 
 static bool isLCD(const SkScalerContextRec& rec) {
     return SkMask::kLCD16_Format == rec.fMaskFormat;
 }
 
-static bool is_hinted(DWriteFontTypeface* typeface) {
-    SkAutoExclusive l(DWriteFactoryMutex);
+static bool is_hinted(SkScalerContext_DW* ctx, DWriteFontTypeface* typeface) {
+    MaybeExclusive l(ctx);
     AutoTDWriteTable<SkOTTableMaximumProfile> maxp(typeface->fDWriteFontFace.get());
     if (!maxp.fExists) {
         return false;
     }
     if (maxp.fSize < sizeof(SkOTTableMaximumProfile::Version::TT)) {
         return false;
     }
     if (maxp->version.version != SkOTTableMaximumProfile::Version::TT::VERSION) {
@@ -117,18 +146,18 @@ bool get_gasp_range(DWriteFontTypeface* 
 }
 /** If the rendering mode for the specified 'size' is gridfit, then place
  *  the gridfit range into 'range'. Otherwise, leave 'range' alone.
  */
 static bool is_gridfit_only(GaspRange::Behavior flags) {
     return flags.raw.value == GaspRange::Behavior::Raw::GridfitMask;
 }
 
-static bool has_bitmap_strike(DWriteFontTypeface* typeface, GaspRange range) {
-    SkAutoExclusive l(DWriteFactoryMutex);
+static bool has_bitmap_strike(SkScalerContext_DW* ctx, DWriteFontTypeface* typeface, GaspRange range) {
+    MaybeExclusive l(ctx);
     {
         AutoTDWriteTable<SkOTTableEmbeddedBitmapLocation> eblc(typeface->fDWriteFontFace.get());
         if (!eblc.fExists) {
             return false;
         }
         if (eblc.fSize < sizeof(SkOTTableEmbeddedBitmapLocation)) {
             return false;
         }
@@ -263,17 +292,17 @@ SkScalerContext_DW::SkScalerContext_DW(s
         // a bitmap strike if the range is gridfit only and contains a bitmap.
         int bitmapPPEM = SkScalarTruncToInt(gdiTextSize);
         GaspRange range(bitmapPPEM, bitmapPPEM, 0, GaspRange::Behavior());
         if (get_gasp_range(typeface, bitmapPPEM, &range)) {
             if (!is_gridfit_only(range.fFlags)) {
                 range = GaspRange(bitmapPPEM, bitmapPPEM, 0, GaspRange::Behavior());
             }
         }
-        treatLikeBitmap = has_bitmap_strike(typeface, range);
+        treatLikeBitmap = has_bitmap_strike(this, typeface, range);
 
         axisAlignedBitmap = is_axis_aligned(fRec);
     }
 
     GaspRange range(0, 0xFFFF, 0, GaspRange::Behavior());
 
     // If the user requested aliased, do so with aliased compatible metrics.
     if (SkMask::kBW_Format == fRec.fMaskFormat) {
@@ -299,17 +328,17 @@ SkScalerContext_DW::SkScalerContext_DW(s
         fRenderingMode = DWRITE_RENDERING_MODE_NATURAL_SYMMETRIC;
         fTextureType = DWRITE_TEXTURE_CLEARTYPE_3x1;
         fTextSizeMeasure = gdiTextSize;
         fMeasuringMode = DWRITE_MEASURING_MODE_GDI_CLASSIC;
 
     // If the font has a gasp table version 1, use it to determine symmetric rendering.
     } else if ((get_gasp_range(typeface, SkScalarRoundToInt(gdiTextSize), &range) &&
                 range.fVersion >= 1) ||
-               realTextSize > SkIntToScalar(20) || !is_hinted(typeface)) {
+               realTextSize > SkIntToScalar(20) || !is_hinted(this, typeface)) {
         fTextSizeRender = realTextSize;
         fTextureType = DWRITE_TEXTURE_CLEARTYPE_3x1;
         fTextSizeMeasure = realTextSize;
         fMeasuringMode = DWRITE_MEASURING_MODE_NATURAL;
 
         IDWriteRenderingParams* params = sk_get_dwrite_default_rendering_params();
         DWriteFontTypeface* typeface = static_cast<DWriteFontTypeface*>(getTypeface());
         if (params &&
@@ -392,36 +421,36 @@ bool SkScalerContext_DW::generateAdvance
     glyph->fAdvanceY = 0;
 
     uint16_t glyphId = glyph->getGlyphID();
     DWRITE_GLYPH_METRICS gm;
 
     if (DWRITE_MEASURING_MODE_GDI_CLASSIC == fMeasuringMode ||
         DWRITE_MEASURING_MODE_GDI_NATURAL == fMeasuringMode)
     {
-        SkAutoExclusive l(DWriteFactoryMutex);
+        MaybeExclusive l(this);
         HRBM(this->getDWriteTypeface()->fDWriteFontFace->GetGdiCompatibleGlyphMetrics(
                  fTextSizeMeasure,
                  1.0f, // pixelsPerDip
                  // This parameter does not act like the lpmat2 parameter to GetGlyphOutlineW.
                  // If it did then GsA here and G_inv below to mapVectors.
                  nullptr,
                  DWRITE_MEASURING_MODE_GDI_NATURAL == fMeasuringMode,
                  &glyphId, 1,
                  &gm),
              "Could not get gdi compatible glyph metrics.");
     } else {
-        SkAutoExclusive l(DWriteFactoryMutex);
+        MaybeExclusive l(this);
         HRBM(this->getDWriteTypeface()->fDWriteFontFace->GetDesignGlyphMetrics(&glyphId, 1, &gm),
              "Could not get design metrics.");
     }
 
     DWRITE_FONT_METRICS dwfm;
     {
-        Shared l(DWriteFactoryMutex);
+        MaybeShared l(this);
         this->getDWriteTypeface()->fDWriteFontFace->GetMetrics(&dwfm);
     }
     SkScalar advanceX = fTextSizeMeasure * gm.advanceWidth / dwfm.designUnitsPerEm;
 
     SkVector advance = { advanceX, 0 };
     if (DWRITE_MEASURING_MODE_GDI_CLASSIC == fMeasuringMode ||
         DWRITE_MEASURING_MODE_GDI_NATURAL == fMeasuringMode)
     {
@@ -460,17 +489,17 @@ HRESULT SkScalerContext_DW::getBoundingB
     run.fontEmSize = SkScalarToFloat(fTextSizeRender);
     run.bidiLevel = 0;
     run.glyphIndices = &glyphId;
     run.isSideways = FALSE;
     run.glyphOffsets = &offset;
 
     SkTScopedComPtr<IDWriteGlyphRunAnalysis> glyphRunAnalysis;
     {
-        SkAutoExclusive l(DWriteFactoryMutex);
+        MaybeExclusive l(this);
         // IDWriteFactory2::CreateGlyphRunAnalysis is very bad at aliased glyphs.
         if (this->getDWriteTypeface()->fFactory2 &&
                 (fGridFitMode == DWRITE_GRID_FIT_MODE_DISABLED ||
                  fAntiAliasMode == DWRITE_TEXT_ANTIALIAS_MODE_GRAYSCALE))
         {
             HRM(this->getDWriteTypeface()->fFactory2->CreateGlyphRunAnalysis(
                     &run,
                     &fXform,
@@ -490,17 +519,17 @@ HRESULT SkScalerContext_DW::getBoundingB
                     fMeasuringMode,
                     0.0f, // baselineOriginX,
                     0.0f, // baselineOriginY,
                     &glyphRunAnalysis),
                 "Could not create glyph run analysis.");
         }
     }
     {
-        Shared l(DWriteFactoryMutex);
+        MaybeShared l(this);
         HRM(glyphRunAnalysis->GetAlphaTextureBounds(textureType, bbox),
             "Could not get texture bounds.");
     }
     return S_OK;
 }
 
 /** GetAlphaTextureBounds succeeds but sometimes returns empty bounds like
  *  { 0x80000000, 0x80000000, 0x80000000, 0x80000000 }
@@ -583,17 +612,17 @@ void SkScalerContext_DW::generateColorMe
         const DWRITE_COLOR_GLYPH_RUN* colorGlyph;
         HRVM(colorLayers->GetCurrentRun(&colorGlyph), "Could not get current color glyph run");
 
         SkPath path;
         SkTScopedComPtr<IDWriteGeometrySink> geometryToPath;
         HRVM(SkDWriteGeometrySink::Create(&path, &geometryToPath),
             "Could not create geometry to path converter.");
         {
-            SkAutoExclusive l(DWriteFactoryMutex);
+            MaybeExclusive l(this);
             HRVM(colorGlyph->glyphRun.fontFace->GetGlyphRunOutline(
                     colorGlyph->glyphRun.fontEmSize,
                     colorGlyph->glyphRun.glyphIndices,
                     colorGlyph->glyphRun.glyphAdvances,
                     colorGlyph->glyphRun.glyphOffsets,
                     colorGlyph->glyphRun.glyphCount,
                     colorGlyph->glyphRun.isSideways,
                     colorGlyph->glyphRun.bidiLevel % 2, //rtl
@@ -942,17 +971,17 @@ const void* SkScalerContext_DW::drawDWMa
     run.fontEmSize = SkScalarToFloat(fTextSizeRender);
     run.bidiLevel = 0;
     run.glyphIndices = &index;
     run.isSideways = FALSE;
     run.glyphOffsets = &offset;
     {
         SkTScopedComPtr<IDWriteGlyphRunAnalysis> glyphRunAnalysis;
         {
-            SkAutoExclusive l(DWriteFactoryMutex);
+            MaybeExclusive l(this);
             // IDWriteFactory2::CreateGlyphRunAnalysis is very bad at aliased glyphs.
             if (this->getDWriteTypeface()->fFactory2 &&
                     (fGridFitMode == DWRITE_GRID_FIT_MODE_DISABLED ||
                      fAntiAliasMode == DWRITE_TEXT_ANTIALIAS_MODE_GRAYSCALE))
             {
                 HRNM(this->getDWriteTypeface()->fFactory2->CreateGlyphRunAnalysis(&run,
                          &fXform,
                          renderingMode,
@@ -978,17 +1007,17 @@ const void* SkScalerContext_DW::drawDWMa
         //NOTE: this assumes that the glyph has already been measured
         //with an exact same glyph run analysis.
         RECT bbox;
         bbox.left = glyph.fLeft;
         bbox.top = glyph.fTop;
         bbox.right = glyph.fLeft + glyph.fWidth;
         bbox.bottom = glyph.fTop + glyph.fHeight;
         {
-            Shared l(DWriteFactoryMutex);
+            MaybeShared l(this);
             HRNM(glyphRunAnalysis->CreateAlphaTexture(textureType,
                     &bbox,
                     fBits.begin(),
                     sizeNeeded),
                 "Could not draw mask.");
         }
     }
     return fBits.begin();
@@ -1042,17 +1071,17 @@ void SkScalerContext_DW::generateColorGl
         }
         paint.setColor(color);
 
         SkPath path;
         SkTScopedComPtr<IDWriteGeometrySink> geometryToPath;
         HRVM(SkDWriteGeometrySink::Create(&path, &geometryToPath),
              "Could not create geometry to path converter.");
         {
-            SkAutoExclusive l(DWriteFactoryMutex);
+            MaybeExclusive l(this);
             HRVM(colorGlyph->glyphRun.fontFace->GetGlyphRunOutline(
                 colorGlyph->glyphRun.fontEmSize,
                 colorGlyph->glyphRun.glyphIndices,
                 colorGlyph->glyphRun.glyphAdvances,
                 colorGlyph->glyphRun.glyphOffsets,
                 colorGlyph->glyphRun.glyphCount,
                 colorGlyph->glyphRun.isSideways,
                 colorGlyph->glyphRun.bidiLevel % 2, //rtl
@@ -1181,17 +1210,17 @@ bool SkScalerContext_DW::generatePath(Sk
 
     path->reset();
 
     SkTScopedComPtr<IDWriteGeometrySink> geometryToPath;
     HRBM(SkDWriteGeometrySink::Create(path, &geometryToPath),
          "Could not create geometry to path converter.");
     UINT16 glyphId = SkTo<UINT16>(glyph);
     {
-        SkAutoExclusive l(DWriteFactoryMutex);
+        MaybeExclusive l(this);
         //TODO: convert to<->from DIUs? This would make a difference if hinting.
         //It may not be needed, it appears that DirectWrite only hints at em size.
         HRBM(this->getDWriteTypeface()->fDWriteFontFace->GetGlyphRunOutline(
              SkScalarToFloat(fTextSizeRender),
              &glyphId,
              nullptr, //advances
              nullptr, //offsets
              1, //num glyphs
--- a/gfx/skia/skia/src/ports/SkScalerContext_win_dw.h
+++ b/gfx/skia/skia/src/ports/SkScalerContext_win_dw.h
@@ -21,16 +21,20 @@ class SkDescriptor;
 
 class SkScalerContext_DW : public SkScalerContext {
 public:
     SkScalerContext_DW(sk_sp<DWriteFontTypeface>,
                        const SkScalerContextEffects&,
                        const SkDescriptor*);
     ~SkScalerContext_DW() override;
 
+    // The IDWriteFontFace4 interface is only available in DWrite 3,
+    // so checking if it was found is sufficient to detect DWrite 3.
+    bool isDWrite3() { return bool(getDWriteTypeface()->fDWriteFontFace4); }
+
 protected:
     unsigned generateGlyphCount() override;
     uint16_t generateCharToGlyph(SkUnichar uni) override;
     bool generateAdvance(SkGlyph* glyph) override;
     void generateMetrics(SkGlyph* glyph) override;
     void generateImage(const SkGlyph& glyph) override;
     bool generatePath(SkGlyphID glyph, SkPath* path) override;
     void generateFontMetrics(SkFontMetrics*) override;
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -719,17 +719,17 @@ class gfxPrefs final {
   DECL_GFX_PREF(Live, "layout.display-list.show-rebuild-area", LayoutDisplayListShowArea, bool, false);
   DECL_GFX_PREF(Live, "layout.display-list.flatten-transform", LayoutFlattenTransform, bool, true);
 
   DECL_GFX_PREF(Live, "layout.frame_rate",                     LayoutFrameRate, int32_t, -1);
   DECL_GFX_PREF(Live, "layout.min-active-layer-size",          LayoutMinActiveLayerSize, int, 64);
   DECL_GFX_PREF(Once, "layout.paint_rects_separately",         LayoutPaintRectsSeparately, bool, true);
 
   // This and code dependent on it should be removed once containerless scrolling looks stable.
-  DECL_OVERRIDE_PREF(Live, "layout.scroll.root-frame-containers",   LayoutUseContainersForRootFrames, !OverrideBase_WebRender());
+  DECL_GFX_PREF(Live, "layout.scroll.root-frame-containers",   LayoutUseContainersForRootFrames, bool, false);
   // This pref is to be set by test code only.
   DECL_GFX_PREF(Live, "layout.scrollbars.always-layerize-track", AlwaysLayerizeScrollbarTrackTestOnly, bool, false);
   DECL_GFX_PREF(Live, "layout.smaller-painted-layers",         LayoutSmallerPaintedLayers, bool, false);
 
   DECL_GFX_PREF(Once, "media.hardware-video-decoding.force-enabled",
                                                                HardwareVideoDecodingForceEnabled, bool, false);
 #ifdef XP_WIN
   DECL_GFX_PREF(Live, "media.wmf.dxva.d3d11.enabled", PDMWMFAllowD3D11, bool, true);
--- a/gfx/webrender_bindings/RenderCompositorANGLE.h
+++ b/gfx/webrender_bindings/RenderCompositorANGLE.h
@@ -45,17 +45,17 @@ class RenderCompositorANGLE : public Ren
   gl::GLContext* gl() const override { return RenderThread::Get()->SharedGL(); }
 
   bool MakeCurrent() override;
 
   bool UseANGLE() const override { return true; }
 
   bool UseDComp() const override { return !!mCompositionDevice; }
 
-  bool UseTripleBuffering() const { return mUseTripleBuffering; }
+  bool UseTripleBuffering() const override { return mUseTripleBuffering; }
 
   LayoutDeviceIntSize GetBufferSize() override;
 
  protected:
   void InsertPresentWaitQuery();
   void WaitForPreviousPresentQuery();
   bool ResizeBufferIfNeeded();
   void DestroyEGLSurface();
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/geometry-properties-in-css-ref.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<svg width="800" height="600">
+  <g>
+    <rect x="40" y="40" width="100" height="100" rx="30" ry="30" fill="purple" />
+    <rect x="40" y="150" width="30" height="200" rx="20" ry="20" fill="magenta" />
+  </g>
+  <circle cx="170" cy="340" r="70px" fill="pink" />
+  <g>
+    <circle cx="230" cy="130" r="70px" fill="skyblue" />
+  </g>
+  <svg x="300" width="200" height="200" viewBox="0 0 100 100">
+    <ellipse cx="30" cy="100" rx="20" ry="40" fill="cyan" />
+    <ellipse cx="80" cy="50" rx="20" ry="40" fill="navy" />
+  </svg>
+  <foreignObject x="450" y="200" width="80" height="130">
+    <svg>
+      <rect width="50" height="50" rx="4" ry="4" fill="brown" />
+    </svg>
+  </foreignObject>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/geometry-properties-in-css.html
@@ -0,0 +1,75 @@
+<!doctype html>
+<style>
+  svg {
+    width: 800px;
+    height: 600px;
+    font-size: 10px;
+  }
+  svg svg {
+    width: 80px;
+    height: 80px;
+  }
+  rect:first-child {
+    x: 40px;
+    y: calc(5% + 10px);
+    width: calc(80px + 2em);
+    height: 10em;
+    rx: auto;
+    ry: 5%;
+    cx: 100px;
+    cy: 200px;
+  }
+  circle {
+    r: calc(70px);
+  }
+  g > #c2 {
+    cx: 80px;
+    cy: calc(20% + 10px);
+    x: 40px;
+    y: calc(5% + 10px);
+  }
+  svg > svg > ellipse {
+    cx: 30%;
+    cy: 100px;
+    rx: 20px;
+    ry: 40px;
+  }
+  svg > svg > ellipse:nth-child(2) {
+    transform: translate(50px, -50px);
+  }
+  svg ellipse {
+    cx: 10px;
+    cy: 10px;
+    rx: 10px;
+    ry: 10px;
+  }
+  foreignObject {
+    transform: translate(450px,0);
+    y: 200px;
+    width: 80px;
+    height: 130px;
+  }
+  #r2 {
+    width: 50px;
+    height:50px;
+  }
+</style>
+<svg>
+  <g>
+    <rect x="0" y="-10" width="30px" height="10px" rx="-5px" ry="auto" fill="purple" />
+    <rect x=" 40px /* some nonsense */ " y="150" width="30" height="20em" rx="20px" ry="20px" fill="magenta" />
+  </g>
+  <circle cx="/* more nonsense */ 170" cy="340" r="-5px" fill="pink" />
+  <g transform="translate(150,0)">
+    <circle id="c2" cx="20" cy="40" fill="skyblue" />
+  </g>
+  <svg x="300" width="200px" height="200px" viewBox="0 0 100 100">
+    <ellipse fill="cyan" />
+    <ellipse fill="navy" />
+  </svg>
+  <foreignObject>
+    <svg>
+      <rect id="r2" style="x:0;y:0" fill="brown" />
+    </svg>
+  </foreignObject>
+</svg>
--- a/layout/reftests/svg/reftest.list
+++ b/layout/reftests/svg/reftest.list
@@ -239,16 +239,17 @@ fuzzy-if(Android,0-18,0-600) == foreignO
 == foreignObject-dynamic-line-height-01.html foreignObject-dynamic-line-height-01-ref.html
 == foreignObject-vertical-01.svg foreignObject-vertical-01-ref.svg
 
 == fragmentIdentifier-01.xhtml pass.svg
 
 == g-transform-01.svg pass.svg
 
 == getElementById-a-element-01.svg pass.svg
+== geometry-properties-in-css.html geometry-properties-in-css-ref.html
 
 fuzzy-if(Android,0-9,0-980) fuzzy-if(skiaContent,0-3,0-32000) == gradient-live-01a.svg gradient-live-01-ref.svg
 fuzzy-if(Android,0-9,0-980) fuzzy-if(skiaContent,0-3,0-32000) == gradient-live-01b.svg gradient-live-01-ref.svg
 fuzzy-if(Android,0-9,0-980) fuzzy-if(skiaContent,0-3,0-32000) == gradient-live-01c.svg gradient-live-01-ref.svg
 fuzzy-if(Android,0-9,0-980) fuzzy-if(skiaContent,0-3,0-32000) == gradient-live-01d.svg gradient-live-01-ref.svg
 == gradient-transform-01.svg pass.svg
 == href-attr-change-restyles.svg href-attr-change-restyles-ref.svg
 fuzzy-if(skiaContent,0-1,0-550) == import-svg-01.html pass.svg
--- a/layout/style/nsDOMCSSAttrDeclaration.cpp
+++ b/layout/style/nsDOMCSSAttrDeclaration.cpp
@@ -5,20 +5,22 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* DOM object for element.style */
 
 #include "nsDOMCSSAttrDeclaration.h"
 
 #include "mozilla/dom/Document.h"
 #include "mozilla/dom/Element.h"
+#include "mozilla/dom/SVGElement.h"
 #include "mozilla/dom/MutationEventBinding.h"
 #include "mozilla/DeclarationBlock.h"
 #include "mozilla/InternalMutationEvent.h"
 #include "mozilla/SMILCSSValueType.h"
+#include "mozilla/SMILValue.h"
 #include "mozAutoDocUpdate.h"
 #include "nsIURI.h"
 #include "nsNodeUtils.h"
 #include "nsWrapperCacheInlines.h"
 #include "nsIFrame.h"
 #include "ActiveLayerTracker.h"
 
 using namespace mozilla;
@@ -128,39 +130,59 @@ nsDOMCSSAttributeDeclaration::GetParsing
     nsIPrincipal* aSubjectPrincipal) const {
   return {
       mElement->GetURLDataForStyleAttr(aSubjectPrincipal),
       mElement->OwnerDoc()->GetCompatibilityMode(),
       mElement->OwnerDoc()->CSSLoader(),
   };
 }
 
-nsresult nsDOMCSSAttributeDeclaration::SetSMILValue(
-    const nsCSSPropertyID aPropID, const SMILValue& aValue) {
+template <typename SetterFunc>
+nsresult nsDOMCSSAttributeDeclaration::SetSMILValueHelper(SetterFunc aFunc) {
   MOZ_ASSERT(mIsSMILOverride);
+
   // No need to do the ActiveLayerTracker / ScrollLinkedEffectDetector bits,
   // since we're in a SMIL animation anyway, no need to try to detect we're a
   // scripted animation.
   RefPtr<DeclarationBlock> created;
   DeclarationBlock* olddecl =
       GetOrCreateCSSDeclaration(eOperation_Modify, getter_AddRefs(created));
   if (!olddecl) {
     return NS_ERROR_NOT_AVAILABLE;
   }
   mozAutoDocUpdate autoUpdate(DocToUpdate(), true);
   RefPtr<DeclarationBlock> decl = olddecl->EnsureMutable();
-  bool changed = SMILCSSValueType::SetPropertyValues(aValue, *decl);
+
+  bool changed = aFunc(*decl);
+
   if (changed) {
     // We can pass nullptr as the latter param, since this is
     // mIsSMILOverride == true case.
     SetCSSDeclaration(decl, nullptr);
   }
   return NS_OK;
 }
 
+nsresult nsDOMCSSAttributeDeclaration::SetSMILValue(
+    const nsCSSPropertyID /*aPropID*/, const SMILValue& aValue) {
+  MOZ_ASSERT(aValue.mType == &SMILCSSValueType::sSingleton,
+             "We should only try setting a CSS value type");
+  return SetSMILValueHelper([&aValue](DeclarationBlock& aDecl) {
+    return SMILCSSValueType::SetPropertyValues(aValue, aDecl);
+  });
+}
+
+nsresult nsDOMCSSAttributeDeclaration::SetSMILValue(
+    const nsCSSPropertyID aPropID, const SVGAnimatedLength& aLength) {
+  return SetSMILValueHelper([aPropID, &aLength](DeclarationBlock& aDecl) {
+    return SVGElement::UpdateDeclarationBlockFromLength(
+        aDecl, aPropID, aLength, SVGElement::ValToUse::Anim);
+  });
+}
+
 nsresult nsDOMCSSAttributeDeclaration::SetPropertyValue(
     const nsCSSPropertyID aPropID, const nsAString& aValue,
     nsIPrincipal* aSubjectPrincipal) {
   // Scripted modifications to style.opacity or style.transform (or other
   // transform-like properties, e.g. style.translate, style.rotate, style.scale)
   // could immediately force us into the animated state if heuristics suggest
   // this is scripted animation.
   // FIXME: This is missing the margin shorthand and the logical versions of
--- a/layout/style/nsDOMCSSAttrDeclaration.h
+++ b/layout/style/nsDOMCSSAttrDeclaration.h
@@ -13,44 +13,48 @@
 #include "mozilla/dom/DocGroup.h"
 #include "nsDOMCSSDeclaration.h"
 
 struct RawServoUnlockedDeclarationBlock;
 
 namespace mozilla {
 
 class SMILValue;
+class SVGAnimatedLength;
 
 namespace dom {
 class DomGroup;
 class Element;
 }  // namespace dom
 }  // namespace mozilla
 
 class nsDOMCSSAttributeDeclaration final : public nsDOMCSSDeclaration {
  public:
   typedef mozilla::dom::Element Element;
   typedef mozilla::SMILValue SMILValue;
+  typedef mozilla::SVGAnimatedLength SVGAnimatedLength;
   nsDOMCSSAttributeDeclaration(Element* aContent, bool aIsSMILOverride);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_AMBIGUOUS(
       nsDOMCSSAttributeDeclaration, nsICSSDeclaration)
 
   mozilla::DeclarationBlock* GetOrCreateCSSDeclaration(
       Operation aOperation, mozilla::DeclarationBlock** aCreated) final;
 
   nsDOMCSSDeclaration::ParsingEnvironment GetParsingEnvironment(
       nsIPrincipal* aSubjectPrincipal) const final;
 
   mozilla::css::Rule* GetParentRule() override { return nullptr; }
 
   nsINode* GetParentObject() override { return mElement; }
 
-  nsresult SetSMILValue(const nsCSSPropertyID aPropID, const SMILValue&);
+  nsresult SetSMILValue(const nsCSSPropertyID aPropID, const SMILValue& aValue);
+  nsresult SetSMILValue(const nsCSSPropertyID aPropID,
+                        const SVGAnimatedLength& aLength);
 
   nsresult SetPropertyValue(const nsCSSPropertyID aPropID,
                             const nsAString& aValue,
                             nsIPrincipal* aSubjectPrincipal) override;
 
   static void MutationClosureFunction(void* aData);
 
   void GetPropertyChangeClosure(
@@ -74,11 +78,15 @@ class nsDOMCSSAttributeDeclaration final
 
   RefPtr<Element> mElement;
 
   /* If true, this indicates that this nsDOMCSSAttributeDeclaration
    * should interact with mContent's SMIL override style rule (rather
    * than the inline style rule).
    */
   const bool mIsSMILOverride;
+
+ private:
+  template <typename SetterFunc>
+  nsresult SetSMILValueHelper(SetterFunc aFunc);
 };
 
 #endif /* nsDOMCSSAttributeDeclaration_h */
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -1064,32 +1064,46 @@ void nsStyleFilter::SetDropShadow(nsCSSS
   mDropShadow->AddRef();
   mType = NS_STYLE_FILTER_DROP_SHADOW;
 }
 
 // --------------------
 // nsStyleSVGReset
 //
 nsStyleSVGReset::nsStyleSVGReset(const Document& aDocument)
-    : mMask(nsStyleImageLayers::LayerType::Mask),
+    : mX(LengthPercentage::Zero()),
+      mY(LengthPercentage::Zero()),
+      mCx(LengthPercentage::Zero()),
+      mCy(LengthPercentage::Zero()),
+      mRx(NonNegativeLengthPercentageOrAuto::Auto()),
+      mRy(NonNegativeLengthPercentageOrAuto::Auto()),
+      mR(NonNegativeLengthPercentage::Zero()),
+      mMask(nsStyleImageLayers::LayerType::Mask),
       mStopColor(StyleColor::Black()),
       mFloodColor(StyleColor::Black()),
       mLightingColor(StyleColor::White()),
       mStopOpacity(1.0f),
       mFloodOpacity(1.0f),
       mDominantBaseline(NS_STYLE_DOMINANT_BASELINE_AUTO),
       mVectorEffect(NS_STYLE_VECTOR_EFFECT_NONE),
       mMaskType(NS_STYLE_MASK_TYPE_LUMINANCE) {
   MOZ_COUNT_CTOR(nsStyleSVGReset);
 }
 
 nsStyleSVGReset::~nsStyleSVGReset() { MOZ_COUNT_DTOR(nsStyleSVGReset); }
 
 nsStyleSVGReset::nsStyleSVGReset(const nsStyleSVGReset& aSource)
-    : mMask(aSource.mMask),
+    : mX(aSource.mX),
+      mY(aSource.mY),
+      mCx(aSource.mCx),
+      mCy(aSource.mCy),
+      mRx(aSource.mRx),
+      mRy(aSource.mRy),
+      mR(aSource.mR),
+      mMask(aSource.mMask),
       mClipPath(aSource.mClipPath),
       mStopColor(aSource.mStopColor),
       mFloodColor(aSource.mFloodColor),
       mLightingColor(aSource.mLightingColor),
       mStopOpacity(aSource.mStopOpacity),
       mFloodOpacity(aSource.mFloodOpacity),
       mDominantBaseline(aSource.mDominantBaseline),
       mVectorEffect(aSource.mVectorEffect),
@@ -1131,16 +1145,22 @@ void nsStyleSVGReset::TriggerImageLoads(
     }
   }
 }
 
 nsChangeHint nsStyleSVGReset::CalcDifference(
     const nsStyleSVGReset& aNewData) const {
   nsChangeHint hint = nsChangeHint(0);
 
+  if (mX != aNewData.mX || mY != aNewData.mY || mCx != aNewData.mCx ||
+      mCy != aNewData.mCy || mR != aNewData.mR || mRx != aNewData.mRx ||
+      mRy != aNewData.mRy) {
+    hint |= nsChangeHint_InvalidateRenderingObservers | nsChangeHint_NeedReflow;
+  }
+
   if (mClipPath != aNewData.mClipPath) {
     hint |= nsChangeHint_UpdateEffects | nsChangeHint_RepaintFrame;
   }
 
   if (mDominantBaseline != aNewData.mDominantBaseline) {
     // XXXjwatt: why NS_STYLE_HINT_REFLOW? Isn't that excessive?
     hint |= NS_STYLE_HINT_REFLOW;
   } else if (mVectorEffect != aNewData.mVectorEffect) {
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -2854,16 +2854,25 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
   }
 
   bool HasMask() const;
 
   bool HasNonScalingStroke() const {
     return mVectorEffect == NS_STYLE_VECTOR_EFFECT_NON_SCALING_STROKE;
   }
 
+  // geometry properties
+  mozilla::LengthPercentage mX;
+  mozilla::LengthPercentage mY;
+  mozilla::LengthPercentage mCx;
+  mozilla::LengthPercentage mCy;
+  mozilla::NonNegativeLengthPercentageOrAuto mRx;
+  mozilla::NonNegativeLengthPercentageOrAuto mRy;
+  mozilla::NonNegativeLengthPercentage mR;
+
   nsStyleImageLayers mMask;
   mozilla::StyleShapeSource mClipPath;
   mozilla::StyleColor mStopColor;
   mozilla::StyleColor mFloodColor;
   mozilla::StyleColor mLightingColor;
 
   float mStopOpacity;
   float mFloodOpacity;
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -5627,16 +5627,72 @@ var gCSSProperties = {
   "stroke-width": {
     domProp: "strokeWidth",
     inherited: true,
     type: CSS_TYPE_LONGHAND,
     initial_values: [ "1px" ],
     other_values: [ "0", "0px", "-0em", "17px", "0.2em", "0.0002", "context-value" ],
     invalid_values: [ "-0.1px", "-3px" ]
   },
+  "x": {
+    domProp: "x",
+    inherited: false,
+    type: CSS_TYPE_LONGHAND,
+    initial_values: [ "0px" ],
+    other_values: [ "-1em", "17px", "0.2em", "23.4%" ],
+    invalid_values: [ "auto", "context-value", "0.0002" ]
+  },
+  "y": {
+    domProp: "y",
+    inherited: false,
+    type: CSS_TYPE_LONGHAND,
+    initial_values: [ "0px" ],
+    other_values: [ "-1em", "17px", "0.2em", "23.4%" ],
+    invalid_values: [ "auto", "context-value", "0.0002" ]
+  },
+  "cx": {
+    domProp: "cx",
+    inherited: false,
+    type: CSS_TYPE_LONGHAND,
+    initial_values: [ "0px" ],
+    other_values: [ "-1em", "17px", "0.2em", "23.4%" ],
+    invalid_values: [ "auto", "context-value", "0.0002" ]
+  },
+  "cy": {
+    domProp: "cy",
+    inherited: false,
+    type: CSS_TYPE_LONGHAND,
+    initial_values: [ "0px" ],
+    other_values: [ "-1em", "17px", "0.2em", "23.4%" ],
+    invalid_values: [ "auto", "context-value", "0.0002" ]
+  },
+  "r": {
+    domProp: "r",
+    inherited: false,
+    type: CSS_TYPE_LONGHAND,
+    initial_values: [ "0px" ],
+    other_values: [ "17px", "0.2em", "23.4%" ],
+    invalid_values: [ "auto", "-1", "-1.5px", "0.0002" ]
+  },
+  "rx": {
+    domProp: "rx",
+    inherited: false,
+    type: CSS_TYPE_LONGHAND,
+    initial_values: [ "auto" ],
+    other_values: [ "17px", "0.2em", "23.4%" ],
+    invalid_values: [ "hello", "-12px", "0.0002" ]
+  },
+  "ry": {
+    domProp: "ry",
+    inherited: false,
+    type: CSS_TYPE_LONGHAND,
+    initial_values: [ "auto" ],
+    other_values: [ "17px", "0.2em", "23.4%" ],
+    invalid_values: [ "hello", "-1.3px", "0.0002" ]
+  },
   "text-anchor": {
     domProp: "textAnchor",
     inherited: true,
     type: CSS_TYPE_LONGHAND,
     initial_values: [ "start" ],
     other_values: [ "middle", "end" ],
     invalid_values: []
   },
--- a/layout/style/test/test_transitions_per_property.html
+++ b/layout/style/test/test_transitions_per_property.html
@@ -75,16 +75,20 @@ var supported_properties = {
     "column-count": [ test_pos_integer_or_auto_transition,
                       test_integer_at_least_one_clamping ],
     "column-rule-color": [ test_color_transition,
                            test_currentcolor_transition ],
     "column-rule-width": [ test_length_transition,
                            test_length_clamped ],
     "column-width": [ test_length_transition,
                       test_length_clamped ],
+    "cx": [ test_length_transition, test_percent_transition,
+            test_length_unclamped, test_percent_unclamped ],
+    "cy": [ test_length_transition, test_percent_transition,
+            test_length_unclamped, test_percent_unclamped ],
     "-moz-image-region": [ test_rect_transition ],
     "-moz-outline-radius-bottomleft": [ test_radius_transition ],
     "-moz-outline-radius-bottomright": [ test_radius_transition ],
     "-moz-outline-radius-topleft": [ test_radius_transition ],
     "-moz-outline-radius-topright": [ test_radius_transition ],
     "background-color": [ test_color_transition,
                           test_currentcolor_transition ],
     "background-position": [ test_background_position_transition,
@@ -239,16 +243,22 @@ var supported_properties = {
                      test_length_clamped, test_percent_clamped ],
     "perspective": [ test_length_transition ],
     "perspective-origin": [ test_length_pair_transition,
                             test_length_percent_pair_transition,
                             test_length_percent_pair_unclamped ],
     "right": [ test_length_transition, test_percent_transition,
                test_length_percent_calc_transition,
                test_length_unclamped, test_percent_unclamped ],
+    "r": [ test_length_transition, test_percent_transition,
+           test_length_clamped, test_percent_clamped ],
+    "rx": [ test_length_transition, test_percent_transition,
+            test_length_clamped, test_percent_clamped ],
+    "ry": [ test_length_transition, test_percent_transition,
+            test_length_clamped, test_percent_clamped ],
     "shape-image-threshold": [ test_float_zeroToOne_transition,
                                // shape-image-threshold (like opacity) is
                                // clamped in computed style
                                // (not parsing/interpolation)
                                test_float_zeroToOne_clamped ],
     "shape-margin": [ test_length_transition, test_percent_transition,
                       test_length_clamped, test_percent_clamped ],
     "shape-outside": [ test_basic_shape_or_url_transition ],
@@ -292,16 +302,20 @@ var supported_properties = {
                           test_length_percent_pair_unclamped ],
     "vertical-align": [ test_length_transition, test_percent_transition,
                         test_length_unclamped, test_percent_unclamped ],
     "visibility": [ test_visibility_transition ],
     "width": [ test_length_transition, test_percent_transition,
                test_length_percent_calc_transition,
                test_length_clamped, test_percent_clamped ],
     "word-spacing": [ test_length_transition, test_length_unclamped ],
+    "x": [ test_length_transition, test_percent_transition,
+           test_length_unclamped, test_percent_unclamped ],
+    "y": [ test_length_transition, test_percent_transition,
+           test_length_unclamped, test_percent_unclamped ],
     "z-index": [ test_integer_transition, test_pos_integer_or_auto_transition ],
     "-webkit-line-clamp": [ test_pos_integer_or_none_transition ],
     "-webkit-text-fill-color": [ test_color_transition,
                                  test_currentcolor_transition ],
     "-webkit-text-stroke-color": [ test_color_transition,
                                    test_currentcolor_transition ]
 };
 
--- a/layout/svg/SVGGeometryFrame.cpp
+++ b/layout/svg/SVGGeometryFrame.cpp
@@ -192,16 +192,20 @@ void SVGGeometryFrame::DidSetComputedSty
         }
       } else {
         if (StyleSVG()->mFillRule != oldStyleSVG->mFillRule) {
           // Moz2D Path objects are fill-rule specific.
           element->ClearAnyCachedPath();
         }
       }
     }
+
+    if (element->IsGeometryChangedViaCSS(*Style(), *aOldComputedStyle)) {
+      element->ClearAnyCachedPath();
+    }
   }
 }
 
 bool SVGGeometryFrame::IsSVGTransformed(
     gfx::Matrix* aOwnTransform, gfx::Matrix* aFromParentTransform) const {
   bool foundTransform = false;
 
   // Check if our parent has children-only transforms:
--- a/layout/svg/nsSVGForeignObjectFrame.cpp
+++ b/layout/svg/nsSVGForeignObjectFrame.cpp
@@ -14,24 +14,26 @@
 #include "mozilla/PresShell.h"
 #include "mozilla/dom/SVGForeignObjectElement.h"
 #include "nsDisplayList.h"
 #include "nsGkAtoms.h"
 #include "nsNameSpaceManager.h"
 #include "nsLayoutUtils.h"
 #include "nsRegion.h"
 #include "nsSVGContainerFrame.h"
+#include "SVGGeometryProperty.h"
 #include "SVGObserverUtils.h"
 #include "nsSVGIntegrationUtils.h"
 #include "nsSVGOuterSVGFrame.h"
 #include "nsSVGUtils.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::image;
+namespace SVGT = SVGGeometryProperty::Tags;
 
 //----------------------------------------------------------------------
 // Implementation
 
 nsContainerFrame* NS_NewSVGForeignObjectFrame(PresShell* aPresShell,
                                               ComputedStyle* aStyle) {
   return new (aPresShell)
       nsSVGForeignObjectFrame(aStyle, aPresShell->GetPresContext());
@@ -237,18 +239,19 @@ void nsSVGForeignObjectFrame::PaintSVG(g
       return;
     }
   }
 
   aContext.Save();
 
   if (StyleDisplay()->IsScrollableOverflow()) {
     float x, y, width, height;
-    static_cast<SVGElement*>(GetContent())
-        ->GetAnimatedLengthValues(&x, &y, &width, &height, nullptr);
+    SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width,
+                                    SVGT::Height>(
+        static_cast<SVGElement*>(GetContent()), &x, &y, &width, &height);
 
     gfxRect clipRect =
         nsSVGUtils::GetClipRectForFrame(this, 0.0f, 0.0f, width, height);
     nsSVGUtils::SetClipRect(&aContext, aTransform, clipRect);
   }
 
   // SVG paints in CSS px, but normally frames paint in dev pixels. Here we
   // multiply a CSS-px-to-dev-pixel factor onto aTransform so our children
@@ -286,18 +289,18 @@ nsIFrame* nsSVGForeignObjectFrame::GetFr
   }
 
   nsIFrame* kid = PrincipalChildList().FirstChild();
   if (!kid) {
     return nullptr;
   }
 
   float x, y, width, height;
-  static_cast<SVGElement*>(GetContent())
-      ->GetAnimatedLengthValues(&x, &y, &width, &height, nullptr);
+  SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height>(
+      static_cast<SVGElement*>(GetContent()), &x, &y, &width, &height);
 
   if (!gfxRect(x, y, width, height).Contains(aPoint) ||
       !nsSVGUtils::HitTestClip(this, aPoint)) {
     return nullptr;
   }
 
   // Convert the point to app units relative to the top-left corner of the
   // viewport that's established by the foreignObject element:
@@ -318,18 +321,18 @@ void nsSVGForeignObjectFrame::ReflowSVG(
   if (!nsSVGUtils::NeedsReflowSVG(this)) {
     return;
   }
 
   // We update mRect before the DoReflow call so that DoReflow uses the
   // correct dimensions:
 
   float x, y, w, h;
-  static_cast<SVGForeignObjectElement*>(GetContent())
-      ->GetAnimatedLengthValues(&x, &y, &w, &h, nullptr);
+  SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height>(
+      static_cast<SVGElement*>(GetContent()), &x, &y, &w, &h);
 
   // If mRect's width or height are negative, reflow blows up! We must clamp!
   if (w < 0.0f) w = 0.0f;
   if (h < 0.0f) h = 0.0f;
 
   mRect = nsLayoutUtils::RoundGfxRectToAppRect(gfxRect(x, y, w, h),
                                                AppUnitsPerCSSPixel());
 
@@ -371,31 +374,27 @@ void nsSVGForeignObjectFrame::NotifySVGC
   MOZ_ASSERT(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED),
              "Invalidation logic may need adjusting");
 
   bool needNewBounds = false;  // i.e. mRect or visual overflow rect
   bool needReflow = false;
   bool needNewCanvasTM = false;
 
   if (aFlags & COORD_CONTEXT_CHANGED) {
-    SVGForeignObjectElement* fO =
-        static_cast<SVGForeignObjectElement*>(GetContent());
     // Coordinate context changes affect mCanvasTM if we have a
     // percentage 'x' or 'y'
-    if (fO->mLengthAttributes[SVGForeignObjectElement::ATTR_X].IsPercentage() ||
-        fO->mLengthAttributes[SVGForeignObjectElement::ATTR_Y].IsPercentage()) {
+    if (StyleSVGReset()->mX.HasPercent() || StyleSVGReset()->mY.HasPercent()) {
       needNewBounds = true;
       needNewCanvasTM = true;
     }
+
     // Our coordinate context's width/height has changed. If we have a
     // percentage width/height our dimensions will change so we must reflow.
-    if (fO->mLengthAttributes[SVGForeignObjectElement::ATTR_WIDTH]
-            .IsPercentage() ||
-        fO->mLengthAttributes[SVGForeignObjectElement::ATTR_HEIGHT]
-            .IsPercentage()) {
+    if (StylePosition()->mWidth.HasPercent() ||
+        StylePosition()->mHeight.HasPercent()) {
       needNewBounds = true;
       needReflow = true;
     }
   }
 
   if (aFlags & TRANSFORM_CHANGED) {
     if (mCanvasTM && mCanvasTM->IsSingular()) {
       needNewBounds = true;  // old bounds are bogus
@@ -438,17 +437,18 @@ void nsSVGForeignObjectFrame::NotifySVGC
 }
 
 SVGBBox nsSVGForeignObjectFrame::GetBBoxContribution(
     const Matrix& aToBBoxUserspace, uint32_t aFlags) {
   SVGForeignObjectElement* content =
       static_cast<SVGForeignObjectElement*>(GetContent());
 
   float x, y, w, h;
-  content->GetAnimatedLengthValues(&x, &y, &w, &h, nullptr);
+  SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height>(
+      content, &x, &y, &w, &h);
 
   if (w < 0.0f) w = 0.0f;
   if (h < 0.0f) h = 0.0f;
 
   if (aToBBoxUserspace.IsSingular()) {
     // XXX ReportToConsole
     return SVGBBox();
   }
--- a/layout/tools/reftest/remotereftest.py
+++ b/layout/tools/reftest/remotereftest.py
@@ -427,17 +427,17 @@ def run_test_harness(parser, options):
     except Exception:
         print "Automation Error: Exception caught while running tests"
         traceback.print_exc()
         retVal = 1
 
     reftest.stopWebServer(options)
 
     if options.printDeviceInfo and not options.verify:
-        reftest.printDeviceInfo(printLogcat=True)
+        reftest.printDeviceInfo(printLogcat=(retVal != 0))
 
     return retVal
 
 
 if __name__ == "__main__":
     parser = reftestcommandline.RemoteArgumentsParser()
     options = parser.parse_args()
     sys.exit(run_test_harness(parser, options))
--- a/mobile/android/app/geckoview-prefs.js
+++ b/mobile/android/app/geckoview-prefs.js
@@ -31,17 +31,17 @@ pref("dom.push.enabled", false);
 
 // enable external storage API
 pref("dom.storageManager.enabled", true);
 
 // enable Visual Viewport API
 pref("dom.visualviewport.enabled", true);
 
 // Use containerless scrolling.
-pref("layout.scroll.root-frame-containers", 0);
+pref("layout.scroll.root-frame-containers", false);
 
 // Inherit locale from the OS, used for multi-locale builds
 pref("intl.locale.requested", "");
 
 // Enable Safe Browsing blocklist updates
 pref("browser.safebrowsing.features.phishing.update", true);
 pref("browser.safebrowsing.features.malware.update", true);
 
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -537,17 +537,17 @@ pref("layers.low-precision-opacity", "1.
 // We want to limit layers for two reasons:
 // 1) We can't scroll smoothly if we have to many draw calls
 // 2) Pages that have too many layers consume too much memory and crash.
 // By limiting the number of layers on mobile we're making the main thread
 // work harder keep scrolling smooth and memory low.
 pref("layers.max-active", 20);
 
 // Use containerless scrolling on Fennec.
-pref("layout.scroll.root-frame-containers", 0);
+pref("layout.scroll.root-frame-containers", false);
 
 pref("notification.feature.enabled", true);
 pref("dom.webnotifications.enabled", true);
 
 // prevent tooltips from showing up
 pref("browser.chrome.toolbar_tips", false);
 
 // don't allow meta-refresh when backgrounded
rename from mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ActivityResultHandler.java
rename to mobile/android/base/java/org/mozilla/gecko/util/ActivityResultHandler.java
rename from mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ActivityResultHandlerMap.java
rename to mobile/android/base/java/org/mozilla/gecko/util/ActivityResultHandlerMap.java
rename from mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/InputOptionsUtils.java
rename to mobile/android/base/java/org/mozilla/gecko/util/InputOptionsUtils.java
rename from mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/JSONUtils.java
rename to mobile/android/base/java/org/mozilla/gecko/util/JSONUtils.java
rename from mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/MapUtils.java
rename to mobile/android/base/java/org/mozilla/gecko/util/MapUtils.java
rename from mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/MenuUtils.java
rename to mobile/android/base/java/org/mozilla/gecko/util/MenuUtils.java
rename from mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/PrefUtils.java
rename to mobile/android/base/java/org/mozilla/gecko/util/PrefUtils.java
rename from mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/WindowUtils.java
rename to mobile/android/base/java/org/mozilla/gecko/util/WindowUtils.java
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/SpeechSynthesisService.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/SpeechSynthesisService.java
@@ -15,23 +15,30 @@ import android.speech.tts.TextToSpeech;
 import android.speech.tts.UtteranceProgressListener;
 import android.util.Log;
 
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Locale;
 import java.util.Set;
 import java.util.UUID;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 public class SpeechSynthesisService  {
     private static final String LOGTAG = "GeckoSpeechSynthesis";
-    private static TextToSpeech sTTS;
+    // Object type is used to make it easier to remove android.speech dependencies using Proguard.
+    private static Object sTTS;
 
     @WrapForJNI(calledFrom = "gecko")
     public static void initSynth() {
+        initSynthInternal();
+    }
+
+    // Extra internal method to make it easier to remove android.speech dependencies using Proguard.
+    private static void initSynthInternal() {
         if (sTTS != null) {
             return;
         }
 
         final Context ctx = GeckoAppShell.getApplicationContext();
 
         sTTS = new TextToSpeech(ctx, new TextToSpeech.OnInitListener() {
             @Override
@@ -42,86 +49,96 @@ public class SpeechSynthesisService  {
                 }
 
                 setUtteranceListener();
                 registerVoicesByLocale();
             }
         });
     }
 
+    private static TextToSpeech getTTS() {
+        return (TextToSpeech) sTTS;
+    }
+
     private static void registerVoicesByLocale() {
         ThreadUtils.postToBackgroundThread(new Runnable() {
             @Override
             public void run() {
+                TextToSpeech tss = getTTS();
                 Locale defaultLocale = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2
-                        ? sTTS.getDefaultLanguage()
-                        : sTTS.getLanguage();
+                        ? tss.getDefaultLanguage()
+                        : tss.getLanguage();
                 for (Locale locale : getAvailableLanguages()) {
-                    final Set<String> features = sTTS.getFeatures(locale);
+                    final Set<String> features = tss.getFeatures(locale);
                     boolean isLocal = features != null && features.contains(TextToSpeech.Engine.KEY_FEATURE_EMBEDDED_SYNTHESIS);
                     String localeStr = locale.toString();
                     registerVoice("moz-tts:android:" + localeStr, locale.getDisplayName(), localeStr.replace("_", "-"), !isLocal, defaultLocale == locale);
                 }
                 doneRegisteringVoices();
             }
         });
     }
 
     private static Set<Locale> getAvailableLanguages() {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
             // While this method was introduced in 21, it seems that it
             // has not been implemented in the speech service side until 23.
-            return sTTS.getAvailableLanguages();
+            return getTTS().getAvailableLanguages();
         }
         Set<Locale> locales = new HashSet<Locale>();
         for (Locale locale : Locale.getAvailableLocales()) {
-            if (locale.getVariant().isEmpty() && sTTS.isLanguageAvailable(locale) > 0) {
+            if (locale.getVariant().isEmpty() && getTTS().isLanguageAvailable(locale) > 0) {
                 locales.add(locale);
             }
         }
 
         return locales;
     }
 
     @WrapForJNI(dispatchTo = "gecko")
     private static native void registerVoice(String uri, String name, String locale, boolean isNetwork, boolean isDefault);
 
     @WrapForJNI(dispatchTo = "gecko")
     private static native void doneRegisteringVoices();
 
     @WrapForJNI(calledFrom = "gecko")
     public static String speak(final String uri, final String text, final float rate,
                                final float pitch, final float volume) {
+        AtomicBoolean result = new AtomicBoolean(false);
+        final String utteranceId = UUID.randomUUID().toString();
+        speakInternal(uri, text, rate, pitch, volume, utteranceId, result);
+        return result.get() ? utteranceId : null;
+    }
+
+    // Extra internal method to make it easier to remove android.speech dependencies using Proguard.
+    private static void speakInternal(final String uri, final String text, final float rate,
+                                      final float pitch, final float volume, final String utteranceId, final AtomicBoolean result) {
         if (sTTS == null) {
             Log.w(LOGTAG, "TextToSpeech is not initialized");
-            return null;
+            return;
         }
 
         HashMap<String, String> params = new HashMap<String, String>();
-        final String utteranceId = UUID.randomUUID().toString();
         params.put(TextToSpeech.Engine.KEY_PARAM_VOLUME, Float.toString(volume));
         params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, utteranceId);
-        sTTS.setLanguage(new Locale(uri.substring("moz-tts:android:".length())));
-        sTTS.setSpeechRate(rate);
-        sTTS.setPitch(pitch);
-        int result = sTTS.speak(text, TextToSpeech.QUEUE_FLUSH, params);
-        if (result != TextToSpeech.SUCCESS) {
-            return null;
-        }
-
-        return utteranceId;
+        TextToSpeech tss = (TextToSpeech) sTTS;
+        tss.setLanguage(new Locale(uri.substring("moz-tts:android:".length())));
+        tss.setSpeechRate(rate);
+        tss.setPitch(pitch);
+        int speakRes = tss.speak(text, TextToSpeech.QUEUE_FLUSH, params);
+        result.set(speakRes == TextToSpeech.SUCCESS);
     }
 
     private static void setUtteranceListener() {
         if (sTTS == null) {
             Log.w(LOGTAG, "TextToSpeech is not initialized");
             return;
         }
 
-        sTTS.setOnUtteranceProgressListener(new UtteranceProgressListener() {
+        getTTS().setOnUtteranceProgressListener(new UtteranceProgressListener() {
             @Override
             public void onDone(final String utteranceId) {
                 dispatchEnd(utteranceId);
             }
 
             @Override
             public void onError(final String utteranceId) {
                 dispatchError(utteranceId);
@@ -158,21 +175,26 @@ public class SpeechSynthesisService  {
     @WrapForJNI(dispatchTo = "gecko")
     private static native void dispatchError(String utteranceId);
 
     @WrapForJNI(dispatchTo = "gecko")
     private static native void dispatchBoundary(String utteranceId, int start, int end);
 
     @WrapForJNI(calledFrom = "gecko")
     public static void stop() {
+        stopInternal();
+    }
+
+    // Extra internal method to make it easier to remove android.speech dependencies using Proguard.
+    private static void stopInternal() {
         if (sTTS == null) {
             Log.w(LOGTAG, "TextToSpeech is not initialized");
             return;
         }
 
-        sTTS.stop();
+        getTTS().stop();
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
             // Android M has onStop method.  If Android L or above, dispatch
             // event
             dispatchEnd(null);
         }
     }
 }
deleted file mode 100644
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/NonEvictingLruCache.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/* -*- 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 android.util.LruCache;
-
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * An LruCache that also supports a set of items that will never be evicted.
- *
- * Alas, LruCache is final, so we compose rather than inherit.
- */
-public class NonEvictingLruCache<K, V> {
-    private final ConcurrentHashMap<K, V> mPermanent = new ConcurrentHashMap<K, V>();
-    private final LruCache<K, V> mEvitable;
-
-    public NonEvictingLruCache(final int evictableSize) {
-        mEvitable = new LruCache<K, V>(evictableSize);
-    }
-
-    public V get(final K key) {
-        V val = mPermanent.get(key);
-        if (val == null) {
-            return mEvitable.get(key);
-        }
-        return val;
-    }
-
-    public void putWithoutEviction(final K key, final V value) {
-        mPermanent.put(key, value);
-    }
-
-    public void put(final K key, final V value) {
-        mEvitable.put(key, value);
-    }
-
-    public void evictAll() {
-        mEvitable.evictAll();
-    }
-}
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -2583,16 +2583,23 @@ VARCACHE_PREF(
 
 // Blocked plugin content
 VARCACHE_PREF(
   "browser.safebrowsing.blockedURIs.enabled",
    browser_safebrowsing_blockedURIs_enabled,
   bool, true
 )
 
+// Maximum size for an array to store the safebrowsing prefixset.
+VARCACHE_PREF(
+  "browser.safebrowsing.prefixset_max_array_size",
+   browser_safebrowsing_prefixset_max_array_size,
+  RelaxedAtomicUint32, 512*1024
+)
+
 // When this pref is enabled document loads with a mismatched
 // Cross-Origin header will fail to load
 VARCACHE_PREF("browser.tabs.remote.useCrossOriginPolicy",
               browser_tabs_remote_useCrossOriginPolicy,
               bool, false
 )
 
 // Prevent system colors from being exposed to CSS or canvas.
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -772,17 +772,17 @@ pref("apz.y_stationary_size_multiplier",
 // Whether to run in native HiDPI mode on machines with "Retina"/HiDPI display;
 //   <= 0 : hidpi mode disabled, display will just use pixel-based upscaling
 //   == 1 : hidpi supported if all screens share the same backingScaleFactor
 //   >= 2 : hidpi supported even with mixed backingScaleFactors (somewhat broken)
 pref("gfx.hidpi.enabled", 2);
 #endif
 
 // Default to containerless scrolling
-pref("layout.scroll.root-frame-containers", 0);
+pref("layout.scroll.root-frame-containers", false);
 
 pref("layout.scrollbars.always-layerize-track", false);
 
 // Whether to enable LayerScope tool and default listening port
 pref("gfx.layerscope.enabled", false);
 pref("gfx.layerscope.port", 23456);
 
 // Log severe performance warnings to the error console and profiles.
@@ -6032,18 +6032,19 @@ pref("dom.datatransfer.mozAtAPIs", false
 #else
 pref("dom.datatransfer.mozAtAPIs", true);
 #endif
 
 // External.AddSearchProvider is deprecated and it will be removed in the next
 // cycles.
 pref("dom.sidebar.enabled", true);
 
-// Turn on fission frameloader swapping
-pref("fission.rebuild_frameloaders_on_remoteness_change", true);
+// Turn off fission frameloader swapping while regressions are being fixed.
+// Should be turned back on to resolve bug 1551993.
+pref("fission.rebuild_frameloaders_on_remoteness_change", false);
 
 // If true, preserve browsing contexts between process swaps. Should be set to
 // true in bug 1550571.
 pref("fission.preserve_browsing_contexts", false);
 
 // Support for legacy customizations that rely on checking the
 // user profile directory for these stylesheets:
 //  * userContent.css
--- a/mozglue/linker/moz.build
+++ b/mozglue/linker/moz.build
@@ -16,27 +16,17 @@ SOURCES += [
 Library('linker')
 
 FINAL_LIBRARY = 'mozglue'
 
 DEFINES['IMPL_MFBT'] = True
 
 DisableStlWrapping()
 
-# Avoid building the linker tests if building with icecc since it doesn't deal
-# well with .incbin.
-#
-# A better solution would be to set ICECC=no in the environment before building
-# these objects to force the local build, but moz.build lacks such a capability
-# at the moment.
-#
-# TODO: Remove this when https://github.com/icecc/icecream/pull/463 is merged
-# and in a release.
-if not CONFIG['CXX_IS_ICECREAM']:
-    TEST_DIRS += ['tests']
+TEST_DIRS += ['tests']
 
 if CONFIG['CC_TYPE'] in ('clang', 'gcc'):
     CXXFLAGS += ['-Wno-error=shadow']
 
 DEFINES['XZ_USE_CRC64'] = 1
 
 USE_LIBS += [
     'xz-embedded',
--- a/mozglue/linker/tests/TestZip.cpp
+++ b/mozglue/linker/tests/TestZip.cpp
@@ -7,63 +7,41 @@
 #include "Zip.h"
 #include "mozilla/RefPtr.h"
 
 #include "gtest/gtest.h"
 
 Logging Logging::Singleton;
 
 /**
- * ZIP_DATA(FOO, "foo") defines the variables FOO and FOO_SIZE.
- * The former contains the content of the "foo" file in the same directory
- * as this file, and FOO_SIZE its size.
- */
-/* clang-format off */
-#define ZIP_DATA(name, file)                          \
-  __asm__(".global " #name "\n"                       \
-          ".data\n"                                   \
-          ".balign 16\n"                              \
-          #name ":\n"                                 \
-          "  .incbin \"" SRCDIR "/" file "\"\n"       \
-          ".L" #name "_END:\n"                        \
-          "  .size " #name ", .L" #name "_END-" #name \
-          "\n"                                        \
-          ".global " #name "_SIZE\n"                  \
-          ".data\n"                                   \
-          ".balign 4\n"                               \
-          #name "_SIZE:\n"                            \
-          "  .int .L" #name "_END-" #name "\n");      \
-  extern const unsigned char name[];                  \
-  extern const unsigned int name##_SIZE
-/* clang-format on */
-
-/**
  * test.zip is a basic test zip file with a central directory. It contains
  * four entries, in the following order:
  * "foo", "bar", "baz", "qux".
  * The entries are going to be read out of order.
  */
-ZIP_DATA(TEST_ZIP, "test.zip");
+extern const unsigned char TEST_ZIP[];
+extern const unsigned int TEST_ZIP_SIZE;
 const char* test_entries[] = {"baz", "foo", "bar", "qux"};
 
 /**
  * no_central_dir.zip is a hand crafted test zip with no central directory
  * entries. The Zip reader is expected to be able to traverse these entries
  * if requested in order, without reading a central directory
  * - First entry is a file "a", STOREd.
  * - Second entry is a file "b", STOREd, using a data descriptor. CRC is
  *   unknown, but compressed and uncompressed sizes are known in the local
  *   file header.
  * - Third entry is a file "c", DEFLATEd, using a data descriptor. CRC,
  *   compressed and uncompressed sizes are known in the local file header.
  *   This is the kind of entry that can be found in a zip that went through
  *   zipalign if it had a data descriptor originally.
  * - Fourth entry is a file "d", STOREd.
  */
-ZIP_DATA(NO_CENTRAL_DIR_ZIP, "no_central_dir.zip");
+extern const unsigned char NO_CENTRAL_DIR_ZIP[];
+extern const unsigned int NO_CENTRAL_DIR_ZIP_SIZE;
 const char* no_central_dir_entries[] = {"a", "b", "c", "d"};
 
 TEST(Zip, TestZip)
 {
   Zip::Stream s;
   RefPtr<Zip> z = Zip::Create((void*)TEST_ZIP, TEST_ZIP_SIZE);
   for (auto& entry : test_entries) {
     ASSERT_TRUE(z->GetStream(entry, &s))
new file mode 100644
--- /dev/null
+++ b/mozglue/linker/tests/TestZipData.S
@@ -0,0 +1,17 @@
+.macro zip_data name, path
+  .global \name
+  .data
+  .balign 16
+  \name:
+  .incbin "\path"
+  .L\name\()_END:
+  .size \name, .L\name\()_END-\name
+  .global \name\()_SIZE
+  .data
+  .balign 4
+  \name\()_SIZE:
+  .int .L\name\()_END-\name
+.endm
+
+zip_data TEST_ZIP, "test.zip"
+zip_data NO_CENTRAL_DIR_ZIP, "no_central_dir.zip"
--- a/mozglue/linker/tests/moz.build
+++ b/mozglue/linker/tests/moz.build
@@ -6,14 +6,18 @@
 
 FINAL_LIBRARY = 'xul-gtest'
 
 UNIFIED_SOURCES += [
     '../Zip.cpp',
     'TestZip.cpp',
 ]
 
+SOURCES += [
+    'TestZipData.S',
+]
+
 LOCAL_INCLUDES += ['..']
 
 if CONFIG['CC_TYPE'] in ('clang', 'gcc'):
     CXXFLAGS += ['-Wno-error=shadow']
 
-DEFINES['SRCDIR'] = '"%s"' % SRCDIR
+ASFLAGS += ['-I', SRCDIR]
--- a/old-configure.in
+++ b/old-configure.in
@@ -1686,16 +1686,23 @@ fi
 if test "$BUILDING_RELEASE"; then
   # Override value in defines.sh, if any
   EARLY_BETA_OR_EARLIER=
 elif test "$EARLY_BETA_OR_EARLIER"; then
   AC_DEFINE(EARLY_BETA_OR_EARLIER)
 fi
 AC_SUBST(EARLY_BETA_OR_EARLIER)
 
+
+if test "$EARLY_BETA_OR_EARLIER"; then
+    MOZ_NEW_CERT_STORAGE=1
+    AC_DEFINE(MOZ_NEW_CERT_STORAGE)
+fi
+AC_SUBST(MOZ_NEW_CERT_STORAGE)
+
 # Allow someone to change MOZ_APP_NAME and MOZ_APP_BASENAME in mozconfig
 MOZ_ARG_WITH_STRING(app-name,
 [--with-app-name=APPNAME sets MOZ_APP_NAME to APPNAME],
 WITH_APP_NAME=$withval,
 )
 
 if test -n "$WITH_APP_NAME" ; then
     MOZ_APP_NAME="$WITH_APP_NAME"
--- a/security/manager/ssl/RemoteSecuritySettings.jsm
+++ b/security/manager/ssl/RemoteSecuritySettings.jsm
@@ -78,16 +78,25 @@ function stringToBytes(s) {
 // Converts an array of bytes to a JS string using fromCharCode on each byte.
 function bytesToString(bytes) {
   if (bytes.length > 65535) {
     throw new Error("input too long for bytesToString");
   }
   return String.fromCharCode.apply(null, bytes);
 }
 
+class CertInfo {
+  constructor(cert, subject) {
+    this.cert = cert;
+    this.subject = subject;
+    this.trust = Ci.nsICertStorage.TRUST_INHERIT;
+  }
+}
+CertInfo.prototype.QueryInterface = ChromeUtils.generateQI([Ci.nsICertInfo]);
+
 this.RemoteSecuritySettings = class RemoteSecuritySettings {
     constructor() {
         this.client = RemoteSettings(Services.prefs.getCharPref(INTERMEDIATES_COLLECTION_PREF), {
           bucketNamePref: INTERMEDIATES_BUCKET_PREF,
           lastCheckTimePref: INTERMEDIATES_CHECKED_SECONDS_PREF,
           signerName: Services.prefs.getCharPref(INTERMEDIATES_SIGNER_PREF),
           localFields: ["cert_import_complete"],
         });
@@ -143,32 +152,52 @@ this.RemoteSecuritySettings = class Remo
         }
         const current = await this.client.get();
         const waiting = current.filter(record => !record.cert_import_complete);
 
         log.debug(`There are ${waiting.length} intermediates awaiting download.`);
 
         TelemetryStopwatch.start(INTERMEDIATES_UPDATE_MS_TELEMETRY);
 
-        Promise.all(waiting.slice(0, maxDownloadsPerRun)
-          .map(record => this.maybeDownloadAttachment(record, col, certStorage))
-        ).then(async () => {
-          const finalCurrent = await this.client.get();
-          const finalWaiting = finalCurrent.filter(record => !record.cert_import_complete);
-          const countPreloaded = finalCurrent.length - finalWaiting.length;
+        let toDownload = waiting.slice(0, maxDownloadsPerRun);
+        let recordsCertsAndSubjects = await Promise.all(
+          toDownload.map(record => this.maybeDownloadAttachment(record)));
+        let certInfos = [];
+        let recordsToUpdate = [];
+        for (let {record, cert, subject} of recordsCertsAndSubjects) {
+          if (cert && subject) {
+            certInfos.push(new CertInfo(cert, subject));
+            recordsToUpdate.push(record);
+          }
+        }
+        let result = await new Promise((resolve) => {
+          certStorage.addCerts(certInfos, resolve);
+        }).catch((err) => err);
+        if (result != Cr.NS_OK) {
+          Cu.reportError(`certStorage.addCerts failed: ${result}`);
+          Services.telemetry.getHistogramById(INTERMEDIATES_ERRORS_TELEMETRY)
+            .add("failedToUpdateDB");
+          return;
+        }
+        await Promise.all(recordsToUpdate.map((record) => {
+          record.cert_import_complete = true;
+          return col.update(record);
+        }));
+        const finalCurrent = await this.client.get();
+        const finalWaiting = finalCurrent.filter(record => !record.cert_import_complete);
+        const countPreloaded = finalCurrent.length - finalWaiting.length;
 
-          TelemetryStopwatch.finish(INTERMEDIATES_UPDATE_MS_TELEMETRY);
-          Services.telemetry.scalarSet(INTERMEDIATES_PRELOADED_TELEMETRY,
-                                       countPreloaded);
-          Services.telemetry.scalarSet(INTERMEDIATES_PENDING_TELEMETRY,
-                                       finalWaiting.length);
+        TelemetryStopwatch.finish(INTERMEDIATES_UPDATE_MS_TELEMETRY);
+        Services.telemetry.scalarSet(INTERMEDIATES_PRELOADED_TELEMETRY,
+                                     countPreloaded);
+        Services.telemetry.scalarSet(INTERMEDIATES_PENDING_TELEMETRY,
+                                     finalWaiting.length);
 
-          Services.obs.notifyObservers(null, "remote-security-settings:intermediates-updated",
-                                       "success");
-        });
+        Services.obs.notifyObservers(null, "remote-security-settings:intermediates-updated",
+                                     "success");
     }
 
     async onObservePollEnd(subject, topic, data) {
         log.debug(`onObservePollEnd ${subject} ${topic}`);
 
         try {
           await this.updatePreloadedIntermediates();
         } catch (err) {
@@ -222,126 +251,108 @@ this.RemoteSecuritySettings = class Remo
         return resp.arrayBuffer();
       })
       .then(buffer => new Uint8Array(buffer));
     }
 
     /**
      * Attempts to download the attachment, assuming it's not been processed
      * already. Does not retry, and always resolves (e.g., does not reject upon
-     * failure.) Errors are reported via Cu.reportError; If you need to know
-     * success/failure, check record.cert_import_complete.
+     * failure.) Errors are reported via Cu.reportError.
      * @param  {AttachmentRecord} record defines which data to obtain
-     * @param  {KintoCollection}  col The kinto collection to update
-     * @param  {nsICertStorage}   certStorage The certificate storage to update
-     * @return {Promise}          a Promise representing the transaction
+     * @return {Promise}          a Promise that will resolve to an object with the properties
+     *                            record, cert, and subject. record is the original record.
+     *                            cert is the base64-encoded bytes of the downloaded certificate (if
+     *                            downloading was successful), and null otherwise.
+     *                            subject is the base64-encoded bytes of the subject distinguished
+     *                            name of the same.
      */
-    async maybeDownloadAttachment(record, col, certStorage) {
+    async maybeDownloadAttachment(record) {
       const {attachment: {hash, size}} = record;
-
-      return this._downloadAttachmentBytes(record)
-      .then(async function(attachmentData) {
-        if (!attachmentData || attachmentData.length == 0) {
-          // Bug 1519273 - Log telemetry for these rejections
-          log.debug(`Empty attachment. Hash=${hash}`);
-
-          Services.telemetry.getHistogramById(INTERMEDIATES_ERRORS_TELEMETRY)
-            .add("emptyAttachment");
-
-          return;
-        }
-
-        // check the length
-        if (attachmentData.length !== size) {
-          log.debug(`Unexpected attachment length. Hash=${hash} Lengths ${attachmentData.length} != ${size}`);
-
-          Services.telemetry.getHistogramById(INTERMEDIATES_ERRORS_TELEMETRY)
-            .add("unexpectedLength");
-
-          return;
-        }
-
-        // check the hash
-        let dataAsString = gTextDecoder.decode(attachmentData);
-        let calculatedHash = getHash(dataAsString);
-        if (calculatedHash !== hash) {
-          log.warn(`Invalid hash. CalculatedHash=${calculatedHash}, Hash=${hash}, data=${dataAsString}`);
-
-          Services.telemetry.getHistogramById(INTERMEDIATES_ERRORS_TELEMETRY)
-            .add("unexpectedHash");
-
-          return;
-        }
+      let result = { record, cert: null, subject: null };
 
-        let certBase64;
-        let subjectBase64;
-        try {
-          // split off the header and footer
-          certBase64 = dataAsString.split("-----")[2].replace(/\s/g, "");
-          // get an array of bytes so we can use X509.jsm
-          let certBytes = stringToBytes(atob(certBase64));
-          let cert = new X509.Certificate();
-          cert.parse(certBytes);
-          // get the DER-encoded subject and get a base64-encoded string from it
-          // TODO(bug 1542028): add getters for _der and _bytes
-          subjectBase64 = btoa(bytesToString(cert.tbsCertificate.subject._der._bytes));
-        } catch (err) {
-          Cu.reportError(`Failed to decode cert: ${err}`);
-
-          // Re-purpose the "failedToUpdateNSS" telemetry tag as "failed to
-          // decode preloaded intermediate certificate"
-          Services.telemetry.getHistogramById(INTERMEDIATES_ERRORS_TELEMETRY)
-            .add("failedToUpdateNSS");
-
-          return;
-        }
-        log.debug(`Adding cert. Hash=${hash}. Size=${size}`);
-        // We can assume that certs obtained from remote-settings are part of
-        // the root program. If they aren't, they won't be used for path-
-        // building anyway, so just add it to the DB with trust set to
-        // "inherit".
-        let result = await new Promise((resolve) => {
-          certStorage.addCertBySubject(certBase64, subjectBase64,
-                                       Ci.nsICertStorage.TRUST_INHERIT,
-                                       resolve);
-        });
-        if (result != Cr.NS_OK) {
-          Cu.reportError(`Failed to add to cert storage: ${result}`);
-          Services.telemetry.getHistogramById(INTERMEDIATES_ERRORS_TELEMETRY)
-            .add("failedToUpdateDB");
-          return;
-        }
-
-        record.cert_import_complete = true;
-        await col.update(record);
-      })
-      .catch(() => {
-        // Don't abort the outer Promise.all because of an error. Errors were
-        // sent using Cu.reportError()
+      let attachmentData;
+      try {
+        attachmentData = await this._downloadAttachmentBytes(record);
+      } catch (err) {
+        Cu.reportError(`Failed to download attachment: ${err}`);
         Services.telemetry.getHistogramById(INTERMEDIATES_ERRORS_TELEMETRY)
           .add("failedToDownloadMisc");
-      });
+        return result;
+      }
+
+      if (!attachmentData || attachmentData.length == 0) {
+        // Bug 1519273 - Log telemetry for these rejections
+        log.debug(`Empty attachment. Hash=${hash}`);
+
+        Services.telemetry.getHistogramById(INTERMEDIATES_ERRORS_TELEMETRY)
+          .add("emptyAttachment");
+
+        return result;
+      }
+
+      // check the length
+      if (attachmentData.length !== size) {
+        log.debug(`Unexpected attachment length. Hash=${hash} Lengths ${attachmentData.length} != ${size}`);
+
+        Services.telemetry.getHistogramById(INTERMEDIATES_ERRORS_TELEMETRY)
+          .add("unexpectedLength");
+
+        return result;
+      }
+
+      // check the hash
+      let dataAsString = gTextDecoder.decode(attachmentData);
+      let calculatedHash = getHash(dataAsString);
+      if (calculatedHash !== hash) {
+        log.warn(`Invalid hash. CalculatedHash=${calculatedHash}, Hash=${hash}, data=${dataAsString}`);
+
+        Services.telemetry.getHistogramById(INTERMEDIATES_ERRORS_TELEMETRY)
+          .add("unexpectedHash");
+
+        return result;
+      }
+      log.debug(`downloaded cert with hash=${hash}, size=${size}`);
+
+      let certBase64;
+      let subjectBase64;
+      try {
+        // split off the header and footer
+        certBase64 = dataAsString.split("-----")[2].replace(/\s/g, "");
+        // get an array of bytes so we can use X509.jsm
+        let certBytes = stringToBytes(atob(certBase64));
+        let cert = new X509.Certificate();
+        cert.parse(certBytes);
+        // get the DER-encoded subject and get a base64-encoded string from it
+        // TODO(bug 1542028): add getters for _der and _bytes
+        subjectBase64 = btoa(bytesToString(cert.tbsCertificate.subject._der._bytes));
+      } catch (err) {
+        Cu.reportError(`Failed to decode cert: ${err}`);
+
+        // Re-purpose the "failedToUpdateNSS" telemetry tag as "failed to
+        // decode preloaded intermediate certificate"
+        Services.telemetry.getHistogramById(INTERMEDIATES_ERRORS_TELEMETRY)
+          .add("failedToUpdateNSS");
+
+        return result;
+      }
+      result.cert = certBase64;
+      result.subject = subjectBase64;
+      return result;
     }
 
     async maybeSync(expectedTimestamp, options) {
       return this.client.maybeSync(expectedTimestamp, options);
     }
 
     async removeCerts(recordsToRemove) {
       let certStorage = Cc["@mozilla.org/security/certstorage;1"].getService(Ci.nsICertStorage);
-      let failures = 0;
-      for (let record of recordsToRemove) {
-        let result = await new Promise((resolve) => {
-          certStorage.removeCertByHash(record.pubKeyHash, resolve);
-        });
-        if (result != Cr.NS_OK) {
-          Cu.reportError(`Failed to remove intermediate certificate Hash=${record.pubKeyHash}: ${result}`);
-          failures++;
-        }
-      }
-
-      if (failures > 0) {
-        Cu.reportError(`Failed to remove ${failures} intermediate certificates`);
+      let hashes = recordsToRemove.map(record => record.pubKeyHash);
+      let result = await new Promise((resolve) => {
+          certStorage.removeCertsByHashes(hashes, resolve);
+      }).catch((err) => err);
+      if (result != Cr.NS_OK) {
+        Cu.reportError(`Failed to remove some intermediate certificates`);
         Services.telemetry.getHistogramById(INTERMEDIATES_ERRORS_TELEMETRY)
           .add("failedToRemove");
       }
     }
 };
--- a/security/manager/ssl/cert_storage/src/lib.rs
+++ b/security/manager/ssl/cert_storage/src/lib.rs
@@ -21,17 +21,17 @@ extern crate storage_variant;
 extern crate style;
 
 use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt};
 use crossbeam_utils::atomic::AtomicCell;
 use lmdb::EnvironmentFlags;
 use moz_task::{create_thread, is_main_thread, Task, TaskRunnable};
 use nserror::{
     nsresult, NS_ERROR_FAILURE, NS_ERROR_NOT_SAME_THREAD, NS_ERROR_NO_AGGREGATION,
-    NS_ERROR_UNEXPECTED, NS_OK,
+    NS_ERROR_NULL_POINTER, NS_ERROR_UNEXPECTED, NS_OK,
 };
 use nsstring::{nsACString, nsAString, nsCStr, nsCString, nsString};
 use rkv::error::StoreError;
 use rkv::{Rkv, SingleStore, StoreOptions, Value};
 use sha2::{Digest, Sha256};
 use std::collections::HashMap;
 use std::ffi::{CStr, CString};
 use std::fmt::Display;
@@ -42,19 +42,19 @@ use std::os::raw::c_char;
 use std::path::{Path, PathBuf};
 use std::slice;
 use std::str;
 use std::sync::{Arc, Mutex, RwLock};
 use std::time::{Duration, SystemTime};
 use storage_variant::VariantType;
 use thin_vec::ThinVec;
 use xpcom::interfaces::{
-    nsICertStorage, nsICertStorageCallback, nsIFile, nsIIssuerAndSerialRevocationState,
-    nsIObserver, nsIPrefBranch, nsIRevocationState, nsISubjectAndPubKeyRevocationState,
-    nsISupports, nsIThread,
+    nsICertInfo, nsICertStorage, nsICertStorageCallback, nsIFile,
+    nsIIssuerAndSerialRevocationState, nsIObserver, nsIPrefBranch, nsIRevocationState,
+    nsISubjectAndPubKeyRevocationState, nsISupports, nsIThread,
 };
 use xpcom::{nsIID, GetterAddrefs, RefPtr, ThreadBoundRefPtr, XpCom};
 
 const PREFIX_REV_IS: &str = "is";
 const PREFIX_REV_SPK: &str = "spk";
 const PREFIX_SUBJECT: &str = "subject";
 const PREFIX_CERT: &str = "cert";
 const PREFIX_DATA_TYPE: &str = "datatype";
@@ -390,31 +390,29 @@ impl SecurityState {
             "security.onecrl.maximum_staleness_in_seconds",
         )
     }
 
     pub fn pref_seen(&mut self, name: &str, value: u32) {
         self.int_prefs.insert(name.to_owned(), value);
     }
 
-    // To store a certificate by subject, we first create a Cert out of the given cert, subject, and
-    // trust. We hash the certificate with sha-256 to obtain a unique* key for that certificate, and
-    // we store the Cert in the database. We also look up or create a CertHashList for the given
-    // subject and add the new certificate's hash if it isn't present in the list. If it wasn't
-    // present, we write out the updated CertHashList.
+    // To store certificates, we create a Cert out of each given cert, subject, and trust tuple. We
+    // hash each certificate with sha-256 to obtain a unique* key for that certificate, and we store
+    // the Cert in the database. We also look up or create a CertHashList for the given subject and
+    // add the new certificate's hash if it isn't present in the list. If it wasn't present, we
+    // write out the updated CertHashList.
     // *By the pigeon-hole principle, there exist collisions for sha-256, so this key is not
     // actually unique. We rely on the assumption that sha-256 is a cryptographically strong hash.
     // If an adversary can find two different certificates with the same sha-256 hash, they can
     // probably forge a sha-256-based signature, so assuming the keys we create here are unique is
     // not a security issue.
-    pub fn add_cert_by_subject(
+    pub fn add_certs(
         &mut self,
-        cert_der: &[u8],
-        subject: &[u8],
-        trust: i16,
+        certs: &[(Vec<u8>, Vec<u8>, i16)],
     ) -> Result<(), SecurityStateError> {
         self.reopen_store_read_write()?;
         {
             let env_and_store = match self.env_and_store.as_mut() {
                 Some(env_and_store) => env_and_store,
                 None => return Err(SecurityStateError::from("env and store not initialized?")),
             };
             let mut writer = env_and_store.env.write()?;
@@ -423,89 +421,94 @@ impl SecurityState {
                 &mut writer,
                 &make_key!(
                     PREFIX_DATA_TYPE,
                     &[nsICertStorage::DATA_TYPE_CERTIFICATE as u8]
                 ),
                 &Value::Bool(true),
             )?;
 
-            let mut digest = Sha256::default();
-            digest.input(cert_der);
-            let cert_hash = digest.result();
-            let cert_key = make_key!(PREFIX_CERT, &cert_hash);
-            let cert = Cert::new(cert_der, subject, trust)?;
-            env_and_store
-                .store
-                .put(&mut writer, &cert_key, &Value::Blob(&cert.to_bytes()?))?;
-            let subject_key = make_key!(PREFIX_SUBJECT, subject);
-            // This reader will only be able to "see" data outside the current transaction. This is
-            // fine, though, because what we're reading has not yet been touched by this
-            // transaction.
-            let reader = env_and_store.env.read()?;
-            let empty_vec = Vec::new();
-            let old_cert_hash_list = match env_and_store.store.get(&reader, &subject_key)? {
-                Some(Value::Blob(hashes)) => hashes,
-                Some(_) => &empty_vec,
-                None => &empty_vec,
-            };
-            let new_cert_hash_list = CertHashList::add(old_cert_hash_list, &cert_hash)?;
-            if new_cert_hash_list.len() != old_cert_hash_list.len() {
-                env_and_store.store.put(
-                    &mut writer,
-                    &subject_key,
-                    &Value::Blob(&new_cert_hash_list),
-                )?;
+            for (cert_der, subject, trust) in certs {
+                let mut digest = Sha256::default();
+                digest.input(cert_der);
+                let cert_hash = digest.result();
+                let cert_key = make_key!(PREFIX_CERT, &cert_hash);
+                let cert = Cert::new(cert_der, subject, *trust)?;
+                env_and_store
+                    .store
+                    .put(&mut writer, &cert_key, &Value::Blob(&cert.to_bytes()?))?;
+                let subject_key = make_key!(PREFIX_SUBJECT, subject);
+                let empty_vec = Vec::new();
+                let old_cert_hash_list = match env_and_store.store.get(&writer, &subject_key)? {
+                    Some(Value::Blob(hashes)) => hashes.to_owned(),
+                    Some(_) => empty_vec,
+                    None => empty_vec,
+                };
+                let new_cert_hash_list = CertHashList::add(&old_cert_hash_list, &cert_hash)?;
+                if new_cert_hash_list.len() != old_cert_hash_list.len() {
+                    env_and_store.store.put(
+                        &mut writer,
+                        &subject_key,
+                        &Value::Blob(&new_cert_hash_list),
+                    )?;
+                }
             }
 
             writer.commit()?;
         }
         self.reopen_store_read_only()?;
         Ok(())
     }
 
-    // Given a certificate's sha-256 hash, we can look up its Cert entry in the database. We use
-    // this to find its subject so we can look up the CertHashList it should appear in. If that list
-    // contains the given hash, we remove it and update the CertHashList. Finally we delete the Cert
-    // entry.
-    pub fn remove_cert_by_hash(&mut self, hash: &[u8]) -> Result<(), SecurityStateError> {
+    // Given a list of certificate sha-256 hashes, we can look up each Cert entry in the database.
+    // We use this to find the corresponding subject so we can look up the CertHashList it should
+    // appear in. If that list contains the given hash, we remove it and update the CertHashList.
+    // Finally we delete the Cert entry.
+    pub fn remove_certs_by_hashes(&mut self, hashes: &[Vec<u8>]) -> Result<(), SecurityStateError> {
         self.reopen_store_read_write()?;
         {
             let env_and_store = match self.env_and_store.as_mut() {
                 Some(env_and_store) => env_and_store,
                 None => return Err(SecurityStateError::from("env and store not initialized?")),
             };
             let mut writer = env_and_store.env.write()?;
+            let reader = env_and_store.env.read()?;
 
-            let reader = env_and_store.env.read()?;
-            let cert_key = make_key!(PREFIX_CERT, hash);
-            if let Some(Value::Blob(cert_bytes)) = env_and_store.store.get(&reader, &cert_key)? {
-                if let Ok(cert) = Cert::from_bytes(cert_bytes) {
-                    let subject_key = make_key!(PREFIX_SUBJECT, &cert.subject);
-                    let empty_vec = Vec::new();
-                    let old_cert_hash_list = match env_and_store.store.get(&reader, &subject_key)? {
-                        Some(Value::Blob(hashes)) => hashes,
-                        Some(_) => &empty_vec,
-                        None => &empty_vec,
-                    };
-                    let new_cert_hash_list = CertHashList::remove(old_cert_hash_list, hash)?;
-                    if new_cert_hash_list.len() != old_cert_hash_list.len() {
-                        env_and_store.store.put(
-                            &mut writer,
-                            &subject_key,
-                            &Value::Blob(&new_cert_hash_list),
-                        )?;
+            for hash in hashes {
+                let cert_key = make_key!(PREFIX_CERT, hash);
+                if let Some(Value::Blob(cert_bytes)) =
+                    env_and_store.store.get(&reader, &cert_key)?
+                {
+                    if let Ok(cert) = Cert::from_bytes(cert_bytes) {
+                        let subject_key = make_key!(PREFIX_SUBJECT, &cert.subject);
+                        let empty_vec = Vec::new();
+                        // We have to use the writer here to make sure we have an up-to-date view of
+                        // the cert hash list.
+                        let old_cert_hash_list =
+                            match env_and_store.store.get(&writer, &subject_key)? {
+                                Some(Value::Blob(hashes)) => hashes.to_owned(),
+                                Some(_) => empty_vec,
+                                None => empty_vec,
+                            };
+                        let new_cert_hash_list = CertHashList::remove(&old_cert_hash_list, hash)?;
+                        if new_cert_hash_list.len() != old_cert_hash_list.len() {
+                            env_and_store.store.put(
+                                &mut writer,
+                                &subject_key,
+                                &Value::Blob(&new_cert_hash_list),
+                            )?;
+                        }
                     }
                 }
+                match env_and_store.store.delete(&mut writer, &cert_key) {
+                    Ok(()) => {}
+                    Err(StoreError::LmdbError(lmdb::Error::NotFound)) => {}
+                    Err(e) => return Err(SecurityStateError::from(e)),
+                };
             }
-            match env_and_store.store.delete(&mut writer, &cert_key) {
-                Ok(()) => {}
-                Err(StoreError::LmdbError(lmdb::Error::NotFound)) => {}
-                Err(e) => return Err(SecurityStateError::from(e)),
-            };
             writer.commit()?;
         }
         self.reopen_store_read_only()?;
         Ok(())
     }
 
     // Given a certificate's subject, we look up the corresponding CertHashList. In theory, each
     // hash in that list corresponds to a certificate with the given subject, so we look up each of
@@ -1024,17 +1027,17 @@ impl CertStorage {
         &self,
         data_type: u8,
         callback: *const nsICertStorageCallback,
     ) -> nserror::nsresult {
         if !is_main_thread() {
             return NS_ERROR_NOT_SAME_THREAD;
         }
         if callback.is_null() {
-            return NS_ERROR_FAILURE;
+            return NS_ERROR_NULL_POINTER;
         }
         let task = Box::new(SecurityStateTask::new(
             &*callback,
             &self.security_state,
             move |ss| ss.get_has_prior_data(data_type),
         ));
         let thread = try_ns!(self.thread.lock());
         let runnable = try_ns!(TaskRunnable::new("HasPriorData", task));
@@ -1046,17 +1049,17 @@ impl CertStorage {
         &self,
         revocations: *const ThinVec<RefPtr<nsIRevocationState>>,
         callback: *const nsICertStorageCallback,
     ) -> nserror::nsresult {
         if !is_main_thread() {
             return NS_ERROR_NOT_SAME_THREAD;
         }
         if revocations.is_null() || callback.is_null() {
-            return NS_ERROR_FAILURE;
+            return NS_ERROR_NULL_POINTER;
         }
 
         let revocations = &*revocations;
         let mut entries = Vec::with_capacity(revocations.len());
 
         // By continuing when an nsIRevocationState attribute value is invalid,
         // we prevent errors relating to individual blocklist entries from
         // causing sync to fail. We will accumulate telemetry on these failures
@@ -1110,17 +1113,17 @@ impl CertStorage {
         serial: *const ThinVec<u8>,
         subject: *const ThinVec<u8>,
         pub_key: *const ThinVec<u8>,
         state: *mut i16,
     ) -> nserror::nsresult {
         // TODO (bug 1541212): We really want to restrict this to non-main-threads only, but we
         // can't do so until bug 1406854 and bug 1534600 are fixed.
         if issuer.is_null() || serial.is_null() || subject.is_null() || pub_key.is_null() {
-            return NS_ERROR_FAILURE;
+            return NS_ERROR_NULL_POINTER;
         }
         *state = nsICertStorage::STATE_UNSET as i16;
         let ss = get_security_state!(self);
         match ss.get_revocation_state(&*issuer, &*serial, &*subject, &*pub_key) {
             Ok(st) => {
                 *state = st;
                 NS_OK
             }
@@ -1135,74 +1138,88 @@ impl CertStorage {
         *fresh = match ss.is_blocklist_fresh() {
             Ok(is_fresh) => is_fresh,
             Err(_) => false,
         };
 
         NS_OK
     }
 
-    unsafe fn AddCertBySubject(
+    unsafe fn AddCerts(
         &self,
-        cert: *const nsACString,
-        subject: *const nsACString,
-        trust: i16,
+        certs: *const ThinVec<RefPtr<nsICertInfo>>,
         callback: *const nsICertStorageCallback,
     ) -> nserror::nsresult {
         if !is_main_thread() {
             return NS_ERROR_NOT_SAME_THREAD;
         }
-        if cert.is_null() || subject.is_null() || callback.is_null() {
-            return NS_ERROR_FAILURE;
+        if certs.is_null() || callback.is_null() {
+            return NS_ERROR_NULL_POINTER;
         }
-        let cert_decoded = try_ns!(base64::decode(&*cert));
-        let subject_decoded = try_ns!(base64::decode(&*subject));
+        let certs = &*certs;
+        let mut cert_entries = Vec::with_capacity(certs.len());
+        for cert in certs {
+            let mut der = nsCString::new();
+            try_ns!((*cert).GetCert(&mut *der).to_result(), or continue);
+            let der = try_ns!(base64::decode(&der), or continue);
+            let mut subject = nsCString::new();
+            try_ns!((*cert).GetSubject(&mut *subject).to_result(), or continue);
+            let subject = try_ns!(base64::decode(&subject), or continue);
+            let mut trust: i16 = 0;
+            try_ns!((*cert).GetTrust(&mut trust).to_result(), or continue);
+            cert_entries.push((der, subject, trust));
+        }
         let task = Box::new(SecurityStateTask::new(
             &*callback,
             &self.security_state,
-            move |ss| ss.add_cert_by_subject(&cert_decoded, &subject_decoded, trust),
+            move |ss| ss.add_certs(&cert_entries),
         ));
         let thread = try_ns!(self.thread.lock());
-        let runnable = try_ns!(TaskRunnable::new("AddCertBySubject", task));
+        let runnable = try_ns!(TaskRunnable::new("AddCerts", task));
         try_ns!(runnable.dispatch(&*thread));
         NS_OK
     }
 
-    unsafe fn RemoveCertByHash(
+    unsafe fn RemoveCertsByHashes(
         &self,
-        hash: *const nsACString,
+        hashes: *const ThinVec<nsCString>,
         callback: *const nsICertStorageCallback,
     ) -> nserror::nsresult {
         if !is_main_thread() {
             return NS_ERROR_NOT_SAME_THREAD;
         }
-        if hash.is_null() || callback.is_null() {
-            return NS_ERROR_FAILURE;
+        if hashes.is_null() || callback.is_null() {
+            return NS_ERROR_NULL_POINTER;
         }
-        let hash_decoded = try_ns!(base64::decode(&*hash));
+        let hashes = &*hashes;
+        let mut hash_entries = Vec::with_capacity(hashes.len());
+        for hash in hashes {
+            let hash_decoded = try_ns!(base64::decode(&*hash), or continue);
+            hash_entries.push(hash_decoded);
+        }
         let task = Box::new(SecurityStateTask::new(
             &*callback,
             &self.security_state,
-            move |ss| ss.remove_cert_by_hash(&hash_decoded),
+            move |ss| ss.remove_certs_by_hashes(&hash_entries),
         ));
         let thread = try_ns!(self.thread.lock());
-        let runnable = try_ns!(TaskRunnable::new("RemoveCertByHash", task));
+        let runnable = try_ns!(TaskRunnable::new("RemoveCertsByHashes", task));
         try_ns!(runnable.dispatch(&*thread));
         NS_OK
     }
 
     unsafe fn FindCertsBySubject(
         &self,
         subject: *const ThinVec<u8>,
         certs: *mut ThinVec<ThinVec<u8>>,
     ) -> nserror::nsresult {
         // TODO (bug 1541212): We really want to restrict this to non-main-threads only, but we
         // can't do so until bug 1406854 and bug 1534600 are fixed.
         if subject.is_null() || certs.is_null() {
-            return NS_ERROR_FAILURE;
+            return NS_ERROR_NULL_POINTER;
         }
         let ss = get_security_state!(self);
         match ss.find_certs_by_subject(&*subject, &mut *certs) {
             Ok(()) => NS_OK,
             Err(_) => NS_ERROR_FAILURE,
         }
     }
 
--- a/security/manager/ssl/nsICertStorage.idl
+++ b/security/manager/ssl/nsICertStorage.idl
@@ -50,16 +50,32 @@ interface nsIIssuerAndSerialRevocationSt
  * and public key hash are base64-encoded.
  */
 [scriptable, uuid(e78b51b4-6fa4-41e2-92ce-e9404f541e96)]
 interface nsISubjectAndPubKeyRevocationState : nsIRevocationState {
     readonly attribute ACString subject;
     readonly attribute ACString pubKey;
 };
 
+/**
+ * An interface representing a certificate to add to storage. Consists of the
+ * base64-encoded DER bytes of the certificate (cert), the base64-encoded DER
+ * bytes of the subject distinguished name of the certificate (subject), and the
+ * trust of the certificate (one of the nsICertStorage.TRUST_* constants).
+ * (Note that this implementation does not validate that the given subject DN
+ * actually matches the subject DN of the certificate, nor that the given cert
+ * is a valid DER X.509 certificate.)
+ */
+[scriptable, uuid(27b66f5e-0faf-403b-95b4-bc11691ac50d)]
+interface nsICertInfo : nsISupports {
+  readonly attribute ACString cert;
+  readonly attribute ACString subject;
+  readonly attribute short trust;
+};
+
 [scriptable, uuid(327100a7-3401-45ef-b160-bf880f1016fd)]
 interface nsICertStorage : nsISupports {
   const octet DATA_TYPE_REVOCATION = 1;
   const octet DATA_TYPE_CERTIFICATE = 2;
 
   /**
    * Asynchronously check if the backing storage has stored data of the given
    * type in the past. This is useful if the backing storage may have had to
@@ -113,44 +129,37 @@ interface nsICertStorage : nsISupports {
    * TRUST_INHERIT indicates a certificate inherits trust from another
    * certificate.
    * TRUST_ANCHOR indicates the certificate is a root of trust.
    */
   const short TRUST_INHERIT = 0;
   const short TRUST_ANCHOR = 1;
 
   /**
-   * Asynchronously add a certificate to the backing storage.
-   * cert is the bytes of the certificate as base64-encoded DER.
-   * subject is the subject distinguished name of the certificate as
-   * base64-encoded DER (although we don't actually validate that the given
-   * certificate has the indicated subject).
-   * trust is one of the TRUST_* constants in this interface.
+   * Asynchronously add a list of certificates to the backing storage.
+   * See the documentation for nsICertInfo.
    * The given callback is called with the result of the operation when it
    * completes.
    * Must only be called from the main thread.
    */
   [must_use]
-  void addCertBySubject(in ACString cert,
-                        in ACString subject,
-                        in short trust,
-                        in nsICertStorageCallback callback);
+  void addCerts(in Array<nsICertInfo> certs, in nsICertStorageCallback callback);
 
   /**
-   * Asynchronously remove the certificate with the given sha-256 hash from the
-   * backing storage.
-   * hash is the base64-encoded bytes of the sha-256 hash of the certificate's
-   * bytes (DER-encoded).
+   * Asynchronously remove the certificates with the given sha-256 hashes from
+   * the backing storage.
+   * hashes is an array of base64-encoded bytes of the sha-256 hashes of each
+   * certificate's bytes (DER-encoded).
    * The given callback is called with the result of the operation when it
    * completes.
    * Must only be called from the main thread.
    */
   [must_use]
-  void removeCertByHash(in ACString hash,
-                        in nsICertStorageCallback callback);
+  void removeCertsByHashes(in Array<ACString> hashes,
+                           in nsICertStorageCallback callback);
 
   /**
    * Find all certificates in the backing storage with the given subject
    * distinguished name.
    * subject is the DER-encoded bytes of the subject distinguished name.
    * Returns an array of arrays of bytes, where each inner array corresponds to
    * the DER-encoded bytes of a certificate that has the given subject (although
    * as these certificates were presumably added via addCertBySubject, this
--- a/security/manager/ssl/nsNSSCallbacks.cpp
+++ b/security/manager/ssl/nsNSSCallbacks.cpp
@@ -1266,20 +1266,22 @@ void HandshakeCallback(PRFileDesc* fd, v
     }
   } else {
     // TLS 1.3 dropped support for renegotiation.
     siteSupportsSafeRenego = true;
   }
   bool renegotiationUnsafe = !siteSupportsSafeRenego &&
                              ioLayerHelpers.treatUnsafeNegotiationAsBroken();
 
+  bool deprecatedTlsVer =
+      (channelInfo.protocolVersion < SSL_LIBRARY_VERSION_TLS_1_2);
   RememberCertErrorsTable::GetInstance().LookupCertErrorBits(infoObject);
 
   uint32_t state;
-  if (renegotiationUnsafe) {
+  if (renegotiationUnsafe || deprecatedTlsVer) {
     state = nsIWebProgressListener::STATE_IS_BROKEN;
   } else {
     state = nsIWebProgressListener::STATE_IS_SECURE;
     SSLVersionRange defVersion;
     rv = SSL_VersionRangeGetDefault(ssl_variant_stream, &defVersion);
     if (rv == SECSuccess && versions.max >= defVersion.max) {
       // we know this site no longer requires a version fallback
       ioLayerHelpers.removeInsecureFallbackSite(infoObject->GetHostName(),
--- a/security/manager/ssl/security-prefs.js
+++ b/security/manager/ssl/security-prefs.js
@@ -159,18 +159,18 @@ pref("security.pki.mitm_canary_issuer.en
 // Firefox update service's connection.
 // This value is set automatically.
 // The difference between security.pki.mitm_canary_issuer and this pref is that
 // here the root is trusted but not a built-in, whereas for
 // security.pki.mitm_canary_issuer.enabled, the root is not trusted.
 pref("security.pki.mitm_detected", false);
 
 // Intermediate CA Preloading settings
-#if defined(RELEASE_OR_BETA) || defined(MOZ_WIDGET_ANDROID)
+#if defined(MOZ_NEW_CERT_STORAGE) && !defined(MOZ_WIDGET_ANDROID)
+pref("security.remote_settings.intermediates.enabled", true);
+#else
 pref("security.remote_settings.intermediates.enabled", false);
-#else
-pref("security.remote_settings.intermediates.enabled", true);
 #endif
 pref("security.remote_settings.intermediates.bucket", "security-state");
 pref("security.remote_settings.intermediates.collection", "intermediates");
 pref("security.remote_settings.intermediates.checked", 0);
 pref("security.remote_settings.intermediates.downloads_per_poll", 100);
 pref("security.remote_settings.intermediates.signer", "onecrl.content-signature.mozilla.org");
--- a/security/manager/ssl/tests/unit/test_cert_storage_broken_db.js
+++ b/security/manager/ssl/tests/unit/test_cert_storage_broken_db.js
@@ -49,16 +49,15 @@ add_task({
     certStorage.setRevocations([], resolve);
   });
   Assert.equal(result, Cr.NS_OK, "setRevocations should succeed");
 
   check_has_prior_revocation_data(certStorage, true);
   check_has_prior_cert_data(certStorage, false);
 
   result = await new Promise((resolve) => {
-    certStorage.addCertBySubject(btoa("some cert"), btoa("some subject"),
-                                 Ci.nsICertStorage.TRUST_INHERIT, resolve);
+    certStorage.addCerts([], resolve);
   });
-  Assert.equal(result, Cr.NS_OK, "addCertBySubject should succeed");
+  Assert.equal(result, Cr.NS_OK, "addCerts should succeed");
 
   check_has_prior_revocation_data(certStorage, true);
   check_has_prior_cert_data(certStorage, true);
 });
--- a/security/manager/ssl/tests/unit/test_cert_storage_direct.js
+++ b/security/manager/ssl/tests/unit/test_cert_storage_direct.js
@@ -8,29 +8,28 @@
 // integration test).
 
 do_get_profile();
 
 if (AppConstants.MOZ_NEW_CERT_STORAGE) {
   this.certStorage = Cc["@mozilla.org/security/certstorage;1"].getService(Ci.nsICertStorage);
 }
 
-async function addCertBySubject(cert, subject) {
+async function addCerts(certInfos) {
   let result = await new Promise((resolve) => {
-    certStorage.addCertBySubject(btoa(cert), btoa(subject), Ci.nsICertStorage.TRUST_INHERIT,
-                                 resolve);
+    certStorage.addCerts(certInfos, resolve);
   });
-  Assert.equal(result, Cr.NS_OK, "addCertBySubject should succeed");
+  Assert.equal(result, Cr.NS_OK, "addCerts should succeed");
 }
 
-async function removeCertByHash(hashBase64) {
+async function removeCertsByHashes(hashesBase64) {
   let result = await new Promise((resolve) => {
-    certStorage.removeCertByHash(hashBase64, resolve);
+    certStorage.removeCertsByHashes(hashesBase64, resolve);
   });
-  Assert.equal(result, Cr.NS_OK, "removeCertByHash should succeed");
+  Assert.equal(result, Cr.NS_OK, "removeCertsByHashes should succeed");
 }
 
 function stringToArray(s) {
   let a = [];
   for (let i = 0; i < s.length; i++) {
     a.push(s.charCodeAt(i));
   }
   return a;
@@ -43,29 +42,41 @@ function arrayToString(a) {
   }
   return s;
 }
 
 function getLongString(uniquePart, length) {
   return String(uniquePart).padStart(length, "0");
 }
 
+class CertInfo {
+  constructor(cert, subject) {
+    this.cert = btoa(cert);
+    this.subject = btoa(subject);
+    this.trust = Ci.nsICertStorage.TRUST_INHERIT;
+  }
+}
+if (AppConstants.MOZ_NEW_CERT_STORAGE) {
+  CertInfo.prototype.QueryInterface = ChromeUtils.generateQI([Ci.nsICertInfo]);
+}
+
 add_task({
     skip_if: () => !AppConstants.MOZ_NEW_CERT_STORAGE,
   }, async function test_common_subject() {
-  await addCertBySubject("some certificate bytes 1", "some common subject");
-  await addCertBySubject("some certificate bytes 2", "some common subject");
-  await addCertBySubject("some certificate bytes 3", "some common subject");
+  let someCert1 = new CertInfo("some certificate bytes 1", "some common subject");
+  let someCert2 = new CertInfo("some certificate bytes 2", "some common subject");
+  let someCert3 = new CertInfo("some certificate bytes 3", "some common subject");
+  await addCerts([someCert1, someCert2, someCert3]);
   let storedCerts = certStorage.findCertsBySubject(stringToArray("some common subject"));
   let storedCertsAsStrings = storedCerts.map(arrayToString);
   let expectedCerts = ["some certificate bytes 1", "some certificate bytes 2",
                        "some certificate bytes 3"];
   Assert.deepEqual(storedCertsAsStrings.sort(), expectedCerts.sort(), "should find expected certs");
 
-  await addCertBySubject("some other certificate bytes", "some other subject");
+  await addCerts([new CertInfo("some other certificate bytes", "some other subject")]);
   storedCerts = certStorage.findCertsBySubject(stringToArray("some common subject"));
   storedCertsAsStrings = storedCerts.map(arrayToString);
   Assert.deepEqual(storedCertsAsStrings.sort(), expectedCerts.sort(),
                    "should still find expected certs");
 
   let storedOtherCerts = certStorage.findCertsBySubject(stringToArray("some other subject"));
   let storedOtherCertsAsStrings = storedOtherCerts.map(arrayToString);
   let expectedOtherCerts = ["some other certificate bytes"];
@@ -73,64 +84,90 @@ add_task({
 });
 
 add_task({
     skip_if: () => !AppConstants.MOZ_NEW_CERT_STORAGE,
   }, async function test_many_entries() {
   const NUM_CERTS = 500;
   const CERT_LENGTH = 3000;
   const SUBJECT_LENGTH = 40;
+  let certs = [];
   for (let i = 0; i < NUM_CERTS; i++) {
-    await addCertBySubject(getLongString(i, CERT_LENGTH), getLongString(i, SUBJECT_LENGTH));
+    certs.push(new CertInfo(getLongString(i, CERT_LENGTH), getLongString(i, SUBJECT_LENGTH)));
   }
+  await addCerts(certs);
   for (let i = 0; i < NUM_CERTS; i++) {
     let subject = stringToArray(getLongString(i, SUBJECT_LENGTH));
     let storedCerts = certStorage.findCertsBySubject(subject);
     Assert.equal(storedCerts.length, 1, "should have 1 certificate (lots of data test)");
     let storedCertAsString = arrayToString(storedCerts[0]);
     Assert.equal(storedCertAsString, getLongString(i, CERT_LENGTH),
                  "certificate should be as expected (lots of data test)");
   }
 });
 
 add_task({
     skip_if: () => !AppConstants.MOZ_NEW_CERT_STORAGE,
   }, async function test_removal() {
   // As long as cert_storage is given valid base64, attempting to delete some nonexistent
   // certificate will "succeed" (it'll do nothing).
-  await removeCertByHash(btoa("thishashisthewrongsize"));
+  await removeCertsByHashes([btoa("thishashisthewrongsize")]);
 
-  await addCertBySubject("removal certificate bytes 1", "common subject to remove");
-  await addCertBySubject("removal certificate bytes 2", "common subject to remove");
-  await addCertBySubject("removal certificate bytes 3", "common subject to remove");
+  let removalCert1 = new CertInfo("removal certificate bytes 1", "common subject to remove");
+  let removalCert2 = new CertInfo("removal certificate bytes 2", "common subject to remove");
+  let removalCert3 = new CertInfo("removal certificate bytes 3", "common subject to remove");
+  await addCerts([removalCert1, removalCert2, removalCert3]);
 
   let storedCerts = certStorage.findCertsBySubject(stringToArray("common subject to remove"));
   let storedCertsAsStrings = storedCerts.map(arrayToString);
   let expectedCerts = ["removal certificate bytes 1", "removal certificate bytes 2",
                        "removal certificate bytes 3"];
   Assert.deepEqual(storedCertsAsStrings.sort(), expectedCerts.sort(),
                    "should find expected certs before removing them");
 
   // echo -n "removal certificate bytes 2" | sha256sum | xxd -r -p | base64
-  await removeCertByHash("2nUPHwl5TVr1mAD1FU9FivLTlTb0BAdnVUhsYgBccN4=");
+  await removeCertsByHashes(["2nUPHwl5TVr1mAD1FU9FivLTlTb0BAdnVUhsYgBccN4="]);
   storedCerts = certStorage.findCertsBySubject(stringToArray("common subject to remove"));
   storedCertsAsStrings = storedCerts.map(arrayToString);
   expectedCerts = ["removal certificate bytes 1", "removal certificate bytes 3"];
   Assert.deepEqual(storedCertsAsStrings.sort(), expectedCerts.sort(),
                    "should only have first and third certificates now");
 
   // echo -n "removal certificate bytes 1" | sha256sum | xxd -r -p | base64
-  await removeCertByHash("8zoRqHYrklr7Zx6UWpzrPuL+ol8KL1Ml6XHBQmXiaTY=");
+  await removeCertsByHashes(["8zoRqHYrklr7Zx6UWpzrPuL+ol8KL1Ml6XHBQmXiaTY="]);
   storedCerts = certStorage.findCertsBySubject(stringToArray("common subject to remove"));
   storedCertsAsStrings = storedCerts.map(arrayToString);
   expectedCerts = ["removal certificate bytes 3"];
   Assert.deepEqual(storedCertsAsStrings.sort(), expectedCerts.sort(),
                    "should only have third certificate now");
 
   // echo -n "removal certificate bytes 3" | sha256sum | xxd -r -p | base64
-  await removeCertByHash("vZn7GwDSabB/AVo0T+N26nUsfSXIIx4NgQtSi7/0p/w=");
+  await removeCertsByHashes(["vZn7GwDSabB/AVo0T+N26nUsfSXIIx4NgQtSi7/0p/w="]);
   storedCerts = certStorage.findCertsBySubject(stringToArray("common subject to remove"));
   Assert.equal(storedCerts.length, 0, "shouldn't have any certificates now");
 
   // echo -n "removal certificate bytes 3" | sha256sum | xxd -r -p | base64
   // Again, removing a nonexistent certificate should "succeed".
-  await removeCertByHash("vZn7GwDSabB/AVo0T+N26nUsfSXIIx4NgQtSi7/0p/w=");
+  await removeCertsByHashes(["vZn7GwDSabB/AVo0T+N26nUsfSXIIx4NgQtSi7/0p/w="]);
 });
+
+add_task({
+    skip_if: () => !AppConstants.MOZ_NEW_CERT_STORAGE,
+}, async function test_batched_removal() {
+  let removalCert1 = new CertInfo("batch removal certificate bytes 1", "batch subject to remove");
+  let removalCert2 = new CertInfo("batch removal certificate bytes 2", "batch subject to remove");
+  let removalCert3 = new CertInfo("batch removal certificate bytes 3", "batch subject to remove");
+  await addCerts([removalCert1, removalCert2, removalCert3]);
+  let storedCerts = certStorage.findCertsBySubject(stringToArray("batch subject to remove"));
+  let storedCertsAsStrings = storedCerts.map(arrayToString);
+  let expectedCerts = ["batch removal certificate bytes 1", "batch removal certificate bytes 2",
+                       "batch removal certificate bytes 3"];
+  Assert.deepEqual(storedCertsAsStrings.sort(), expectedCerts.sort(),
+                   "should find expected certs before removing them");
+  // echo -n "batch removal certificate bytes 1" | sha256sum | xxd -r -p | base64
+  // echo -n "batch removal certificate bytes 2" | sha256sum | xxd -r -p | base64
+  // echo -n "batch removal certificate bytes 3" | sha256sum | xxd -r -p | base64
+  await removeCertsByHashes(["EOEEUTuanHZX9NFVCoMKVT22puIJC6g+ZuNPpJgvaa8=",
+                             "Xz6h/Kvn35cCLJEZXkjPqk1GG36b56sreLyAXpO+0zg=",
+                             "Jr7XdiTT8ZONUL+ogNNMW2oxKxanvYOLQPKBPgH/has="]);
+  storedCerts = certStorage.findCertsBySubject(stringToArray("batch subject to remove"));
+  Assert.equal(storedCerts.length, 0, "shouldn't have any certificates now");
+});
--- a/services/common/blocklist-clients.js
+++ b/services/common/blocklist-clients.js
@@ -177,81 +177,16 @@ async function updatePinningList({ data:
     } catch (e) {
       // prevent errors relating to individual preload entries from causing
       // sync to fail. We will accumulate telemetry for such failures in bug
       // 1254099.
     }
   }
 }
 
-/**
- * This custom filter function is used to limit the entries returned
- * by `RemoteSettings("...").get()` depending on the target app information
- * defined on entries.
- *
- * Note that this is async because `jexlFilterFunc` is async.
- */
-async function targetAppFilter(entry, environment) {
-  // If the entry has a JEXL filter expression, it should prevail.
-  // The legacy target app mechanism will be kept in place for old entries.
-  // See https://bugzilla.mozilla.org/show_bug.cgi?id=1463377
-  const { filter_expression } = entry;
-  if (filter_expression) {
-    return jexlFilterFunc(entry, environment);
-  }
-
-  // Keep entries without target information.
-  if (!("versionRange" in entry)) {
-    return entry;
-  }
-
-  const { appID, version: appVersion, toolkitVersion } = environment;
-  const { versionRange } = entry;
-
-  // Everywhere in this method, we avoid checking the minVersion, because
-  // we want to retain items whose minVersion is higher than the current
-  // app version, so that we have the items around for app updates.
-
-  // Gfx blocklist has a specific versionRange object, which is not a list.
-  if (!Array.isArray(versionRange)) {
-    const { maxVersion = "*" } = versionRange;
-    const matchesRange = (Services.vc.compare(appVersion, maxVersion) <= 0);
-    return matchesRange ? entry : null;
-  }
-
-  // Iterate the targeted applications, at least one of them must match.
-  // If no target application, keep the entry.
-  if (versionRange.length == 0) {
-    return entry;
-  }
-  for (const vr of versionRange) {
-    const { targetApplication = [] } = vr;
-    if (targetApplication.length == 0) {
-      return entry;
-    }
-    for (const ta of targetApplication) {
-      const { guid } = ta;
-      if (!guid) {
-        return entry;
-      }
-      const { maxVersion = "*" } = ta;
-      if (guid == appID &&
-          Services.vc.compare(appVersion, maxVersion) <= 0) {
-        return entry;
-      }
-      if (guid == "toolkit@mozilla.org" &&
-          Services.vc.compare(toolkitVersion, maxVersion) <= 0) {
-        return entry;
-      }
-    }
-  }
-  // Skip this entry.
-  return null;
-}
-
 var OneCRLBlocklistClient;
 var PinningBlocklistClient;
 var RemoteSecuritySettingsClient;
 
 function initialize(options = {}) {
   const { verifySignature = true } = options;
 
   OneCRLBlocklistClient = RemoteSettings(Services.prefs.getCharPref(PREF_SECURITY_SETTINGS_ONECRL_COLLECTION), {
@@ -286,10 +221,10 @@ function initialize(options = {}) {
   }
 
   return {
     OneCRLBlocklistClient,
     PinningBlocklistClient,
   };
 }
 
-let BlocklistClients = {initialize, targetAppFilter};
+let BlocklistClients = {initialize};
 
--- a/servo/components/style/properties/data.py
+++ b/servo/components/style/properties/data.py
@@ -167,17 +167,17 @@ def parse_property_aliases(alias_list):
 
 class Longhand(object):
     def __init__(self, style_struct, name, spec=None, animation_value_type=None, keyword=None,
                  predefined_type=None, servo_pref=None, gecko_pref=None,
                  enabled_in="content", need_index=False,
                  gecko_ffi_name=None,
                  allowed_in_keyframe_block=True, cast_type='u8',
                  logical=False, logical_group=None, alias=None, extra_prefixes=None, boxed=False,
-                 flags=None, allowed_in_page_rule=False, allow_quirks=False,
+                 flags=None, allowed_in_page_rule=False, allow_quirks="No",
                  ignored_when_colors_disabled=False,
                  vector=False, servo_restyle_damage="repaint"):
         self.name = name
         if not spec:
             raise TypeError("Spec should be specified for %s" % name)
         self.spec = spec
         self.keyword = keyword
         self.predefined_type = predefined_type
--- a/servo/components/style/properties/helpers.mako.rs
+++ b/servo/components/style/properties/helpers.mako.rs
@@ -5,17 +5,17 @@
 <%!
     from data import Keyword, to_rust_ident, to_camel_case, SYSTEM_FONT_LONGHANDS
     from data import LOGICAL_CORNERS, PHYSICAL_CORNERS, LOGICAL_SIDES, PHYSICAL_SIDES, LOGICAL_SIZES
 %>
 
 <%def name="predefined_type(name, type, initial_value, parse_method='parse',
             needs_context=True, vector=False,
             computed_type=None, initial_specified_value=None,
-            allow_quirks=False, allow_empty=False, **kwargs)">
+            allow_quirks='No', allow_empty=False, **kwargs)">
     <%def name="predefined_type_inner(name, type, initial_value, parse_method)">
         #[allow(unused_imports)]
         use app_units::Au;
         #[allow(unused_imports)]
         use cssparser::{Color as CSSParserColor, RGBA};
         #[allow(unused_imports)]
         use crate::values::specified::AllowQuirks;
         #[allow(unused_imports)]
@@ -37,18 +37,18 @@
         #[inline] pub fn get_initial_specified_value() -> SpecifiedValue { ${initial_specified_value} }
         % endif
         #[allow(unused_variables)]
         #[inline]
         pub fn parse<'i, 't>(
             context: &ParserContext,
             input: &mut Parser<'i, 't>,
         ) -> Result<SpecifiedValue, ParseError<'i>> {
-            % if allow_quirks:
-            specified::${type}::${parse_method}_quirky(context, input, AllowQuirks::Yes)
+            % if allow_quirks != "No":
+            specified::${type}::${parse_method}_quirky(context, input, AllowQuirks::${allow_quirks})
             % elif needs_context:
             specified::${type}::${parse_method}(context, input)
             % else:
             specified::${type}::${parse_method}(input)
             % endif
         }
     </%def>
     % if vector:
@@ -400,18 +400,18 @@
                 context.builder.set_${property.ident}(computed)
             % endif
         }
 
         pub fn parse_declared<'i, 't>(
             context: &ParserContext,
             input: &mut Parser<'i, 't>,
         ) -> Result<PropertyDeclaration, ParseError<'i>> {
-            % if property.allow_quirks:
-                parse_quirky(context, input, specified::AllowQuirks::Yes)
+            % if property.allow_quirks != "No":
+                parse_quirky(context, input, specified::AllowQuirks::${property.allow_quirks})
             % else:
                 parse(context, input)
             % endif
             % if property.boxed:
                 .map(Box::new)
             % endif
                 .map(PropertyDeclaration::${property.camel_case})
         }
@@ -863,31 +863,31 @@
             }
             Ok(())
         }
     }
 </%call>
 </%def>
 
 <%def name="four_sides_shorthand(name, sub_property_pattern, parser_function,
-                                 needs_context=True, allow_quirks=False, **kwargs)">
+                                 needs_context=True, allow_quirks='No', **kwargs)">
     <% sub_properties=' '.join(sub_property_pattern % side for side in PHYSICAL_SIDES) %>
     <%call expr="self.shorthand(name, sub_properties=sub_properties, **kwargs)">
         #[allow(unused_imports)]
         use crate::parser::Parse;
         use crate::values::generics::rect::Rect;
         use crate::values::specified;
 
         pub fn parse_value<'i, 't>(
             context: &ParserContext,
             input: &mut Parser<'i, 't>,
         ) -> Result<Longhands, ParseError<'i>> {
             let rect = Rect::parse_with(context, input, |_c, i| {
-            % if allow_quirks:
-                ${parser_function}_quirky(_c, i, specified::AllowQuirks::Yes)
+            % if allow_quirks != "No":
+                ${parser_function}_quirky(_c, i, specified::AllowQuirks::${allow_quirks})
             % elif needs_context:
                 ${parser_function}(_c, i)
             % else:
                 ${parser_function}(i)
             % endif
             })?;
             Ok(expanded! {
                 % for index, side in enumerate(["top", "right", "bottom", "left"]):
--- a/servo/components/style/properties/longhands/background.mako.rs
+++ b/servo/components/style/properties/longhands/background.mako.rs
@@ -9,17 +9,17 @@
 ${helpers.predefined_type(
     "background-color",
     "Color",
     "computed::Color::transparent()",
     initial_specified_value="SpecifiedValue::transparent()",
     spec="https://drafts.csswg.org/css-backgrounds/#background-color",
     animation_value_type="AnimatedColor",
     ignored_when_colors_disabled=True,
-    allow_quirks=True,
+    allow_quirks="Yes",
     flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER \
            CAN_ANIMATE_ON_COMPOSITOR",
 )}
 
 ${helpers.predefined_type(
     "background-image",
     "ImageLayer",
     initial_value="Either::First(None_)",
--- a/servo/components/style/properties/longhands/border.mako.rs
+++ b/servo/components/style/properties/longhands/border.mako.rs
@@ -23,17 +23,17 @@
     ${helpers.predefined_type(
         "border-%s-color" % side_name, "Color",
         "computed_value::T::currentcolor()",
         alias=maybe_moz_logical_alias(product, side, "-moz-border-%s-color"),
         spec=maybe_logical_spec(side, "color"),
         animation_value_type="AnimatedColor",
         logical=is_logical,
         logical_group="border-color",
-        allow_quirks=not is_logical,
+        allow_quirks="No" if is_logical else "Yes",
         flags="APPLIES_TO_FIRST_LETTER",
         ignored_when_colors_disabled=True,
     )}
 
     ${helpers.predefined_type(
         "border-%s-style" % side_name, "BorderStyle",
         "specified::BorderStyle::None",
         alias=maybe_moz_logical_alias(product, side, "-moz-border-%s-style"),
@@ -51,17 +51,17 @@
         "crate::values::computed::NonNegativeLength::new(3.)",
         computed_type="crate::values::computed::NonNegativeLength",
         alias=maybe_moz_logical_alias(product, side, "-moz-border-%s-width"),
         spec=maybe_logical_spec(side, "width"),
         animation_value_type="NonNegativeLength",
         logical=is_logical,
         logical_group="border-width",
         flags="APPLIES_TO_FIRST_LETTER GETCS_NEEDS_LAYOUT_FLUSH",
-        allow_quirks=not is_logical,
+        allow_quirks="No" if is_logical else "Yes",
         servo_restyle_damage="reflow rebuild_and_reflow_inline"
     )}
 % endfor
 
 % for corner in ALL_CORNERS:
     <%
         corner_name = corner[0]
         is_logical = corner[1]
--- a/servo/components/style/properties/longhands/effects.mako.rs
+++ b/servo/components/style/properties/longhands/effects.mako.rs
@@ -32,17 +32,17 @@
 )}
 
 ${helpers.predefined_type(
     "clip",
     "ClipRectOrAuto",
     "computed::ClipRectOrAuto::auto()",
     animation_value_type="ComputedValue",
     boxed=True,
-    allow_quirks=True,
+    allow_quirks="Yes",
     spec="https://drafts.fxtf.org/css-masking/#clip-property",
 )}
 
 ${helpers.predefined_type(
     "filter",
     "Filter",
     None,
     vector=True,
--- a/servo/components/style/properties/longhands/font.mako.rs
+++ b/servo/components/style/properties/longhands/font.mako.rs
@@ -59,17 +59,17 @@
 )}
 
 ${helpers.predefined_type(
     "font-size",
     "FontSize",
     initial_value="computed::FontSize::medium()",
     initial_specified_value="specified::FontSize::medium()",
     animation_value_type="NonNegativeLength",
-    allow_quirks=True,
+    allow_quirks="Yes",
     flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER",
     spec="https://drafts.csswg.org/css-fonts/#propdef-font-size",
     servo_restyle_damage="rebuild_and_reflow",
 )}
 
 ${helpers.predefined_type(
     "font-size-adjust",
     "FontSizeAdjust",
--- a/servo/components/style/properties/longhands/inherited_text.mako.rs
+++ b/servo/components/style/properties/longhands/inherited_text.mako.rs
@@ -51,17 +51,17 @@
 )}
 
 ${helpers.predefined_type(
     "text-indent",
     "LengthPercentage",
     "computed::LengthPercentage::zero()",
     animation_value_type="ComputedValue",
     spec="https://drafts.csswg.org/css-text/#propdef-text-indent",
-    allow_quirks=True,
+    allow_quirks="Yes",
     servo_restyle_damage = "reflow",
 )}
 
 // Also known as "word-wrap" (which is more popular because of IE), but this is
 // the preferred name per CSS-TEXT 6.2.
 ${helpers.predefined_type(
     "overflow-wrap",
     "OverflowWrap",
--- a/servo/components/style/properties/longhands/margin.mako.rs
+++ b/servo/components/style/properties/longhands/margin.mako.rs
@@ -12,17 +12,17 @@
         if side[1]:
             spec = "https://drafts.csswg.org/css-logical-props/#propdef-margin-%s" % side[1]
     %>
     ${helpers.predefined_type(
         "margin-%s" % side[0],
         "LengthPercentageOrAuto",
         "computed::LengthPercentageOrAuto::zero()",
         alias=maybe_moz_logical_alias(product, side, "-moz-margin-%s"),
-        allow_quirks=not side[1],
+        allow_quirks="No" if side[1] else "Yes",
         animation_value_type="ComputedValue",
         logical=side[1],
         logical_group="margin",
         spec=spec,
         flags="APPLIES_TO_FIRST_LETTER GETCS_NEEDS_LAYOUT_FLUSH",
         allowed_in_page_rule=True,
         servo_restyle_damage="reflow"
     )}
--- a/servo/components/style/properties/longhands/padding.mako.rs
+++ b/servo/components/style/properties/longhands/padding.mako.rs
@@ -19,17 +19,17 @@
         "NonNegativeLengthPercentage",
         "computed::NonNegativeLengthPercentage::zero()",
         alias=maybe_moz_logical_alias(product, side, "-moz-padding-%s"),
         animation_value_type="NonNegativeLengthPercentage",
         logical=side[1],
         logical_group="padding",
         spec=spec,
         flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_PLACEHOLDER GETCS_NEEDS_LAYOUT_FLUSH",
-        allow_quirks=not side[1],
+        allow_quirks="No" if side[1] else "Yes",
         servo_restyle_damage="reflow rebuild_and_reflow_inline"
     )}
 % endfor
 
 % for side in ALL_SIDES:
     ${helpers.predefined_type(
         "scroll-padding-%s" % side[0],
         "NonNegativeLengthPercentageOrAuto",
--- a/servo/components/style/properties/longhands/position.mako.rs
+++ b/servo/components/style/properties/longhands/position.mako.rs
@@ -12,17 +12,17 @@
 % for side in PHYSICAL_SIDES:
     ${helpers.predefined_type(
         side,
         "LengthPercentageOrAuto",
         "computed::LengthPercentageOrAuto::auto()",
         spec="https://www.w3.org/TR/CSS2/visuren.html#propdef-%s" % side,
         flags="GETCS_NEEDS_LAYOUT_FLUSH",
         animation_value_type="ComputedValue",
-        allow_quirks=True,
+        allow_quirks="Yes",
         servo_restyle_damage="reflow_out_of_flow",
         logical_group="inset",
     )}
 % endfor
 // inset-* logical properties, map to "top" / "left" / "bottom" / "right"
 % for side in LOGICAL_SIDES:
     ${helpers.predefined_type(
         "inset-%s" % side,
@@ -248,41 +248,41 @@ macro_rules! impl_align_conversions {
     %>
     // width, height, block-size, inline-size
     ${helpers.predefined_type(
         size,
         "Size",
         "computed::Size::auto()",
         logical=logical,
         logical_group="size",
-        allow_quirks=not logical,
+        allow_quirks="No" if logical else "Yes",
         spec=spec % size,
         animation_value_type="Size",
         flags="GETCS_NEEDS_LAYOUT_FLUSH",
         servo_restyle_damage="reflow",
     )}
     // min-width, min-height, min-block-size, min-inline-size
     ${helpers.predefined_type(
         "min-%s" % size,
         "Size",
         "computed::Size::auto()",
         logical=logical,
         logical_group="min-size",
-        allow_quirks=not logical,
+        allow_quirks="No" if logical else "Yes",
         spec=spec % size,
         animation_value_type="Size",
         servo_restyle_damage="reflow",
     )}
     ${helpers.predefined_type(
         "max-%s" % size,
         "MaxSize",
         "computed::MaxSize::none()",
         logical=logical,
         logical_group="max-size",
-        allow_quirks=not logical,
+        allow_quirks="No" if logical else "Yes",
         spec=spec % size,
         animation_value_type="MaxSize",
         servo_restyle_damage="reflow",
     )}
 % endfor
 
 ${helpers.single_keyword(
     "box-sizing",
--- a/servo/components/style/properties/longhands/svg.mako.rs
+++ b/servo/components/style/properties/longhands/svg.mako.rs
@@ -186,8 +186,71 @@
     parse_method="parse_with_cors_anonymous",
     spec="https://drafts.fxtf.org/css-masking/#propdef-mask-image",
     vector=True,
     products="gecko",
     extra_prefixes="webkit",
     animation_value_type="discrete",
     flags="CREATES_STACKING_CONTEXT",
 )}
+
+${helpers.predefined_type(
+    "x",
+    "LengthPercentage",
+    "computed::LengthPercentage::zero()",
+    products="gecko",
+    animation_value_type="ComputedValue",
+    spec="https://svgwg.org/svg2-draft/geometry.html#X",
+)}
+
+${helpers.predefined_type(
+    "y",
+    "LengthPercentage",
+    "computed::LengthPercentage::zero()",
+    products="gecko",
+    animation_value_type="ComputedValue",
+    spec="https://svgwg.org/svg2-draft/geometry.html#Y",
+)}
+
+${helpers.predefined_type(
+    "cx",
+    "LengthPercentage",
+    "computed::LengthPercentage::zero()",
+    products="gecko",
+    animation_value_type="ComputedValue",
+    spec="https://svgwg.org/svg2-draft/geometry.html#CX",
+)}
+
+${helpers.predefined_type(
+    "cy",
+    "LengthPercentage",
+    "computed::LengthPercentage::zero()",
+    products="gecko",
+    animation_value_type="ComputedValue",
+    spec="https://svgwg.org/svg2-draft/geometry.html#CY",
+)}
+
+${helpers.predefined_type(
+    "rx",
+    "NonNegativeLengthPercentageOrAuto",
+    "computed::NonNegativeLengthPercentageOrAuto::auto()",
+    products="gecko",
+    animation_value_type="LengthPercentageOrAuto",
+    spec="https://svgwg.org/svg2-draft/geometry.html#RX",
+)}
+
+${helpers.predefined_type(
+    "ry",
+    "NonNegativeLengthPercentageOrAuto",
+    "computed::NonNegativeLengthPercentageOrAuto::auto()",
+    products="gecko",
+    animation_value_type="LengthPercentageOrAuto",
+    spec="https://svgwg.org/svg2-draft/geometry.html#RY",
+)}
+
+${helpers.predefined_type(
+    "r",
+    "NonNegativeLengthPercentage",
+    "computed::NonNegativeLengthPercentage::zero()",
+    products="gecko",
+    animation_value_type="LengthPercentage",
+    spec="https://svgwg.org/svg2-draft/geometry.html#R",
+)}
--- a/servo/components/style/properties/shorthands/border.mako.rs
+++ b/servo/components/style/properties/shorthands/border.mako.rs
@@ -2,17 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
 
 <%namespace name="helpers" file="/helpers.mako.rs" />
 <% from data import to_rust_ident, ALL_SIDES, PHYSICAL_SIDES, maybe_moz_logical_alias %>
 
 ${helpers.four_sides_shorthand("border-color", "border-%s-color", "specified::Color::parse",
                                spec="https://drafts.csswg.org/css-backgrounds/#border-color",
-                               allow_quirks=True)}
+                               allow_quirks="Yes")}
 
 ${helpers.four_sides_shorthand(
     "border-style",
     "border-%s-style",
     "specified::BorderStyle::parse",
     needs_context=False,
     spec="https://drafts.csswg.org/css-backgrounds/#border-style",
 )}
--- a/servo/components/style/properties/shorthands/margin.mako.rs
+++ b/servo/components/style/properties/shorthands/margin.mako.rs
@@ -5,17 +5,17 @@
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
 ${helpers.four_sides_shorthand(
     "margin",
     "margin-%s",
     "specified::LengthPercentageOrAuto::parse",
     spec="https://drafts.csswg.org/css-box/#propdef-margin",
     allowed_in_page_rule=True,
-    allow_quirks=True,
+    allow_quirks="Yes",
 )}
 
 ${helpers.two_properties_shorthand(
     "margin-block",
     "margin-block-start",
     "margin-block-end",
     "specified::LengthPercentageOrAuto::parse",
     spec="https://drafts.csswg.org/css-logical/#propdef-margin-block"
--- a/servo/components/style/properties/shorthands/padding.mako.rs
+++ b/servo/components/style/properties/shorthands/padding.mako.rs
@@ -4,17 +4,17 @@
 
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
 ${helpers.four_sides_shorthand(
     "padding",
     "padding-%s",
     "specified::NonNegativeLengthPercentage::parse",
     spec="https://drafts.csswg.org/css-box-3/#propdef-padding",
-    allow_quirks=True,
+    allow_quirks="Yes",
 )}
 
 ${helpers.two_properties_shorthand(
     "padding-block",
     "padding-block-start",
     "padding-block-end",
     "specified::NonNegativeLengthPercentage::parse",
     spec="https://drafts.csswg.org/css-logical/#propdef-padding-block"
--- a/servo/components/style/properties/shorthands/position.mako.rs
+++ b/servo/components/style/properties/shorthands/position.mako.rs
@@ -763,17 +763,17 @@
 </%helpers:shorthand>
 
 // See https://github.com/w3c/csswg-drafts/issues/3525 for the quirks stuff.
 ${helpers.four_sides_shorthand(
     "inset",
     "%s",
     "specified::LengthPercentageOrAuto::parse",
     spec="https://drafts.csswg.org/css-logical/#propdef-inset",
-    allow_quirks=False,
+    allow_quirks="No",
 )}
 
 ${helpers.two_properties_shorthand(
     "inset-block",
     "inset-block-start",
     "inset-block-end",
     "specified::LengthPercentageOrAuto::parse",
     spec="https://drafts.csswg.org/css-logical/#propdef-inset-block"
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -4508,17 +4508,17 @@ pub extern "C" fn Servo_DeclarationBlock
     declarations: &RawServoDeclarationBlock,
     property: nsCSSPropertyID,
     value: f32,
     unit: structs::nsCSSUnit,
 ) {
     use style::properties::longhands::_moz_script_min_size::SpecifiedValue as MozScriptMinSize;
     use style::properties::PropertyDeclaration;
     use style::values::generics::NonNegative;
-    use style::values::generics::length::Size;
+    use style::values::generics::length::{Size, LengthPercentageOrAuto};
     use style::values::specified::length::NoCalcLength;
     use style::values::specified::length::{AbsoluteLength, FontRelativeLength};
     use style::values::specified::length::LengthPercentage;
 
     let long = get_longhand_from_id!(property);
     let nocalc = match unit {
         structs::nsCSSUnit::eCSSUnit_EM => {
             NoCalcLength::FontRelative(FontRelativeLength::Em(value))
@@ -4537,16 +4537,24 @@ pub extern "C" fn Servo_DeclarationBlock
         structs::nsCSSUnit::eCSSUnit_Point => NoCalcLength::Absolute(AbsoluteLength::Pt(value)),
         structs::nsCSSUnit::eCSSUnit_Pica => NoCalcLength::Absolute(AbsoluteLength::Pc(value)),
         structs::nsCSSUnit::eCSSUnit_Quarter => NoCalcLength::Absolute(AbsoluteLength::Q(value)),
         _ => unreachable!("Unknown unit passed to SetLengthValue"),
     };
 
     let prop = match_wrap_declared! { long,
         Width => Size::LengthPercentage(NonNegative(LengthPercentage::Length(nocalc))),
+        Height => Size::LengthPercentage(NonNegative(LengthPercentage::Length(nocalc))),
+        X =>  LengthPercentage::Length(nocalc),
+        Y =>  LengthPercentage::Length(nocalc),
+        Cx => LengthPercentage::Length(nocalc),
+        Cy => LengthPercentage::Length(nocalc),
+        R =>  NonNegative(LengthPercentage::Length(nocalc)),
+        Rx => LengthPercentageOrAuto::LengthPercentage(NonNegative(LengthPercentage::Length(nocalc))),
+        Ry => LengthPercentageOrAuto::LengthPercentage(NonNegative(LengthPercentage::Length(nocalc))),
         FontSize => LengthPercentage::from(nocalc).into(),
         MozScriptMinSize => MozScriptMinSize(nocalc),
     };
     write_locked_arc(declarations, |decls: &mut PropertyDeclarationBlock| {
         decls.push(prop, Importance::Normal);
     })
 }
 
@@ -4587,16 +4595,23 @@ pub extern "C" fn Servo_DeclarationBlock
     let long = get_longhand_from_id!(property);
     let pc = Percentage(value);
     let lp = LengthPercentage::Percentage(pc);
     let lp_or_auto = LengthPercentageOrAuto::LengthPercentage(lp.clone());
 
     let prop = match_wrap_declared! { long,
         Height => Size::LengthPercentage(NonNegative(lp)),
         Width => Size::LengthPercentage(NonNegative(lp)),
+        X =>  lp,
+        Y =>  lp,
+        Cx => lp,
+        Cy => lp,
+        R =>  NonNegative(lp),
+        Rx => LengthPercentageOrAuto::LengthPercentage(NonNegative(lp)),
+        Ry => LengthPercentageOrAuto::LengthPercentage(NonNegative(lp)),
         MarginTop => lp_or_auto,
         MarginRight => lp_or_auto,
         MarginBottom => lp_or_auto,
         MarginLeft => lp_or_auto,
         FontSize => LengthPercentage::from(pc).into(),
     };
     write_locked_arc(declarations, |decls: &mut PropertyDeclarationBlock| {
         decls.push(prop, Importance::Normal);
--- a/taskcluster/ci/build/windows.yml
+++ b/taskcluster/ci/build/windows.yml
@@ -265,16 +265,17 @@ win32-shippable/opt:
     attributes:
         shippable: true
         enable-full-crashsymbols: true
     stub-installer:
         by-release-type:
             nightly: true
             beta: true
             release: true
+            esr.*: false
             default:
                 by-project:
                     # browser/confvars.sh looks for nightly-try
                     try: true
                     default: false
     shipping-phase: build
     shipping-product: firefox
     treeherder:
--- a/taskcluster/ci/config.yml
+++ b/taskcluster/ci/config.yml
@@ -259,17 +259,17 @@ partner-urls:
                         by-release-level:
                             production: 'git@github.com:mozilla-partners/mozilla-EME-free-manifest.git'
                             staging: 'git@github.com:moz-releng-automation-stage/mozilla-EME-free-manifest.git'
 
 
 task-priority:
     by-project:
         'mozilla-release': 'highest'
-        'mozilla-esr60': 'very-high'
+        'mozilla-esr.*': 'very-high'
         'mozilla-beta': 'high'
         'mozilla-central': 'medium'
         'autoland': 'low'
         'mozilla-inbound': 'low'
         'default': 'very-low'
 
 workers:
     aliases:
--- a/taskcluster/ci/release-early-tagging/kind.yml
+++ b/taskcluster/ci/release-early-tagging/kind.yml
@@ -22,20 +22,17 @@ job-defaults:
             default: scriptworker-prov-v1/treescript-dev
     worker:
         implementation: treescript
         tags: ['buildN']
         bump: false
         dontbuild: true
         push:
             by-project:
-                mozilla-beta: true
-                mozilla-release: true
-                mozilla-esr52: true
-                mozilla-esr60: true
+                mozilla-(beta|release|esr.*): true
                 maple: true
                 birch: true
                 default: false
 
 jobs:
     fennec:
         name: fennec-tag-buildN
         shipping-product: fennec
--- a/taskcluster/ci/release-notify-promote/kind.yml
+++ b/taskcluster/ci/release-notify-promote/kind.yml
@@ -29,19 +29,17 @@ job-defaults:
             - /bin/bash
             - -c
             - echo "Dummy task"
     notifications:
         subject: "{task[shipping-product]} {release_config[version]} build{release_config[build_number]}/{config[params][project]} is in the candidates directory"
         message: "{task[shipping-product]} {release_config[version]} build{release_config[build_number]}/{config[params][project]} is in the candidates directory"
         emails:
             by-project:
-                mozilla-beta: ["release-signoff@mozilla.org"]
-                mozilla-release: ["release-signoff@mozilla.org"]
-                mozilla-esr60: ["release-signoff@mozilla.org"]
+                mozilla-(beta|release|esr.*): ["release-signoff@mozilla.org"]
                 try: ["{config[params][owner]}"]
                 default: []
 
 jobs:
     fennec:
         shipping-product: fennec
     firefox:
         shipping-product: firefox
--- a/taskcluster/ci/release-notify-push/kind.yml
+++ b/taskcluster/ci/release-notify-push/kind.yml
@@ -27,19 +27,17 @@ job-defaults:
             - /bin/bash
             - -c
             - echo "Dummy task"
     notifications:
         subject: "{task[shipping-product]} {release_config[version]} build{release_config[build_number]}/{config[params][project]} has been pushed to cdntest"
         message: "{task[shipping-product]} {release_config[version]} build{release_config[build_number]}/{config[params][project]} has been pushed to cdntest"
         emails:
             by-project:
-                mozilla-beta: ["release-signoff@mozilla.org"]
-                mozilla-release: ["release-signoff@mozilla.org"]
-                mozilla-esr60: ["release-signoff@mozilla.org"]
+                mozilla-(beta|release|esr.*): ["release-signoff@mozilla.org"]
                 try: ["{config[params][owner]}"]
                 default: []
 
 jobs:
     firefox:
         shipping-product: firefox
     devedition:
         shipping-product: devedition
--- a/taskcluster/ci/release-notify-ship/kind.yml
+++ b/taskcluster/ci/release-notify-ship/kind.yml
@@ -29,19 +29,17 @@ job-defaults:
         max-run-time: 600
         command:
             - /bin/bash
             - -c
             - echo "Dummy task"
     notifications:
         emails:
             by-project:
-                mozilla-beta: ["release-signoff@mozilla.org"]
-                mozilla-release: ["release-signoff@mozilla.org"]
-                mozilla-esr60: ["release-signoff@mozilla.org"]
+                mozilla-(beta|release|esr.*): ["release-signoff@mozilla.org"]
                 try: ["{config[params][owner]}"]
                 default: []
 
 jobs:
     fennec:
         shipping-product: fennec
         notifications:
             subject: "{task[shipping-product]} {release_config[version]} build{release_config[build_number]}/{config[params][project]} has shipped!"
--- a/taskcluster/ci/release-notify-started/kind.yml
+++ b/taskcluster/ci/release-notify-started/kind.yml
@@ -16,19 +16,17 @@ job-defaults:
     run-on-projects: []
     shipping-phase: promote
     worker-type: b-linux
     worker:
         docker-image: {in-tree: "debian9-base"}
         max-run-time: 600
     emails:
         by-project:
-            mozilla-beta: ["release-signoff@mozilla.org"]
-            mozilla-release: ["release-signoff@mozilla.org"]
-            mozilla-esr60: ["release-signoff@mozilla.org"]
+            mozilla-(beta|release|esr.*): ["release-signoff@mozilla.org"]
             try: ["{config[params][owner]}"]
             default: []
 
 jobs:
     fennec:
         shipping-product: fennec
     firefox:
         shipping-product: firefox
--- a/taskcluster/ci/release-secondary-update-verify-config/kind.yml
+++ b/taskcluster/ci/release-secondary-update-verify-config/kind.yml
@@ -52,29 +52,29 @@ job-defaults:
                 default: "56.0b3"
         mar-channel-id-override: beta
         channel: "beta-localtest"
 
 jobs:
     firefox-secondary-linux:
         treeherder:
             symbol: UVCS
-            platform: linux/opt
+            platform: linux-shippable/opt
             kind: test
             tier: 1
         attributes:
             build_platform: linux-shippable
         extra:
             platform: linux-i686
             updater-platform: linux-x86_64
 
     firefox-secondary-linux64:
         treeherder:
             symbol: UVCS
-            platform: linux64/opt
+            platform: linux64-shippable/opt
             kind: test
             tier: 1
         attributes:
             build_platform: linux64-shippable
         extra:
             platform: linux-x86_64
             updater-platform: linux-x86_64
 
@@ -88,40 +88,40 @@ jobs:
             build_platform: macosx64-shippable
         extra:
             platform: mac
             updater-platform: linux-x86_64
 
     firefox-secondary-win32:
         treeherder:
             symbol: UVCS
-            platform: win32/opt
+            platform: windows2012-32-shippable/opt
             kind: test
             tier: 1
         attributes:
             build_platform: win32-shippable
         extra:
             platform: win32
             updater-platform: linux-x86_64
 
     firefox-secondary-win64:
         treeherder:
             symbol: UVCS
-            platform: win64/opt
+            platform: windows2012-64-shippable/opt
             kind: test
             tier: 1
         attributes:
             build_platform: win64-shippable
         extra:
             platform: win64
             updater-platform: linux-x86_64
 
     firefox-secondary-win64-aarch64:
         treeherder:
             symbol: UVCS
-            platform: win64-aarch64/opt
+            platform: windows2012-aarch64-shippable/opt
             kind: test
             tier: 1
         attributes:
             build_platform: win64-aarch64-shippable
         extra:
             platform: win64-aarch64
             updater-platform: linux-x86_64
--- a/taskcluster/ci/release-secondary-update-verify/kind.yml
+++ b/taskcluster/ci/release-secondary-update-verify/kind.yml
@@ -28,77 +28,50 @@ job-defaults:
               type: file
         docker-image:
             in-tree: "update-verify"
         max-run-time: 7200
         retry-exit-status:
             - 255
         env:
             CHANNEL: "beta-localtest"
+    treeherder:
+        symbol: UV(UVS)
+        kind: test
     extra:
         chunks: 12
 
 jobs:
     firefox-secondary-linux64:
         description: linux64 secondary channel update verify
         shipping-product: firefox
-        treeherder:
-            symbol: UV(UVS)
-            platform: linux64/opt
-            kind: test
-            tier: 1
         attributes:
             build_platform: linux64-shippable
 
     firefox-secondary-linux:
         description: linux secondary channel update verify
         shipping-product: firefox
-        treeherder:
-            symbol: UV(UVS)
-            platform: linux32/opt
-            kind: test
-            tier: 1
         attributes:
             build_platform: linux-shippable
 
     firefox-secondary-win64:
         description: win64 secondary channel update verify
         shipping-product: firefox
-        treeherder:
-            symbol: UV(UVS)
-            platform: windows2012-64/opt
-            kind: test
-            tier: 1
         attributes:
             build_platform: win64-shippable
 
     firefox-secondary-win64-aarch64:
         description: win64 secondary channel update verify
         shipping-product: firefox
-        treeherder:
-            symbol: UV(UVS)
-            platform: windows2012-aarch6464/opt
-            kind: test
-            tier: 1
         attributes:
             build_platform: win64-aarch64-shippable
 
     firefox-secondary-win32:
         description: win32 secondary channel update verify
         shipping-product: firefox
-        treeherder:
-            symbol: UV(UVS)
-            platform: windows2012-32/opt
-            kind: test
-            tier: 1
         attributes:
             build_platform: win32-shippable
 
     firefox-secondary-macosx64:
         description: macosx64 secondary channel update verify
         shipping-product: firefox
-        treeherder:
-            symbol: UV(UVS)
-            platform: osx-cross/opt
-            kind: test
-            tier: 1
         attributes:
             build_platform: macosx64-shippable
--- a/taskcluster/ci/release-update-verify-config/kind.yml
+++ b/taskcluster/ci/release-update-verify-config/kind.yml
@@ -47,17 +47,17 @@ job-defaults:
         # because of the special case added by
         # https://bugzilla.mozilla.org/show_bug.cgi?id=1419189
         # The devedition override can be removed after 58.0b1
         # is behind a watershed
         include-version:
             by-release-type:
                 beta: beta
                 release(-rc)?: nonbeta
-                esr60: esr
+                esr.*: esr
                 default: beta
         last-watershed:
             by-release-type:
                 beta:
                     by-platform:
                         win64-aarch64.*: "67.0b2"
                         default: "56.0b3"
                 release(-rc)?:
@@ -72,150 +72,150 @@ job-defaults:
                 esr60: "52.0esr"
                 default: "default"
 
 jobs:
     firefox-linux:
         shipping-product: firefox
         treeherder:
             symbol: UVC
-            platform: linux32/opt
+            platform: linux32-shippable/opt
             kind: test
             tier: 1
         attributes:
             build_platform: linux-shippable
         extra:
             product: firefox
             platform: linux-i686
             updater-platform: linux-x86_64
             channel:
                 by-release-type:
                     beta: "beta-localtest"
                     release(-rc)?: "release-localtest"
-                    esr60: "esr-localtest"
+                    esr.*: "esr-localtest"
                     default: "default"
             mar-channel-id-override:
                 by-release-type:
                     beta: beta
                     default: null
 
     firefox-linux64:
         shipping-product: firefox
         treeherder:
             symbol: UVC
-            platform: linux64/opt
+            platform: linux64-shippable/opt
             kind: test
             tier: 1
         attributes:
             build_platform: linux64-shippable
         extra:
             product: firefox
             platform: linux-x86_64
             updater-platform: linux-x86_64
             channel:
                 by-release-type:
                     beta: "beta-localtest"
                     release(-rc)?: "release-localtest"
-                    esr60: "esr-localtest"
+                    esr.*: "esr-localtest"
                     default: "default"
             mar-channel-id-override:
                 by-release-type:
                     beta: beta
                     default: null
 
     firefox-macosx64:
         shipping-product: firefox
         treeherder:
             symbol: UVC
-            platform: osx-cross/opt
+            platform: osx-shippable/opt
             kind: test
             tier: 1
         attributes:
             build_platform: macosx64-shippable
         extra:
             product: firefox
             platform: mac
             updater-platform: linux-x86_64
             channel:
                 by-release-type:
                     beta: "beta-localtest"
                     release(-rc)?: "release-localtest"
-                    esr60: "esr-localtest"
+                    esr.*: "esr-localtest"
                     default: "default"
             mar-channel-id-override:
                 by-release-type:
                     beta: beta
                     default: null
 
     firefox-win32:
         shipping-product: firefox
         treeherder:
             symbol: UVC
-            platform: windows2012-32/opt
+            platform: windows2012-32-shippable/opt
             kind: test
             tier: 1
         attributes:
             build_platform: win32-shippable
         extra:
             product: firefox
             platform: win32
             updater-platform: linux-x86_64
             channel:
                 by-release-type:
                     beta: "beta-localtest"
                     release(-rc)?: "release-localtest"
-                    esr60: "esr-localtest"
+                    esr.*: "esr-localtest"
                     default: "default"
             mar-channel-id-override:
                 by-release-type:
                     beta: beta
                     default: null
 
     firefox-win64:
         shipping-product: firefox
         treeherder:
             symbol: UVC
-            platform: windows2012-64/opt
+            platform: windows2012-64-shippable/opt
             kind: test
             tier: 1
         attributes:
             build_platform: win64-shippable
         extra:
             product: firefox
             platform: win64
             updater-platform: linux-x86_64
             channel:
                 by-release-type:
                     beta: "beta-localtest"
                     release(-rc)?: "release-localtest"
-                    esr60: "esr-localtest"
+                    esr.*: "esr-localtest"
                     default: "default"
             mar-channel-id-override:
                 by-release-type:
                     beta: beta
                     default: null
 
     firefox-win64-aarch64:
         shipping-product: firefox
         treeherder:
             symbol: UVC
-            platform: windows2012-aarch64/opt
+            platform: windows2012-aarch64-shippable/opt
             kind: test
             tier: 1
         attributes:
             build_platform: win64-aarch64-shippable
         extra:
             product: firefox
             platform: win64-aarch64
             updater-platform: linux-x86_64
             channel:
                 by-release-type:
                     beta: "beta-localtest"
                     release(-rc)?: "release-localtest"
-                    esr60: "esr-localtest"
+                    esr.*: "esr-localtest"
                     default: "default"
             mar-channel-id-override:
                 by-release-type:
                     beta: beta
                     default: null
 
     devedition-linux:
         shipping-product: devedition
--- a/taskcluster/ci/release-update-verify/kind.yml
+++ b/taskcluster/ci/release-update-verify/kind.yml
@@ -26,215 +26,86 @@ job-defaults:
             - name: 'public/build/diff-summary.log'
               path: '/builds/worker/tools/release/updates/diff-summary.log'
               type: file
         docker-image:
             in-tree: "update-verify"
         max-run-time: 5400
         retry-exit-status:
             - 255
+    treeherder:
+        symbol: UV(UV)
+        kind: test
     extra:
         chunks: 16
 
 jobs:
     firefox-linux64:
         description: linux64 update verify
         shipping-product: firefox
-        worker:
-            env:
-                CHANNEL:
-                    by-release-type:
-                        beta: "beta-localtest"
-                        release(-rc)?: "release-localtest"
-                        esr60: "esr-localtest"
-                        nightly: "nightly"
-                        default: "default"
-        treeherder:
-            symbol: UV(UV)
-            platform: linux64/opt
-            kind: test
-            tier: 1
         attributes:
             build_platform: linux64-shippable
 
     firefox-linux:
         description: linux update verify
         shipping-product: firefox
-        worker:
-            env:
-                CHANNEL:
-                    by-release-type:
-                        beta: "beta-localtest"
-                        release(-rc)?: "release-localtest"
-                        esr60: "esr-localtest"
-                        nightly: "nightly"
-                        default: "default"
-        treeherder:
-            symbol: UV(UV)
-            platform: linux32/opt
-            kind: test
-            tier: 1
         attributes:
             build_platform: linux-shippable
 
     firefox-win64:
         description: win64 update verify
         shipping-product: firefox
-        worker:
-            env:
-                CHANNEL:
-                    by-release-type:
-                        beta: "beta-localtest"
-                        release(-rc)?: "release-localtest"
-                        esr60: "esr-localtest"
-                        nightly: "nightly"
-                        default: "default"
-        treeherder:
-            symbol: UV(UV)
-            platform: windows2012-64/opt
-            kind: test
-            tier: 1
         attributes:
             build_platform: win64-shippable
 
     firefox-win64-aarch64:
         description: win64-aarch64 update verify
         shipping-product: firefox
-        worker:
-            env:
-                CHANNEL:
-                    by-release-type:
-                        beta: "beta-localtest"
-                        release(-rc)?: "release-localtest"
-                        esr60: "esr-localtest"
-                        nightly: "nightly"
-                        default: "default"
-        treeherder:
-            symbol: UV(UV)
-            platform: windows2012-aarch64/opt
-            kind: test
-            tier: 1
         attributes:
             build_platform: win64-aarch64-shippable
 
     firefox-win32:
         description: win32 update verify
         shipping-product: firefox
-        worker:
-            env:
-                CHANNEL:
-                    by-release-type:
-                        beta: "beta-localtest"
-                        release(-rc)?: "release-localtest"
-                        esr60: "esr-localtest"
-                        nightly: "nightly"
-                        default: "default"
-        treeherder:
-            symbol: UV(UV)
-            platform: windows2012-32/opt
-            kind: test
-            tier: 1
         attributes:
             build_platform: win32-shippable
 
     firefox-macosx64:
         description: macosx64 update verify
         shipping-product: firefox
-        worker:
-            env:
-                CHANNEL:
-                    by-release-type:
-                        beta: "beta-localtest"
-                        release(-rc)?: "release-localtest"
-                        esr60: "esr-localtest"
-                        nightly: "nightly"
-                        default: "default"
-        treeherder:
-            symbol: UV(UV)
-            platform: osx-cross/opt
-            kind: test
-            tier: 1
         attributes:
             build_platform: macosx64-shippable
 
     devedition-linux64:
         description: linux64 update verify
         shipping-product: devedition
-        worker:
-            env:
-                CHANNEL: "aurora-localtest"
-        treeherder:
-            symbol: UV(UV)
-            platform: linux64-devedition/opt
-            kind: test
-            tier: 1
         attributes:
             build_platform: linux64-devedition-nightly
 
     devedition-linux:
         description: linux update verify
         shipping-product: devedition
-        worker:
-            env:
-                CHANNEL: "aurora-localtest"
-        treeherder:
-            symbol: UV(UV)
-            platform: linux32-devedition/opt
-            kind: test
-            tier: 1
         attributes:
             build_platform: linux-devedition-nightly
 
     devedition-win64:
         description: win64 update verify
         shipping-product: devedition
-        worker:
-            env:
-                CHANNEL: "aurora-localtest"
-        treeherder:
-            symbol: UV(UV)
-            platform: windows2012-64-devedition/opt
-            kind: test
-            tier: 1
         attributes:
             build_platform: win64-devedition-nightly
 
     devedition-win64-aarch64:
         description: win64-aarch64 update verify
         shipping-product: devedition
-        worker:
-            env:
-                CHANNEL: "aurora-localtest"
-        treeherder:
-            symbol: UV(UV)
-            platform: windows2012-aarch64-devedition/opt
-            kind: test
-            tier: 1
         attributes:
             build_platform: win64-aarch64-devedition-nightly
 
     devedition-win32:
         description: win32 update verify
         shipping-product: devedition
-        worker:
-            env:
-                CHANNEL: "aurora-localtest"
-        treeherder:
-            symbol: UV(UV)
-            platform: windows2012-32-devedition/opt
-            kind: test
-            tier: 1
         attributes:
             build_platform: win32-devedition-nightly
 
     devedition-macosx64:
         description: macosx64 update verify
         shipping-product: devedition
-        worker:
-            env:
-                CHANNEL: "aurora-localtest"
-        treeherder:
-            symbol: UV(UV)
-            platform: osx-cross-devedition/opt
-            kind: test
-            tier: 1
         attributes:
             build_platform: macosx64-devedition-nightly
--- a/taskcluster/ci/release-version-bump/kind.yml
+++ b/taskcluster/ci/release-version-bump/kind.yml
@@ -26,38 +26,27 @@ job-defaults:
     worker:
         implementation: treescript
         dontbuild: true
         tags: ['release']
         bump: true
         bump-files:
             by-project:
                 default: ["browser/config/version_display.txt"]
-                mozilla-release:
-                    - "browser/config/version.txt"
-                    - "browser/config/version_display.txt"
-                    - "config/milestone.txt"
-                mozilla-esr52:
-                    - "browser/config/version.txt"
-                    - "browser/config/version_display.txt"
-                    - "config/milestone.txt"
-                mozilla-esr60:
+                mozilla-(release|esr.*):
                     - "browser/config/version.txt"
                     - "browser/config/version_display.txt"
                     - "config/milestone.txt"
                 jamun:
                     - "browser/config/version.txt"
                     - "browser/config/version_display.txt"
                     - "config/milestone.txt"
         push:
             by-project:
-                mozilla-beta: true
-                mozilla-release: true
-                mozilla-esr52: true
-                mozilla-esr60: true
+                mozilla-(beta|release|esr.*): true
                 maple: true
                 birch: true
                 jamun: true
                 default: false
 
 jobs:
     fennec:
         name: fennec-version-bump
--- a/taskcluster/ci/repo-update/kind.yml
+++ b/taskcluster/ci/repo-update/kind.yml
@@ -9,45 +9,33 @@ transforms:
     - taskgraph.transforms.task:transforms
 
 
 job-defaults:
     worker:
         env:
             DO_HSTS:
                 by-project:
-                    mozilla-central: "1"
-                    mozilla-esr60: "1"
-                    mozilla-beta: "1"
+                    mozilla-(central|beta|esr.*): "1"
                     default: ""
             DO_HPKP:
                 by-project:
-                    mozilla-central: "1"
-                    mozilla-esr60: "1"
-                    mozilla-beta: "1"
+                    mozilla-(central|beta|esr.*): "1"
                     default: ""
             DO_BLOCKLIST:
                 by-project:
-                    mozilla-central: "1"
-                    mozilla-esr60: "1"
-                    mozilla-beta: "1"
-                    mozilla-release: "1"
+                    mozilla-(central|beta|release|esr.*): "1"
                     default: ""
             DO_REMOTE_SETTINGS:
                 by-project:
-                    mozilla-central: "1"
-                    mozilla-esr60: "1"
-                    mozilla-beta: "1"
-                    mozilla-release: "1"
+                    mozilla-(central|beta|release|esr.*): "1"
                     default: ""
             DO_SUFFIX_LIST:
                 by-project:
-                    mozilla-central: "1"
-                    mozilla-esr60: "1"
-                    mozilla-beta: "1"
+                    mozilla-(central|beta|esr.*): "1"
                     default: ""
             USE_MOZILLA_CENTRAL:
                 by-project:
                     mozilla-central: "1"
                     default: ""
 
 
 jobs:
--- a/taskcluster/docs/parameters.rst
+++ b/taskcluster/docs/parameters.rst
@@ -151,17 +151,17 @@ Release Promotion
 
 ``app_version``
    Specify the application version for release tasks. For releases, this is often a less specific version number than ``version``.
 
 ``next_version``
    Specify the next version for version bump tasks.
 
 ``release_type``
-   The type of release being promoted. One of "nightly", "beta", "esr60", "release-rc", or "release".
+   The type of release being promoted. One of "nightly", "beta", "esr60", "esr68", "release-rc", or "release".
 
 ``release_eta``
    The time and date when a release is scheduled to live. This value is passed to Balrog.
 
 ``release_enable_partners``
    Boolean which controls repacking vanilla Firefox builds for partners.
 
 ``release_partners``
--- a/taskcluster/scripts/run-task
+++ b/taskcluster/scripts/run-task
@@ -22,27 +22,27 @@ if sys.version_info[0:2] < (3, 5):
 
 
 import argparse
 import datetime
 import errno
 import io
 import json
 import os
+import platform
 import random
 import re
 import shutil
 import socket
 import stat
 import subprocess
 
 import urllib.error
 import urllib.request
 
-
 FINGERPRINT_URL = 'http://taskcluster/secrets/v1/secret/project/taskcluster/gecko/hgfingerprint'
 FALLBACK_FINGERPRINT = {
     'fingerprints':
         "sha256:17:38:aa:92:0b:84:3e:aa:8e:52:52:e9:4c:2f:98:a9:0e:bf:6c:3e:e9"
         ":15:ff:0a:29:80:f7:06:02:5b:e8:48"}
 
 HGMOINTERNAL_CONFIG_URL = 'http://taskcluster/secrets/v1/secret/project/taskcluster/gecko/hgmointernal'
 
@@ -400,17 +400,22 @@ def vcs_checkout(source_repo, dest, stor
     elif branch:
         revision_flag = '--branch'
         revision_value = branch
     else:
         print('revision is not specified for checkout')
         sys.exit(1)
 
     if IS_MACOSX:
+        release = platform.mac_ver()
+        versionNums = release[0].split('.')[:2]
+        os_version = "%s.%s" % (versionNums[0], versionNums[1])
         hg_bin = '/tools/python27-mercurial/bin/hg'
+        if os_version == "10.14":
+            hg_bin = 'hg'
     elif IS_POSIX:
         hg_bin = 'hg'
     elif IS_WINDOWS:
         # This is where OCC installs it in the AMIs.
         hg_bin = r'C:\Program Files\Mercurial\hg.exe'
         if not os.path.exists(hg_bin):
             print('could not find Mercurial executable: %s' % hg_bin)
             sys.exit(1)
--- a/taskcluster/taskgraph/decision.py
+++ b/taskcluster/taskgraph/decision.py
@@ -77,31 +77,41 @@ PER_PROJECT_PARAMETERS = {
         'release_type': 'release',
     },
 
     'mozilla-esr60': {
         'target_tasks_method': 'mozilla_esr60_tasks',
         'release_type': 'esr60',
     },
 
+    'mozilla-esr68': {
+        'target_tasks_method': 'mozilla_esr68_tasks',
+        'release_type': 'esr68',
+    },
+
     'comm-central': {
         'target_tasks_method': 'default',
         'release_type': 'nightly',
     },
 
     'comm-beta': {
         'target_tasks_method': 'mozilla_beta_tasks',
         'release_type': 'beta',
     },
 
     'comm-esr60': {
         'target_tasks_method': 'mozilla_esr60_tasks',
         'release_type': 'release',
     },
 
+    'comm-esr68': {
+        'target_tasks_method': 'mozilla_esr68_tasks',
+        'release_type': 'release',
+    },
+
     'pine': {
         'target_tasks_method': 'pine_tasks',
     },
 
     # the default parameters are used for projects that do not match above.
     'default': {
         'target_tasks_method': 'default',
     }
--- a/taskcluster/taskgraph/task.py
+++ b/taskcluster/taskgraph/task.py
@@ -41,16 +41,25 @@ class Task(object):
     release_artifacts = attr.ib(
         converter=attr.converters.optional(frozenset),
         default=None,
     )
 
     def __attrs_post_init__(self):
         self.attributes['kind'] = self.kind
 
+    @property
+    def name(self):
+        if self.label.startswith(self.kind + "-"):
+            return self.label[len(self.kind)+1:]
+        elif self.label.startswith("build-docker-image-"):
+            return self.label[len("build-docker-image-"):]
+        else:
+            raise AttributeError("Task {} does not have a name.".format(self.label))
+
     def to_json(self):
         rv = {
             'kind': self.kind,
             'label': self.label,
             'attributes': self.attributes,
             'dependencies': self.dependencies,
             'soft_dependencies': self.soft_dependencies,
             'optimization': self.optimization,
--- a/taskcluster/taskgraph/test/python.ini
+++ b/taskcluster/taskgraph/test/python.ini
@@ -9,16 +9,17 @@ skip-if = python == 3
 [test_files_changed.py]
 [test_generator.py]
 [test_graph.py]
 [test_morph.py]
 [test_optimize.py]
 [test_parameters.py]
 [test_target_tasks.py]
 [test_taskgraph.py]
+[test_taskcluster_yml.py]
 [test_transforms_base.py]
 [test_transforms_job.py]
 [test_try_option_syntax.py]
 [test_util_attributes.py]
 [test_util_docker.py]
 [test_util_parameterization.py]
 [test_util_python_path.py]
 [test_util_runnable_jobs.py]
new file mode 100644
--- /dev/null
+++ b/taskcluster/taskgraph/test/test_taskcluster_yml.py
@@ -0,0 +1,118 @@
+# 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/.
+
+from __future__ import absolute_import, unicode_literals
+
+import jsone
+import pprint
+import slugid
+import unittest
+
+from mozunit import main
+
+from taskgraph.util.yaml import load_yaml
+from taskgraph.util.time import current_json_time
+from taskgraph import GECKO
+
+
+class TestTaskclusterYml(unittest.TestCase):
+
+    taskcluster_yml = load_yaml(GECKO, ".taskcluster.yml")
+
+    def test_push(self):
+        context = {
+            "tasks_for": "hg-push",
+            "push": {
+                "revision": "e8d2d9aff5026ef1f1777b781b47fdcbdb9d8f20",
+                "owner": "dustin@mozilla.com",
+                "pushlog_id": 1556565286,
+                "pushdate": 112957,
+            },
+            "repository": {
+                "url": "https://hg.mozilla.org/mozilla-central",
+                "project": "mozilla-central",
+                "level": "3",
+            },
+            "ownTaskId": slugid.nice().encode("ascii"),
+        }
+        rendered = jsone.render(self.taskcluster_yml, context)
+        pprint.pprint(rendered)
+        self.assertEqual(
+            rendered["tasks"][0]["metadata"]["name"], "Gecko Decision Task"
+        )
+
+    def test_cron(self):
+        context = {
+            "tasks_for": "cron",
+            "repository": {
+                "url": "https://hg.mozilla.org/mozilla-central",
+                "project": "mozilla-central",
+                "level": 3,
+            },
+            "push": {
+                "revision": "e8aebe488b2f2e567940577de25013d00e818f7c",
+                "pushlog_id": -1,
+                "pushdate": 0,
+                "owner": "cron",
+            },
+            "cron": {
+                "task_id": "<cron task id>",
+                "job_name": "test",
+                "job_symbol": "T",
+                "quoted_args": "abc def",
+            },
+            "now": current_json_time(),
+            "ownTaskId": slugid.nice().encode("ascii"),
+        }
+        rendered = jsone.render(self.taskcluster_yml, context)
+        pprint.pprint(rendered)
+        self.assertEqual(
+            rendered["tasks"][0]["metadata"]["name"], "Decision Task for cron job test"
+        )
+
+    def test_action(self):
+        context = {
+            "tasks_for": "action",
+            "repository": {
+                "url": "https://hg.mozilla.org/mozilla-central",
+                "project": "mozilla-central",
+                "level": 3,
+            },
+            "push": {
+                "revision": "e8d2d9aff5026ef1f1777b781b47fdcbdb9d8f20",
+                "owner": "dustin@mozilla.com",
+                "pushlog_id": 1556565286,
+                "pushdate": 112957,
+            },
+            "action": {
+                "name": "test-action",
+                "title": "Test Action",
+                "description": "Just testing",
+                "taskGroupId": slugid.nice().encode("ascii"),
+                "symbol": "t",
+                "repo_scope": "assume:repo:hg.mozilla.org/try:action:generic",
+                "cb_name": "test_action",
+            },
+            "input": {},
+            "parameters": {},
+            "now": current_json_time(),
+            "taskId": slugid.nice().encode("ascii"),
+            "ownTaskId": slugid.nice().encode("ascii"),
+            "clientId": "testing/testing/testing",
+        }
+        rendered = jsone.render(self.taskcluster_yml, context)
+        pprint.pprint(rendered)
+        self.assertEqual(
+            rendered["tasks"][0]["metadata"]["name"], "Action: Test Action"
+        )
+
+    def test_unknown(self):
+        context = {"tasks_for": "bitkeeper-push"}
+        rendered = jsone.render(self.taskcluster_yml, context)
+        pprint.pprint(rendered)
+        self.assertEqual(rendered["tasks"], [])
+
+
+if __name__ == "__main__":
+    main()
--- a/taskcluster/taskgraph/transforms/beetmover_langpack_checksums.py
+++ b/taskcluster/taskgraph/transforms/beetmover_langpack_checksums.py
@@ -12,16 +12,17 @@ from taskgraph.transforms.base import Tr
 from taskgraph.transforms.beetmover import craft_release_properties
 from taskgraph.util.attributes import copy_attributes_from_dependent_job
 from taskgraph.util.scriptworker import (generate_beetmover_artifact_map,
                                          generate_beetmover_upstream_artifacts,
                                          get_beetmover_action_scope,
                                          get_beetmover_bucket_scope,
                                          get_worker_type_for_scope,
                                          should_use_artifact_map)
+from taskgraph.util.treeherder import inherit_treeherder_from_dep
 from taskgraph.transforms.task import task_description_schema
 from voluptuous import Required, Optional
 
 beetmover_checksums_description_schema = schema.extend({
     Required('depname', default='build'): basestring,
     Required('attributes'): {basestring: object},
     Optional('label'): basestring,
     Optional('treeherder'): task_description_schema['treeherder'],
@@ -35,27 +36,21 @@ transforms.add_validate(beetmover_checks
 
 
 @transforms.add
 def make_beetmover_checksums_description(config, jobs):
     for job in jobs:
         dep_job = job['primary-dependency']
         attributes = dep_job.attributes
 
-        treeherder = job.get('treeherder', {})
+        treeherder = inherit_treeherder_from_dep(job, dep_job)
         treeherder.setdefault(
             'symbol',
             'BMcslang(N{})'.format(attributes.get('l10n_chunk', ''))
             )
-        dep_th_platform = dep_job.task.get('extra', {}).get(
-            'treeherder', {}).get('machine', {}).get('platform', '')
-        treeherder.setdefault('platform',
-                              "{}/opt".format(dep_th_platform))
-        treeherder.setdefault('tier', 1)
-        treeherder.setdefault('kind', 'build')
 
         label = job['label']
         build_platform = attributes.get('build_platform')
 
         description = "Beetmover submission of checksums for langpack files"
 
         extra = {}
         if 'devedition' in build_platform:
--- a/taskcluster/taskgraph/transforms/job/mach.py
+++ b/taskcluster/taskgraph/transforms/job/mach.py
@@ -39,16 +39,19 @@ defaults = {
 
 
 @run_job_using("docker-worker", "mach", schema=mach_schema, defaults=defaults)
 @run_job_using("generic-worker", "mach", schema=mach_schema, defaults=defaults)
 def configure_mach(config, job, taskdesc):
     run = job['run']
 
     command_prefix = 'cd $GECKO_PATH && ./mach '
+    if job['worker-type'].endswith('1014'):
+        command_prefix = 'cd $GECKO_PATH && LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 ./mach '
+
     mach = run['mach']
     if isinstance(mach, dict):
         ref, pattern = next(iter(mach.items()))
         command = {ref: command_prefix + pattern}
     else:
         command = command_prefix + mach
 
     # defer to the run_task implementation
--- a/taskcluster/taskgraph/transforms/job/python_test.py
+++ b/taskcluster/taskgraph/transforms/job/python_test.py
@@ -35,15 +35,17 @@ defaults = {
 @run_job_using('generic-worker', 'python-test', schema=python_test_schema, defaults=defaults)
 def configure_python_test(config, job, taskdesc):
     run = job['run']
     worker = job['worker']
 
     if worker['os'] == 'macosx' and run['python-version'] == 3:
         # OSX hosts can't seem to find python 3 on their own
         run['python-version'] = '/tools/python36/bin/python3.6'
+        if job['worker-type'].endswith('1014'):
+            run['python-version'] = '/usr/local/bin/python3'
 
     # defer to the mach implementation
     run['mach'] = 'python-test --python {python-version} --subsuite {subsuite}'.format(**run)
     run['using'] = 'mach'
     del run['python-version']
     del run['subsuite']
     configure_taskdesc_for_run(config, job, taskdesc, worker['implementation'])
--- a/taskcluster/taskgraph/transforms/job/run_task.py
+++ b/taskcluster/taskgraph/transforms/job/run_task.py
@@ -125,16 +125,18 @@ def generic_worker_run_task(config, job,
     worker = taskdesc['worker'] = job['worker']
     is_win = worker['os'] == 'windows'
     is_mac = worker['os'] == 'macosx'
 
     if is_win:
         command = ['C:/mozilla-build/python3/python3.exe', 'run-task']
     elif is_mac:
         command = ['/tools/python36/bin/python3.6', 'run-task']
+        if job['worker-type'].endswith('1014'):
+            command = ['/usr/local/bin/python3', 'run-task']
     else:
         command = ['./run-task']
 
     common_setup(config, job, taskdesc, command)
 
     worker.setdefault('mounts', [])
     if run.get('cache-dotcache'):
         worker['mounts'].append({
--- a/taskcluster/taskgraph/transforms/l10n.py
+++ b/taskcluster/taskgraph/transforms/l10n.py
@@ -182,18 +182,17 @@ def _remove_locales(locales, to_remove=N
 
 
 @transforms.add
 def setup_name(config, jobs):
     for job in jobs:
         dep = job['primary-dependency']
         # Set the name to the same as the dep task, without kind name.
         # Label will get set automatically with this kinds name.
-        job['name'] = job.get('name',
-                              dep.task['metadata']['name'][len(dep.kind) + 1:])
+        job['name'] = job.get('name', dep.name)
         yield job
 
 
 @transforms.add
 def copy_in_useful_magic(config, jobs):
     for job in jobs:
         dep = job['primary-dependency']
         attributes = copy_attributes_from_dependent_job(dep)
--- a/taskcluster/taskgraph/transforms/release_beetmover_signed_addons.py
+++ b/taskcluster/taskgraph/transforms/release_beetmover_signed_addons.py
@@ -12,16 +12,17 @@ from taskgraph.transforms.base import Tr
 from taskgraph.transforms.beetmover import craft_release_properties
 from taskgraph.util.attributes import copy_attributes_from_dependent_job
 from taskgraph.util.schema import optionally_keyed_by, resolve_keyed_by
 from taskgraph.util.scriptworker import (get_beetmover_bucket_scope,
                                          get_beetmover_action_scope,
                                          generate_beetmover_upstream_artifacts,
                                          generate_beetmover_artifact_map,
                                          should_use_artifact_map)
+from taskgraph.util.treeherder import inherit_treeherder_from_dep
 from taskgraph.transforms.task import task_description_schema
 from taskgraph.transforms.release_sign_and_push_langpacks import get_upstream_task_ref
 from voluptuous import Required, Optional
 
 import logging
 import copy
 
 logger = logging.getLogger(__name__)
@@ -84,24 +85,18 @@ def resolve_keys(config, jobs):
 
 
 @transforms.add
 def make_task_description(config, jobs):
     for job in jobs:
         dep_job = job['primary-dependency']
         attributes = dep_job.attributes
 
-        treeherder = job.get('treeherder', {})
+        treeherder = inherit_treeherder_from_dep(job, dep_job)
         treeherder.setdefault('symbol', 'langpack(BM{})'.format(attributes.get('l10n_chunk', '')))
-        dep_th_platform = dep_job.task.get('extra', {}).get(
-            'treeherder', {}).get('machine', {}).get('platform', '')
-        treeherder.setdefault('platform',
-                              "{}/opt".format(dep_th_platform))
-        treeherder.setdefault('tier', 1)
-        treeherder.setdefault('kind', 'build')
 
         job['attributes'].update(copy_attributes_from_dependent_job(dep_job))
         job['attributes']['chunk_locales'] = dep_job.attributes['chunk_locales']
 
         job['description'] = job['description'].format(
             locales='/'.join(job['attributes']['chunk_locales']),
             platform=job['attributes']['build_platform']
         )
--- a/taskcluster/taskgraph/transforms/release_sign_and_push_langpacks.py
+++ b/taskcluster/taskgraph/transforms/release_sign_and_push_langpacks.py
@@ -6,16 +6,17 @@ Transform the release-sign-and-push task
 """
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.loader.single_dep import schema
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.util.attributes import copy_attributes_from_dependent_job
 from taskgraph.util.schema import resolve_keyed_by, optionally_keyed_by
+from taskgraph.util.treeherder import inherit_treeherder_from_dep
 from taskgraph.transforms.task import task_description_schema
 from voluptuous import Any, Required
 
 transforms = TransformSequence()
 
 langpack_sign_push_description_schema = schema.extend({
     Required('label'): basestring,
     Required('description'): basestring,
@@ -98,25 +99,20 @@ def filter_out_macos_jobs_but_mac_only_l
             yield job
 
 
 @transforms.add
 def make_task_description(config, jobs):
     for job in jobs:
         dep_job = job['primary-dependency']
 
-        treeherder = job.get('treeherder', {})
+        treeherder = inherit_treeherder_from_dep(job, dep_job)
         treeherder.setdefault('symbol', 'langpack(SnP{})'.format(
             job['attributes'].get('l10n_chunk', '')
         ))
-        dep_th_platform = dep_job.task.get('extra', {}).get(
-            'treeherder', {}).get('machine', {}).get('platform', '')
-        treeherder.setdefault('platform', '{}/opt'.format(dep_th_platform))
-        treeherder.setdefault('tier', 1)
-        treeherder.setdefault('kind', 'build')
 
         job['description'] = job['description'].format(
             locales='/'.join(job['attributes']['chunk_locales']),
         )
 
         job['dependencies'] = {dep_job.kind: dep_job.label}
         job['treeherder'] = treeherder
 
--- a/taskcluster/taskgraph/transforms/update_verify.py
+++ b/taskcluster/taskgraph/transforms/update_verify.py
@@ -5,26 +5,38 @@
 Transform the beetmover task into an actual task description.
 """
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 from copy import deepcopy
 
 from taskgraph.transforms.base import TransformSequence
-from taskgraph.util.schema import resolve_keyed_by
-from taskgraph.util.treeherder import add_suffix
+from taskgraph.util.treeherder import add_suffix, inherit_treeherder_from_dep
 
 transforms = TransformSequence()
 
 
 @transforms.add
 def add_command(config, tasks):
+    config_tasks = {}
+    for dep in config.kind_dependencies_tasks:
+        if 'update-verify-config' in dep.kind:
+            config_tasks[dep.name] = dep
+
     for task in tasks:
+        config_task = config_tasks[task['name']]
         total_chunks = task["extra"]["chunks"]
+        task['worker'].setdefault('env', {}).update(
+            CHANNEL=config_task.task['extra']['channel'],
+        )
+        task.setdefault('fetches', {})[config_task.label] = [
+            "update-verify.cfg",
+        ]
+        task['treeherder'] = inherit_treeherder_from_dep(task, config_task)
 
         for this_chunk in range(1, total_chunks+1):
             chunked = deepcopy(task)
             chunked["treeherder"]["symbol"] = add_suffix(
                 chunked["treeherder"]["symbol"], this_chunk)
             chunked["label"] = "release-update-verify-{}-{}/{}".format(
                 chunked["name"], this_chunk, total_chunks
             )
@@ -35,28 +47,10 @@ def add_command(config, tasks):
                 'command': 'cd /builds/worker/checkouts/gecko && '
                            'tools/update-verify/scripts/chunked-verify.sh '
                            '{} {}'.format(
                                total_chunks,
                                this_chunk,
                            ),
                 'sparse-profile': 'update-verify',
             }
-            for thing in ("CHANNEL", "VERIFY_CONFIG"):
-                thing = "worker.env.{}".format(thing)
-                resolve_keyed_by(
-                    chunked, thing, thing,
-                    **{
-                        'project': config.params['project'],
-                        'release-type': config.params['release_type'],
-                    }
-                )
-
-            for upstream in chunked.get("dependencies", {}).keys():
-                if 'update-verify-config' in upstream:
-                    chunked.setdefault('fetches', {})[upstream] = [
-                        "update-verify.cfg",
-                    ]
-                    break
-            else:
-                raise Exception("Couldn't find upate verify config")
 
             yield chunked
--- a/taskcluster/taskgraph/util/attributes.py
+++ b/taskcluster/taskgraph/util/attributes.py
@@ -14,19 +14,21 @@ INTEGRATION_PROJECTS = {
 
 TRUNK_PROJECTS = INTEGRATION_PROJECTS | {'mozilla-central', 'comm-central'}
 
 RELEASE_PROJECTS = {
     'mozilla-central',
     'mozilla-beta',
     'mozilla-release',
     'mozilla-esr60',
+    'mozilla-esr68',
     'comm-central',
     'comm-beta',
     'comm-esr60',
+    'comm-esr68',
     'oak',
 }
 
 RELEASE_PROMOTION_PROJECTS = {
     'jamun',
     'maple',
     'try',
     'try-comm-central',
--- a/taskcluster/taskgraph/util/scriptworker.py
+++ b/taskcluster/taskgraph/util/scriptworker.py
@@ -53,16 +53,17 @@ SIGNING_SCOPE_ALIAS_TO_PROJECT = [[
         'comm-central',
         'oak',
     ])
 ], [
     'all-release-branches', set([
         'mozilla-beta',
         'mozilla-release',
         'mozilla-esr60',
+        'mozilla-esr68',
         'comm-beta',
         'comm-esr60',
     ])
 ]]
 
 """Map the signing scope aliases to the actual scopes.
 """
 SIGNING_CERT_SCOPES = {
@@ -90,18 +91,20 @@ BEETMOVER_SCOPE_ALIAS_TO_PROJECT = [[
         'comm-central',
         'oak',
     ])
 ], [
     'all-release-branches', set([
         'mozilla-beta',
         'mozilla-release',
         'mozilla-esr60',
+        'mozilla-esr68',
         'comm-beta',
         'comm-esr60',
+        'comm-esr68',
     ])
 ]]
 
 """Map the beetmover scope aliases to the actual scopes.
 """
 BEETMOVER_BUCKET_SCOPES = {
     'all-release-branches': 'beetmover:bucket:release',
     'all-nightly-branches': 'beetmover:bucket:nightly',
@@ -140,30 +143,31 @@ BALROG_SCOPE_ALIAS_TO_PROJECT = [[
         'mozilla-release',
     ])
 ], [
     'esr60', set([
         'mozilla-esr60',
         'comm-esr60',
     ])
 ], [
-    'esr', set([
-        'mozilla-esr52',
+    'esr68', set([
+        'mozilla-esr68',
+        'comm-esr68',
     ])
 ]]
 
 """Map the balrog scope aliases to the actual scopes.
 """
 BALROG_SERVER_SCOPES = {
     'nightly': 'balrog:server:nightly',
     'aurora': 'balrog:server:aurora',
     'beta': 'balrog:server:beta',
     'release': 'balrog:server:release',
-    'esr': 'balrog:server:esr',
     'esr60': 'balrog:server:esr',
+    'esr68': 'balrog:server:esr',
     'default': 'balrog:server:dep',
 }
 
 
 PUSH_APK_SCOPE_ALIAS_TO_PROJECT = [[
     'central', set([
         'mozilla-central',
     ])
--- a/taskcluster/taskgraph/util/workertypes.py
+++ b/taskcluster/taskgraph/util/workertypes.py
@@ -1,17 +1,19 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 from mozbuild.util import memoize
 
+from .taskcluster import get_root_url
 from .keyed_by import evaluate_keyed_by
+from .attributes import keymatch
 
 WORKER_TYPES = {
     'gce/gecko-1-b-linux': ('docker-worker', 'linux'),
     'releng-hardware/gecko-1-b-win2012-gamma': ('generic-worker', 'windows'),
     'gce/gecko-2-b-linux': ('docker-worker', 'linux'),
     'releng-hardware/gecko-2-b-win2012-gamma': ('generic-worker', 'windows'),
     'gce/gecko-3-b-linux': ('docker-worker', 'linux'),
     'releng-hardware/gecko-3-b-win2012-gamma': ('generic-worker', 'windows'),
@@ -26,45 +28,73 @@ WORKER_TYPES = {
     "scriptworker-prov-v1/shipit-dev": ('shipit', None),
     "scriptworker-prov-v1/treescript-v1": ('treescript', None),
     'terraform-packet/gecko-t-linux': ('docker-worker', 'linux'),
     'releng-hardware/gecko-t-osx-1014': ('generic-worker', 'macosx'),
 }
 
 
 @memoize
+def _get(graph_config, alias, level):
+    """Get the configuration for this worker_type alias: {provisioner,
+    worker-type, implementation, os}"""
+    level = str(level)
+
+    # handle the legacy (non-alias) format
+    if '/' in alias:
+        alias = alias.format(level=level)
+        provisioner, worker_type = alias.split("/", 1)
+        try: