Merge inbound to mozilla-central. a=merge
authorBogdan Tara <btara@mozilla.com>
Wed, 31 Jan 2018 12:00:14 +0200
changeset 401683 7b46ef2ae1412b15ed45e7d2367ca491344729f7
parent 401682 e4df6520194038efd1fc4e6a8803b483a1833442 (current diff)
parent 401586 05f077d8f612a29db4495988e5b1fbafc8d12263 (diff)
child 401684 db023d15fb0e52b10679c2d32b839d41df1d0b24
child 401782 cd3a60d995906603a671687f7ac95b523ccbe7db
push id99411
push userbtara@mozilla.com
push dateWed, 31 Jan 2018 10:12:13 +0000
treeherdermozilla-inbound@db023d15fb0e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone60.0a1
first release with
nightly linux32
7b46ef2ae141 / 60.0a1 / 20180131100706 / files
nightly linux64
7b46ef2ae141 / 60.0a1 / 20180131100706 / files
nightly mac
7b46ef2ae141 / 60.0a1 / 20180131100706 / files
nightly win32
7b46ef2ae141 / 60.0a1 / 20180131100706 / files
nightly win64
7b46ef2ae141 / 60.0a1 / 20180131100706 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
browser/extensions/formautofill/ProfileStorage.jsm
modules/libpref/init/all.js
toolkit/components/payments/res/components/address-option.js
--- a/browser/components/extensions/ext-find.js
+++ b/browser/components/extensions/ext-find.js
@@ -15,16 +15,23 @@
  */
 function runFindOperation(params, message) {
   let {tabId} = params;
   let tab = tabId ? tabTracker.getTab(tabId) : tabTracker.activeTab;
   let browser = tab.linkedBrowser;
   let mm = browser.messageManager;
   tabId = tabId || tabTracker.getId(tab);
 
+  // We disallow find in about: urls.
+  if (tab.linkedBrowser.contentPrincipal.isSystemPrincipal ||
+      (["about", "chrome", "resource"].includes(tab.linkedBrowser.currentURI.scheme) &&
+      tab.linkedBrowser.currentURI.spec != "about:blank")) {
+    return Promise.reject({message: `Unable to search: ${tabId}`});
+  }
+
   return new Promise((resolve, reject) => {
     mm.addMessageListener(`ext-Finder:${message}Finished`, function messageListener(message) {
       mm.removeMessageListener(`ext-Finder:${message}Finished`, messageListener);
       switch (message.data) {
         case "Success":
           resolve();
           break;
         case "OutOfRange":
--- a/browser/components/extensions/test/browser/browser_ext_find.js
+++ b/browser/components/extensions/test/browser/browser_ext_find.js
@@ -155,8 +155,33 @@ add_task(async function testDuplicatePin
 
   info("Test that text was highlighted properly.");
   is(message.data.text, "bananA", `The text that was highlighted: - Expected: bananA, Actual: ${message.data.text}`);
 
   info("Test that rectangle data returned from the search matches the highlighted result.");
   is(message.data.rect.top, top, `rect.top: - Expected: ${message.data.rect.top}, Actual: ${top}`);
   is(message.data.rect.left, left, `rect.left: - Expected: ${message.data.rect.left}, Actual: ${left}`);
 });
+
+add_task(async function testAboutFind() {
+  async function background() {
+    await browser.test.assertRejects(
+      browser.find.find("banana"),
+      /Unable to search:/,
+      "Should not be able to search about tabs");
+
+    browser.test.sendMessage("done");
+  }
+
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home");
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "permissions": ["find", "tabs"],
+    },
+    background,
+  });
+
+  await extension.startup();
+  await extension.awaitMessage("done");
+  await extension.unload();
+  await BrowserTestUtils.removeTab(tab);
+});
--- a/browser/components/migration/tests/marionette/test_refresh_firefox.py
+++ b/browser/components/migration/tests/marionette/test_refresh_firefox.py
@@ -419,17 +419,17 @@ class TestFirefoxRefresh(MarionetteTestC
           window.global = {};
           global.LoginInfo = Components.Constructor("@mozilla.org/login-manager/loginInfo;1", "nsILoginInfo", "init");
           global.profSvc = Cc["@mozilla.org/toolkit/profile-service;1"].getService(Ci.nsIToolkitProfileService);
           global.Preferences = Cu.import("resource://gre/modules/Preferences.jsm", {}).Preferences;
           global.FormHistory = Cu.import("resource://gre/modules/FormHistory.jsm", {}).FormHistory;
         """)
         self._formAutofillAvailable = self.runCode("""
           try {
-            global.profileStorage = Cu.import("resource://formautofill/ProfileStorage.jsm", {}).profileStorage;
+            global.profileStorage = Cu.import("resource://formautofill/FormAutofillStorage.jsm", {}).profileStorage;
           } catch(e) {
             return false;
           }
           return true;
         """)
 
     def runCode(self, script, *args, **kwargs):
         return self.marionette.execute_script(script,
--- a/browser/extensions/formautofill/FormAutofillParent.jsm
+++ b/browser/extensions/formautofill/FormAutofillParent.jsm
@@ -55,17 +55,17 @@ const {
   ENABLED_AUTOFILL_CREDITCARDS_PREF,
   CREDITCARDS_COLLECTION_NAME,
 } = FormAutofillUtils;
 
 function FormAutofillParent() {
   // Lazily load the storage JSM to avoid disk I/O until absolutely needed.
   // Once storage is loaded we need to update saved field names and inform content processes.
   XPCOMUtils.defineLazyGetter(this, "profileStorage", () => {
-    let {profileStorage} = ChromeUtils.import("resource://formautofill/ProfileStorage.jsm", {});
+    let {profileStorage} = ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm", {});
     log.debug("Loading profileStorage");
 
     profileStorage.initialize().then(() => {
       // Update the saved field names to compute the status and update child processes.
       this._updateSavedFieldNames();
     });
 
     return profileStorage;
@@ -91,17 +91,17 @@ FormAutofillParent.prototype = {
    *
    * @returns {boolean} Whether FormAutofillParent is initialized.
    */
   get initialized() {
     return this._initialized;
   },
 
   /**
-   * Initializes ProfileStorage and registers the message handler.
+   * Initializes FormAutofillStorage and registers the message handler.
    */
   async init() {
     if (this._initialized) {
       return;
     }
     this._initialized = true;
 
     Services.obs.addObserver(this, "sync-pane-loaded");
@@ -438,17 +438,17 @@ FormAutofillParent.prototype = {
         Services.prefs.setBoolPref(FormAutofillUtils.ADDRESSES_FIRST_TIME_USE_PREF, false);
         showDoorhanger = async () => {
           const description = FormAutofillUtils.getAddressLabel(address.record);
           const state = await FormAutofillDoorhanger.show(target, "firstTimeUse", description);
           if (state !== "open-pref") {
             return;
           }
 
-          target.ownerGlobal.openPreferences("panePrivacy",
+          target.ownerGlobal.openPreferences("privacy-address-autofill",
                                              {origin: "autofillDoorhanger"});
         };
       } else {
         // We want to exclude the first time form filling.
         Services.telemetry.scalarAdd("formautofill.addresses.fill_type_manual", 1);
       }
     }
     return showDoorhanger;
rename from browser/extensions/formautofill/ProfileStorage.jsm
rename to browser/extensions/formautofill/FormAutofillStorage.jsm
--- a/browser/extensions/formautofill/ProfileStorage.jsm
+++ b/browser/extensions/formautofill/FormAutofillStorage.jsm
@@ -89,17 +89,17 @@
  * When saving or updating a credit-card record, the storage will encrypt the
  * value of "cc-number", store the encrypted number in "cc-number-encrypted"
  * field, and replace "cc-number" field with the masked number. These all happen
  * in "_computeFields". We do reverse actions in "_stripComputedFields", which
  * decrypts "cc-number-encrypted", restores it to "cc-number", and deletes
  * "cc-number-encrypted". Therefore, calling "_stripComputedFields" followed by
  * "_computeFields" can make sure the encrypt-related fields are up-to-date.
  *
- * In general, you have to decrypt the number by your own outside ProfileStorage
+ * In general, you have to decrypt the number by your own outside FormAutofillStorage
  * when necessary. However, you will get the decrypted records when querying
  * data with "rawData=true" to ensure they're ready to sync.
  *
  *
  * Sync Metadata:
  *
  * Records may also have a _sync field, which consists of:
  * {
@@ -1736,23 +1736,23 @@ class CreditCards extends AutofillRecord
       return true;
     }
 
     this.update(guid, creditCardToMerge, true);
     return true;
   }
 }
 
-function ProfileStorage(path) {
+function FormAutofillStorage(path) {
   this._path = path;
   this._initializePromise = null;
   this.INTERNAL_FIELDS = INTERNAL_FIELDS;
 }
 
-ProfileStorage.prototype = {
+FormAutofillStorage.prototype = {
   get version() {
     return STORAGE_SCHEMA_VERSION;
   },
 
   get addresses() {
     if (!this._addresses) {
       this._store.ensureDataReady();
       this._addresses = new Addresses(this._store);
@@ -1799,10 +1799,10 @@ ProfileStorage.prototype = {
 
   // For test only.
   _saveImmediately() {
     return this._store._save();
   },
 };
 
 // The singleton exposed by this module.
-this.profileStorage = new ProfileStorage(
+this.profileStorage = new FormAutofillStorage(
   OS.Path.join(OS.Constants.Path.profileDir, PROFILE_JSON_FILE_NAME));
--- a/browser/extensions/formautofill/FormAutofillSync.jsm
+++ b/browser/extensions/formautofill/FormAutofillSync.jsm
@@ -14,17 +14,17 @@ ChromeUtils.import("resource://services-
 ChromeUtils.import("resource://services-sync/record.js");
 ChromeUtils.import("resource://services-sync/util.js");
 ChromeUtils.import("resource://services-sync/constants.js");
 ChromeUtils.import("resource://formautofill/FormAutofillUtils.jsm");
 
 ChromeUtils.defineModuleGetter(this, "Log",
                                "resource://gre/modules/Log.jsm");
 ChromeUtils.defineModuleGetter(this, "profileStorage",
-                               "resource://formautofill/ProfileStorage.jsm");
+                               "resource://formautofill/FormAutofillStorage.jsm");
 
 // A helper to sanitize address and creditcard records suitable for logging.
 function sanitizeStorageObject(ob) {
   if (!ob) {
     return null;
   }
   const whitelist = ["timeCreated", "timeLastUsed", "timeLastModified"];
   let result = {};
@@ -218,17 +218,17 @@ FormAutofillTracker.prototype = {
     Services.obs.addObserver(this, "formautofill-storage-changed");
   },
 
   stopTracking() {
     Services.obs.removeObserver(this, "formautofill-storage-changed");
   },
 
   // We never want to persist changed IDs, as the changes are already stored
-  // in ProfileStorage
+  // in FormAutofillStorage
   persistChangedIDs: false,
 
   // Ensure we aren't accidentally using the base persistence.
   get changedIDs() {
     throw new Error("changedIDs isn't meaningful for this engine");
   },
 
   set changedIDs(obj) {
@@ -268,17 +268,17 @@ class AutofillChangeset extends Changese
     }
     return false;
   }
 
   delete(id) {
     let change = this.changes[id];
     if (change) {
       // Mark the change as synced without removing it from the set. We do this
-      // so that we can update ProfileStorage in `trackRemainingChanges`.
+      // so that we can update FormAutofillStorage in `trackRemainingChanges`.
       change.synced = true;
     }
   }
 }
 
 function FormAutofillEngine(service, name) {
   SyncEngine.call(this, name, service);
 }
@@ -287,17 +287,17 @@ FormAutofillEngine.prototype = {
   __proto__: SyncEngine.prototype,
 
   // the priority for this engine is == addons, so will happen after bookmarks
   // prefs and tabs, but before forms, history, etc.
   syncPriority: 5,
 
   // We don't use SyncEngine.initialize() for this, as we initialize even if
   // the engine is disabled, and we don't want to be the loader of
-  // ProfileStorage in this case.
+  // FormAutofillStorage in this case.
   async _syncStartup() {
     await profileStorage.initialize();
     await SyncEngine.prototype._syncStartup.call(this);
   },
 
   // We handle reconciliation in the store, not the engine.
   async _reconcile() {
     return true;
--- a/browser/extensions/formautofill/MasterPassword.jsm
+++ b/browser/extensions/formautofill/MasterPassword.jsm
@@ -109,17 +109,17 @@ this.MasterPassword = {
     }
     return cryptoSDR.decrypt(cipherText);
   },
 
   /**
    * Decrypts cipherText synchronously. "ensureLoggedIn()" needs to be called
    * outside in case another dialog is showing.
    *
-   * NOTE: This method will be removed soon once the ProfileStorage APIs are
+   * NOTE: This method will be removed soon once the FormAutofillStorage APIs are
    *       refactored to be async functions (bug 1399367). Please use async
    *       version instead.
    *
    * @deprecated
    * @param   {string} cipherText Encrypted string including the algorithm details.
    * @returns {string} The decrypted string.
    */
   decryptSync(cipherText) {
@@ -142,17 +142,17 @@ this.MasterPassword = {
 
     return cryptoSDR.encrypt(plainText);
   },
 
   /**
    * Encrypts plainText synchronously. "ensureLoggedIn()" needs to be called
    * outside in case another dialog is showing.
    *
-   * NOTE: This method will be removed soon once the ProfileStorage APIs are
+   * NOTE: This method will be removed soon once the FormAutofillStorage APIs are
    *       refactored to be async functions (bug 1399367). Please use async
    *       version instead.
    *
    * @deprecated
    * @param   {string} plainText A plain string to be encrypted.
    * @returns {string} The encrypted cipher string.
    */
   encryptSync(plainText) {
--- a/browser/extensions/formautofill/content/editDialog.js
+++ b/browser/extensions/formautofill/content/editDialog.js
@@ -10,17 +10,17 @@ const {classes: Cc, interfaces: Ci, util
 const AUTOFILL_BUNDLE_URI = "chrome://formautofill/locale/formautofill.properties";
 const REGIONS_BUNDLE_URI = "chrome://global/locale/regionNames.properties";
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://formautofill/FormAutofillUtils.jsm");
 
 ChromeUtils.defineModuleGetter(this, "profileStorage",
-                               "resource://formautofill/ProfileStorage.jsm");
+                               "resource://formautofill/FormAutofillStorage.jsm");
 ChromeUtils.defineModuleGetter(this, "MasterPassword",
                                "resource://formautofill/MasterPassword.jsm");
 
 class EditDialog {
   constructor(subStorageName, elements, record) {
     this._storageInitPromise = profileStorage.initialize();
     this._subStorageName = subStorageName;
     this._elements = elements;
--- a/browser/extensions/formautofill/content/manageDialog.js
+++ b/browser/extensions/formautofill/content/manageDialog.js
@@ -11,17 +11,17 @@ const EDIT_ADDRESS_URL = "chrome://forma
 const EDIT_CREDIT_CARD_URL = "chrome://formautofill/content/editCreditCard.xhtml";
 const AUTOFILL_BUNDLE_URI = "chrome://formautofill/locale/formautofill.properties";
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://formautofill/FormAutofillUtils.jsm");
 
 ChromeUtils.defineModuleGetter(this, "profileStorage",
-                               "resource://formautofill/ProfileStorage.jsm");
+                               "resource://formautofill/FormAutofillStorage.jsm");
 ChromeUtils.defineModuleGetter(this, "MasterPassword",
                                "resource://formautofill/MasterPassword.jsm");
 
 this.log = null;
 FormAutofillUtils.defineLazyLogGetter(this, "manageAddresses");
 
 class ManageRecords {
   constructor(subStorageName, elements) {
--- a/browser/extensions/formautofill/test/browser/browser_first_time_use_doorhanger.js
+++ b/browser/extensions/formautofill/test/browser/browser_first_time_use_doorhanger.js
@@ -82,17 +82,17 @@ add_task(async function test_first_time_
       [SYNC_USERNAME_PREF, "foo@bar.com"],
     ],
   });
 
   await BrowserTestUtils.withNewTab({gBrowser, url: FORM_URL},
     async function(browser) {
       let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
                                                        "popupshown");
-      let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, "about:preferences#privacy");
+      let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, "about:preferences#privacy-address-autofill");
       await ContentTask.spawn(browser, null, async function() {
         let form = content.document.getElementById("form");
         form.querySelector("#organization").focus();
         form.querySelector("#organization").value = "Foobar";
         form.querySelector("#email").value = "foo@bar.com";
         form.querySelector("#tel").value = "1-234-567-8900";
 
         // Wait 500ms before submission to make sure the input value applied
--- a/browser/extensions/formautofill/test/mochitest/formautofill_parent_utils.js
+++ b/browser/extensions/formautofill/test/mochitest/formautofill_parent_utils.js
@@ -4,17 +4,17 @@
 
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://formautofill/FormAutofillUtils.jsm");
 
-let {profileStorage} = ChromeUtils.import("resource://formautofill/ProfileStorage.jsm", {});
+let {profileStorage} = ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm", {});
 
 const {ADDRESSES_COLLECTION_NAME, CREDITCARDS_COLLECTION_NAME} = FormAutofillUtils;
 
 var ParentUtils = {
   async _getRecords(collectionName) {
     return new Promise(resolve => {
       Services.cpmm.addMessageListener("FormAutofill:Records", function getResult({data}) {
         Services.cpmm.removeMessageListener("FormAutofill:Records", getResult);
--- a/browser/extensions/formautofill/test/unit/head.js
+++ b/browser/extensions/formautofill/test/unit/head.js
@@ -54,19 +54,19 @@ Components.manager.addBootstrappedManife
 
 // Returns a reference to a temporary file that is guaranteed not to exist and
 // is cleaned up later. See FileTestUtils.getTempFile for details.
 function getTempFile(leafName) {
   return FileTestUtils.getTempFile(leafName);
 }
 
 async function initProfileStorage(fileName, records, collectionName = "addresses") {
-  let {ProfileStorage} = ChromeUtils.import("resource://formautofill/ProfileStorage.jsm", {});
+  let {FormAutofillStorage} = ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm", {});
   let path = getTempFile(fileName).path;
-  let profileStorage = new ProfileStorage(path);
+  let profileStorage = new FormAutofillStorage(path);
   await profileStorage.initialize();
 
   if (!records || !Array.isArray(records)) {
     return profileStorage;
   }
 
   let onChanged = TestUtils.topicObserved(
     "formautofill-storage-changed",
--- a/browser/extensions/formautofill/test/unit/test_activeStatus.js
+++ b/browser/extensions/formautofill/test/unit/test_activeStatus.js
@@ -1,16 +1,16 @@
 /*
  * Test for status handling in Form Autofill Parent.
  */
 
 "use strict";
 
 let {FormAutofillParent} = ChromeUtils.import("resource://formautofill/FormAutofillParent.jsm", {});
-ChromeUtils.import("resource://formautofill/ProfileStorage.jsm");
+ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm");
 
 add_task(async function test_activeStatus_init() {
   let formAutofillParent = new FormAutofillParent();
   sinon.spy(formAutofillParent, "_updateStatus");
 
   // Default status is null before initialization
   Assert.equal(formAutofillParent._active, null);
   Assert.equal(Services.ppmm.initialProcessData.autofillEnabled, undefined);
--- a/browser/extensions/formautofill/test/unit/test_addressRecords.js
+++ b/browser/extensions/formautofill/test/unit/test_addressRecords.js
@@ -1,10 +1,10 @@
 /**
- * Tests ProfileStorage object with addresses records.
+ * Tests FormAutofillStorage object with addresses records.
  */
 
 "use strict";
 
 const TEST_STORE_FILE_NAME = "test-profile.json";
 const COLLECTION_NAME = "addresses";
 
 const TEST_ADDRESS_1 = {
--- a/browser/extensions/formautofill/test/unit/test_creditCardRecords.js
+++ b/browser/extensions/formautofill/test/unit/test_creditCardRecords.js
@@ -1,15 +1,15 @@
 /**
- * Tests ProfileStorage object with creditCards records.
+ * Tests FormAutofillStorage object with creditCards records.
  */
 
 "use strict";
 
-const {ProfileStorage} = ChromeUtils.import("resource://formautofill/ProfileStorage.jsm", {});
+const {FormAutofillStorage} = ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm", {});
 
 const TEST_STORE_FILE_NAME = "test-credit-card.json";
 const COLLECTION_NAME = "creditCards";
 
 const TEST_CREDIT_CARD_1 = {
   "cc-name": "John Doe",
   "cc-number": "1234567812345678",
   "cc-exp-month": 4,
@@ -141,17 +141,17 @@ const MERGE_TESTCASES = [
       "cc-number": "1234567812345678",
       "cc-exp-month": 4,
       "cc-exp-year": 2017,
     },
   },
 ];
 
 let prepareTestCreditCards = async function(path) {
-  let profileStorage = new ProfileStorage(path);
+  let profileStorage = new FormAutofillStorage(path);
   await profileStorage.initialize();
 
   let onChanged = TestUtils.topicObserved(
     "formautofill-storage-changed",
     (subject, data) =>
       data == "add" &&
       subject.wrappedJSObject.collectionName == COLLECTION_NAME
   );
@@ -175,38 +175,38 @@ let do_check_credit_card_matches = (cred
     } else {
       Assert.equal(creditCardWithMeta[key], creditCard[key]);
     }
   }
 };
 
 add_task(async function test_initialize() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
-  let profileStorage = new ProfileStorage(path);
+  let profileStorage = new FormAutofillStorage(path);
   await profileStorage.initialize();
 
   Assert.equal(profileStorage._store.data.version, 1);
   Assert.equal(profileStorage._store.data.creditCards.length, 0);
 
   let data = profileStorage._store.data;
   Assert.deepEqual(data.creditCards, []);
 
   await profileStorage._saveImmediately();
 
-  profileStorage = new ProfileStorage(path);
+  profileStorage = new FormAutofillStorage(path);
   await profileStorage.initialize();
 
   Assert.deepEqual(profileStorage._store.data, data);
 });
 
 add_task(async function test_getAll() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
   await prepareTestCreditCards(path);
 
-  let profileStorage = new ProfileStorage(path);
+  let profileStorage = new FormAutofillStorage(path);
   await profileStorage.initialize();
 
   let creditCards = profileStorage.creditCards.getAll();
 
   Assert.equal(creditCards.length, 2);
   do_check_credit_card_matches(creditCards[0], TEST_CREDIT_CARD_1);
   do_check_credit_card_matches(creditCards[1], TEST_CREDIT_CARD_2);
 
@@ -225,17 +225,17 @@ add_task(async function test_getAll() {
   creditCards[0]["cc-name"] = "test";
   do_check_credit_card_matches(profileStorage.creditCards.getAll()[0], TEST_CREDIT_CARD_1);
 });
 
 add_task(async function test_get() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
   await prepareTestCreditCards(path);
 
-  let profileStorage = new ProfileStorage(path);
+  let profileStorage = new FormAutofillStorage(path);
   await profileStorage.initialize();
 
   let creditCards = profileStorage.creditCards.getAll();
   let guid = creditCards[0].guid;
 
   let creditCard = profileStorage.creditCards.get(guid);
   do_check_credit_card_matches(creditCard, TEST_CREDIT_CARD_1);
 
@@ -245,17 +245,17 @@ add_task(async function test_get() {
 
   Assert.equal(profileStorage.creditCards.get("INVALID_GUID"), null);
 });
 
 add_task(async function test_add() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
   await prepareTestCreditCards(path);
 
-  let profileStorage = new ProfileStorage(path);
+  let profileStorage = new FormAutofillStorage(path);
   await profileStorage.initialize();
 
   let creditCards = profileStorage.creditCards.getAll();
 
   Assert.equal(creditCards.length, 2);
 
   do_check_credit_card_matches(creditCards[0], TEST_CREDIT_CARD_1);
   do_check_credit_card_matches(creditCards[1], TEST_CREDIT_CARD_2);
@@ -288,17 +288,17 @@ add_task(async function test_add() {
   Assert.throws(() => profileStorage.creditCards.add(TEST_CREDIT_CARD_EMPTY_AFTER_NORMALIZE),
     /Record contains no valid field\./);
 });
 
 add_task(async function test_update() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
   await prepareTestCreditCards(path);
 
-  let profileStorage = new ProfileStorage(path);
+  let profileStorage = new FormAutofillStorage(path);
   await profileStorage.initialize();
 
   let creditCards = profileStorage.creditCards.getAll();
   let guid = creditCards[1].guid;
   let timeLastModified = creditCards[1].timeLastModified;
 
   let onChanged = TestUtils.topicObserved(
     "formautofill-storage-changed",
@@ -307,17 +307,17 @@ add_task(async function test_update() {
       subject.wrappedJSObject.collectionName == COLLECTION_NAME
   );
 
   Assert.notEqual(creditCards[1]["cc-name"], undefined);
   profileStorage.creditCards.update(guid, TEST_CREDIT_CARD_3);
   await onChanged;
   await profileStorage._saveImmediately();
 
-  profileStorage = new ProfileStorage(path);
+  profileStorage = new FormAutofillStorage(path);
   await profileStorage.initialize();
 
   let creditCard = profileStorage.creditCards.get(guid);
 
   Assert.equal(creditCard["cc-name"], undefined);
   Assert.notEqual(creditCard.timeLastModified, timeLastModified);
   do_check_credit_card_matches(creditCard, TEST_CREDIT_CARD_3);
 
@@ -362,17 +362,17 @@ add_task(async function test_update() {
     () => profileStorage.creditCards.update(guid, TEST_CREDIT_CARD_EMPTY_AFTER_UPDATE_CREDIT_CARD_1),
     /Record contains no valid field\./
   );
 });
 
 add_task(async function test_validate() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
 
-  let profileStorage = new ProfileStorage(path);
+  let profileStorage = new FormAutofillStorage(path);
   await profileStorage.initialize();
 
   profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_INVALID_EXPIRY_DATE);
   profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_2_DIGITS_YEAR);
   profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_SPACES_BETWEEN_DIGITS);
 
   let creditCards = profileStorage.creditCards.getAll();
 
@@ -388,48 +388,48 @@ add_task(async function test_validate() 
 
   Assert.equal(creditCards[2]["cc-number"].length, 16);
 });
 
 add_task(async function test_notifyUsed() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
   await prepareTestCreditCards(path);
 
-  let profileStorage = new ProfileStorage(path);
+  let profileStorage = new FormAutofillStorage(path);
   await profileStorage.initialize();
 
   let creditCards = profileStorage.creditCards.getAll();
   let guid = creditCards[1].guid;
   let timeLastUsed = creditCards[1].timeLastUsed;
   let timesUsed = creditCards[1].timesUsed;
 
   let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
                                           (subject, data) => data == "notifyUsed");
 
   profileStorage.creditCards.notifyUsed(guid);
   await onChanged;
   await profileStorage._saveImmediately();
 
-  profileStorage = new ProfileStorage(path);
+  profileStorage = new FormAutofillStorage(path);
   await profileStorage.initialize();
 
   let creditCard = profileStorage.creditCards.get(guid);
 
   Assert.equal(creditCard.timesUsed, timesUsed + 1);
   Assert.notEqual(creditCard.timeLastUsed, timeLastUsed);
 
   Assert.throws(() => profileStorage.creditCards.notifyUsed("INVALID_GUID"),
     /No matching record\./);
 });
 
 add_task(async function test_remove() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
   await prepareTestCreditCards(path);
 
-  let profileStorage = new ProfileStorage(path);
+  let profileStorage = new FormAutofillStorage(path);
   await profileStorage.initialize();
 
   let creditCards = profileStorage.creditCards.getAll();
   let guid = creditCards[1].guid;
 
   let onChanged = TestUtils.topicObserved(
     "formautofill-storage-changed",
     (subject, data) =>
@@ -438,17 +438,17 @@ add_task(async function test_remove() {
   );
 
   Assert.equal(creditCards.length, 2);
 
   profileStorage.creditCards.remove(guid);
   await onChanged;
   await profileStorage._saveImmediately();
 
-  profileStorage = new ProfileStorage(path);
+  profileStorage = new FormAutofillStorage(path);
   await profileStorage.initialize();
 
   creditCards = profileStorage.creditCards.getAll();
 
   Assert.equal(creditCards.length, 1);
 
   Assert.equal(profileStorage.creditCards.get(guid), null);
 });
--- a/browser/extensions/formautofill/test/unit/test_getRecords.js
+++ b/browser/extensions/formautofill/test/unit/test_getRecords.js
@@ -1,17 +1,17 @@
 /*
  * Test for make sure getRecords can retrieve right collection from storage.
  */
 
 "use strict";
 
 let {FormAutofillParent} = ChromeUtils.import("resource://formautofill/FormAutofillParent.jsm", {});
 ChromeUtils.import("resource://formautofill/MasterPassword.jsm");
-ChromeUtils.import("resource://formautofill/ProfileStorage.jsm");
+ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm");
 
 const TEST_ADDRESS_1 = {
   "given-name": "Timothy",
   "additional-name": "John",
   "family-name": "Berners-Lee",
   organization: "World Wide Web Consortium",
   "street-address": "32 Vassar Street\nMIT Room 32-G524",
   "address-level2": "Cambridge",
@@ -257,9 +257,8 @@ add_task(async function test_getRecords_
     }
     let mock = sinon.mock(target);
     mock.expects("sendAsyncMessage").once().withExactArgs("FormAutofill:Records",
                                                           testCase.expectedResult);
     await formAutofillParent._getRecords(testCase.filter, target);
     mock.verify();
   }
 });
-
--- a/browser/extensions/formautofill/test/unit/test_migrateRecords.js
+++ b/browser/extensions/formautofill/test/unit/test_migrateRecords.js
@@ -1,15 +1,15 @@
 /**
  * Tests the migration algorithm in profileStorage.
  */
 
 "use strict";
 
-const {ProfileStorage} = ChromeUtils.import("resource://formautofill/ProfileStorage.jsm", {});
+const {FormAutofillStorage} = ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm", {});
 
 const TEST_STORE_FILE_NAME = "test-profile.json";
 
 const ADDRESS_SCHEMA_VERSION = 1;
 const CREDIT_CARD_SCHEMA_VERSION = 1;
 
 const ADDRESS_TESTCASES = [
   {
@@ -234,30 +234,30 @@ let do_check_record_matches = (expectedR
   for (let key in expectedRecord) {
     Assert.equal(expectedRecord[key], record[key]);
   }
 };
 
 add_task(async function test_migrateAddressRecords() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
 
-  let profileStorage = new ProfileStorage(path);
+  let profileStorage = new FormAutofillStorage(path);
   await profileStorage.initialize();
 
   ADDRESS_TESTCASES.forEach(testcase => {
     info(testcase.description);
     profileStorage.addresses._migrateRecord(testcase.record);
     do_check_record_matches(testcase.expectedResult, testcase.record);
   });
 });
 
 add_task(async function test_migrateCreditCardRecords() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
 
-  let profileStorage = new ProfileStorage(path);
+  let profileStorage = new FormAutofillStorage(path);
   await profileStorage.initialize();
 
   CREDIT_CARD_TESTCASES.forEach(testcase => {
     info(testcase.description);
     profileStorage.creditCards._migrateRecord(testcase.record);
     do_check_record_matches(testcase.expectedResult, testcase.record);
   });
 });
--- a/browser/extensions/formautofill/test/unit/test_savedFieldNames.js
+++ b/browser/extensions/formautofill/test/unit/test_savedFieldNames.js
@@ -1,16 +1,16 @@
 /*
  * Test for keeping the valid fields information in initialProcessData.
  */
 
 "use strict";
 
 let {FormAutofillParent} = ChromeUtils.import("resource://formautofill/FormAutofillParent.jsm", {});
-ChromeUtils.import("resource://formautofill/ProfileStorage.jsm");
+ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm");
 
 add_task(async function test_profileSavedFieldNames_init() {
   let formAutofillParent = new FormAutofillParent();
   sinon.stub(formAutofillParent, "_updateSavedFieldNames");
 
   await formAutofillParent.init();
   await formAutofillParent.profileStorage.initialize();
   Assert.equal(formAutofillParent._updateSavedFieldNames.called, true);
--- a/browser/extensions/formautofill/test/unit/test_storage_syncfields.js
+++ b/browser/extensions/formautofill/test/unit/test_storage_syncfields.js
@@ -1,10 +1,10 @@
 /**
- * Tests ProfileStorage objects support for sync related fields.
+ * Tests FormAutofillStorage objects support for sync related fields.
  */
 
 "use strict";
 
 // The duplication of some of these fixtures between tests is unfortunate.
 const TEST_STORE_FILE_NAME = "test-profile.json";
 
 const TEST_ADDRESS_1 = {
--- a/browser/extensions/formautofill/test/unit/test_storage_tombstones.js
+++ b/browser/extensions/formautofill/test/unit/test_storage_tombstones.js
@@ -1,15 +1,15 @@
 /**
  * Tests tombstones in address/creditcard records.
  */
 
 "use strict";
 
-const {ProfileStorage} = ChromeUtils.import("resource://formautofill/ProfileStorage.jsm", {});
+const {FormAutofillStorage} = ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm", {});
 
 const TEST_STORE_FILE_NAME = "test-tombstones.json";
 
 const TEST_ADDRESS_1 = {
   "given-name": "Timothy",
   "additional-name": "John",
   "family-name": "Berners-Lee",
   organization: "World Wide Web Consortium",
@@ -34,17 +34,17 @@ let do_check_tombstone_record = (profile
   Assert.deepEqual(Object.keys(profile).sort(),
                    ["guid", "timeLastModified", "deleted"].sort());
 };
 
 // Like add_task, but actually adds 2 - one for addresses and one for cards.
 function add_storage_task(test_function) {
   add_task(async function() {
     let path = getTempFile(TEST_STORE_FILE_NAME).path;
-    let profileStorage = new ProfileStorage(path);
+    let profileStorage = new FormAutofillStorage(path);
     let testCC1 = Object.assign({}, TEST_CC_1);
     await profileStorage.initialize();
 
     for (let [storage, record] of [[profileStorage.addresses, TEST_ADDRESS_1],
                                    [profileStorage.creditCards, testCC1]]) {
       await test_function(storage, record);
     }
   });
--- a/browser/extensions/formautofill/test/unit/test_transformFields.js
+++ b/browser/extensions/formautofill/test/unit/test_transformFields.js
@@ -1,15 +1,15 @@
 /**
  * Tests the transform algorithm in profileStorage.
  */
 
 "use strict";
 
-const {ProfileStorage} = ChromeUtils.import("resource://formautofill/ProfileStorage.jsm", {});
+const {FormAutofillStorage} = ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm", {});
 
 const TEST_STORE_FILE_NAME = "test-profile.json";
 
 const ADDRESS_COMPUTE_TESTCASES = [
   // Name
   {
     description: "Has split names",
     address: {
@@ -836,68 +836,68 @@ let do_check_record_matches = (expectedR
   for (let key in expectedRecord) {
     Assert.equal(expectedRecord[key], record[key]);
   }
 };
 
 add_task(async function test_computeAddressFields() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
 
-  let profileStorage = new ProfileStorage(path);
+  let profileStorage = new FormAutofillStorage(path);
   await profileStorage.initialize();
 
   ADDRESS_COMPUTE_TESTCASES.forEach(testcase => {
     info("Verify testcase: " + testcase.description);
 
     let guid = profileStorage.addresses.add(testcase.address);
     let address = profileStorage.addresses.get(guid);
     do_check_record_matches(testcase.expectedResult, address);
 
     profileStorage.addresses.remove(guid);
   });
 });
 
 add_task(async function test_normalizeAddressFields() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
 
-  let profileStorage = new ProfileStorage(path);
+  let profileStorage = new FormAutofillStorage(path);
   await profileStorage.initialize();
 
   ADDRESS_NORMALIZE_TESTCASES.forEach(testcase => {
     info("Verify testcase: " + testcase.description);
 
     let guid = profileStorage.addresses.add(testcase.address);
     let address = profileStorage.addresses.get(guid);
     do_check_record_matches(testcase.expectedResult, address);
 
     profileStorage.addresses.remove(guid);
   });
 });
 
 add_task(async function test_computeCreditCardFields() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
 
-  let profileStorage = new ProfileStorage(path);
+  let profileStorage = new FormAutofillStorage(path);
   await profileStorage.initialize();
 
   CREDIT_CARD_COMPUTE_TESTCASES.forEach(testcase => {
     info("Verify testcase: " + testcase.description);
 
     let guid = profileStorage.creditCards.add(testcase.creditCard);
     let creditCard = profileStorage.creditCards.get(guid);
     do_check_record_matches(testcase.expectedResult, creditCard);
 
     profileStorage.creditCards.remove(guid);
   });
 });
 
 add_task(async function test_normalizeCreditCardFields() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
 
-  let profileStorage = new ProfileStorage(path);
+  let profileStorage = new FormAutofillStorage(path);
   await profileStorage.initialize();
 
   CREDIT_CARD_NORMALIZE_TESTCASES.forEach(testcase => {
     info("Verify testcase: " + testcase.description);
 
     let guid = profileStorage.creditCards.add(testcase.creditCard);
     let creditCard = profileStorage.creditCards.get(guid, {rawData: true});
     do_check_record_matches(testcase.expectedResult, creditCard);
--- a/chrome/nsChromeRegistry.cpp
+++ b/chrome/nsChromeRegistry.cpp
@@ -230,16 +230,22 @@ nsChromeRegistry::Canonify(nsIURL* aChro
     }
     aChromeURL->SetPathQueryRef(path);
   }
   else {
     // prevent directory traversals ("..")
     // path is already unescaped once, but uris can get unescaped twice
     const char* pos = path.BeginReading();
     const char* end = path.EndReading();
+    // Must start with [a-zA-Z0-9].
+    if (!('a' <= *pos && *pos <= 'z') &&
+        !('A' <= *pos && *pos <= 'Z') &&
+        !('0' <= *pos && *pos <= '9')) {
+      return NS_ERROR_DOM_BAD_URI;
+    }
     while (pos < end) {
       switch (*pos) {
         case ':':
           return NS_ERROR_DOM_BAD_URI;
         case '.':
           if (pos[1] == '.')
             return NS_ERROR_DOM_BAD_URI;
           break;
--- a/dom/animation/KeyframeEffectReadOnly.cpp
+++ b/dom/animation/KeyframeEffectReadOnly.cpp
@@ -1671,16 +1671,22 @@ KeyframeEffectReadOnly::ShouldBlockAsync
     if (property.mProperty == eCSSProperty_transform) {
       if (!CanAnimateTransformOnCompositor(aFrame,
                                            aPerformanceWarning)) {
         return true;
       }
     }
   }
 
+  // XXX cku temporarily disable async-animation when this frame has any
+  // individual transforms before bug 1425837 been fixed.
+  if (aFrame->StyleDisplay()->HasIndividualTransform()) {
+    return true;
+  }
+
   return false;
 }
 
 bool
 KeyframeEffectReadOnly::HasGeometricProperties() const
 {
   for (const AnimationProperty& property : mProperties) {
     if (IsGeometricProperty(property.mProperty)) {
--- a/dom/ipc/ContentPrefs.cpp
+++ b/dom/ipc/ContentPrefs.cpp
@@ -148,16 +148,17 @@ const char* mozilla::dom::ContentPrefs::
   "layout.css.float-logical-values.enabled",
   "layout.css.font-display.enabled",
   "layout.css.font-variations.enabled",
   "layout.css.frames-timing.enabled",
   "layout.css.getBoxQuads.enabled",
   "layout.css.grid-template-subgrid-value.enabled",
   "layout.css.grid.enabled",
   "layout.css.image-orientation.enabled",
+  "layout.css.individual-transform.enabled",
   "layout.css.initial-letter.enabled",
   "layout.css.isolation.enabled",
   "layout.css.mix-blend-mode.enabled",
   "layout.css.moz-document.content.enabled",
   "layout.css.osx-font-smoothing.enabled",
   "layout.css.overflow-clip-box.enabled",
   "layout.css.overscroll-behavior.enabled",
   "layout.css.prefixes.animations",
--- a/gfx/doc/README.webrender
+++ b/gfx/doc/README.webrender
@@ -31,34 +31,34 @@ try pushes to verify the update. After t
 actually land the update into the tree.
 
 Option A:
    Use a script to do the update for you. This will usually work, if you satisfy
    all the assumptions the script is making. The script can be found at
    https://github.com/staktrace/moz-scripts/blob/master/try-latest-webrender.sh
    and contains documentation on how to use it. Read the documentation carefully
    before trying to use it. The only extra change you need to make with this
-   option is to manually update the revision at the bottom of gfx/doc/README.webrender
+   option is to manually update the revision in gfx/webrender_bindings/revision.txt
    so that it points to the new WR version you are landing. The script doesn't
    do that yet.
 
 Option B:
    Do the update manually. This is a little more cumbersome but may be required
    if the script doesn't work or the repos are in a state that violates hidden
    assumptions in the script (e.g. if the webrender_bindings/Cargo.toml file is
    no longer in the format expected by the script). The steps to do this are,
    roughly:
    - Update your mozilla-central checkout to the latest code on mozilla-central.
    - Check out and update the webrender repo to the version you want
    - Copy over the webrender, webrender_api, and part of the wrench folders into
      gfx/. The best way to do this is to simply delete the gfx/webrender,
      gfx/webrender_api, and gfx/wrench folders and use |cp -R| to copy them in
      again from the webrender repo, and then delete the gfx/wrench/reftests,
-     gfx/wrench/benchmarks, and gfx/wrench/script folders. Update the "latest
-     commit" information at the bottom of this file with the version.
+     gfx/wrench/benchmarks, and gfx/wrench/script folders. Update the revision
+     in gfx/webrender_bindings/revision.txt file with the git changeset hash.
    - If you need to modify webrender_bindings/Cargo.toml file, do so now. Changes
      at this step usually consist of:
      (a) Updating version numbers. Go through the version numbers of ALL the
          dependencies in the Cargo.toml file (webrender, euclid, etc.) and make
          sure the version numbers listed match what's in the new
          gfx/webrender/Cargo.toml and gfx/webrender_api/Cargo.toml files.
      (b) Turning on or off any new features that were added in upstream WR. This
          used to happen a lot but is pretty rare now.
@@ -171,10 +171,9 @@ 2. Sometimes autoland tip has changed en
    on top of autoland tip rather than central. (The script-based update in option A
    has an env var you can set to do this). In theory you can get the same
    result by resolving the conflict manually but Cargo.lock files are usually not
    trivial to merge by hand. If it's just the third_party/rust dir that has conflicts
    you can delete it and run |mach vendor rust| again to repopulate it.
 
 -------------------------------------------------------------------------------
 
-The version of WebRender currently in the tree is:
-b6e69a8efbcd8dc3e0c0a8a9925e6a9355635de3
+The current WebRender revision can be found in gfx/webrender_bindings/revision.txt file.
new file mode 100644
--- /dev/null
+++ b/gfx/webrender_bindings/revision.txt
@@ -0,0 +1,1 @@
+b6e69a8efbcd8dc3e0c0a8a9925e6a9355635de3
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -1158,20 +1158,30 @@ pub extern "C" fn wr_resource_updates_ad
 }
 
 #[no_mangle]
 pub extern "C" fn wr_api_capture(
     dh: &mut DocumentHandle,
     path: *const c_char,
     bits_raw: u32,
 ) {
+    use std::fs::File;
+    use std::io::Write;
+
     let cstr = unsafe { CStr::from_ptr(path) };
     let path = PathBuf::from(&*cstr.to_string_lossy());
+    let revision_path = path.join("wr.txt");
     let bits = CaptureBits::from_bits(bits_raw as _).unwrap();
     dh.api.save_capture(path, bits);
+
+    let revision = include_bytes!("../revision.txt");
+    File::create(revision_path)
+        .unwrap()
+        .write(revision)
+        .unwrap();
 }
 
 #[cfg(target_os = "windows")]
 fn read_font_descriptor(
     bytes: &mut WrVecU8,
     index: u32
 ) -> NativeFontHandle {
     let wchars = bytes.convert_into_vec::<u16>();
--- a/js/src/jit/MacroAssembler.cpp
+++ b/js/src/jit/MacroAssembler.cpp
@@ -3138,20 +3138,18 @@ MacroAssembler::wasmEmitOldTrapOutOfLine
     breakpoint();
 
     oldTrapSites().clear();
 }
 
 void
 MacroAssembler::wasmEmitStackCheck(Register sp, Register scratch, Label* onOverflow)
 {
-    loadPtr(Address(WasmTlsReg, offsetof(wasm::TlsData, addressOfContext)), scratch);
-    loadPtr(Address(scratch, 0), scratch);
     branchPtr(Assembler::AboveOrEqual,
-              Address(scratch, offsetof(JSContext, jitStackLimitNoInterrupt)),
+              Address(WasmTlsReg, offsetof(wasm::TlsData, stackLimit)),
               sp,
               onOverflow);
 }
 
 void
 MacroAssembler::emitPreBarrierFastPath(JSRuntime* rt, MIRType type, Register temp1, Register temp2,
                                        Register temp3, Label* noBarrier)
 {
--- a/js/src/wasm/WasmFrameIter.cpp
+++ b/js/src/wasm/WasmFrameIter.cpp
@@ -309,18 +309,17 @@ PushRetAddr(MacroAssembler& masm, unsign
     // The x86/x64 call instruction pushes the return address.
 #endif
 }
 
 static void
 LoadActivation(MacroAssembler& masm, const Register& dest)
 {
     // WasmCall pushes a JitActivation.
-    masm.loadPtr(Address(WasmTlsReg, offsetof(wasm::TlsData, addressOfContext)), dest);
-    masm.loadPtr(Address(dest, 0), dest);
+    masm.loadPtr(Address(WasmTlsReg, offsetof(wasm::TlsData, cx)), dest);
     masm.loadPtr(Address(dest, JSContext::offsetOfActivation()), dest);
 }
 
 void
 wasm::SetExitFP(MacroAssembler& masm, ExitReason reason, Register scratch)
 {
     MOZ_ASSERT(!reason.isNone());
 
--- a/js/src/wasm/WasmInstance.cpp
+++ b/js/src/wasm/WasmInstance.cpp
@@ -405,17 +405,18 @@ Instance::Instance(JSContext* cx,
 #endif
     MOZ_ASSERT(tables_.length() == metadata().tables.length());
 
     tlsData()->memoryBase = memory ? memory->buffer().dataPointerEither().unwrap() : nullptr;
 #ifndef WASM_HUGE_MEMORY
     tlsData()->boundsCheckLimit = memory ? memory->buffer().wasmBoundsCheckLimit() : 0;
 #endif
     tlsData()->instance = this;
-    tlsData()->addressOfContext = (JSContext**)object->zone()->group()->addressOfOwnerContext();
+    tlsData()->cx = cx;
+    tlsData()->stackLimit = cx->stackLimitForJitCode(JS::StackForUntrustedScript);
     tlsData()->jumpTable = code_->jumpTable();
 
     Tier callerTier = code_->bestTier();
 
     for (size_t i = 0; i < metadata(callerTier).funcImports.length(); i++) {
         HandleFunction f = funcImports[i];
         const FuncImport& fi = metadata(callerTier).funcImports[i];
         FuncImportTls& import = funcImportTls(fi);
--- a/js/src/wasm/WasmTypes.h
+++ b/js/src/wasm/WasmTypes.h
@@ -1501,18 +1501,22 @@ struct TlsData
 #ifndef WASM_HUGE_MEMORY
     // Bounds check limit of memory, in bytes (or zero if there is no memory).
     uint32_t boundsCheckLimit;
 #endif
 
     // Pointer to the Instance that contains this TLS data.
     Instance* instance;
 
-    // Shortcut to instance->zone->group->addressOfOwnerContext
-    JSContext** addressOfContext;
+    // The containing JSContext.
+    JSContext* cx;
+
+    // The native stack limit which is checked by prologues. Shortcut for
+    // cx->stackLimitForJitCode(JS::StackForUntrustedScript).
+    uintptr_t stackLimit;
 
     // Pointer that should be freed (due to padding before the TlsData).
     void* allocatedBase;
 
     // When compiling with tiering, the jumpTable has one entry for each
     // baseline-compiled function.
     void** jumpTable;
 
--- a/layout/inspector/InspectorUtils.cpp
+++ b/layout/inspector/InspectorUtils.cpp
@@ -667,16 +667,17 @@ PropertySupportsVariant(nsCSSPropertyID 
       case eCSSProperty_grid_auto_columns:
       case eCSSProperty_grid_auto_rows:
       case eCSSProperty_grid_template_columns:
       case eCSSProperty_grid_template_rows:
       case eCSSProperty_object_position:
       case eCSSProperty_scroll_snap_coordinate:
       case eCSSProperty_scroll_snap_destination:
       case eCSSProperty_transform_origin:
+      case eCSSProperty_translate:
       case eCSSProperty_perspective_origin:
       case eCSSProperty__moz_outline_radius_topleft:
       case eCSSProperty__moz_outline_radius_topright:
       case eCSSProperty__moz_outline_radius_bottomleft:
       case eCSSProperty__moz_outline_radius_bottomright:
       case eCSSProperty__moz_window_transform_origin:
         supported |= VARIANT_LP;
         break;
@@ -700,29 +701,31 @@ PropertySupportsVariant(nsCSSPropertyID 
         break;
 
       case eCSSProperty_fill:
       case eCSSProperty_stroke:
         supported |= VARIANT_COLOR | VARIANT_URL;
         break;
 
       case eCSSProperty_image_orientation:
+      case eCSSProperty_rotate:
         supported |= VARIANT_ANGLE;
         break;
 
       case eCSSProperty_filter:
         supported |= VARIANT_URL;
         break;
 
       case eCSSProperty_grid_column_start:
       case eCSSProperty_grid_column_end:
       case eCSSProperty_grid_row_start:
       case eCSSProperty_grid_row_end:
       case eCSSProperty_font_weight:
       case eCSSProperty_initial_letter:
+      case eCSSProperty_scale:
         supported |= VARIANT_NUMBER;
         break;
 
       default:
         break;
     }
   }
 
--- a/layout/painting/ActiveLayerTracker.cpp
+++ b/layout/painting/ActiveLayerTracker.cpp
@@ -243,30 +243,31 @@ ActiveLayerTracker::TransferActivityToFr
   aFrame->AddStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY);
   aFrame->SetProperty(LayerActivityProperty(), layerActivity);
 }
 
 static void
 IncrementScaleRestyleCountIfNeeded(nsIFrame* aFrame, LayerActivity* aActivity)
 {
   const nsStyleDisplay* display = aFrame->StyleDisplay();
-  if (!display->mSpecifiedTransform) {
+  RefPtr<nsCSSValueSharedList> transformList = display->GetCombinedTransform();
+  if (!transformList) {
     // The transform was removed.
     aActivity->mPreviousTransformScale = Nothing();
     IncrementMutationCount(&aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]);
     return;
   }
 
   // Compute the new scale due to the CSS transform property.
   nsPresContext* presContext = aFrame->PresContext();
   RuleNodeCacheConditions dummy;
   bool dummyBool;
   nsStyleTransformMatrix::TransformReferenceBox refBox(aFrame);
   Matrix4x4 transform =
-    nsStyleTransformMatrix::ReadTransforms(display->mSpecifiedTransform->mHead,
+    nsStyleTransformMatrix::ReadTransforms(transformList->mHead,
                                            aFrame->StyleContext(),
                                            presContext,
                                            dummy, refBox,
                                            presContext->AppUnitsPerCSSPixel(),
                                            &dummyBool);
   Matrix transform2D;
   if (!transform.Is2D(&transform2D)) {
     // We don't attempt to handle 3D transforms; just assume the scale changed.
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -8117,17 +8117,17 @@ nsDisplayTransform::ComputePerspectiveMa
   aOutMatrix.ChangeBasis(Point3D(perspectiveOrigin.x, perspectiveOrigin.y, 0));
   return true;
 }
 
 nsDisplayTransform::FrameTransformProperties::FrameTransformProperties(const nsIFrame* aFrame,
                                                                        float aAppUnitsPerPixel,
                                                                        const nsRect* aBoundsOverride)
   : mFrame(aFrame)
-  , mTransformList(aFrame->StyleDisplay()->mSpecifiedTransform)
+  , mTransformList(aFrame->StyleDisplay()->GetCombinedTransform())
   , mToTransformOrigin(GetDeltaToTransformOrigin(aFrame, aAppUnitsPerPixel, aBoundsOverride))
 {
 }
 
 /* Wraps up the transform matrix in a change-of-basis matrix pair that
  * translates from local coordinate space to transform coordinate space, then
  * hands it back.
  */
--- a/layout/style/CSSFontFeatureValuesRule.h
+++ b/layout/style/CSSFontFeatureValuesRule.h
@@ -19,17 +19,17 @@ class CSSFontFeatureValuesRule : public 
 public:
   virtual bool IsCCLeaf() const override;
 
   int32_t GetType() const final override { return Rule::FONT_FEATURE_VALUES_RULE; }
   virtual already_AddRefed<mozilla::css::Rule> Clone() const override = 0;
 
   // WebIDL interfaces
   uint16_t Type() const final override { return CSSRuleBinding::FONT_FEATURE_VALUES_RULE; }
-  virtual void GetCssTextImpl(nsAString& aCssText) const override = 0;
+  virtual void GetCssText(nsAString& aCssText) const override = 0;
   virtual void GetFontFamily(nsAString& aFamily) = 0;
   virtual void SetFontFamily(const nsAString& aFamily, mozilla::ErrorResult& aRv) = 0;
   virtual void GetValueText(nsAString& aValueText) = 0;
   virtual void SetValueText(const nsAString& aValueText, mozilla::ErrorResult& aRv) = 0;
 
   virtual size_t
   SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override = 0;
 
--- a/layout/style/CSSPageRule.h
+++ b/layout/style/CSSPageRule.h
@@ -22,17 +22,17 @@ protected:
 
 public:
   virtual bool IsCCLeaf() const override = 0;
 
   int32_t GetType() const final override { return Rule::PAGE_RULE; }
 
   // WebIDL interfaces
   uint16_t Type() const final override { return CSSRuleBinding::PAGE_RULE; }
-  virtual void GetCssTextImpl(nsAString& aCssText) const override = 0;
+  virtual void GetCssText(nsAString& aCssText) const override = 0;
   virtual nsICSSDeclaration* Style() = 0;
 
   virtual size_t
   SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override = 0;
 
   JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 };
--- a/layout/style/ImportRule.h
+++ b/layout/style/ImportRule.h
@@ -48,17 +48,17 @@ public:
 #endif
   virtual already_AddRefed<Rule> Clone() const override;
 
   void SetSheet(CSSStyleSheet*);
 
   virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override;
 
   // WebIDL interface
-  void GetCssTextImpl(nsAString& aCssText) const override;
+  void GetCssText(nsAString& aCssText) const override;
   void GetHref(nsAString& aHref) const final override;
   dom::MediaList* GetMedia() const final override;
   StyleSheet* GetStyleSheet() const final override;
 
 private:
   nsString  mURLSpec;
   RefPtr<nsMediaList> mMedia;
   RefPtr<CSSStyleSheet> mChildSheet;
--- a/layout/style/NameSpaceRule.h
+++ b/layout/style/NameSpaceRule.h
@@ -41,17 +41,17 @@ public:
   virtual void List(FILE* out = stdout, int32_t aIndent = 0) const override;
 #endif
   virtual already_AddRefed<Rule> Clone() const override;
 
   nsAtom* GetPrefix() const final override { return mPrefix; }
   void GetURLSpec(nsString& aURLSpec) const final override { aURLSpec = mURLSpec; }
 
   // WebIDL interface
-  void GetCssTextImpl(nsAString& aCssText) const override;
+  void GetCssText(nsAString& aCssText) const override;
 
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const final override;
 
 private:
   RefPtr<nsAtom> mPrefix;
   nsString          mURLSpec;
 };
 
--- a/layout/style/Rule.h
+++ b/layout/style/Rule.h
@@ -110,18 +110,17 @@ public:
 
   // This is pure virtual because all of Rule's data members are non-owning and
   // thus measured elsewhere.
   virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
     const MOZ_MUST_OVERRIDE = 0;
 
   // WebIDL interface
   virtual uint16_t Type() const = 0;
-  virtual void GetCssTextImpl(nsAString& aCssText) const = 0;
-  void GetCssText(nsAString& aCssText) const { GetCssTextImpl(aCssText); }
+  virtual void GetCssText(nsAString& aCssText) const = 0;
   void SetCssText(const nsAString& aCssText);
   Rule* GetParentRule() const;
   StyleSheet* GetParentStyleSheet() const { return GetStyleSheet(); }
   nsIDocument* GetParentObject() const { return GetDocument(); }
 
 protected:
   // True if we're known-live for cycle collection purposes.
   bool IsKnownLive() const;
--- a/layout/style/ServoDocumentRule.cpp
+++ b/layout/style/ServoDocumentRule.cpp
@@ -74,17 +74,17 @@ ServoDocumentRule::GetConditionText(nsAS
 void
 ServoDocumentRule::SetConditionText(const nsAString& aConditionText,
                                     ErrorResult& aRv)
 {
   aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
 }
 
 /* virtual */ void
-ServoDocumentRule::GetCssTextImpl(nsAString& aCssText) const
+ServoDocumentRule::GetCssText(nsAString& aCssText) const
 {
   Servo_DocumentRule_GetCssText(mRawRule, &aCssText);
 }
 
 /* virtual */ size_t
 ServoDocumentRule::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
   const
 {
--- a/layout/style/ServoDocumentRule.h
+++ b/layout/style/ServoDocumentRule.h
@@ -27,17 +27,17 @@ public:
                           nsMediaQueryResultCacheKey& aKey) final override;
 #ifdef DEBUG
   void List(FILE* out = stdout, int32_t aIndent = 0) const final override;
 #endif
 
   RawServoDocumentRule* Raw() const { return mRawRule; }
 
   // WebIDL interface
-  void GetCssTextImpl(nsAString& aCssText) const final override;
+  void GetCssText(nsAString& aCssText) const final override;
   void GetConditionText(nsAString& aConditionText) final override;
   void SetConditionText(const nsAString& aConditionText,
                         ErrorResult& aRv) final override;
 
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
     const override;
 
 private:
--- a/layout/style/ServoFontFeatureValuesRule.cpp
+++ b/layout/style/ServoFontFeatureValuesRule.cpp
@@ -54,17 +54,17 @@ ServoFontFeatureValuesRule::List(FILE* o
   Servo_FontFeatureValuesRule_Debug(mRawRule, &str);
   fprintf_stderr(out, "%s\n", str.get());
 }
 #endif
 
 /* CSSRule implementation */
 
 void
-ServoFontFeatureValuesRule::GetCssTextImpl(nsAString& aCssText) const
+ServoFontFeatureValuesRule::GetCssText(nsAString& aCssText) const
 {
   Servo_FontFeatureValuesRule_GetCssText(mRawRule, &aCssText);
 }
 
 /* CSSFontFeatureValuesRule implementation */
 
 void
 ServoFontFeatureValuesRule::GetFontFamily(nsAString& aFamilyListStr)
--- a/layout/style/ServoFontFeatureValuesRule.h
+++ b/layout/style/ServoFontFeatureValuesRule.h
@@ -18,17 +18,17 @@ class ServoFontFeatureValuesRule final :
 {
 public:
   ServoFontFeatureValuesRule(RefPtr<RawServoFontFeatureValuesRule> aRawRule,
                              uint32_t aLine, uint32_t aColumn);
 
   RawServoFontFeatureValuesRule* Raw() const { return mRawRule; }
 
   // WebIDL interface
-  void GetCssTextImpl(nsAString& aCssText) const override;
+  void GetCssText(nsAString& aCssText) const override;
   void GetFontFamily(nsAString& aFamily) final override;
   void SetFontFamily(const nsAString& aFamily, mozilla::ErrorResult& aRv) final override;
   void GetValueText(nsAString& aValueText) final override;
   void SetValueText(const nsAString& aValueText, mozilla::ErrorResult& aRv) final override;
 
   // Methods of mozilla::css::Rule
   already_AddRefed<css::Rule> Clone() const final override;
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
--- a/layout/style/ServoImportRule.cpp
+++ b/layout/style/ServoImportRule.cpp
@@ -98,17 +98,17 @@ ServoImportRule::GetStyleSheet() const
 
 void
 ServoImportRule::GetHref(nsAString& aHref) const
 {
   Servo_ImportRule_GetHref(mRawRule, &aHref);
 }
 
 /* virtual */ void
-ServoImportRule::GetCssTextImpl(nsAString& aCssText) const
+ServoImportRule::GetCssText(nsAString& aCssText) const
 {
   Servo_ImportRule_GetCssText(mRawRule, &aCssText);
 }
 
 /* virtual */ size_t
 ServoImportRule::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
 {
   // TODO Implement this!
--- a/layout/style/ServoImportRule.h
+++ b/layout/style/ServoImportRule.h
@@ -28,17 +28,17 @@ public:
 
 #ifdef DEBUG
   void List(FILE* out = stdout, int32_t aIndent = 0) const final override;
 #endif
   already_AddRefed<css::Rule> Clone() const final override;
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const final override;
 
   // WebIDL interface
-  void GetCssTextImpl(nsAString& aCssText) const override;
+  void GetCssText(nsAString& aCssText) const override;
   void GetHref(nsAString& aHref) const final override;
   dom::MediaList* GetMedia() const final override;
   StyleSheet* GetStyleSheet() const final override;
 
 private:
   ~ServoImportRule();
 
   RefPtr<RawServoImportRule> mRawRule;
--- a/layout/style/ServoKeyframeRule.cpp
+++ b/layout/style/ServoKeyframeRule.cpp
@@ -185,17 +185,17 @@ ServoKeyframeRule::SetKeyText(const nsAS
 {
   NS_ConvertUTF16toUTF8 keyText(aKeyText);
   UpdateRule([this, &keyText]() {
     Servo_Keyframe_SetKeyText(mRaw, &keyText);
   });
 }
 
 void
-ServoKeyframeRule::GetCssTextImpl(nsAString& aCssText) const
+ServoKeyframeRule::GetCssText(nsAString& aCssText) const
 {
   Servo_Keyframe_GetCssText(mRaw, &aCssText);
 }
 
 nsICSSDeclaration*
 ServoKeyframeRule::Style()
 {
   if (!mDeclaration) {
--- a/layout/style/ServoKeyframeRule.h
+++ b/layout/style/ServoKeyframeRule.h
@@ -31,17 +31,17 @@ public:
 #ifdef DEBUG
   void List(FILE* out = stdout, int32_t aIndent = 0) const final override;
 #endif
   already_AddRefed<mozilla::css::Rule> Clone() const final override;
 
   RawServoKeyframe* Raw() const { return mRaw; }
 
   // WebIDL interface
-  void GetCssTextImpl(nsAString& aCssText) const final override;
+  void GetCssText(nsAString& aCssText) const final override;
   void GetKeyText(nsAString& aKeyText) final override;
   void SetKeyText(const nsAString& aKeyText) final override;
   nsICSSDeclaration* Style() final override;
 
   size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const final override;
 
 private:
   virtual ~ServoKeyframeRule();
--- a/layout/style/ServoKeyframesRule.cpp
+++ b/layout/style/ServoKeyframesRule.cpp
@@ -308,17 +308,17 @@ ServoKeyframesRule::DeleteRule(const nsA
     Servo_KeyframesRule_DeleteRule(mRawRule, index);
     if (mKeyframeList) {
       mKeyframeList->RemoveRule(index);
     }
   });
 }
 
 /* virtual */ void
-ServoKeyframesRule::GetCssTextImpl(nsAString& aCssText) const
+ServoKeyframesRule::GetCssText(nsAString& aCssText) const
 {
   Servo_KeyframesRule_GetCssText(mRawRule, &aCssText);
 }
 
 /* virtual */ dom::CSSRuleList*
 ServoKeyframesRule::CssRules()
 {
   if (!mKeyframeList) {
--- a/layout/style/ServoKeyframesRule.h
+++ b/layout/style/ServoKeyframesRule.h
@@ -27,17 +27,17 @@ public:
 
   already_AddRefed<css::Rule> Clone() const final override;
 #ifdef DEBUG
   void List(FILE* out = stdout, int32_t aIndent = 0) const final override;
 #endif
   void SetStyleSheet(StyleSheet* aSheet) final override;
 
   // WebIDL interface
-  void GetCssTextImpl(nsAString& aCssText) const final override;
+  void GetCssText(nsAString& aCssText) const final override;
   void GetName(nsAString& aName) const final override;
   void SetName(const nsAString& aName) final override;
   dom::CSSRuleList* CssRules() final override;
   void AppendRule(const nsAString& aRule) final override;
   void DeleteRule(const nsAString& aKey) final override;
   dom::CSSKeyframeRule* FindRule(const nsAString& aKey) final override;
 
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const final override;
--- a/layout/style/ServoMediaRule.cpp
+++ b/layout/style/ServoMediaRule.cpp
@@ -100,17 +100,17 @@ ServoMediaRule::GetConditionText(nsAStri
 void
 ServoMediaRule::SetConditionText(const nsAString& aConditionText,
                                  ErrorResult& aRv)
 {
   Media()->SetMediaText(aConditionText);
 }
 
 /* virtual */ void
-ServoMediaRule::GetCssTextImpl(nsAString& aCssText) const
+ServoMediaRule::GetCssText(nsAString& aCssText) const
 {
   Servo_MediaRule_GetCssText(mRawRule, &aCssText);
 }
 
 /* virtual */ dom::MediaList*
 ServoMediaRule::Media()
 {
   if (!mMediaList) {
--- a/layout/style/ServoMediaRule.h
+++ b/layout/style/ServoMediaRule.h
@@ -31,17 +31,17 @@ public:
   void SetStyleSheet(StyleSheet* aSheet) override;
 #ifdef DEBUG
   void List(FILE* out = stdout, int32_t aIndent = 0) const final override;
 #endif
 
   RawServoMediaRule* Raw() const { return mRawRule; }
 
   // WebIDL interface
-  void GetCssTextImpl(nsAString& aCssText) const final override;
+  void GetCssText(nsAString& aCssText) const final override;
   void GetConditionText(nsAString& aConditionText) final override;
   void SetConditionText(const nsAString& aConditionText,
                         ErrorResult& aRv) final override;
   dom::MediaList* Media() final override;
 
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
     const override;
 
--- a/layout/style/ServoNamespaceRule.cpp
+++ b/layout/style/ServoNamespaceRule.cpp
@@ -54,17 +54,17 @@ ServoNamespaceRule::GetPrefix() const
 void
 ServoNamespaceRule::GetURLSpec(nsString& aURLSpec) const
 {
   nsAtom* atom = Servo_NamespaceRule_GetURI(mRawRule);
   atom->ToString(aURLSpec);
 }
 
 void
-ServoNamespaceRule::GetCssTextImpl(nsAString& aCssText) const
+ServoNamespaceRule::GetCssText(nsAString& aCssText) const
 {
   Servo_NamespaceRule_GetCssText(mRawRule, &aCssText);
 }
 
 size_t
 ServoNamespaceRule::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
 {
   return aMallocSizeOf(this);
--- a/layout/style/ServoNamespaceRule.h
+++ b/layout/style/ServoNamespaceRule.h
@@ -28,17 +28,17 @@ public:
   void List(FILE* out = stdout, int32_t aIndent = 0) const final override;
 #endif
   already_AddRefed<Rule> Clone() const final override;
 
   nsAtom* GetPrefix() const final override;
   void GetURLSpec(nsString& aURLSpec) const final override;
 
   // WebIDL interface
-  void GetCssTextImpl(nsAString& aCssText) const final override;
+  void GetCssText(nsAString& aCssText) const final override;
 
   size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const final override;
 
 private:
   ~ServoNamespaceRule();
 
   RefPtr<RawServoNamespaceRule> mRawRule;
 };
--- a/layout/style/ServoPageRule.cpp
+++ b/layout/style/ServoPageRule.cpp
@@ -188,17 +188,17 @@ ServoPageRule::List(FILE* out, int32_t a
   Servo_PageRule_Debug(mRawRule, &str);
   fprintf_stderr(out, "%s\n", str.get());
 }
 #endif
 
 /* CSSRule implementation */
 
 void
-ServoPageRule::GetCssTextImpl(nsAString& aCssText) const
+ServoPageRule::GetCssText(nsAString& aCssText) const
 {
   Servo_PageRule_GetCssText(mRawRule, &aCssText);
 }
 
 /* CSSPageRule implementation */
 
 nsICSSDeclaration*
 ServoPageRule::Style()
--- a/layout/style/ServoPageRule.h
+++ b/layout/style/ServoPageRule.h
@@ -64,17 +64,17 @@ public:
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(
     ServoPageRule, dom::CSSPageRule
   )
   bool IsCCLeaf() const final override;
 
   RawServoPageRule* Raw() const { return mRawRule; }
 
   // WebIDL interface
-  void GetCssTextImpl(nsAString& aCssText) const final override;
+  void GetCssText(nsAString& aCssText) const final override;
   nsICSSDeclaration* Style() final override;
 
   // Methods of mozilla::css::Rule
   already_AddRefed<css::Rule> Clone() const final override;
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
     const final override;
 
 #ifdef DEBUG
--- a/layout/style/ServoStyleRule.cpp
+++ b/layout/style/ServoStyleRule.cpp
@@ -196,17 +196,17 @@ ServoStyleRule::List(FILE* out, int32_t 
 
 uint16_t
 ServoStyleRule::Type() const
 {
   return CSSRuleBinding::STYLE_RULE;
 }
 
 void
-ServoStyleRule::GetCssTextImpl(nsAString& aCssText) const
+ServoStyleRule::GetCssText(nsAString& aCssText) const
 {
   Servo_StyleRule_GetCssText(mRawRule, &aCssText);
 }
 
 nsICSSDeclaration*
 ServoStyleRule::Style()
 {
   return &mDecls;
--- a/layout/style/ServoStyleRule.h
+++ b/layout/style/ServoStyleRule.h
@@ -77,17 +77,17 @@ public:
   nsresult SelectorMatchesElement(dom::Element* aElement,
                                   uint32_t aSelectorIndex,
                                   const nsAString& aPseudo,
                                   bool* aMatches) override;
   NotNull<DeclarationBlock*> GetDeclarationBlock() const override;
 
   // WebIDL interface
   uint16_t Type() const final override;
-  void GetCssTextImpl(nsAString& aCssText) const final override;
+  void GetCssText(nsAString& aCssText) const final override;
   void GetSelectorText(nsAString& aSelectorText) final override;
   void SetSelectorText(const nsAString& aSelectorText) final override;
   nsICSSDeclaration* Style() final override;
 
   RawServoStyleRule* Raw() const { return mRawRule; }
 
   // Methods of mozilla::css::Rule
   int32_t GetType() const final override { return css::Rule::STYLE_RULE; }
--- a/layout/style/ServoSupportsRule.cpp
+++ b/layout/style/ServoSupportsRule.cpp
@@ -75,17 +75,17 @@ ServoSupportsRule::GetConditionText(nsAS
 void
 ServoSupportsRule::SetConditionText(const nsAString& aConditionText,
                                     ErrorResult& aRv)
 {
   aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
 }
 
 /* virtual */ void
-ServoSupportsRule::GetCssTextImpl(nsAString& aCssText) const
+ServoSupportsRule::GetCssText(nsAString& aCssText) const
 {
   Servo_SupportsRule_GetCssText(mRawRule, &aCssText);
 }
 
 /* virtual */ size_t
 ServoSupportsRule::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
   const
 {
--- a/layout/style/ServoSupportsRule.h
+++ b/layout/style/ServoSupportsRule.h
@@ -27,17 +27,17 @@ public:
                           nsMediaQueryResultCacheKey& aKey) final override;
 #ifdef DEBUG
   void List(FILE* out = stdout, int32_t aIndent = 0) const final override;
 #endif
 
   RawServoSupportsRule* Raw() const { return mRawRule; }
 
   // WebIDL interface
-  void GetCssTextImpl(nsAString& aCssText) const final override;
+  void GetCssText(nsAString& aCssText) const final override;
   void GetConditionText(nsAString& aConditionText) final override;
   void SetConditionText(const nsAString& aConditionText,
                         ErrorResult& aRv) final override;
 
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
     const override;
 
 private:
--- a/layout/style/StyleAnimationValue.cpp
+++ b/layout/style/StyleAnimationValue.cpp
@@ -4631,20 +4631,21 @@ StyleAnimationValue::ExtractComputedValu
                                                       eUnit_Filter);
           break;
         }
 
         case eCSSProperty_transform: {
           const nsStyleDisplay *display =
             static_cast<const nsStyleDisplay*>(styleStruct);
           nsAutoPtr<nsCSSValueList> result;
-          if (display->mSpecifiedTransform) {
+          RefPtr<nsCSSValueSharedList> transformList = display->GetCombinedTransform();
+          if (transformList) {
             // Clone, and convert all lengths (not percents) to pixels.
             nsCSSValueList **resultTail = getter_Transfers(result);
-            for (const nsCSSValueList *l = display->mSpecifiedTransform->mHead;
+            for (const nsCSSValueList *l = transformList->mHead;
                  l; l = l->mNext) {
               nsCSSValueList *clone = new nsCSSValueList;
               *resultTail = clone;
               resultTail = &clone->mNext;
 
               SubstitutePixelValues(aStyleContext, l->mValue, clone->mValue);
             }
           } else {
--- a/layout/style/StyleRule.cpp
+++ b/layout/style/StyleRule.cpp
@@ -1327,17 +1327,17 @@ StyleRule::List(FILE* out, int32_t aInde
     str.AppendLiteral("{ null declaration }");
   }
   str.Append('\n');
   fprintf_stderr(out, "%s", str.get());
 }
 #endif
 
 void
-StyleRule::GetCssTextImpl(nsAString& aCssText) const
+StyleRule::GetCssText(nsAString& aCssText) const
 {
   if (mSelector) {
     mSelector->ToString(aCssText, GetStyleSheet());
     aCssText.Append(char16_t(' '));
   }
   aCssText.Append(char16_t('{'));
   aCssText.Append(char16_t(' '));
   if (mDeclaration)
--- a/layout/style/StyleRule.h
+++ b/layout/style/StyleRule.h
@@ -340,17 +340,17 @@ public:
   nsresult SelectorMatchesElement(dom::Element* aElement,
                                   uint32_t aSelectorIndex,
                                   const nsAString& aPseudo,
                                   bool* aMatches) override;
   mozilla::NotNull<DeclarationBlock*> GetDeclarationBlock() const override;
 
   // WebIDL interface
   uint16_t Type() const override;
-  void GetCssTextImpl(nsAString& aCssText) const override;
+  void GetCssText(nsAString& aCssText) const override;
   void GetSelectorText(nsAString& aSelectorText) final override;
   void SetSelectorText(const nsAString& aSelectorText) final override;
   nsICSSDeclaration* Style() override;
 
   // null for style attribute
   nsCSSSelectorList* Selector() { return mSelector; }
 
   Declaration* GetDeclaration() const { return mDeclaration; }
--- a/layout/style/nsCSSCounterStyleRule.cpp
+++ b/layout/style/nsCSSCounterStyleRule.cpp
@@ -83,17 +83,17 @@ nsCSSCounterStyleRule::GetType() const
 
 uint16_t
 nsCSSCounterStyleRule::Type() const
 {
   return CSSRuleBinding::COUNTER_STYLE_RULE;
 }
 
 void
-nsCSSCounterStyleRule::GetCssTextImpl(nsAString& aCssText) const
+nsCSSCounterStyleRule::GetCssText(nsAString& aCssText) const
 {
   aCssText.AssignLiteral(u"@counter-style ");
   nsDependentAtomString name(mName);
   nsStyleUtil::AppendEscapedCSSIdent(name, aCssText);
   aCssText.AppendLiteral(u" {\n");
   for (nsCSSCounterDesc id = nsCSSCounterDesc(0);
        id < eCSSCounterDesc_COUNT;
        id = nsCSSCounterDesc(id + 1)) {
--- a/layout/style/nsCSSCounterStyleRule.h
+++ b/layout/style/nsCSSCounterStyleRule.h
@@ -32,17 +32,17 @@ public:
 #ifdef DEBUG
   virtual void List(FILE* out = stdout, int32_t aIndent = 0) const override;
 #endif
   virtual int32_t GetType() const override;
   virtual already_AddRefed<mozilla::css::Rule> Clone() const override;
 
   // WebIDL interface
   uint16_t Type() const override;
-  void GetCssTextImpl(nsAString& aCssText) const override;
+  void GetCssText(nsAString& aCssText) const override;
   void GetName(nsAString& aName);
   void SetName(const nsAString& aName);
   void GetSystem(nsAString& aSystem);
   void SetSystem(const nsAString& aSystem);
   void GetSymbols(nsAString& aSymbols);
   void SetSymbols(const nsAString& aSymbols);
   void GetAdditiveSymbols(nsAString& aAdditiveSymbols);
   void SetAdditiveSymbols(const nsAString& aAdditiveSymbols);
--- a/layout/style/nsCSSFontFaceRule.cpp
+++ b/layout/style/nsCSSFontFaceRule.cpp
@@ -365,17 +365,17 @@ nsCSSFontFaceRule::GetType() const
 
 uint16_t
 nsCSSFontFaceRule::Type() const
 {
   return CSSRuleBinding::FONT_FACE_RULE;
 }
 
 void
-nsCSSFontFaceRule::GetCssTextImpl(nsAString& aCssText) const
+nsCSSFontFaceRule::GetCssText(nsAString& aCssText) const
 {
   nsAutoString propText;
   mDecl.GetCssTextImpl(propText);
 
   aCssText.AssignLiteral("@font-face {\n");
   aCssText.Append(propText);
   aCssText.Append('}');
 }
--- a/layout/style/nsCSSFontFaceRule.h
+++ b/layout/style/nsCSSFontFaceRule.h
@@ -58,16 +58,18 @@ protected:
   friend class nsCSSFontFaceRule;
 
   inline nsCSSFontFaceRule* ContainingRule();
   inline const nsCSSFontFaceRule* ContainingRule() const;
 
   mozilla::CSSFontFaceDescriptors mDescriptors;
 
   // The actual implementation of GetCssText, so we can make it const.
+  // We can't make nsICSSDeclaration::GetCssText const, because some
+  // subclasses call non-const methods in their implementations.
   void GetCssTextImpl(nsAString& aCssText) const;
 
 private:
   // NOT TO BE IMPLEMENTED
   // This object cannot be allocated on its own, only as part of
   // nsCSSFontFaceRule.
   void* operator new(size_t size) CPP_THROW_NEW;
 };
@@ -97,17 +99,17 @@ public:
   virtual int32_t GetType() const override;
   virtual already_AddRefed<mozilla::css::Rule> Clone() const override;
 
   void SetDesc(nsCSSFontDesc aDescID, nsCSSValue const & aValue);
   void GetDesc(nsCSSFontDesc aDescID, nsCSSValue & aValue);
 
   // WebIDL interface
   uint16_t Type() const override;
-  void GetCssTextImpl(nsAString& aCssText) const override;
+  void GetCssText(nsAString& aCssText) const override;
   nsICSSDeclaration* Style();
 
   virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override;
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
   void GetDescriptors(mozilla::CSSFontFaceDescriptors& aDescriptors) const
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -11820,16 +11820,21 @@ CSSParserImpl::ParsePropertyByFunction(n
     return ParseImageLayerPositionCoord(aPropID,
                aPropID == eCSSProperty_mask_position_x);
   case eCSSProperty_mask_size:
     return ParseImageLayerSize(eCSSProperty_mask_size);
   case eCSSProperty__webkit_text_stroke:
     return ParseWebkitTextStroke();
   case eCSSProperty_all:
     return ParseAll();
+  case eCSSProperty_translate:
+  case eCSSProperty_rotate:
+  case eCSSProperty_scale:
+    // These properties aren't implemented in the old style system.
+    return false;
   default:
     MOZ_ASSERT(false, "should not be called");
     return false;
   }
 }
 
 // Bits used in determining which background position info we have
 #define BG_CENTER  NS_STYLE_IMAGELAYER_POSITION_CENTER
--- a/layout/style/nsCSSPropList.h
+++ b/layout/style/nsCSSPropList.h
@@ -3542,16 +3542,29 @@ CSS_PROP_POSITION(
         CSS_PROPERTY_STORES_CALC |
         CSS_PROPERTY_UNITLESS_LENGTH_QUIRK |
         CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH,
     "",
     VARIANT_AHLP | VARIANT_CALC,
     nullptr,
     offsetof(nsStylePosition, mOffset),
     eStyleAnimType_Sides_Right)
+CSS_PROP_DISPLAY(
+    rotate,
+    rotate,
+    Rotate,
+    CSS_PROPERTY_PARSE_FUNCTION |
+        CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH |
+        CSS_PROPERTY_CREATES_STACKING_CONTEXT |
+        CSS_PROPERTY_FIXPOS_CB,
+    "layout.css.individual-transform.enabled",
+    0,
+    nullptr,
+    offsetof(nsStyleDisplay, mSpecifiedRotate),
+    eStyleAnimType_None)
 CSS_PROP_TEXT(
     ruby-align,
     ruby_align,
     RubyAlign,
     CSS_PROPERTY_PARSE_VALUE,
     "",
     VARIANT_HK,
     kRubyAlignKTable,
@@ -4171,16 +4184,29 @@ CSS_PROP_TEXT(
         CSS_PROPERTY_VALUE_NONNEGATIVE |
         CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
         CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
     "layout.css.prefixes.webkit",
     VARIANT_HKL | VARIANT_CALC,
     kBorderWidthKTable,
     CSS_PROP_NO_OFFSET,
     eStyleAnimType_Discrete)
+CSS_PROP_DISPLAY(
+    scale,
+    scale,
+    Scale,
+    CSS_PROPERTY_PARSE_FUNCTION |
+        CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH |
+        CSS_PROPERTY_CREATES_STACKING_CONTEXT |
+        CSS_PROPERTY_FIXPOS_CB,
+    "layout.css.individual-transform.enabled",
+    0,
+    nullptr,
+    offsetof(nsStyleDisplay, mSpecifiedScale),
+    eStyleAnimType_None)
 CSS_PROP_TEXT(
     text-transform,
     text_transform,
     TextTransform,
     CSS_PROPERTY_PARSE_VALUE |
         CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
         CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
     "",
@@ -4343,16 +4369,29 @@ CSS_PROP_DISPLAY(
     TransitionTimingFunction,
     CSS_PROPERTY_PARSE_VALUE_LIST |
         CSS_PROPERTY_VALUE_LIST_USES_COMMAS,
     "",
     VARIANT_KEYWORD | VARIANT_TIMING_FUNCTION, // used by list parsing
     kTransitionTimingFunctionKTable,
     CSS_PROP_NO_OFFSET,
     eStyleAnimType_None)
+CSS_PROP_DISPLAY(
+    translate,
+    translate,
+    Translate,
+    CSS_PROPERTY_PARSE_FUNCTION |
+        CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH |
+        CSS_PROPERTY_CREATES_STACKING_CONTEXT |
+        CSS_PROPERTY_FIXPOS_CB,
+    "layout.css.individual-transform.enabled",
+    0,
+    nullptr,
+    offsetof(nsStyleDisplay, mSpecifiedTranslate),
+    eStyleAnimType_None)
 #ifndef CSS_PROP_LIST_ONLY_COMPONENTS_OF_ALL_SHORTHAND
 CSS_PROP_TEXTRESET(
     unicode-bidi,
     unicode_bidi,
     UnicodeBidi,
     CSS_PROPERTY_PARSE_VALUE,
     "",
     VARIANT_HK,
--- a/layout/style/nsCSSRules.cpp
+++ b/layout/style/nsCSSRules.cpp
@@ -150,17 +150,17 @@ ImportRule::SetSheet(CSSStyleSheet* aShe
   mChildSheet = aSheet;
   aSheet->SetOwnerRule(this);
 
   // set our medialist to be the same as the sheet's medialist
   mMedia = static_cast<nsMediaList*>(mChildSheet->Media());
 }
 
 void
-ImportRule::GetCssTextImpl(nsAString& aCssText) const
+ImportRule::GetCssText(nsAString& aCssText) const
 {
   aCssText.AssignLiteral("@import url(");
   nsStyleUtil::AppendEscapedCSSString(mURLSpec, aCssText);
   aCssText.Append(')');
   if (mMedia) {
     nsAutoString mediaText;
     mMedia->GetText(mediaText);
     if (!mediaText.IsEmpty()) {
@@ -309,17 +309,17 @@ MediaList*
 MediaRule::Media()
 {
   // In practice, if we end up being parsed at all, we have non-null mMedia.  So
   // it's OK to claim we don't return null here.
   return mMedia;
 }
 
 void
-MediaRule::GetCssTextImpl(nsAString& aCssText) const
+MediaRule::GetCssText(nsAString& aCssText) const
 {
   aCssText.AssignLiteral("@media ");
   AppendConditionText(aCssText);
   GroupRule::AppendRulesToCssText(aCssText);
 }
 
 void
 MediaRule::GetConditionText(nsAString& aConditionText)
@@ -440,17 +440,17 @@ DocumentRule::List(FILE* out, int32_t aI
 /* virtual */ already_AddRefed<Rule>
 DocumentRule::Clone() const
 {
   RefPtr<Rule> clone = new DocumentRule(*this);
   return clone.forget();
 }
 
 void
-DocumentRule::GetCssTextImpl(nsAString& aCssText) const
+DocumentRule::GetCssText(nsAString& aCssText) const
 {
   aCssText.AssignLiteral("@-moz-document ");
   AppendConditionText(aCssText);
   GroupRule::AppendRulesToCssText(aCssText);
 }
 
 void
 DocumentRule::GetConditionText(nsAString& aConditionText)
@@ -605,17 +605,17 @@ NameSpaceRule::List(FILE* out, int32_t a
 /* virtual */ already_AddRefed<Rule>
 NameSpaceRule::Clone() const
 {
   RefPtr<Rule> clone = new NameSpaceRule(*this);
   return clone.forget();
 }
 
 void
-NameSpaceRule::GetCssTextImpl(nsAString& aCssText) const
+NameSpaceRule::GetCssText(nsAString& aCssText) const
 {
   aCssText.AssignLiteral("@namespace ");
   if (mPrefix) {
     aCssText.Append(nsDependentAtomString(mPrefix) + NS_LITERAL_STRING(" "));
   }
   aCssText.AppendLiteral("url(");
   nsStyleUtil::AppendEscapedCSSString(mURLSpec, aCssText);
   aCssText.AppendLiteral(");");
@@ -750,17 +750,17 @@ nsCSSFontFeatureValuesRule::SetFontFamil
 void
 nsCSSFontFeatureValuesRule::SetValueText(const nsAString& aValueText,
                                          ErrorResult& aRv)
 {
   aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
 }
 
 void
-nsCSSFontFeatureValuesRule::GetCssTextImpl(nsAString& aCssText) const
+nsCSSFontFeatureValuesRule::GetCssText(nsAString& aCssText) const
 {
   FontFeatureValuesRuleToString(mFamilyList, mFeatureValues, aCssText);
 }
 
 struct MakeFamilyArray {
   explicit MakeFamilyArray(nsTArray<nsString>& aFamilyArray)
     : familyArray(aFamilyArray), hasGeneric(false)
   {}
@@ -960,17 +960,17 @@ nsCSSKeyframeRule::List(FILE* out, int32
   mDeclaration->ToString(tmp);
   AppendUTF16toUTF8(tmp, str);
   str.AppendLiteral("}\n");
   fprintf_stderr(out, "%s", str.get());
 }
 #endif
 
 void
-nsCSSKeyframeRule::GetCssTextImpl(nsAString& aCssText) const
+nsCSSKeyframeRule::GetCssText(nsAString& aCssText) const
 {
   DoGetKeyText(aCssText);
   aCssText.AppendLiteral(" { ");
   nsAutoString tmp;
   mDeclaration->ToString(tmp);
   aCssText.Append(tmp);
   aCssText.AppendLiteral(" }");
 }
@@ -1098,17 +1098,17 @@ nsCSSKeyframesRule::List(FILE* out, int3
 
   GroupRule::List(out, aIndent);
 
   fprintf_stderr(out, "%s}\n", indentStr.get());
 }
 #endif
 
 void
-nsCSSKeyframesRule::GetCssTextImpl(nsAString& aCssText) const
+nsCSSKeyframesRule::GetCssText(nsAString& aCssText) const
 {
   aCssText.AssignLiteral("@keyframes ");
   aCssText.Append(nsDependentAtomString(mName));
   aCssText.AppendLiteral(" {\n");
   nsAutoString tmp;
   for (const Rule* rule : GeckoRules()) {
     static_cast<const nsCSSKeyframeRule*>(rule)->GetCssText(tmp);
     aCssText.Append(tmp);
@@ -1370,17 +1370,17 @@ nsCSSPageRule::List(FILE* out, int32_t a
   mDeclaration->ToString(tmp);
   AppendUTF16toUTF8(tmp, str);
   str.AppendLiteral("}\n");
   fprintf_stderr(out, "%s", str.get());
 }
 #endif
 
 void
-nsCSSPageRule::GetCssTextImpl(nsAString& aCssText) const
+nsCSSPageRule::GetCssText(nsAString& aCssText) const
 {
   aCssText.AppendLiteral("@page { ");
   nsAutoString tmp;
   mDeclaration->ToString(tmp);
   aCssText.Append(tmp);
   aCssText.AppendLiteral(" }");
 }
 
@@ -1472,17 +1472,17 @@ CSSSupportsRule::UseForPresentation(nsPr
 NS_IMPL_ADDREF_INHERITED(mozilla::CSSSupportsRule, dom::CSSSupportsRule)
 NS_IMPL_RELEASE_INHERITED(mozilla::CSSSupportsRule, dom::CSSSupportsRule)
 
 // QueryInterface implementation for CSSSupportsRule
 NS_INTERFACE_MAP_BEGIN(CSSSupportsRule)
 NS_INTERFACE_MAP_END_INHERITING(dom::CSSSupportsRule)
 
 void
-CSSSupportsRule::GetCssTextImpl(nsAString& aCssText) const
+CSSSupportsRule::GetCssText(nsAString& aCssText) const
 {
   aCssText.AssignLiteral("@supports ");
   aCssText.Append(mCondition);
   css::GroupRule::AppendRulesToCssText(aCssText);
 }
 
 void
 CSSSupportsRule::GetConditionText(nsAString& aConditionText)
--- a/layout/style/nsCSSRules.h
+++ b/layout/style/nsCSSRules.h
@@ -70,17 +70,17 @@ public:
   // rest of GroupRule
   virtual bool UseForPresentation(nsPresContext* aPresContext,
                                     nsMediaQueryResultCacheKey& aKey) override;
 
   // @media rule methods
   nsresult SetMedia(nsMediaList* aMedia);
 
   // WebIDL interface
-  void GetCssTextImpl(nsAString& aCssText) const final override;
+  void GetCssText(nsAString& aCssText) const final override;
   void GetConditionText(nsAString& aConditionText) final override;
   void SetConditionText(const nsAString& aConditionText,
                         ErrorResult& aRv) final override;
   dom::MediaList* Media() final override;
 
   virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
     const override MOZ_MUST_OVERRIDE;
 
@@ -126,17 +126,17 @@ public:
     {
     }
     ~URL();
   };
 
   void SetURLs(URL *aURLs) { mURLs = aURLs; }
 
   // WebIDL interface
-  void GetCssTextImpl(nsAString& aCssText) const final override;
+  void GetCssText(nsAString& aCssText) const final override;
   void GetConditionText(nsAString& aConditionText) final override;
   void SetConditionText(const nsAString& aConditionText,
                         ErrorResult& aRv) final override;
 
   virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
     const override MOZ_MUST_OVERRIDE;
 
 protected:
@@ -166,17 +166,17 @@ public:
   }
 
 #ifdef DEBUG
   void List(FILE* out = stdout, int32_t aIndent = 0) const final override;
 #endif
   already_AddRefed<mozilla::css::Rule> Clone() const final override;
 
   // WebIDL interface
-  void GetCssTextImpl(nsAString& aCssText) const final override;
+  void GetCssText(nsAString& aCssText) const final override;
   void GetFontFamily(nsAString& aFamily) final override;
   void SetFontFamily(const nsAString& aFamily, mozilla::ErrorResult& aRv) final override;
   void GetValueText(nsAString& aValueText) final override;
   void SetValueText(const nsAString& aValueText, mozilla::ErrorResult& aRv) final override;
 
   mozilla::SharedFontList* GetFamilyList() const { return mFamilyList; }
   void SetFamilyList(mozilla::SharedFontList* aFamilyList)
   {
@@ -254,17 +254,17 @@ public:
   virtual bool IsCCLeaf() const override;
 
 #ifdef DEBUG
   virtual void List(FILE* out = stdout, int32_t aIndent = 0) const override;
 #endif
   virtual already_AddRefed<mozilla::css::Rule> Clone() const override;
 
   // WebIDL interface
-  void GetCssTextImpl(nsAString& aCssText) const final override;
+  void GetCssText(nsAString& aCssText) const final override;
   void GetKeyText(nsAString& aKeyText) final override;
   void SetKeyText(const nsAString& aKeyText) final override;
   nsICSSDeclaration* Style() final override;
 
   const nsTArray<float>& GetKeys() const     { return mKeys; }
   mozilla::css::Declaration* Declaration()   { return mDeclaration; }
 
   void ChangeDeclaration(mozilla::css::Declaration* aDeclaration);
@@ -295,17 +295,17 @@ private:
 public:
   // Rule methods
 #ifdef DEBUG
   virtual void List(FILE* out = stdout, int32_t aIndent = 0) const override;
 #endif
   virtual already_AddRefed<mozilla::css::Rule> Clone() const override;
 
   // WebIDL interface
-  void GetCssTextImpl(nsAString& aCssText) const final override;
+  void GetCssText(nsAString& aCssText) const final override;
   void GetName(nsAString& aName) const final override;
   void SetName(const nsAString& aName) final override;
   mozilla::dom::CSSRuleList* CssRules() final override { return GroupRule::CssRules(); }
   void AppendRule(const nsAString& aRule) final override;
   void DeleteRule(const nsAString& aKey) final override;
   nsCSSKeyframeRule* FindRule(const nsAString& aKey) final override;
 
   const nsAtom* GetName() const { return mName; }
@@ -368,17 +368,17 @@ public:
   virtual bool IsCCLeaf() const override;
 
 #ifdef DEBUG
   virtual void List(FILE* out = stdout, int32_t aIndent = 0) const override;
 #endif
   virtual already_AddRefed<mozilla::css::Rule> Clone() const override;
 
   // WebIDL interfaces
-  virtual void GetCssTextImpl(nsAString& aCssText) const override;
+  virtual void GetCssText(nsAString& aCssText) const override;
   virtual nsICSSDeclaration* Style() override;
 
   mozilla::css::Declaration* Declaration()   { return mDeclaration; }
 
   void ChangeDeclaration(mozilla::css::Declaration* aDeclaration);
 
   virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override;
 
@@ -403,17 +403,17 @@ public:
 #endif
   virtual already_AddRefed<mozilla::css::Rule> Clone() const override;
   virtual bool UseForPresentation(nsPresContext* aPresContext,
                                   nsMediaQueryResultCacheKey& aKey) override;
 
   NS_DECL_ISUPPORTS_INHERITED
 
   // WebIDL interface
-  void GetCssTextImpl(nsAString& aCssText) const final override;
+  void GetCssText(nsAString& aCssText) const final override;
   void GetConditionText(nsAString& aConditionText) final override;
   void SetConditionText(const nsAString& aConditionText,
                         ErrorResult& aRv) final override;
 
   virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override;
 
 protected:
   virtual ~CSSSupportsRule();
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -1715,16 +1715,223 @@ nsComputedDOMStyle::DoGetTransformBox()
 {
   RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
   val->SetIdent(
       nsCSSProps::ValueToKeywordEnum(StyleDisplay()->mTransformBox,
                                      nsCSSProps::kTransformBoxKTable));
   return val.forget();
 }
 
+static already_AddRefed<CSSValue>
+ReadIndividualTransformValue(nsCSSValueSharedList* aList,
+                             const std::function<void(const nsCSSValue::Array*,
+                                                      nsString&)>& aCallback)
+{
+  if (!aList) {
+    RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+    val->SetIdent(eCSSKeyword_none);
+    return val.forget();
+  }
+
+  nsAutoString result;
+  const nsCSSValue::Array* data = aList->mHead->mValue.GetArrayValue();
+  aCallback(data, result);
+
+  RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+  val->SetString(result);
+  return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetTranslate()
+{
+  typedef nsStyleTransformMatrix::TransformReferenceBox TransformReferenceBox;
+
+  RefPtr<nsComputedDOMStyle> self(this);
+  return ReadIndividualTransformValue(StyleDisplay()->mSpecifiedTranslate,
+    [self](const nsCSSValue::Array* aData, nsString& aResult) {
+      GeckoStyleContext* contextIfGecko =
+        self->mStyleContext ? self->mStyleContext->GetAsGecko() : nullptr;
+      TransformReferenceBox refBox(self->mInnerFrame, nsSize(0, 0));
+      RuleNodeCacheConditions dummy;
+
+      // Even though the spec doesn't say to resolve percentage values, Blink
+      // and Edge do and so until that is clarified we do as well:
+      //
+      // https://github.com/w3c/csswg-drafts/issues/2124
+      switch (nsStyleTransformMatrix::TransformFunctionOf(aData)) {
+        /* translate : <length-percentage> */
+        case eCSSKeyword_translatex: {
+          NS_PRECONDITION(aData->Count() == 2, "Invalid array!");
+          float tx = ProcessTranslatePart(aData->Item(1),
+                                          contextIfGecko,
+                                          self->mStyleContext->PresContext(),
+                                          dummy,
+                                          &refBox,
+                                          &TransformReferenceBox::Width);
+          aResult.AppendFloat(tx);
+          aResult.AppendLiteral("px");
+          break;
+        }
+        /* translate : <length-percentage> <length-percentage> */
+        case eCSSKeyword_translate: {
+          NS_PRECONDITION(aData->Count() == 3, "Invalid array!");
+          float tx = ProcessTranslatePart(aData->Item(1),
+                                          contextIfGecko,
+                                          self->mStyleContext->PresContext(),
+                                          dummy,
+                                          &refBox,
+                                          &TransformReferenceBox::Width);
+          aResult.AppendFloat(tx);
+          aResult.AppendLiteral("px");
+
+          float ty = ProcessTranslatePart(aData->Item(2),
+                                          contextIfGecko,
+                                          self->mStyleContext->PresContext(),
+                                          dummy,
+                                          &refBox,
+                                          &TransformReferenceBox::Height);
+          if (ty != 0) {
+            aResult.AppendLiteral(" ");
+            aResult.AppendFloat(ty);
+            aResult.AppendLiteral("px");
+          }
+          break;
+        }
+        /* translate : <length-percentage> <length-percentage> <length>*/
+        case eCSSKeyword_translate3d: {
+          NS_PRECONDITION(aData->Count() == 4, "Invalid array!");
+          float tx = ProcessTranslatePart(aData->Item(1),
+                                          contextIfGecko,
+                                          self->mStyleContext->PresContext(),
+                                          dummy,
+                                          &refBox,
+                                          &TransformReferenceBox::Width);
+          aResult.AppendFloat(tx);
+          aResult.AppendLiteral("px");
+
+          float ty = ProcessTranslatePart(aData->Item(2),
+                                          contextIfGecko,
+                                          self->mStyleContext->PresContext(),
+                                          dummy,
+                                          &refBox,
+                                          &TransformReferenceBox::Height);
+
+          float tz = ProcessTranslatePart(aData->Item(3),
+                                          contextIfGecko,
+                                          self->mStyleContext->PresContext(),
+                                          dummy,
+                                          &refBox,
+                                          nullptr);
+          if (ty != 0. || tz != 0.) {
+            aResult.AppendLiteral(" ");
+            aResult.AppendFloat(ty);
+            aResult.AppendLiteral("px");
+          }
+          if (tz != 0.) {
+            aResult.AppendLiteral(" ");
+            aResult.AppendFloat(tz);
+            aResult.AppendLiteral("px");
+          }
+
+          break;
+        }
+        default:
+          MOZ_ASSERT_UNREACHABLE("Unexpected CSS keyword.");
+      }
+    });
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetScale()
+{
+  return ReadIndividualTransformValue(StyleDisplay()->mSpecifiedScale,
+    [](const nsCSSValue::Array* aData, nsString& aResult) {
+      switch (nsStyleTransformMatrix::TransformFunctionOf(aData)) {
+        /* scale : <number> */
+        case eCSSKeyword_scalex:
+          NS_PRECONDITION(aData->Count() == 2, "Invalid array!");
+          aResult.AppendFloat(aData->Item(1).GetFloatValue());
+          break;
+        /* scale : <number> <number>*/
+        case eCSSKeyword_scale: {
+          NS_PRECONDITION(aData->Count() == 3, "Invalid array!");
+          aResult.AppendFloat(aData->Item(1).GetFloatValue());
+
+          float sy = aData->Item(2).GetFloatValue();
+          if (sy != 1.) {
+            aResult.AppendLiteral(" ");
+            aResult.AppendFloat(sy);
+          }
+          break;
+        }
+        /* scale : <number> <number> <number> */
+        case eCSSKeyword_scale3d: {
+          NS_PRECONDITION(aData->Count() == 4, "Invalid array!");
+          aResult.AppendFloat(aData->Item(1).GetFloatValue());
+
+          float sy = aData->Item(2).GetFloatValue();
+          float sz = aData->Item(3).GetFloatValue();
+
+          if (sy != 1. || sz != 1.) {
+            aResult.AppendLiteral(" ");
+            aResult.AppendFloat(sy);
+          }
+          if (sz != 1.) {
+            aResult.AppendLiteral(" ");
+            aResult.AppendFloat(sz);
+          }
+          break;
+        }
+        default:
+          MOZ_ASSERT_UNREACHABLE("Unexpected CSS keyword.");
+      }
+    });
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetRotate()
+{
+  return ReadIndividualTransformValue(StyleDisplay()->mSpecifiedRotate,
+    [](const nsCSSValue::Array* aData, nsString& aResult) {
+
+      switch (nsStyleTransformMatrix::TransformFunctionOf(aData)) {
+        /* rotate : <angle> */
+        case eCSSKeyword_rotate: {
+          NS_PRECONDITION(aData->Count() == 2, "Invalid array!");
+          float theta = aData->Item(1).GetAngleValueInDegrees();
+          aResult.AppendFloat(theta);
+          aResult.AppendLiteral("deg");
+          break;
+        }
+        /* rotate : <number> <number> <number> <angle> */
+        case eCSSKeyword_rotate3d: {
+          NS_PRECONDITION(aData->Count() == 5, "Invalid array!");
+          float rx = aData->Item(1).GetFloatValue();
+          float ry = aData->Item(2).GetFloatValue();
+          float rz = aData->Item(3).GetFloatValue();
+          if (rx != 0. || ry != 0. || rz != 1.) {
+            aResult.AppendFloat(rx);
+            aResult.AppendLiteral(" ");
+            aResult.AppendFloat(ry);
+            aResult.AppendLiteral(" ");
+            aResult.AppendFloat(rz);
+            aResult.AppendLiteral(" ");
+          }
+          float theta = aData->Item(4).GetAngleValueInDegrees();
+          aResult.AppendFloat(theta);
+          aResult.AppendLiteral("deg");
+          break;
+        }
+        default:
+          MOZ_ASSERT_UNREACHABLE("Unexpected CSS keyword.");
+      }
+    });
+}
+
 /* static */ already_AddRefed<nsROCSSPrimitiveValue>
 nsComputedDOMStyle::MatrixToCSSValue(const mozilla::gfx::Matrix4x4& matrix)
 {
   bool is3D = !matrix.Is2D();
 
   nsAutoString resultString(NS_LITERAL_STRING("matrix"));
   if (is3D) {
     resultString.AppendLiteral("3d");
--- a/layout/style/nsComputedDOMStyle.h
+++ b/layout/style/nsComputedDOMStyle.h
@@ -499,16 +499,19 @@ private:
   already_AddRefed<CSSValue> DoGetOverflowClipBoxBlock();
   already_AddRefed<CSSValue> DoGetOverflowClipBoxInline();
   already_AddRefed<CSSValue> DoGetResize();
   already_AddRefed<CSSValue> DoGetPageBreakAfter();
   already_AddRefed<CSSValue> DoGetPageBreakBefore();
   already_AddRefed<CSSValue> DoGetPageBreakInside();
   already_AddRefed<CSSValue> DoGetTouchAction();
   already_AddRefed<CSSValue> DoGetTransform();
+  already_AddRefed<CSSValue> DoGetTranslate();
+  already_AddRefed<CSSValue> DoGetRotate();
+  already_AddRefed<CSSValue> DoGetScale();
   already_AddRefed<CSSValue> DoGetTransformBox();
   already_AddRefed<CSSValue> DoGetTransformOrigin();
   already_AddRefed<CSSValue> DoGetPerspective();
   already_AddRefed<CSSValue> DoGetBackfaceVisibility();
   already_AddRefed<CSSValue> DoGetPerspectiveOrigin();
   already_AddRefed<CSSValue> DoGetTransformStyle();
   already_AddRefed<CSSValue> DoGetOrient();
   already_AddRefed<CSSValue> DoGetScrollBehavior();
--- a/layout/style/nsComputedDOMStylePropertyList.h
+++ b/layout/style/nsComputedDOMStylePropertyList.h
@@ -217,18 +217,20 @@ COMPUTED_STYLE_PROP(page_break_before,  
 COMPUTED_STYLE_PROP(page_break_inside,             PageBreakInside)
 COMPUTED_STYLE_PROP(perspective,                   Perspective)
 COMPUTED_STYLE_PROP(perspective_origin,            PerspectiveOrigin)
 COMPUTED_STYLE_PROP(pointer_events,                PointerEvents)
 COMPUTED_STYLE_PROP(position,                      Position)
 COMPUTED_STYLE_PROP(quotes,                        Quotes)
 COMPUTED_STYLE_PROP(resize,                        Resize)
 COMPUTED_STYLE_PROP(right,                         Right)
+COMPUTED_STYLE_PROP(rotate,                        Rotate)
 COMPUTED_STYLE_PROP(ruby_align,                    RubyAlign)
 COMPUTED_STYLE_PROP(ruby_position,                 RubyPosition)
+COMPUTED_STYLE_PROP(scale,                         Scale)
 COMPUTED_STYLE_PROP(scroll_behavior,               ScrollBehavior)
 COMPUTED_STYLE_PROP(scroll_snap_coordinate,        ScrollSnapCoordinate)
 COMPUTED_STYLE_PROP(scroll_snap_destination,       ScrollSnapDestination)
 COMPUTED_STYLE_PROP(scroll_snap_points_x,          ScrollSnapPointsX)
 COMPUTED_STYLE_PROP(scroll_snap_points_y,          ScrollSnapPointsY)
 COMPUTED_STYLE_PROP(scroll_snap_type_x,            ScrollSnapTypeX)
 COMPUTED_STYLE_PROP(scroll_snap_type_y,            ScrollSnapTypeY)
 COMPUTED_STYLE_PROP(shape_image_threshold,         ShapeImageThreshold)
@@ -258,16 +260,17 @@ COMPUTED_STYLE_PROP(transform,          
 COMPUTED_STYLE_PROP(transform_box,                 TransformBox)
 COMPUTED_STYLE_PROP(transform_origin,              TransformOrigin)
 COMPUTED_STYLE_PROP(transform_style,               TransformStyle)
 //// COMPUTED_STYLE_PROP(transition,               Transition)
 COMPUTED_STYLE_PROP(transition_delay,              TransitionDelay)
 COMPUTED_STYLE_PROP(transition_duration,           TransitionDuration)
 COMPUTED_STYLE_PROP(transition_property,           TransitionProperty)
 COMPUTED_STYLE_PROP(transition_timing_function,    TransitionTimingFunction)
+COMPUTED_STYLE_PROP(translate,                     Translate)
 COMPUTED_STYLE_PROP(unicode_bidi,                  UnicodeBidi)
 COMPUTED_STYLE_PROP(vertical_align,                VerticalAlign)
 COMPUTED_STYLE_PROP(visibility,                    Visibility)
 COMPUTED_STYLE_PROP(white_space,                   WhiteSpace)
 // COMPUTED_STYLE_PROP(widows,                     Widows)
 COMPUTED_STYLE_PROP(width,                         Width)
 COMPUTED_STYLE_PROP(will_change,                   WillChange)
 COMPUTED_STYLE_PROP(word_break,                    WordBreak)
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -3576,17 +3576,16 @@ nsStyleDisplay::nsStyleDisplay(const nsP
   , mOverscrollBehaviorY(StyleOverscrollBehavior::Auto)
   , mScrollSnapTypeX(NS_STYLE_SCROLL_SNAP_TYPE_NONE)
   , mScrollSnapTypeY(NS_STYLE_SCROLL_SNAP_TYPE_NONE)
   , mScrollSnapPointsX(eStyleUnit_None)
   , mScrollSnapPointsY(eStyleUnit_None)
   , mBackfaceVisibility(NS_STYLE_BACKFACE_VISIBILITY_VISIBLE)
   , mTransformStyle(NS_STYLE_TRANSFORM_STYLE_FLAT)
   , mTransformBox(StyleGeometryBox::BorderBox)
-  , mSpecifiedTransform(nullptr)
   , mTransformOrigin{ {0.5f, eStyleUnit_Percent}, // Transform is centered on origin
                       {0.5f, eStyleUnit_Percent},
                       {0, nsStyleCoord::CoordConstructor} }
   , mChildPerspective(eStyleUnit_None)
   , mPerspectiveOrigin{ {0.5f, eStyleUnit_Percent},
                         {0.5f, eStyleUnit_Percent} }
   , mVerticalAlign(NS_STYLE_VERTICAL_ALIGN_BASELINE, eStyleUnit_Enumerated)
   , mTransitions(nsStyleAutoArray<StyleTransition>::WITH_SINGLE_INITIAL_ELEMENT)
@@ -3645,16 +3644,20 @@ nsStyleDisplay::nsStyleDisplay(const nsS
   , mScrollSnapPointsX(aSource.mScrollSnapPointsX)
   , mScrollSnapPointsY(aSource.mScrollSnapPointsY)
   , mScrollSnapDestination(aSource.mScrollSnapDestination)
   , mScrollSnapCoordinate(aSource.mScrollSnapCoordinate)
   , mBackfaceVisibility(aSource.mBackfaceVisibility)
   , mTransformStyle(aSource.mTransformStyle)
   , mTransformBox(aSource.mTransformBox)
   , mSpecifiedTransform(aSource.mSpecifiedTransform)
+  , mSpecifiedRotate(aSource.mSpecifiedRotate)
+  , mSpecifiedTranslate(aSource.mSpecifiedTranslate)
+  , mSpecifiedScale(aSource.mSpecifiedScale)
+  , mCombinedTransform(aSource.mCombinedTransform)
   , mTransformOrigin{ aSource.mTransformOrigin[0],
                       aSource.mTransformOrigin[1],
                       aSource.mTransformOrigin[2] }
   , mChildPerspective(aSource.mChildPerspective)
   , mPerspectiveOrigin{ aSource.mPerspectiveOrigin[0],
                         aSource.mPerspectiveOrigin[1] }
   , mVerticalAlign(aSource.mVerticalAlign)
   , mTransitions(aSource.mTransitions)
@@ -3672,55 +3675,89 @@ nsStyleDisplay::nsStyleDisplay(const nsS
   , mAnimationPlayStateCount(aSource.mAnimationPlayStateCount)
   , mAnimationIterationCountCount(aSource.mAnimationIterationCountCount)
   , mShapeImageThreshold(aSource.mShapeImageThreshold)
   , mShapeOutside(aSource.mShapeOutside)
 {
   MOZ_COUNT_CTOR(nsStyleDisplay);
 }
 
-nsStyleDisplay::~nsStyleDisplay()
+
+static
+void ReleaseSharedListOnMainThread(const char* aName,
+                                   RefPtr<nsCSSValueSharedList>& aList)
 {
   // We don't allow releasing nsCSSValues with refcounted data in the Servo
   // traversal, since the refcounts aren't threadsafe. Since Servo may trigger
   // the deallocation of style structs during styling, we need to handle it
   // here.
-  if (mSpecifiedTransform && ServoStyleSet::IsInServoTraversal()) {
+  if (aList && ServoStyleSet::IsInServoTraversal()) {
     // The default behavior of NS_ReleaseOnMainThreadSystemGroup is to only
     // proxy the release if we're not already on the main thread. This is a nice
     // optimization for the cases we happen to be doing a sequential traversal
     // (i.e. a single-core machine), but it trips our assertions which check
     // whether we're in a Servo traversal, parallel or not. So we
     // unconditionally proxy in debug builds.
     bool alwaysProxy =
 #ifdef DEBUG
       true;
 #else
       false;
 #endif
-    NS_ReleaseOnMainThreadSystemGroup(
-      "nsStyleDisplay::mSpecifiedTransform",
-      mSpecifiedTransform.forget(), alwaysProxy);
-  }
+    NS_ReleaseOnMainThreadSystemGroup(aName, aList.forget(), alwaysProxy);
+  }
+}
+
+nsStyleDisplay::~nsStyleDisplay()
+{
+  ReleaseSharedListOnMainThread("nsStyleDisplay::mSpecifiedTransform",
+                                mSpecifiedTransform);
+  ReleaseSharedListOnMainThread("nsStyleDisplay::mSpecifiedRotate",
+                                mSpecifiedRotate);
+  ReleaseSharedListOnMainThread("nsStyleDisplay::mSpecifiedTranslate",
+                                mSpecifiedTranslate);
+  ReleaseSharedListOnMainThread("nsStyleDisplay::mSpecifiedScale",
+                                mSpecifiedScale);
+  ReleaseSharedListOnMainThread("nsStyleDisplay::mCombinedTransform",
+                                mCombinedTransform);
 
   MOZ_COUNT_DTOR(nsStyleDisplay);
 }
 
 void
 nsStyleDisplay::FinishStyle(nsPresContext* aPresContext)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aPresContext->StyleSet()->IsServo());
 
   if (mShapeOutside.GetType() == StyleShapeSourceType::Image) {
     const UniquePtr<nsStyleImage>& shapeImage = mShapeOutside.GetShapeImage();
     if (shapeImage) {
       shapeImage->ResolveImage(aPresContext);
     }
   }
+
+  GenerateCombinedTransform();
+}
+
+static inline nsChangeHint
+CompareTransformValues(const RefPtr<nsCSSValueSharedList>& aList,
+                       const RefPtr<nsCSSValueSharedList>& aNewList)
+{
+  nsChangeHint result = nsChangeHint(0);
+  if (!aList != !aNewList || (aList && *aList != *aNewList)) {
+    result |= nsChangeHint_UpdateTransformLayer;
+    if (aList && aNewList) {
+      result |= nsChangeHint_UpdatePostTransformOverflow;
+    } else {
+      result |= nsChangeHint_UpdateOverflow;
+    }
+  }
+
+  return result;
 }
 
 nsChangeHint
 nsStyleDisplay::CalcDifference(const nsStyleDisplay& aNewData) const
 {
   nsChangeHint hint = nsChangeHint(0);
 
   if (!DefinitelyEqualURIsAndPrincipal(mBinding, aNewData.mBinding)
@@ -3841,28 +3878,24 @@ nsStyleDisplay::CalcDifference(const nsS
      * overflow rect.
      *
      * If the property isn't present in either style struct, we still do the
      * comparisons but turn all the resulting change hints into
      * nsChangeHint_NeutralChange.
      */
     nsChangeHint transformHint = nsChangeHint(0);
 
-    if (!mSpecifiedTransform != !aNewData.mSpecifiedTransform ||
-        (mSpecifiedTransform &&
-         *mSpecifiedTransform != *aNewData.mSpecifiedTransform)) {
-      transformHint |= nsChangeHint_UpdateTransformLayer;
-
-      if (mSpecifiedTransform &&
-          aNewData.mSpecifiedTransform) {
-        transformHint |= nsChangeHint_UpdatePostTransformOverflow;
-      } else {
-        transformHint |= nsChangeHint_UpdateOverflow;
-      }
-    }
+    transformHint |= CompareTransformValues(mSpecifiedTransform,
+                                            aNewData.mSpecifiedTransform);
+    transformHint |= CompareTransformValues(mSpecifiedRotate, aNewData.
+                                            mSpecifiedRotate);
+    transformHint |= CompareTransformValues(mSpecifiedTranslate,
+                                            aNewData.mSpecifiedTranslate);
+    transformHint |= CompareTransformValues(mSpecifiedScale,
+                                            aNewData.mSpecifiedScale);
 
     const nsChangeHint kUpdateOverflowAndRepaintHint =
       nsChangeHint_UpdateOverflow | nsChangeHint_RepaintFrame;
     for (uint8_t index = 0; index < 3; ++index) {
       if (mTransformOrigin[index] != aNewData.mTransformOrigin[index]) {
         transformHint |= nsChangeHint_UpdateTransformLayer |
                          nsChangeHint_UpdatePostTransformOverflow;
         break;
@@ -3967,16 +4000,67 @@ nsStyleDisplay::CalcDifference(const nsS
        mAnimationIterationCountCount != aNewData.mAnimationIterationCountCount ||
        mScrollSnapCoordinate != aNewData.mScrollSnapCoordinate)) {
     hint |= nsChangeHint_NeutralChange;
   }
 
   return hint;
 }
 
+void
+nsStyleDisplay::GenerateCombinedTransform()
+{
+  mCombinedTransform = nullptr;
+
+  // Follow the order defined in the spec to append transform functions.
+  // https://drafts.csswg.org/css-transforms-2/#ctm
+  AutoTArray<nsCSSValueSharedList*, 4> shareLists;
+  if (mSpecifiedTranslate) {
+    shareLists.AppendElement(mSpecifiedTranslate.get());
+  }
+  if (mSpecifiedRotate) {
+    shareLists.AppendElement(mSpecifiedRotate.get());
+  }
+  if (mSpecifiedScale) {
+    shareLists.AppendElement(mSpecifiedScale.get());
+  }
+  if (mSpecifiedTransform) {
+    shareLists.AppendElement(mSpecifiedTransform.get());
+  }
+
+  if (shareLists.Length() == 0) {
+    return;
+  }
+
+  if (shareLists.Length() == 1) {
+    mCombinedTransform = shareLists[0];
+    return;
+  }
+
+  // In common, we may have 3 transform functions(for rotate, translate and
+  // scale) in mSpecifiedTransform, one rotate function in mSpecifiedRotate,
+  // one translate function in mSpecifiedTranslate, and one scale function in
+  // mSpecifiedScale. So 6 slots are enough for the most cases.
+  AutoTArray<nsCSSValueList*, 6> valueLists;
+  for (auto list: shareLists) {
+    if (list) {
+      valueLists.AppendElement(list->mHead->Clone());
+    }
+  }
+
+  // Check we have at least one list or else valueLists.Length() - 1 below will
+  // underflow.
+  MOZ_ASSERT(valueLists.Length());
+
+  for (uint32_t i = 0; i < valueLists.Length() - 1; i++) {
+    valueLists[i]->mNext = valueLists[i + 1];
+  }
+
+  mCombinedTransform = new nsCSSValueSharedList(valueLists[0]);
+}
 // --------------------
 // nsStyleVisibility
 //
 
 nsStyleVisibility::nsStyleVisibility(const nsPresContext* aContext)
   : mDirection(aContext->GetBidi() == IBMBIDI_TEXTDIRECTION_RTL
                  ? NS_STYLE_DIRECTION_RTL
                  : NS_STYLE_DIRECTION_LTR)
@@ -4624,28 +4708,18 @@ nsStyleUIReset::nsStyleUIReset(const nsS
 {
   MOZ_COUNT_CTOR(nsStyleUIReset);
 }
 
 nsStyleUIReset::~nsStyleUIReset()
 {
   MOZ_COUNT_DTOR(nsStyleUIReset);
 
-  // See the nsStyleDisplay destructor for why we're doing this.
-  if (mSpecifiedWindowTransform && ServoStyleSet::IsInServoTraversal()) {
-    bool alwaysProxy =
-#ifdef DEBUG
-      true;
-#else
-      false;
-#endif
-    NS_ReleaseOnMainThreadSystemGroup(
-      "nsStyleUIReset::mSpecifiedWindowTransform",
-      mSpecifiedWindowTransform.forget(), alwaysProxy);
-  }
+  ReleaseSharedListOnMainThread("nsStyleUIReset::mSpecifiedWindowTransform",
+                                mSpecifiedWindowTransform);
 }
 
 nsChangeHint
 nsStyleUIReset::CalcDifference(const nsStyleUIReset& aNewData) const
 {
   nsChangeHint hint = nsChangeHint(0);
 
   if (mForceBrokenImageIcon != aNewData.mForceBrokenImageIcon) {
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -2551,16 +2551,26 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
   // mSpecifiedTransform is the list of transform functions as
   // specified, or null to indicate there is no transform.  (inherit or
   // initial are replaced by an actual list of transform functions, or
   // null, as appropriate.)
   uint8_t mBackfaceVisibility;
   uint8_t mTransformStyle;
   StyleGeometryBox mTransformBox; // [reset] see nsStyleConsts.h
   RefPtr<nsCSSValueSharedList> mSpecifiedTransform; // [reset]
+  RefPtr<nsCSSValueSharedList> mSpecifiedRotate; // [reset]
+  RefPtr<nsCSSValueSharedList> mSpecifiedTranslate; // [reset]
+  RefPtr<nsCSSValueSharedList> mSpecifiedScale; // [reset]
+
+  // Used to store the final combination of mSpecifiedTranslate,
+  // mSpecifiedRotate, mSpecifiedScale and mSpecifiedTransform.
+  // Use GetCombinedTransform() to get the final transform, instead of
+  // accessing mCombinedTransform directly.
+  RefPtr<nsCSSValueSharedList> mCombinedTransform;
+
   nsStyleCoord mTransformOrigin[3]; // [reset] percent, coord, calc, 3rd param is coord, calc only
   nsStyleCoord mChildPerspective; // [reset] none, coord
   nsStyleCoord mPerspectiveOrigin[2]; // [reset] percent, coord, calc
 
   nsStyleCoord mVerticalAlign;  // [reset] coord, percent, calc, enum (see nsStyleConsts.h)
 
   nsStyleAutoArray<mozilla::StyleTransition> mTransitions; // [reset]
 
@@ -2748,21 +2758,26 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
 
   bool IsContainPaint() const {
     return NS_STYLE_CONTAIN_PAINT & mContain;
   }
 
   /* Returns whether the element has the -moz-transform property
    * or a related property. */
   bool HasTransformStyle() const {
-    return mSpecifiedTransform != nullptr ||
+    return mSpecifiedTransform || mSpecifiedRotate || mSpecifiedTranslate ||
+           mSpecifiedScale ||
            mTransformStyle == NS_STYLE_TRANSFORM_STYLE_PRESERVE_3D ||
            (mWillChangeBitField & NS_STYLE_WILL_CHANGE_TRANSFORM);
   }
 
+  bool HasIndividualTransform() const {
+    return mSpecifiedRotate || mSpecifiedTranslate || mSpecifiedScale;
+  }
+
   bool HasPerspectiveStyle() const {
     return mChildPerspective.GetUnit() == eStyleUnit_Coord;
   }
 
   bool BackfaceIsHidden() const {
     return mBackfaceVisibility == NS_STYLE_BACKFACE_VISIBILITY_HIDDEN;
   }
 
@@ -2817,26 +2832,38 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
    * The same as IsFixedPosContainingBlock, except skipping the tests that
    * are based on the frame rather than the style context (thus
    * potentially returning a false positive).
    */
   template<class StyleContextLike>
   inline bool IsFixedPosContainingBlockForAppropriateFrame(
                 StyleContextLike* aStyleContext) const;
 
+  /**
+   * Returns the final combined transform.
+   **/
+  already_AddRefed<nsCSSValueSharedList> GetCombinedTransform() const {
+    if (mCombinedTransform) {
+      return do_AddRef(mCombinedTransform);
+    }
+
+    // backward compatible to gecko-backed style system.
+    return mSpecifiedTransform ? do_AddRef(mSpecifiedTransform) : nullptr;
+  }
+
 private:
   // Helpers for above functions, which do some but not all of the tests
   // for them (since transform must be tested separately for each).
   template<class StyleContextLike>
   inline bool HasAbsPosContainingBlockStyleInternal(
                 StyleContextLike* aStyleContext) const;
   template<class StyleContextLike>
   inline bool HasFixedPosContainingBlockStyleInternal(
                 StyleContextLike* aStyleContext) const;
-
+  void GenerateCombinedTransform();
 public:
   // Return the 'float' and 'clear' properties, with inline-{start,end} values
   // resolved to {left,right} according to the given writing mode. These are
   // defined in WritingModes.h.
   inline mozilla::StyleFloat PhysicalFloats(mozilla::WritingMode aWM) const;
   inline mozilla::StyleClear PhysicalBreakType(mozilla::WritingMode aWM) const;
 };
 
--- a/layout/tools/reftest/runreftest.py
+++ b/layout/tools/reftest/runreftest.py
@@ -326,22 +326,23 @@ class RefTest(object):
 
         # Bug 1300355: Disable canvas cache for win7 as it uses
         # too much memory and causes OOMs.
         if platform.system() in ("Windows", "Microsoft") and \
            '6.1' in platform.version():
             prefs['reftest.nocache'] = True
 
         if options.marionette:
+            # options.marionette can specify host:port
             port = options.marionette.split(":")[1]
             prefs["marionette.port"] = int(port)
 
-            # Enable tracing output for detailed failures in case of
-            # failing connection attempts, and hangs (bug 1397201)
-            prefs["marionette.log.level"] = "TRACE"
+        # Enable tracing output for detailed failures in case of
+        # failing connection attempts, and hangs (bug 1397201)
+        prefs["marionette.log.level"] = "TRACE"
 
         preference_file = os.path.join(here, 'reftest-preferences.js')
         prefs.update(mozprofile.Preferences.read_prefs(preference_file))
 
         for v in options.extraPrefs:
             thispref = v.split('=')
             if len(thispref) < 2:
                 print "Error: syntax error in --setpref=" + v
--- a/media/libvpx/libvpx/vpx/src/vpx_encoder.c
+++ b/media/libvpx/libvpx/vpx/src/vpx_encoder.c
@@ -7,18 +7,21 @@
  *  in the file PATENTS.  All contributing project authors may
  *  be found in the AUTHORS file in the root of the source tree.
  */
 
 /*!\file
  * \brief Provides the high level interface to wrap encoder algorithms.
  *
  */
+#include <assert.h>
 #include <limits.h>
+#include <stdlib.h>
 #include <string.h>
+#include "vp8/common/blockd.h"
 #include "vpx_config.h"
 #include "vpx/internal/vpx_codec_internal.h"
 
 #define SAVE_STATUS(ctx, var) (ctx ? (ctx->err = var) : var)
 
 static vpx_codec_alg_priv_t *get_alg_priv(vpx_codec_ctx_t *ctx) {
   return (vpx_codec_alg_priv_t *)ctx->priv;
 }
@@ -76,63 +79,68 @@ vpx_codec_err_t vpx_codec_enc_init_multi
     res = VPX_CODEC_INCAPABLE;
   else if ((flags & VPX_CODEC_USE_OUTPUT_PARTITION) &&
            !(iface->caps & VPX_CODEC_CAP_OUTPUT_PARTITION))
     res = VPX_CODEC_INCAPABLE;
   else {
     int i;
     void *mem_loc = NULL;
 
+    if (iface->enc.mr_get_mem_loc == NULL) return VPX_CODEC_INCAPABLE;
+
     if (!(res = iface->enc.mr_get_mem_loc(cfg, &mem_loc))) {
       for (i = 0; i < num_enc; i++) {
         vpx_codec_priv_enc_mr_cfg_t mr_cfg;
 
         /* Validate down-sampling factor. */
         if (dsf->num < 1 || dsf->num > 4096 || dsf->den < 1 ||
             dsf->den > dsf->num) {
           res = VPX_CODEC_INVALID_PARAM;
-          break;
-        }
-
-        mr_cfg.mr_low_res_mode_info = mem_loc;
-        mr_cfg.mr_total_resolutions = num_enc;
-        mr_cfg.mr_encoder_id = num_enc - 1 - i;
-        mr_cfg.mr_down_sampling_factor.num = dsf->num;
-        mr_cfg.mr_down_sampling_factor.den = dsf->den;
+        } else {
+          mr_cfg.mr_low_res_mode_info = mem_loc;
+          mr_cfg.mr_total_resolutions = num_enc;
+          mr_cfg.mr_encoder_id = num_enc - 1 - i;
+          mr_cfg.mr_down_sampling_factor.num = dsf->num;
+          mr_cfg.mr_down_sampling_factor.den = dsf->den;
 
-        /* Force Key-frame synchronization. Namely, encoder at higher
-         * resolution always use the same frame_type chosen by the
-         * lowest-resolution encoder.
-         */
-        if (mr_cfg.mr_encoder_id) cfg->kf_mode = VPX_KF_DISABLED;
+          /* Force Key-frame synchronization. Namely, encoder at higher
+           * resolution always use the same frame_type chosen by the
+           * lowest-resolution encoder.
+           */
+          if (mr_cfg.mr_encoder_id) cfg->kf_mode = VPX_KF_DISABLED;
 
-        ctx->iface = iface;
-        ctx->name = iface->name;
-        ctx->priv = NULL;
-        ctx->init_flags = flags;
-        ctx->config.enc = cfg;
-        res = ctx->iface->init(ctx, &mr_cfg);
+          ctx->iface = iface;
+          ctx->name = iface->name;
+          ctx->priv = NULL;
+          ctx->init_flags = flags;
+          ctx->config.enc = cfg;
+          res = ctx->iface->init(ctx, &mr_cfg);
+        }
 
         if (res) {
           const char *error_detail = ctx->priv ? ctx->priv->err_detail : NULL;
           /* Destroy current ctx */
           ctx->err_detail = error_detail;
           vpx_codec_destroy(ctx);
 
           /* Destroy already allocated high-level ctx */
           while (i) {
             ctx--;
             ctx->err_detail = error_detail;
             vpx_codec_destroy(ctx);
             i--;
           }
+#if CONFIG_MULTI_RES_ENCODING
+          assert(mem_loc);
+          free(((LOWER_RES_FRAME_INFO *)mem_loc)->mb_info);
+          free(mem_loc);
+#endif
+          return SAVE_STATUS(ctx, res);
         }
 
-        if (res) break;
-
         ctx++;
         cfg++;
         dsf++;
       }
       ctx--;
     }
   }
 
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -2924,16 +2924,19 @@ pref("layout.css.moz-document.content.en
 pref("layout.css.dpi", -1);
 
 // Set the number of device pixels per CSS pixel. A value <= 0 means choose
 // automatically based on user settings for the platform (e.g., "UI scale factor"
 // on Mac). A positive value is used as-is. This effectively controls the size
 // of a CSS "px". This is only used for windows on the screen, not for printing.
 pref("layout.css.devPixelsPerPx", "-1.0");
 
+// Is support for CSS individual transform enabled?
+pref("layout.css.individual-transform.enabled", false);
+
 // Is support for CSS initial-letter property enabled?
 pref("layout.css.initial-letter.enabled", false);
 
 // Is support for mix-blend-mode enabled?
 pref("layout.css.mix-blend-mode.enabled", true);
 
 // Is support for isolation enabled?
 pref("layout.css.isolation.enabled", true);
--- a/netwerk/base/nsFileStreams.cpp
+++ b/netwerk/base/nsFileStreams.cpp
@@ -800,21 +800,28 @@ nsAtomicFileOutputStream::DoOpen()
 
         // XP_UNIX ignores SetFollowLinks(), so we have to normalize.
         if (mTargetFileExists) {
             tempResult->Normalize();
         }
     }
 
     if (NS_SUCCEEDED(rv) && mTargetFileExists) {
+        // Abort if |file| is not writable; it won't work as an output stream.
+        bool isWritable;
+        if (NS_SUCCEEDED(file->IsWritable(&isWritable)) && !isWritable) {
+            return NS_ERROR_FILE_ACCESS_DENIED;
+        }
+
         uint32_t origPerm;
         if (NS_FAILED(file->GetPermissions(&origPerm))) {
             NS_ERROR("Can't get permissions of target file");
             origPerm = mOpenParams.perm;
         }
+
         // XXX What if |perm| is more restrictive then |origPerm|?
         // This leaves the user supplied permissions as they were.
         rv = tempResult->CreateUnique(nsIFile::NORMAL_FILE_TYPE, origPerm);
     }
     if (NS_SUCCEEDED(rv)) {
         // nsFileOutputStream::DoOpen will work on the temporary file, so we
         // prepare it and place it in mOpenParams.localFile.
         mOpenParams.localFile = tempResult;
--- a/taskcluster/taskgraph/transforms/signing.py
+++ b/taskcluster/taskgraph/transforms/signing.py
@@ -141,16 +141,17 @@ def make_task_description(config, jobs):
             'worker-type': get_worker_type_for_scope(config, signing_cert_scope),
             'worker': {'implementation': 'scriptworker-signing',
                        'upstream-artifacts': job['upstream-artifacts'],
                        'max-run-time': 3600},
             'scopes': [signing_cert_scope] + signing_format_scopes,
             'dependencies': {job['depname']: dep_job.label},
             'attributes': attributes,
             'run-on-projects': dep_job.attributes.get('run_on_projects'),
+            'optimization': dep_job.optimization,
             'treeherder': treeherder,
             'routes': job.get('routes', []),
         }
 
         yield task
 
 
 def _generate_treeherder_platform(dep_th_platform, build_platform, build_type):
--- a/toolkit/components/payments/content/paymentDialog.js
+++ b/toolkit/components/payments/content/paymentDialog.js
@@ -14,18 +14,18 @@ const paymentSrv = Cc["@mozilla.org/dom/
                      .getService(Ci.nsIPaymentRequestService);
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "profileStorage", () => {
   let profileStorage;
   try {
-    profileStorage = ChromeUtils.import(
-      "resource://formautofill/ProfileStorage.jsm", {}).profileStorage;
+    profileStorage = ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm", {})
+                                .profileStorage;
     profileStorage.initialize();
   } catch (ex) {
     profileStorage = null;
     Cu.reportError(ex);
   }
 
   return profileStorage;
 });
--- a/toolkit/components/payments/res/components/address-option.js
+++ b/toolkit/components/payments/res/components/address-option.js
@@ -10,17 +10,17 @@
  *                  email="foo@example.com"
  *                  country="USA"
  *                  name="Jared Wein"
  *                  postal-code="90210"
  *                  street-address="1234 Anywhere St"
  *                  tel="+1 650 555-5555"></address-option>
  * </rich-select>
  *
- * Attribute names follow ProfileStorage.jsm.
+ * Attribute names follow FormAutofillStorage.jsm.
  */
 
 /* global ObservedPropertiesMixin, RichOption */
 
 class AddressOption extends ObservedPropertiesMixin(RichOption) {
   static get observedAttributes() {
     return RichOption.observedAttributes.concat([
       "address-level1",
--- a/toolkit/components/payments/test/browser/head.js
+++ b/toolkit/components/payments/test/browser/head.js
@@ -12,17 +12,17 @@
 const BLANK_PAGE_PATH = "/browser/toolkit/components/payments/test/browser/blank_page.html";
 const BLANK_PAGE_URL = "https://example.com" + BLANK_PAGE_PATH;
 
 const paymentSrv = Cc["@mozilla.org/dom/payments/payment-request-service;1"]
                      .getService(Ci.nsIPaymentRequestService);
 const paymentUISrv = Cc["@mozilla.org/dom/payments/payment-ui-service;1"]
                      .getService().wrappedJSObject;
 const {profileStorage} = ChromeUtils.import(
-  "resource://formautofill/ProfileStorage.jsm", {});
+  "resource://formautofill/FormAutofillStorage.jsm", {});
 const {PaymentTestUtils: PTU} = ChromeUtils.import(
   "resource://testing-common/PaymentTestUtils.jsm", {});
 
 function getPaymentRequests() {
   let requestsEnum = paymentSrv.enumerate();
   let requests = [];
   while (requestsEnum.hasMoreElements()) {
     requests.push(requestsEnum.getNext().QueryInterface(Ci.nsIPaymentRequest));
--- a/tools/lint/eslint/modules.json
+++ b/tools/lint/eslint/modules.json
@@ -153,17 +153,17 @@
   "PdfJsNetwork.jsm": ["NetworkManager"],
   "PhoneNumberMetaData.jsm": ["PHONE_NUMBER_META_DATA"],
   "PlacesUtils.jsm": ["PlacesUtils"],
   "PluginProvider.jsm": [],
   "PointerAdapter.jsm": ["PointerRelay", "PointerAdapter"],
   "policies.js": ["ErrorHandler", "SyncScheduler"],
   "prefs.js": ["PrefsEngine", "PrefRec"],
   "prefs.jsm": ["Preference"],
-  "ProfileStorage.jsm": ["profileStorage"],
+  "FormAutofillStorage.jsm": ["profileStorage"],
   "PromiseWorker.jsm": ["BasePromiseWorker"],
   "PushCrypto.jsm": ["PushCrypto", "concatArray"],
   "quit.js": ["goQuitApplication"],
   "Readability.js": ["Readability"],
   "record.js": ["WBORecord", "RecordManager", "CryptoWrapper", "CollectionKeyManager", "Collection"],
   "recursive_importA.jsm": ["foo", "bar"],
   "recursive_importB.jsm": ["baz", "qux"],
   "reflect.jsm": ["Reflect"],
--- a/xpcom/rust/gtest/xpcom/Test.cpp
+++ b/xpcom/rust/gtest/xpcom/Test.cpp
@@ -1,24 +1,24 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "gtest/gtest.h"
 #include "nsCOMPtr.h"
 #include "nsIRunnable.h"
 #include "mozilla/Services.h"
-#include "nsIIOService.h"
+#include "nsIObserverService.h"
 
-extern "C" nsIIOService* Rust_CallIURIFromRust();
+extern "C" nsIObserverService* Rust_ObserveFromRust();
 
-TEST(RustXpcom, CallIURIFromRust)
+TEST(RustXpcom, ObserverFromRust)
 {
-  nsCOMPtr<nsIIOService> rust = Rust_CallIURIFromRust();
-  nsCOMPtr<nsIIOService> cpp = mozilla::services::GetIOService();
+  nsCOMPtr<nsIObserverService> rust = Rust_ObserveFromRust();
+  nsCOMPtr<nsIObserverService> cpp = mozilla::services::GetObserverService();
   EXPECT_EQ(rust, cpp);
 }
 
 extern "C" void Rust_ImplementRunnableInRust(bool* aItWorked,
                                              nsIRunnable** aRunnable);
 
 TEST(RustXpcom, ImplementRunnableInRust)
 {
--- a/xpcom/rust/gtest/xpcom/test.rs
+++ b/xpcom/rust/gtest/xpcom/test.rs
@@ -4,38 +4,63 @@
 
 #![allow(non_snake_case)]
 
 #[macro_use]
 extern crate xpcom;
 
 extern crate nserror;
 
-extern crate nsstring;
-
 use std::ptr;
-use xpcom::{XpCom, getter_addrefs, interfaces};
+use std::ffi::{CStr, CString};
+use xpcom::interfaces;
 use nserror::{NsresultExt, nsresult, NS_OK};
-use nsstring::{nsCStr, nsCString};
 
 #[no_mangle]
-pub unsafe extern fn Rust_CallIURIFromRust() -> *const interfaces::nsIIOService {
-    let iosvc = xpcom::services::get_IOService().unwrap();
+pub unsafe extern fn Rust_ObserveFromRust() -> *const interfaces::nsIObserverService {
+    let obssvc = xpcom::services::get_ObserverService().unwrap();
 
-    let uri = getter_addrefs(|p| iosvc.NewURI(&nsCStr::from("https://google.com"), ptr::null(), ptr::null(), p)).unwrap();
+    // Define an observer
+    #[derive(xpcom)]
+    #[xpimplements(nsIObserver)]
+    #[refcnt = "nonatomic"]
+    struct InitObserver {
+        run: *mut bool
+    }
+    impl Observer {
+        unsafe fn Observe(
+            &self,
+            _subject: *const interfaces::nsISupports,
+            topic: *const i8,
+            _data: *const i16
+        ) -> nsresult {
+            *self.run = true;
+            assert!(CStr::from_ptr(topic).to_str() == Ok("test-rust-observe"));
+            NS_OK
+        }
+    }
 
-    let mut host = nsCString::new();
-    let rv = uri.GetHost(&mut host);
+    let topic = CString::new("test-rust-observe").unwrap();
+
+    let mut run = false;
+    let observer = Observer::allocate(InitObserver{ run: &mut run });
+    let rv = obssvc.AddObserver(observer.coerce::<interfaces::nsIObserver>(), topic.as_ptr(), false);
     assert!(rv.succeeded());
 
-    assert_eq!(&*host, "google.com");
+    let rv = obssvc.NotifyObservers(ptr::null(), topic.as_ptr(), ptr::null());
+    assert!(rv.succeeded());
+    assert!(run, "The observer should have been run!");
 
-    assert!(iosvc.query_interface::<interfaces::nsISupports>().is_some());
-    assert!(iosvc.query_interface::<interfaces::nsIURI>().is_none());
-    &*iosvc
+    let rv = obssvc.RemoveObserver(observer.coerce::<interfaces::nsIObserver>(), topic.as_ptr());
+    assert!(rv.succeeded());
+
+    assert!(observer.coerce::<interfaces::nsISupports>() as *const _==
+            &*observer.query_interface::<interfaces::nsISupports>().unwrap() as *const _);
+
+    &*obssvc
 }
 
 #[no_mangle]
 pub unsafe extern fn Rust_ImplementRunnableInRust(it_worked: *mut bool,
                                                   runnable: *mut *const interfaces::nsIRunnable) {
     // Define a type which implements nsIRunnable in rust.
     #[derive(xpcom)]
     #[xpimplements(nsIRunnable)]
--- a/xpcom/rust/xpcom/xpcom_macros/src/lib.rs
+++ b/xpcom/rust/xpcom/xpcom_macros/src/lib.rs
@@ -197,17 +197,17 @@ enum RefcntKind {
     Atomic,
     NonAtomic,
 }
 
 /// Produces the tokens for the type representation.
 impl ToTokens for RefcntKind {
     fn to_tokens(&self, tokens: &mut Tokens) {
         match *self {
-            RefcntKind::NonAtomic => quote!(xpcom::RefCnt).to_tokens(tokens),
+            RefcntKind::NonAtomic => quote!(xpcom::Refcnt).to_tokens(tokens),
             RefcntKind::Atomic => quote!(xpcom::AtomicRefcnt).to_tokens(tokens),
         }
     }
 }
 
 /// Scans through the attributes on a struct, and extracts the type of the refcount to use.
 fn get_refcnt_kind(attrs: &[Attribute]) -> Result<RefcntKind, Box<Error>> {
     for attr in attrs {