Merge Autoland to mozilla-central. a=merge
authorDorel Luca <dluca@mozilla.com>
Tue, 06 Mar 2018 23:52:49 +0200
changeset 461828 f89b9a3c44d2a340de57b0645cf6fb682c0c94db
parent 461827 c7543a3e938d5ec408a7d0c93dc71b40607e7d5b (current diff)
parent 461801 c2469b3f1a0441cdaa032633fc61c311ceaabbd8 (diff)
child 461829 bccdc684210431c233622650a91454c09f6af9eb
push id1683
push usersfraser@mozilla.com
push dateThu, 26 Apr 2018 16:43:40 +0000
treeherdermozilla-release@5af6cb21869d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone60.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge Autoland to mozilla-central. a=merge
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -68,20 +68,16 @@
         tabs._positionPinnedTabs();
         tabs._handleTabSelect(true);
       ]]></handler>
     </handlers>
   </binding>
 
   <binding id="tabbrowser-tabs"
            extends="chrome://global/content/bindings/tabbox.xml#tabs">
-    <resources>
-      <stylesheet src="chrome://browser/content/tabbrowser.css"/>
-    </resources>
-
     <content>
       <xul:hbox class="tab-drop-indicator-box">
         <xul:image class="tab-drop-indicator" anonid="tab-drop-indicator" collapsed="true"/>
       </xul:hbox>
       <xul:arrowscrollbox anonid="arrowscrollbox" orient="horizontal" flex="1"
                           style="min-width: 1px;"
                           class="tabbrowser-arrowscrollbox">
 <!--
@@ -1481,20 +1477,16 @@
         this._tabDropIndicator.collapsed = true;
         event.stopPropagation();
       ]]></handler>
     </handlers>
   </binding>
 
   <binding id="tabbrowser-tab" display="xul:hbox"
            extends="chrome://global/content/bindings/tabbox.xml#tab">
-    <resources>
-      <stylesheet src="chrome://browser/content/tabbrowser.css"/>
-    </resources>
-
     <content context="tabContextMenu">
       <xul:stack class="tab-stack" flex="1">
         <xul:vbox xbl:inherits="selected=visuallyselected,fadein"
                   class="tab-background">
           <xul:hbox xbl:inherits="selected=visuallyselected"
                     class="tab-line"/>
           <xul:spacer flex="1"/>
           <xul:hbox class="tab-bottom-line"/>
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -264,16 +264,26 @@ file, You can obtain one at http://mozil
           switch (aEvent.keyCode) {
             case KeyEvent.DOM_VK_LEFT:
             case KeyEvent.DOM_VK_RIGHT:
             case KeyEvent.DOM_VK_HOME:
               // Reset the selected index so that nsAutoCompleteController
               // simply closes the popup without trying to fill anything.
               this.popup.selectedIndex = -1;
               break;
+            case KeyEvent.DOM_VK_TAB:
+              this.userSelectionBehavior = "tab";
+              break;
+            case KeyEvent.DOM_VK_UP:
+            case KeyEvent.DOM_VK_DOWN:
+            case KeyEvent.DOM_VK_PAGE_UP:
+            case KeyEvent.DOM_VK_PAGE_DOWN:
+              if (this.userSelectionBehavior != "tab")
+                this.userSelectionBehavior = "arrow";
+              break;
           }
           if (!this.popup.disableKeyNavigation) {
             if (this._shouldDeferKeyEvent(aEvent)) {
               this._deferKeyEvent(aEvent, "onKeyPress");
               return false;
             }
             if (this.popup.popupOpen && this.popup.handleKeyPress(aEvent)) {
               return true;
@@ -662,17 +672,18 @@ file, You can obtain one at http://mozil
         <parameter name="triggeringPrincipal"/>
         <body><![CDATA[
           let isMouseEvent = event instanceof MouseEvent;
           if (isMouseEvent && event.button == 2) {
             // Do nothing for right clicks.
             return;
           }
 
-          BrowserUsageTelemetry.recordUrlbarSelectedResultMethod(event);
+          BrowserUsageTelemetry.recordUrlbarSelectedResultMethod(
+            event, this.userSelectionBehavior);
 
           // Determine whether to use the selected one-off search button.  In
           // one-off search buttons parlance, "selected" means that the button
           // has been navigated to via the keyboard.  So we want to use it if
           // the triggering event is not a mouse click -- i.e., it's a Return
           // key -- or if the one-off was mouse-clicked.
           let selectedOneOff = this.popup.oneOffSearchButtons.selectedButton;
           if (selectedOneOff &&
@@ -2047,17 +2058,18 @@ file, You can obtain one at http://mozil
             // according to whatever's in the CSS.
             this.margins = undefined;
             needsHandleOverUnderflow = true;
           }
 
           // Now that the margins have been set, start adding items (via
           // _invalidate).
           this.mInput = aInput;
-          aInput.controller.setInitiallySelectedIndex(this._isFirstResultHeuristic ? 0 : -1);
+          this.input.controller.setInitiallySelectedIndex(this._isFirstResultHeuristic ? 0 : -1);
+          this.input.userSelectionBehavior = "none";
           this._invalidate();
 
           try {
             let whichNotification = aInput.whichSearchSuggestionsNotification;
             if (whichNotification != "none") {
               // Update the impressions count on real popupshown, since there's
               // no guarantee openPopup will be respected by the platform.
               // Though, we must ensure the handled event is the expected one.
@@ -2217,17 +2229,17 @@ file, You can obtain one at http://mozil
 
       <method name="createResultLabel">
         <parameter name="item"/>
         <parameter name="proposedLabel"/>
         <body>
           <![CDATA[
             let parts = [proposedLabel];
 
-            let action = this.mInput._parseActionUrl(item.getAttribute("url"));
+            let action = this.input._parseActionUrl(item.getAttribute("url"));
             if (action) {
               switch (action.type) {
               case "searchengine":
                 parts = [
                   action.params.searchSuggestion || action.params.searchQuery,
                   action.params.engineName,
                 ];
                 break;
--- a/browser/components/customizableui/PanelMultiView.jsm
+++ b/browser/components/customizableui/PanelMultiView.jsm
@@ -870,21 +870,25 @@ var PanelMultiView = class extends Assoc
     } else if (viewNode.customRectGetter) {
       // We use a customRectGetter for WebExtensions panels, because they need
       // to query the size from an embedded browser. The presence of this
       // getter also provides an indication that the view node shouldn't be
       // moved around, otherwise the state of the browser would get disrupted.
       let width = prevPanelView.knownWidth;
       let height = prevPanelView.knownHeight;
       viewRect = Object.assign({height, width}, viewNode.customRectGetter());
+      nextPanelView.visible = true;
+      // Until the header is visible, it has 0 height.
+      // Wait for layout before measuring it
       let header = viewNode.firstChild;
       if (header && header.classList.contains("panel-header")) {
-        viewRect.height += this._dwu.getBoundsWithoutFlushing(header).height;
+        viewRect.height += await window.promiseDocumentFlushed(() => {
+          return this._dwu.getBoundsWithoutFlushing(header).height;
+        });
       }
-      nextPanelView.visible = true;
       await nextPanelView.descriptionHeightWorkaround();
     } else {
       this._offscreenViewStack.style.minHeight = olderView.knownHeight + "px";
       this._offscreenViewStack.appendChild(viewNode);
       nextPanelView.visible = true;
 
       // Now that the subview is visible, we can check the height of the
       // description elements it contains.
--- a/browser/components/extensions/ExtensionPopups.jsm
+++ b/browser/components/extensions/ExtensionPopups.jsm
@@ -507,17 +507,20 @@ class ViewPopup extends BasePopup {
     let browser = this.browser;
     await this.createBrowser(this.viewNode);
 
     this.ignoreResizes = false;
 
     this.browser.swapDocShells(browser);
     this.destroyBrowser(browser);
 
-    if (this.dimensions && !this.fixedWidth) {
+    if (this.dimensions) {
+      if (this.fixedWidth) {
+        delete this.dimensions.width;
+      }
       this.resizeBrowser(this.dimensions);
     }
 
     this.viewNode.customRectGetter = () => {
       return {height: this.lastCalculatedInViewHeight || this.viewHeight};
     };
 
     this.tempPanel.remove();
--- a/browser/components/migration/tests/marionette/test_refresh_firefox.py
+++ b/browser/components/migration/tests/marionette/test_refresh_firefox.py
@@ -120,18 +120,18 @@ class TestFirefoxRefresh(MarionetteTestC
             "street-address": "32 Vassar Street\\\nMIT Room 32-G524",
             "address-level2": "Cambridge",
             "address-level1": "MA",
             "postal-code": "02139",
             country: "US",
             tel: "+15195555555",
             email: "user@example.com",
           };
-          return global.profileStorage.initialize().then(() => {
-            return global.profileStorage.addresses.add(TEST_ADDRESS_1);
+          return global.formAutofillStorage.initialize().then(() => {
+            return global.formAutofillStorage.addresses.add(TEST_ADDRESS_1);
           }).then(marionetteScriptFinished);
         """)
 
     def createCookie(self):
         self.runCode("""
           // Expire in 15 minutes:
           let expireTime = Math.floor(Date.now() / 1000) + 15 * 60;
           Services.cookies.add(arguments[0], arguments[1], arguments[2], arguments[3],
@@ -276,18 +276,18 @@ class TestFirefoxRefresh(MarionetteTestC
         """)
         self.assertEqual(formHistoryCount, 1, "There should be only 1 entry in the form history")
 
     def checkFormAutofill(self):
         if not self._formAutofillAvailable:
             return
 
         formAutofillResults = self.runAsyncCode("""
-          return global.profileStorage.initialize().then(() => {
-            return global.profileStorage.addresses.getAll()
+          return global.formAutofillStorage.initialize().then(() => {
+            return global.formAutofillStorage.addresses.getAll()
           }).then(marionetteScriptFinished);
         """,)
         if type(formAutofillResults) == str:
             self.fail(formAutofillResults)
             return
 
         formAutofillAddressCount = len(formAutofillResults)
         self.assertEqual(formAutofillAddressCount, 1, "Should have exactly 1 saved address, got %d" % formAutofillAddressCount)
@@ -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/FormAutofillStorage.jsm", {}).profileStorage;
+            global.formAutofillStorage = Cu.import("resource://formautofill/FormAutofillStorage.jsm", {}).formAutofillStorage;
           } 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
@@ -52,26 +52,26 @@ const {
   ENABLED_AUTOFILL_ADDRESSES_PREF,
   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/FormAutofillStorage.jsm", {});
-    log.debug("Loading profileStorage");
+  XPCOMUtils.defineLazyGetter(this, "formAutofillStorage", () => {
+    let {formAutofillStorage} = ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm", {});
+    log.debug("Loading formAutofillStorage");
 
-    profileStorage.initialize().then(() => {
+    formAutofillStorage.initialize().then(() => {
       // Update the saved field names to compute the status and update child processes.
       this._updateSavedFieldNames();
     });
 
-    return profileStorage;
+    return formAutofillStorage;
   });
 }
 
 FormAutofillParent.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]),
 
   /**
    * Cache of the Form Autofill status (considering preferences and storage).
@@ -200,47 +200,47 @@ FormAutofillParent.prototype = {
    *
    * @param   {string} message.name The name of the message.
    * @param   {object} message.data The data of the message.
    * @param   {nsIFrameMessageManager} message.target Caller's message manager.
    */
   async receiveMessage({name, data, target}) {
     switch (name) {
       case "FormAutofill:InitStorage": {
-        this.profileStorage.initialize();
+        this.formAutofillStorage.initialize();
         break;
       }
       case "FormAutofill:GetRecords": {
         this._getRecords(data, target);
         break;
       }
       case "FormAutofill:SaveAddress": {
         if (data.guid) {
-          this.profileStorage.addresses.update(data.guid, data.address);
+          this.formAutofillStorage.addresses.update(data.guid, data.address);
         } else {
-          this.profileStorage.addresses.add(data.address);
+          this.formAutofillStorage.addresses.add(data.address);
         }
         break;
       }
       case "FormAutofill:SaveCreditCard": {
         // TODO: "MasterPassword.ensureLoggedIn" can be removed after the storage
         // APIs are refactored to be async functions (bug 1399367).
         if (!await MasterPassword.ensureLoggedIn()) {
           log.warn("User canceled master password entry");
           return;
         }
-        this.profileStorage.creditCards.add(data.creditcard);
+        this.formAutofillStorage.creditCards.add(data.creditcard);
         break;
       }
       case "FormAutofill:RemoveAddresses": {
-        data.guids.forEach(guid => this.profileStorage.addresses.remove(guid));
+        data.guids.forEach(guid => this.formAutofillStorage.addresses.remove(guid));
         break;
       }
       case "FormAutofill:RemoveCreditCards": {
-        data.guids.forEach(guid => this.profileStorage.creditCards.remove(guid));
+        data.guids.forEach(guid => this.formAutofillStorage.creditCards.remove(guid));
         break;
       }
       case "FormAutofill:OnFormSubmit": {
         this._onFormSubmit(data, target);
         break;
       }
       case "FormAutofill:OpenPreferences": {
         const win = RecentWindow.getMostRecentBrowserWindow();
@@ -265,17 +265,17 @@ FormAutofillParent.prototype = {
   },
 
   /**
    * Uninitializes FormAutofillParent. This is for testing only.
    *
    * @private
    */
   _uninit() {
-    this.profileStorage._saveImmediately();
+    this.formAutofillStorage._saveImmediately();
 
     Services.ppmm.removeMessageListener("FormAutofill:InitStorage", this);
     Services.ppmm.removeMessageListener("FormAutofill:GetRecords", this);
     Services.ppmm.removeMessageListener("FormAutofill:SaveAddress", this);
     Services.ppmm.removeMessageListener("FormAutofill:RemoveAddresses", this);
     Services.obs.removeObserver(this, "sync-pane-loaded");
     Services.prefs.removeObserver(ENABLED_AUTOFILL_ADDRESSES_PREF, this);
 
@@ -298,17 +298,17 @@ FormAutofillParent.prototype = {
    * @param  {string} data.searchString
    *         The typed string for filtering out the matched records.
    * @param  {string} data.info
    *         The input autocomplete property's information.
    * @param  {nsIFrameMessageManager} target
    *         Content's message manager.
    */
   async _getRecords({collectionName, searchString, info}, target) {
-    let collection = this.profileStorage[collectionName];
+    let collection = this.formAutofillStorage[collectionName];
     if (!collection) {
       target.sendAsyncMessage("FormAutofill:Records", []);
       return;
     }
 
     let recordsInCollection = collection.getAll();
     if (!info || !info.fieldName || !recordsInCollection.length) {
       target.sendAsyncMessage("FormAutofill:Records", recordsInCollection);
@@ -355,85 +355,85 @@ FormAutofillParent.prototype = {
     log.debug("_updateSavedFieldNames");
     if (!Services.ppmm.initialProcessData.autofillSavedFieldNames) {
       Services.ppmm.initialProcessData.autofillSavedFieldNames = new Set();
     } else {
       Services.ppmm.initialProcessData.autofillSavedFieldNames.clear();
     }
 
     ["addresses", "creditCards"].forEach(c => {
-      this.profileStorage[c].getAll().forEach((record) => {
+      this.formAutofillStorage[c].getAll().forEach((record) => {
         Object.keys(record).forEach((fieldName) => {
           if (!record[fieldName]) {
             return;
           }
           Services.ppmm.initialProcessData.autofillSavedFieldNames.add(fieldName);
         });
       });
     });
 
     // Remove the internal guid and metadata fields.
-    this.profileStorage.INTERNAL_FIELDS.forEach((fieldName) => {
+    this.formAutofillStorage.INTERNAL_FIELDS.forEach((fieldName) => {
       Services.ppmm.initialProcessData.autofillSavedFieldNames.delete(fieldName);
     });
 
     Services.ppmm.broadcastAsyncMessage("FormAutofill:savedFieldNames",
                                         Services.ppmm.initialProcessData.autofillSavedFieldNames);
     this._updateStatus();
   },
 
   _onAddressSubmit(address, target, timeStartedFillingMS) {
     let showDoorhanger = null;
     if (address.guid) {
       // Avoid updating the fields that users don't modify.
-      let originalAddress = this.profileStorage.addresses.get(address.guid);
+      let originalAddress = this.formAutofillStorage.addresses.get(address.guid);
       for (let field in address.record) {
         if (address.untouchedFields.includes(field) && originalAddress[field]) {
           address.record[field] = originalAddress[field];
         }
       }
 
-      if (!this.profileStorage.addresses.mergeIfPossible(address.guid, address.record, true)) {
+      if (!this.formAutofillStorage.addresses.mergeIfPossible(address.guid, address.record, true)) {
         this._recordFormFillingTime("address", "autofill-update", timeStartedFillingMS);
 
         showDoorhanger = async () => {
           const description = FormAutofillUtils.getAddressLabel(address.record);
           const state = await FormAutofillDoorhanger.show(target, "updateAddress", description);
-          let changedGUIDs = this.profileStorage.addresses.mergeToStorage(address.record, true);
+          let changedGUIDs = this.formAutofillStorage.addresses.mergeToStorage(address.record, true);
           switch (state) {
             case "create":
               if (!changedGUIDs.length) {
-                changedGUIDs.push(this.profileStorage.addresses.add(address.record));
+                changedGUIDs.push(this.formAutofillStorage.addresses.add(address.record));
               }
               break;
             case "update":
               if (!changedGUIDs.length) {
-                this.profileStorage.addresses.update(address.guid, address.record, true);
+                this.formAutofillStorage.addresses.update(address.guid, address.record, true);
                 changedGUIDs.push(address.guid);
               } else {
-                this.profileStorage.addresses.remove(address.guid);
+                this.formAutofillStorage.addresses.remove(address.guid);
               }
               break;
           }
-          changedGUIDs.forEach(guid => this.profileStorage.addresses.notifyUsed(guid));
+          changedGUIDs.forEach(guid => this.formAutofillStorage.addresses.notifyUsed(guid));
         };
         // Address should be updated
         Services.telemetry.scalarAdd("formautofill.addresses.fill_type_autofill_update", 1);
       } else {
         this._recordFormFillingTime("address", "autofill", timeStartedFillingMS);
-        this.profileStorage.addresses.notifyUsed(address.guid);
+        this.formAutofillStorage.addresses.notifyUsed(address.guid);
         // Address is merged successfully
         Services.telemetry.scalarAdd("formautofill.addresses.fill_type_autofill", 1);
       }
     } else {
-      let changedGUIDs = this.profileStorage.addresses.mergeToStorage(address.record);
+      let changedGUIDs = this.formAutofillStorage.addresses.mergeToStorage(address.record);
       if (!changedGUIDs.length) {
-        changedGUIDs.push(this.profileStorage.addresses.add(address.record));
+        changedGUIDs.push(this.formAutofillStorage.addresses.add(address.record));
       }
-      changedGUIDs.forEach(guid => this.profileStorage.addresses.notifyUsed(guid));
+      changedGUIDs.forEach(guid => this.formAutofillStorage.addresses.notifyUsed(guid));
       this._recordFormFillingTime("address", "manual", timeStartedFillingMS);
 
       // Show first time use doorhanger
       if (FormAutofillUtils.isAutofillAddressesFirstTimeUse) {
         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);
@@ -463,34 +463,34 @@ FormAutofillParent.prototype = {
 
     // We'll show the credit card doorhanger if:
     //   - User applys autofill and changed
     //   - User fills form manually and the filling data is not duplicated to storage
     if (creditCard.guid) {
       // Indicate that the user has used Credit Card Autofill to fill in a form.
       setUsedStatus(3);
 
-      let originalCCData = this.profileStorage.creditCards.get(creditCard.guid);
+      let originalCCData = this.formAutofillStorage.creditCards.get(creditCard.guid);
       let recordUnchanged = true;
       for (let field in creditCard.record) {
         if (creditCard.record[field] === "" && !originalCCData[field]) {
           continue;
         }
         // Avoid updating the fields that users don't modify, but skip number field
         // because we don't want to trigger decryption here.
         let untouched = creditCard.untouchedFields.includes(field);
         if (untouched && field !== "cc-number") {
           creditCard.record[field] = originalCCData[field];
         }
         // recordUnchanged will be false if one of the field is changed.
         recordUnchanged &= untouched;
       }
 
       if (recordUnchanged) {
-        this.profileStorage.creditCards.notifyUsed(creditCard.guid);
+        this.formAutofillStorage.creditCards.notifyUsed(creditCard.guid);
         // Add probe to record credit card autofill(without modification).
         Services.telemetry.scalarAdd("formautofill.creditCards.fill_type_autofill", 1);
         this._recordFormFillingTime("creditCard", "autofill", timeStartedFillingMS);
         return false;
       }
       // Add the probe to record credit card autofill with modification.
       Services.telemetry.scalarAdd("formautofill.creditCards.fill_type_autofill_modified", 1);
       this._recordFormFillingTime("creditCard", "autofill-update", timeStartedFillingMS);
@@ -501,19 +501,19 @@ FormAutofillParent.prototype = {
       setUsedStatus(1);
 
       // Add the probe to record credit card manual filling.
       Services.telemetry.scalarAdd("formautofill.creditCards.fill_type_manual", 1);
       this._recordFormFillingTime("creditCard", "manual", timeStartedFillingMS);
     }
 
     // Early return if it's a duplicate data
-    let dupGuid = this.profileStorage.creditCards.getDuplicateGuid(creditCard.record);
+    let dupGuid = this.formAutofillStorage.creditCards.getDuplicateGuid(creditCard.record);
     if (dupGuid) {
-      this.profileStorage.creditCards.notifyUsed(dupGuid);
+      this.formAutofillStorage.creditCards.notifyUsed(dupGuid);
       return false;
     }
 
     // Indicate that the user has seen the doorhanger.
     setUsedStatus(2);
 
     return async () => {
       // Suppress the pending doorhanger from showing up if user disabled credit card in previous doorhanger.
@@ -539,28 +539,28 @@ FormAutofillParent.prototype = {
       if (!await MasterPassword.ensureLoggedIn()) {
         log.warn("User canceled master password entry");
         return;
       }
 
       let changedGUIDs = [];
       if (creditCard.guid) {
         if (state == "update") {
-          this.profileStorage.creditCards.update(creditCard.guid, creditCard.record, true);
+          this.formAutofillStorage.creditCards.update(creditCard.guid, creditCard.record, true);
           changedGUIDs.push(creditCard.guid);
         } else if ("create") {
-          changedGUIDs.push(this.profileStorage.creditCards.add(creditCard.record));
+          changedGUIDs.push(this.formAutofillStorage.creditCards.add(creditCard.record));
         }
       } else {
-        changedGUIDs.push(...this.profileStorage.creditCards.mergeToStorage(creditCard.record));
+        changedGUIDs.push(...this.formAutofillStorage.creditCards.mergeToStorage(creditCard.record));
         if (!changedGUIDs.length) {
-          changedGUIDs.push(this.profileStorage.creditCards.add(creditCard.record));
+          changedGUIDs.push(this.formAutofillStorage.creditCards.add(creditCard.record));
         }
       }
-      changedGUIDs.forEach(guid => this.profileStorage.creditCards.notifyUsed(guid));
+      changedGUIDs.forEach(guid => this.formAutofillStorage.creditCards.notifyUsed(guid));
     };
   },
 
   async _onFormSubmit(data, target) {
     let {profile: {address, creditCard}, timeStartedFillingMS} = data;
 
     // Don't record filling time if any type of records has more than one section being
     // populated. We've been recording the filling time, so the other cases that aren't
--- a/browser/extensions/formautofill/FormAutofillStorage.jsm
+++ b/browser/extensions/formautofill/FormAutofillStorage.jsm
@@ -116,17 +116,17 @@
  * (meaning they will be synced on the next sync), at which time they will gain
  * this new field.
  */
 
 "use strict";
 
 // We expose a singleton from this module. Some tests may import the
 // constructor via a backstage pass.
-var EXPORTED_SYMBOLS = ["profileStorage"];
+this.EXPORTED_SYMBOLS = ["formAutofillStorage"];
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/osfile.jsm");
 
 ChromeUtils.import("resource://formautofill/FormAutofillUtils.jsm");
 
 ChromeUtils.defineModuleGetter(this, "JSONFile",
@@ -1797,10 +1797,10 @@ FormAutofillStorage.prototype = {
 
   // For test only.
   _saveImmediately() {
     return this._store._save();
   },
 };
 
 // The singleton exposed by this module.
-var profileStorage = new FormAutofillStorage(
+this.formAutofillStorage = 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
@@ -11,17 +11,17 @@ ChromeUtils.import("resource://gre/modul
 ChromeUtils.import("resource://services-sync/engines.js");
 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",
+ChromeUtils.defineModuleGetter(this, "formAutofillStorage",
                                "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"];
@@ -52,17 +52,17 @@ AutofillRecord.prototype = {
       guid: this.id,
     }, this.entry);
   },
 
   fromEntry(entry) {
     this.id = entry.guid;
     this.entry = entry;
     // The GUID is already stored in record.id, so we nuke it from the entry
-    // itself to save a tiny bit of space. The profileStorage clones profiles,
+    // itself to save a tiny bit of space. The formAutofillStorage clones profiles,
     // so nuking in-place is OK.
     delete this.entry.guid;
   },
 
   cleartextToString() {
     // And a helper so logging a *Sync* record auto sanitizes.
     let record = this.cleartext;
     return JSON.stringify({entry: sanitizeStorageObject(record.entry)});
@@ -79,17 +79,17 @@ function FormAutofillStore(name, engine)
 FormAutofillStore.prototype = {
   __proto__: Store.prototype,
 
   _subStorageName: null, // overridden below.
   _storage: null,
 
   get storage() {
     if (!this._storage) {
-      this._storage = profileStorage[this._subStorageName];
+      this._storage = formAutofillStorage[this._subStorageName];
     }
     return this._storage;
   },
 
   async getAllIDs() {
     let result = {};
     for (let {guid} of this.storage.getAll({includeDeleted: true})) {
       result[guid] = true;
@@ -97,17 +97,17 @@ FormAutofillStore.prototype = {
     return result;
   },
 
   async changeItemID(oldID, newID) {
     this.storage.changeGUID(oldID, newID);
   },
 
   // Note: this function intentionally returns false in cases where we only have
-  // a (local) tombstone - and profileStorage.get() filters them for us.
+  // a (local) tombstone - and formAutofillStorage.get() filters them for us.
   async itemExists(id) {
     return Boolean(this.storage.get(id));
   },
 
   async applyIncoming(remoteRecord) {
     if (remoteRecord.deleted) {
       this._log.trace("Deleting record", remoteRecord);
       this.storage.remove(remoteRecord.id, {sourceSync: true});
@@ -286,17 +286,17 @@ FormAutofillEngine.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
   // FormAutofillStorage in this case.
   async _syncStartup() {
-    await profileStorage.initialize();
+    await formAutofillStorage.initialize();
     await SyncEngine.prototype._syncStartup.call(this);
   },
 
   // We handle reconciliation in the store, not the engine.
   async _reconcile() {
     return true;
   },
 
@@ -324,17 +324,17 @@ FormAutofillEngine.prototype = {
     this._store.storage.pushSyncChanges(this._modified.changes);
   },
 
   _deleteId(id) {
     this._noteDeletedId(id);
   },
 
   async _resetClient() {
-    await profileStorage.initialize();
+    await formAutofillStorage.initialize();
     this._store.storage.resetSync();
   },
 };
 
 // The concrete engines
 
 function AddressesRecord(collection, id) {
   AutofillRecord.call(this, collection, id);
--- a/browser/extensions/formautofill/content/editDialog.js
+++ b/browser/extensions/formautofill/content/editDialog.js
@@ -8,24 +8,24 @@
 
 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",
+ChromeUtils.defineModuleGetter(this, "formAutofillStorage",
                                "resource://formautofill/FormAutofillStorage.jsm");
 ChromeUtils.defineModuleGetter(this, "MasterPassword",
                                "resource://formautofill/MasterPassword.jsm");
 
 class EditDialog {
   constructor(subStorageName, elements, record) {
-    this._storageInitPromise = profileStorage.initialize();
+    this._storageInitPromise = formAutofillStorage.initialize();
     this._subStorageName = subStorageName;
     this._elements = elements;
     this._record = record;
     this.localizeDocument();
     window.addEventListener("DOMContentLoaded", this, {once: true});
   }
 
   async init() {
@@ -70,17 +70,17 @@ class EditDialog {
   }
 
   /**
    * Get storage and ensure it has been initialized.
    * @returns {object}
    */
   async getStorage() {
     await this._storageInitPromise;
-    return profileStorage[this._subStorageName];
+    return formAutofillStorage[this._subStorageName];
   }
 
   /**
    * Asks FormAutofillParent to save or update an record.
    * @param  {object} record
    * @param  {string} guid [optional]
    */
   async saveRecord(record, guid) {
--- a/browser/extensions/formautofill/content/manageDialog.js
+++ b/browser/extensions/formautofill/content/manageDialog.js
@@ -9,27 +9,27 @@
 const EDIT_ADDRESS_URL = "chrome://formautofill/content/editAddress.xhtml";
 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",
+ChromeUtils.defineModuleGetter(this, "formAutofillStorage",
                                "resource://formautofill/FormAutofillStorage.jsm");
 ChromeUtils.defineModuleGetter(this, "MasterPassword",
                                "resource://formautofill/MasterPassword.jsm");
 
 this.log = null;
 FormAutofillUtils.defineLazyLogGetter(this, "manageAddresses");
 
 class ManageRecords {
   constructor(subStorageName, elements) {
-    this._storageInitPromise = profileStorage.initialize();
+    this._storageInitPromise = formAutofillStorage.initialize();
     this._subStorageName = subStorageName;
     this._elements = elements;
     this._newRequest = false;
     this._isLoadingRecords = false;
     this.prefWin = window.opener;
     this.localizeDocument();
     window.addEventListener("DOMContentLoaded", this, {once: true});
   }
@@ -62,17 +62,17 @@ class ManageRecords {
   }
 
   /**
    * Get storage and ensure it has been initialized.
    * @returns {object}
    */
   async getStorage() {
     await this._storageInitPromise;
-    return profileStorage[this._subStorageName];
+    return formAutofillStorage[this._subStorageName];
   }
 
   /**
    * Load records and render them. This function is a wrapper for _loadRecords
    * to ensure any reentrant will be handled well.
    */
   async loadRecords() {
     // This function can be early returned when there is any reentrant happends.
--- a/browser/extensions/formautofill/test/mochitest/formautofill_parent_utils.js
+++ b/browser/extensions/formautofill/test/mochitest/formautofill_parent_utils.js
@@ -2,17 +2,17 @@
 /* global assert */
 /* eslint-env mozilla/frame-script */
 
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://formautofill/FormAutofillUtils.jsm");
 
-let {profileStorage} = ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm", {});
+let {formAutofillStorage} = 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);
@@ -108,23 +108,23 @@ var ParentUtils = {
 
   async cleanup() {
     await this.cleanUpAddresses();
     await this.cleanUpCreditCards();
     Services.obs.removeObserver(this, "formautofill-storage-changed");
   },
 
   _areRecordsMatching(recordA, recordB, collectionName) {
-    for (let field of profileStorage[collectionName].VALID_FIELDS) {
+    for (let field of formAutofillStorage[collectionName].VALID_FIELDS) {
       if (recordA[field] !== recordB[field]) {
         return false;
       }
     }
     // Check the internal field if both addresses have valid value.
-    for (let field of profileStorage.INTERNAL_FIELDS) {
+    for (let field of formAutofillStorage.INTERNAL_FIELDS) {
       if (field in recordA && field in recordB && (recordA[field] !== recordB[field])) {
         return false;
       }
     }
     return true;
   },
 
   async _checkRecords(collectionName, expectedRecords) {
--- a/browser/extensions/formautofill/test/unit/test_activeStatus.js
+++ b/browser/extensions/formautofill/test/unit/test_activeStatus.js
@@ -17,17 +17,17 @@ add_task(async function test_activeStatu
 
   await formAutofillParent.init();
   // init shouldn't call updateStatus since that requires storage which will
   // lead to startup time regressions.
   Assert.equal(formAutofillParent._updateStatus.called, false);
   Assert.equal(Services.ppmm.initialProcessData.autofillEnabled, undefined);
 
   // Initialize profile storage
-  await formAutofillParent.profileStorage.initialize();
+  await formAutofillParent.formAutofillStorage.initialize();
   // Upon first initializing profile storage, status should be computed.
   Assert.equal(formAutofillParent._updateStatus.called, true);
   Assert.equal(Services.ppmm.initialProcessData.autofillEnabled, false);
 
   formAutofillParent._uninit();
 });
 
 add_task(async function test_activeStatus_observe() {
@@ -66,30 +66,30 @@ add_task(async function test_activeStatu
 
 add_task(async function test_activeStatus_computeStatus() {
   let formAutofillParent = new FormAutofillParent();
   registerCleanupFunction(function cleanup() {
     Services.prefs.clearUserPref("extensions.formautofill.addresses.enabled");
     Services.prefs.clearUserPref("extensions.formautofill.creditCards.enabled");
   });
 
-  sinon.stub(profileStorage.addresses, "getAll");
-  profileStorage.addresses.getAll.returns([]);
+  sinon.stub(formAutofillParent.formAutofillStorage.addresses, "getAll");
+  formAutofillParent.formAutofillStorage.addresses.getAll.returns([]);
 
   // pref is enabled and profile is empty.
   Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", true);
   Services.prefs.setBoolPref("extensions.formautofill.creditCards.enabled", true);
   Assert.equal(formAutofillParent._computeStatus(), false);
 
   // pref is disabled and profile is empty.
   Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", false);
   Services.prefs.setBoolPref("extensions.formautofill.creditCards.enabled", false);
   Assert.equal(formAutofillParent._computeStatus(), false);
 
-  profileStorage.addresses.getAll.returns([{"given-name": "John"}]);
+  formAutofillParent.formAutofillStorage.addresses.getAll.returns([{"given-name": "John"}]);
   formAutofillParent.observe(null, "formautofill-storage-changed", "add");
   // pref is enabled and profile is not empty.
   Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", true);
   Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", true);
   Assert.equal(formAutofillParent._computeStatus(), true);
 
   // pref is partial enabled and profile is not empty.
   Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", true);
--- a/browser/extensions/formautofill/test/unit/test_getRecords.js
+++ b/browser/extensions/formautofill/test/unit/test_getRecords.js
@@ -44,34 +44,34 @@ let TEST_CREDIT_CARD_2 = {
 let target = {
   sendAsyncMessage: function sendAsyncMessage(msg, payload) {},
 };
 
 add_task(async function test_getRecords() {
   let formAutofillParent = new FormAutofillParent();
 
   await formAutofillParent.init();
-  await formAutofillParent.profileStorage.initialize();
+  await formAutofillParent.formAutofillStorage.initialize();
   let fakeResult = {
     addresses: [{
       "given-name": "Timothy",
       "additional-name": "John",
       "family-name": "Berners-Lee",
       "organization": "World Wide Web Consortium",
     }],
     creditCards: [{
       "cc-name": "John Doe",
       "cc-number": "1234567812345678",
       "cc-exp-month": 4,
       "cc-exp-year": 2017,
     }],
   };
 
   for (let collectionName of ["addresses", "creditCards", "nonExisting"]) {
-    let collection = profileStorage[collectionName];
+    let collection = formAutofillParent.formAutofillStorage[collectionName];
     let expectedResult = fakeResult[collectionName] || [];
     let mock = sinon.mock(target);
     mock.expects("sendAsyncMessage").once().withExactArgs("FormAutofill:Records", expectedResult);
 
     if (collection) {
       sinon.stub(collection, "getAll");
       collection.getAll.returns(expectedResult);
     }
@@ -83,19 +83,19 @@ add_task(async function test_getRecords(
     }
   }
 });
 
 add_task(async function test_getRecords_addresses() {
   let formAutofillParent = new FormAutofillParent();
 
   await formAutofillParent.init();
-  await formAutofillParent.profileStorage.initialize();
+  await formAutofillParent.formAutofillStorage.initialize();
   let mockAddresses = [TEST_ADDRESS_1, TEST_ADDRESS_2];
-  let collection = profileStorage.addresses;
+  let collection = formAutofillParent.formAutofillStorage.addresses;
   sinon.stub(collection, "getAll");
   collection.getAll.returns(mockAddresses);
 
   let testCases = [
     {
       description: "If the search string could match 1 address",
       filter: {
         collectionName: "addresses",
@@ -160,18 +160,18 @@ add_task(async function test_getRecords_
     mock.verify();
   }
 });
 
 add_task(async function test_getRecords_creditCards() {
   let formAutofillParent = new FormAutofillParent();
 
   await formAutofillParent.init();
-  await formAutofillParent.profileStorage.initialize();
-  let collection = profileStorage.creditCards;
+  await formAutofillParent.formAutofillStorage.initialize();
+  let collection = formAutofillParent.formAutofillStorage.creditCards;
   let encryptedCCRecords = [TEST_CREDIT_CARD_1, TEST_CREDIT_CARD_2].map(record => {
     let clonedRecord = Object.assign({}, record);
     clonedRecord["cc-number"] = collection._getMaskedCCNumber(record["cc-number"]);
     clonedRecord["cc-number-encrypted"] = MasterPassword.encryptSync(record["cc-number"]);
     return clonedRecord;
   });
   sinon.stub(collection, "getAll", () => [Object.assign({}, encryptedCCRecords[0]), Object.assign({}, encryptedCCRecords[1])]);
   let CreditCardsWithDecryptedNumber = [
--- a/browser/extensions/formautofill/test/unit/test_savedFieldNames.js
+++ b/browser/extensions/formautofill/test/unit/test_savedFieldNames.js
@@ -7,17 +7,17 @@
 let {FormAutofillParent} = ChromeUtils.import("resource://formautofill/FormAutofillParent.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();
+  await formAutofillParent.formAutofillStorage.initialize();
   Assert.equal(formAutofillParent._updateSavedFieldNames.called, true);
 
   formAutofillParent._uninit();
 });
 
 add_task(async function test_profileSavedFieldNames_observe() {
   let formAutofillParent = new FormAutofillParent();
   sinon.stub(formAutofillParent, "_updateSavedFieldNames");
@@ -38,18 +38,18 @@ add_task(async function test_profileSave
 
 add_task(async function test_profileSavedFieldNames_update() {
   let formAutofillParent = new FormAutofillParent();
   await formAutofillParent.init();
   registerCleanupFunction(function cleanup() {
     Services.prefs.clearUserPref("extensions.formautofill.addresses.enabled");
   });
 
-  sinon.stub(profileStorage.addresses, "getAll");
-  profileStorage.addresses.getAll.returns([]);
+  sinon.stub(formAutofillParent.formAutofillStorage.addresses, "getAll");
+  formAutofillParent.formAutofillStorage.addresses.getAll.returns([]);
 
   // The set is empty if there's no profile in the store.
   formAutofillParent._updateSavedFieldNames();
   Assert.equal(Services.ppmm.initialProcessData.autofillSavedFieldNames.size, 0);
 
   // 2 profiles with 4 valid fields.
   let fakeStorage = [{
     guid: "test-guid-1",
@@ -67,17 +67,17 @@ add_task(async function test_profileSave
     "street-address": "331 E. Evelyn Avenue",
     tel: "1-650-903-0800",
     country: "US",
     timeCreated: 0,
     timeLastUsed: 0,
     timeLastModified: 0,
     timesUsed: 0,
   }];
-  profileStorage.addresses.getAll.returns(fakeStorage);
+  formAutofillParent.formAutofillStorage.addresses.getAll.returns(fakeStorage);
   formAutofillParent._updateSavedFieldNames();
 
   let autofillSavedFieldNames = Services.ppmm.initialProcessData.autofillSavedFieldNames;
   Assert.equal(autofillSavedFieldNames.size, 4);
   Assert.equal(autofillSavedFieldNames.has("organization"), true);
   Assert.equal(autofillSavedFieldNames.has("street-address"), true);
   Assert.equal(autofillSavedFieldNames.has("tel"), true);
   Assert.equal(autofillSavedFieldNames.has("email"), false);
--- a/browser/modules/BrowserUsageTelemetry.jsm
+++ b/browser/modules/BrowserUsageTelemetry.jsm
@@ -76,16 +76,18 @@ const URLBAR_SELECTED_RESULT_TYPES = {
  * `labels` array.  This only needs to be used by tests that need to map from
  * category names to indexes in histogram snapshots.  Actual app code can use
  * these category names directly when they add to a histogram.
  */
 const URLBAR_SELECTED_RESULT_METHODS = {
   enter: 0,
   enterSelection: 1,
   click: 2,
+  arrowEnterSelection: 3,
+  tabEnterSelection: 4,
 };
 
 
 const MINIMUM_TAB_COUNT_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes, in ms
 
 
 function getOpenTabsAndWinsCounts() {
   let tabCount = 0;
@@ -504,56 +506,71 @@ let BrowserUsageTelemetry = {
     this._recordSearch(engine, sourceName, "enter");
   },
 
   /**
    * Records the method by which the user selected a urlbar result.
    *
    * @param {nsIDOMEvent} event
    *        The event that triggered the selection.
+   * @param {string} userSelectionBehavior
+   *        How the user cycled through results before picking the current match.
+   *        Could be one of "tab", "arrow" or "none".
    */
-  recordUrlbarSelectedResultMethod(event) {
+  recordUrlbarSelectedResultMethod(event, userSelectionBehavior = "none") {
     // The reason this method relies on urlbarListener instead of having the
     // caller pass in an index is that by the time the urlbar handles a
     // selection, the selection in its popup has been cleared, so it's not easy
     // to tell which popup index was selected.  Fortunately this file already
     // has urlbarListener, which gets notified of selections in the urlbar
     // before the popup selection is cleared, so just use that.
+
     this._recordUrlOrSearchbarSelectedResultMethod(
       event, urlbarListener.selectedIndex,
-      "FX_URLBAR_SELECTED_RESULT_METHOD"
+      "FX_URLBAR_SELECTED_RESULT_METHOD",
+      userSelectionBehavior
     );
   },
 
   /**
    * Records the method by which the user selected a searchbar result.
    *
    * @param {nsIDOMEvent} event
    *        The event that triggered the selection.
    * @param {number} highlightedIndex
    *        The index that the user chose in the popup, or -1 if there wasn't a
    *        selection.
    */
   recordSearchbarSelectedResultMethod(event, highlightedIndex) {
     this._recordUrlOrSearchbarSelectedResultMethod(
       event, highlightedIndex,
-      "FX_SEARCHBAR_SELECTED_RESULT_METHOD"
+      "FX_SEARCHBAR_SELECTED_RESULT_METHOD",
+      "none"
     );
   },
 
-  _recordUrlOrSearchbarSelectedResultMethod(event, highlightedIndex, histogramID) {
+  _recordUrlOrSearchbarSelectedResultMethod(event, highlightedIndex, histogramID, userSelectionBehavior) {
     let histogram = Services.telemetry.getHistogramById(histogramID);
     // command events are from the one-off context menu.  Treat them as clicks.
     let isClick = event instanceof Ci.nsIDOMMouseEvent ||
                   (event && event.type == "command");
     let category;
     if (isClick) {
       category = "click";
     } else if (highlightedIndex >= 0) {
-      category = "enterSelection";
+      switch (userSelectionBehavior) {
+      case "tab":
+        category = "tabEnterSelection";
+        break;
+      case "arrow":
+        category = "arrowEnterSelection";
+        break;
+      default:
+        category = "enterSelection";
+      }
     } else {
       category = "enter";
     }
     histogram.add(category);
   },
 
   /**
    * This gets called shortly after the SessionStore has finished restoring
--- a/browser/modules/test/browser/browser_UsageTelemetry_urlbar.js
+++ b/browser/modules/test/browser/browser_UsageTelemetry_urlbar.js
@@ -320,17 +320,17 @@ add_task(async function test_oneOff_ente
   info("Select the second result, press Alt+Down to take us to the first one-off engine.");
   EventUtils.synthesizeKey("KEY_ArrowDown");
   EventUtils.synthesizeKey("KEY_ArrowDown", {altKey: true});
   EventUtils.synthesizeKey("KEY_Enter");
   await p;
 
   let resultMethods = resultMethodHist.snapshot();
   checkHistogramResults(resultMethods,
-    URLBAR_SELECTED_RESULT_METHODS.enterSelection,
+    URLBAR_SELECTED_RESULT_METHODS.arrowEnterSelection,
     "FX_URLBAR_SELECTED_RESULT_METHOD");
 
   Services.search.currentEngine = previousEngine;
   Services.search.removeEngine(suggestionEngine);
   await BrowserTestUtils.removeTab(tab);
 });
 
 // Performs a search using a click on a one-off button.  This only tests the
@@ -430,17 +430,17 @@ add_task(async function test_suggestion_
   Services.search.currentEngine = previousEngine;
   Services.search.removeEngine(suggestionEngine);
   await BrowserTestUtils.removeTab(tab);
 });
 
 // Selects and presses the Return (Enter) key on the first suggestion offered by
 // the test search engine.  This only tests the FX_URLBAR_SELECTED_RESULT_METHOD
 // histogram since test_suggestion_click covers everything else.
-add_task(async function test_suggestion_enterSelection() {
+add_task(async function test_suggestion_arrowEnterSelection() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
 
   let resultMethodHist = getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_METHOD");
 
   // Create an engine to generate search suggestions and add it as default
   // for this test.
   const url = getRootDirectory(gTestPath) + "usageTelemetrySearchSuggestions.xml";
@@ -461,15 +461,97 @@ add_task(async function test_suggestion_
   await searchInAwesomebar("query");
   info("Select the second result and press Return.");
   EventUtils.synthesizeKey("KEY_ArrowDown");
   EventUtils.synthesizeKey("KEY_Enter");
   await p;
 
   let resultMethods = resultMethodHist.snapshot();
   checkHistogramResults(resultMethods,
+    URLBAR_SELECTED_RESULT_METHODS.arrowEnterSelection,
+    "FX_URLBAR_SELECTED_RESULT_METHOD");
+
+  Services.search.currentEngine = previousEngine;
+  Services.search.removeEngine(suggestionEngine);
+  await BrowserTestUtils.removeTab(tab);
+});
+
+// Selects through tab and presses the Return (Enter) key on the first
+// suggestion offered by the test search engine.
+add_task(async function test_suggestion_tabEnterSelection() {
+  // Let's reset the counts.
+  Services.telemetry.clearScalars();
+
+  let resultMethodHist = getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_METHOD");
+
+  // Create an engine to generate search suggestions and add it as default
+  // for this test.
+  const url = getRootDirectory(gTestPath) + "usageTelemetrySearchSuggestions.xml";
+  let suggestionEngine = await new Promise((resolve, reject) => {
+    Services.search.addEngine(url, null, "", false, {
+      onSuccess(engine) { resolve(engine); },
+      onError() { reject(); }
+    });
+  });
+
+  let previousEngine = Services.search.currentEngine;
+  Services.search.currentEngine = suggestionEngine;
+
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
+
+  info("Type a query. Suggestions should be generated by the test engine.");
+  let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+  await searchInAwesomebar("query");
+  info("Select the second result and press Return.");
+  EventUtils.synthesizeKey("KEY_Tab");
+  EventUtils.synthesizeKey("KEY_Enter");
+  await p;
+
+  let resultMethods = resultMethodHist.snapshot();
+  checkHistogramResults(resultMethods,
+    URLBAR_SELECTED_RESULT_METHODS.tabEnterSelection,
+    "FX_URLBAR_SELECTED_RESULT_METHOD");
+
+  Services.search.currentEngine = previousEngine;
+  Services.search.removeEngine(suggestionEngine);
+  await BrowserTestUtils.removeTab(tab);
+});
+
+// Selects through code and presses the Return (Enter) key on the first
+// suggestion offered by the test search engine.
+add_task(async function test_suggestion_enterSelection() {
+  // Let's reset the counts.
+  Services.telemetry.clearScalars();
+
+  let resultMethodHist = getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_METHOD");
+
+  // Create an engine to generate search suggestions and add it as default
+  // for this test.
+  const url = getRootDirectory(gTestPath) + "usageTelemetrySearchSuggestions.xml";
+  let suggestionEngine = await new Promise((resolve, reject) => {
+    Services.search.addEngine(url, null, "", false, {
+      onSuccess(engine) { resolve(engine); },
+      onError() { reject(); }
+    });
+  });
+
+  let previousEngine = Services.search.currentEngine;
+  Services.search.currentEngine = suggestionEngine;
+
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
+
+  info("Type a query. Suggestions should be generated by the test engine.");
+  let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+  await searchInAwesomebar("query");
+  info("Select the second result and press Return.");
+  gURLBar.popup.selectedIndex = 1;
+  EventUtils.synthesizeKey("KEY_Enter");
+  await p;
+
+  let resultMethods = resultMethodHist.snapshot();
+  checkHistogramResults(resultMethods,
     URLBAR_SELECTED_RESULT_METHODS.enterSelection,
     "FX_URLBAR_SELECTED_RESULT_METHOD");
 
   Services.search.currentEngine = previousEngine;
   Services.search.removeEngine(suggestionEngine);
   await BrowserTestUtils.removeTab(tab);
 });
--- a/build/clang-plugin/CustomMatchers.h
+++ b/build/clang-plugin/CustomMatchers.h
@@ -105,17 +105,18 @@ AST_MATCHER(BinaryOperator, binaryCommaO
 }
 
 /// This matcher will match floating point types.
 AST_MATCHER(QualType, isFloat) { return Node->isRealFloatingType(); }
 
 /// This matcher will match locations in system headers.  This is adopted from
 /// isExpansionInSystemHeader in newer clangs, but modified in order to work
 /// with old clangs that we use on infra.
-AST_MATCHER(BinaryOperator, isInSystemHeader) {
+AST_POLYMORPHIC_MATCHER(isInSystemHeader,                                      \
+                        AST_POLYMORPHIC_SUPPORTED_TYPES(Decl, Stmt)) {
   return ASTIsInSystemHeader(Finder->getASTContext(), Node);
 }
 
 /// This matcher will match a list of files.  These files contain
 /// known NaN-testing expressions which we would like to whitelist.
 AST_MATCHER(BinaryOperator, isInWhitelistForNaNExpr) {
   const char *whitelist[] = {"SkScalar.h", "json_writer.cpp", "State.cpp"};
 
--- a/dom/media/systemservices/CamerasParent.cpp
+++ b/dom/media/systemservices/CamerasParent.cpp
@@ -880,21 +880,22 @@ CamerasParent::RecvStartCapture(const Ca
           capability.width = ipcCaps.width();
           capability.height = ipcCaps.height();
           capability.maxFPS = ipcCaps.maxFPS();
           capability.expectedCaptureDelay = ipcCaps.expectedCaptureDelay();
           capability.rawType = static_cast<webrtc::RawVideoType>(ipcCaps.rawType());
           capability.codecType = static_cast<webrtc::VideoCodecType>(ipcCaps.codecType());
           capability.interlaced = ipcCaps.interlaced();
 
-#ifdef DEBUG
-          auto deviceUniqueID = sDeviceUniqueIDs.find(capnum);
-          MOZ_ASSERT(deviceUniqueID == sDeviceUniqueIDs.end());
-#endif
+          MOZ_DIAGNOSTIC_ASSERT(sDeviceUniqueIDs.find(capnum) ==
+                                sDeviceUniqueIDs.end());
           sDeviceUniqueIDs.emplace(capnum, cap.VideoCapture()->CurrentDeviceName());
+
+          MOZ_DIAGNOSTIC_ASSERT(sAllRequestedCapabilities.find(capnum) ==
+                                sAllRequestedCapabilities.end());
           sAllRequestedCapabilities.emplace(capnum, capability);
 
           if (aCapEngine == CameraEngine) {
             for (const auto &it : sDeviceUniqueIDs) {
               if (strcmp(it.second, cap.VideoCapture()->CurrentDeviceName()) == 0) {
                 capability.width = std::max(
                   capability.width, sAllRequestedCapabilities[it.first].width);
                 capability.height = std::max(
@@ -944,16 +945,19 @@ CamerasParent::RecvStartCapture(const Ca
             }
           }
 
           error = cap.VideoCapture()->StartCapture(capability);
 
           if (!error) {
             cap.VideoCapture()->RegisterCaptureDataCallback(
               static_cast<rtc::VideoSinkInterface<webrtc::VideoFrame>*>(*cbh));
+          } else {
+            sDeviceUniqueIDs.erase(capnum);
+            sAllRequestedCapabilities.erase(capnum);
           }
         });
       }
       RefPtr<nsIRunnable> ipc_runnable =
         media::NewRunnableFrom([self, error]() -> nsresult {
           if (!self->mChildIsAlive) {
             return NS_ERROR_FAILURE;
           }
--- a/ipc/mscom/Interceptor.cpp
+++ b/ipc/mscom/Interceptor.cpp
@@ -226,16 +226,18 @@ private:
 
 static detail::LiveSet&
 GetLiveSet()
 {
   static detail::LiveSet sLiveSet;
   return sLiveSet;
 }
 
+MOZ_THREAD_LOCAL(bool) Interceptor::tlsCreatingStdMarshal;
+
 /* static */ HRESULT
 Interceptor::Create(STAUniquePtr<IUnknown> aTarget, IInterceptorSink* aSink,
                     REFIID aInitialIid, void** aOutInterface)
 {
   MOZ_ASSERT(aOutInterface && aTarget && aSink);
   if (!aOutInterface) {
     return E_INVALIDARG;
   }
@@ -266,16 +268,19 @@ Interceptor::Create(STAUniquePtr<IUnknow
 
 Interceptor::Interceptor(IInterceptorSink* aSink)
   : WeakReferenceSupport(WeakReferenceSupport::Flags::eDestroyOnMainThread)
   , mEventSink(aSink)
   , mInterceptorMapMutex("mozilla::mscom::Interceptor::mInterceptorMapMutex")
   , mStdMarshalMutex("mozilla::mscom::Interceptor::mStdMarshalMutex")
   , mStdMarshal(nullptr)
 {
+  static const bool kHasTls = tlsCreatingStdMarshal.init();
+  MOZ_ASSERT(kHasTls);
+
   MOZ_ASSERT(aSink);
   RefPtr<IWeakReference> weakRef;
   if (SUCCEEDED(GetWeakReference(getter_AddRefs(weakRef)))) {
     aSink->SetInterceptor(weakRef);
   }
 }
 
 Interceptor::~Interceptor()
@@ -714,16 +719,27 @@ Interceptor::GetInterceptorForIID(REFIID
 
 HRESULT
 Interceptor::QueryInterfaceTarget(REFIID aIid, void** aOutput,
                                   TimeDuration* aOutDuration)
 {
   // NB: This QI needs to run on the main thread because the target object
   // is probably Gecko code that is not thread-safe. Note that this main
   // thread invocation is *synchronous*.
+  if (!NS_IsMainThread() && tlsCreatingStdMarshal.get()) {
+    mStdMarshalMutex.AssertCurrentThreadOwns();
+    // COM queries for special interfaces such as IFastRundown when creating a
+    // marshaler. We don't want these being dispatched to the main thread,
+    // since this would cause a deadlock on mStdMarshalMutex if the main
+    // thread is also querying for IMarshal. If we do need to respond to these
+    // special interfaces, this should be done before this point; e.g. in
+    // Interceptor::QueryInterface like we do for INoMarshal.
+    return E_NOINTERFACE;
+  }
+
   MainThreadInvoker invoker;
   HRESULT hr;
   auto runOnMainThread = [&]() -> void {
     MOZ_ASSERT(NS_IsMainThread());
     hr = mTarget->QueryInterface(aIid, aOutput);
   };
   if (!invoker.Invoke(NS_NewRunnableFunction("Interceptor::QueryInterface", runOnMainThread))) {
     return E_FAIL;
@@ -770,23 +786,26 @@ Interceptor::WeakRefQueryInterface(REFII
   }
 
   if (aIid == IID_IMarshal) {
     MutexAutoLock lock(mStdMarshalMutex);
 
     HRESULT hr;
 
     if (!mStdMarshalUnk) {
+      MOZ_ASSERT(!tlsCreatingStdMarshal.get());
+      tlsCreatingStdMarshal.set(true);
       if (XRE_IsContentProcess()) {
         hr = FastMarshaler::Create(static_cast<IWeakReferenceSource*>(this),
                                    getter_AddRefs(mStdMarshalUnk));
       } else {
         hr = ::CoGetStdMarshalEx(static_cast<IWeakReferenceSource*>(this),
                                  SMEXF_SERVER, getter_AddRefs(mStdMarshalUnk));
       }
+      tlsCreatingStdMarshal.set(false);
 
       ENSURE_HR_SUCCEEDED(hr);
     }
 
     if (!mStdMarshal) {
       hr = mStdMarshalUnk->QueryInterface(IID_IMarshal, (void**)&mStdMarshal);
       ENSURE_HR_SUCCEEDED(hr);
 
--- a/ipc/mscom/Interceptor.h
+++ b/ipc/mscom/Interceptor.h
@@ -140,16 +140,17 @@ private:
   InterceptorTargetPtr<IUnknown>  mTarget;
   RefPtr<IInterceptorSink>  mEventSink;
   mozilla::Mutex            mInterceptorMapMutex; // Guards mInterceptorMap
   // Using a nsTArray since the # of interfaces is not going to be very high
   nsTArray<MapEntry>        mInterceptorMap;
   mozilla::Mutex            mStdMarshalMutex; // Guards mStdMarshalUnk and mStdMarshal
   RefPtr<IUnknown>          mStdMarshalUnk;
   IMarshal*                 mStdMarshal; // WEAK
+  static MOZ_THREAD_LOCAL(bool) tlsCreatingStdMarshal;
 };
 
 template <typename InterfaceT>
 inline HRESULT
 CreateInterceptor(STAUniquePtr<InterfaceT> aTargetInterface,
                   IInterceptorSink* aEventSink,
                   InterfaceT** aOutInterface)
 {
--- a/services/sync/tps/extensions/tps/resource/modules/formautofill.jsm
+++ b/services/sync/tps/extensions/tps/resource/modules/formautofill.jsm
@@ -24,17 +24,17 @@ class FormAutofillBase {
       this.updateProps = props.changes;
     }
     for (const field of this._fields) {
       this.props[field] = (field in props) ? props[field] : null;
     }
   }
 
   get storage() {
-    return profileStorage[this._subStorageName];
+    return formAutofillStorage[this._subStorageName];
   }
 
   Create() {
     this.storage.add(this.props);
   }
 
   Find() {
     return this.storage._data.find(entry =>
@@ -50,17 +50,17 @@ class FormAutofillBase {
   Remove() {
     const {guid} = this.Find();
     this.storage.remove(guid);
   }
 }
 
 function DumpStorage(subStorageName) {
   Logger.logInfo(`\ndumping ${subStorageName} list\n`, true);
-  const entries = profileStorage[subStorageName]._data;
+  const entries = formAutofillStorage[subStorageName]._data;
   for (const entry of entries) {
     Logger.logInfo(JSON.stringify(entry), true);
   }
   Logger.logInfo(`\n\nend ${subStorageName} list\n`, true);
 }
 
 const ADDRESS_FIELDS = [
   "given-name",
--- a/servo/components/atoms/static_atoms.txt
+++ b/servo/components/atoms/static_atoms.txt
@@ -42,16 +42,17 @@ message
 message
 monospace
 month
 mouseover
 none
 number
 onchange
 open
+pageshow
 password
 pause
 play
 playing
 print
 progress
 radio
 range
--- a/servo/components/script/CMakeLists.txt
+++ b/servo/components/script/CMakeLists.txt
@@ -101,17 +101,17 @@ foreach(binding IN LISTS bindings)
   add_custom_command(
     OUTPUT Bindings/${binding}Binding.rs
     COMMAND python -B ${bindings_src}/pythonpath.py -I ${bindings_src}/parser -I ${bindings_src}/ply
                    ${bindings_src}/BindingGen.py
                    ${bindings_src}/Bindings.conf
                    .
                    Bindings/${binding}Binding
                    ${webidls_src}/${binding}.webidl
-    DEPENDS Bindings ${bindinggen_deps} ${webidls_src}/${binding}.webidl ParserResults
+    DEPENDS Bindings ${bindinggen_deps} ${webidls} ParserResults
     VERBATIM
     )
   add_custom_target(${binding} DEPENDS Bindings/${binding}Binding.rs)
   add_dependencies(generate-bindings ${binding})
 endforeach()
 
 PREPEND(globalgen_out ${CMAKE_BINARY_DIR}/ ${globalgen_base_src})
 install(FILES ${globalgen_out} DESTINATION .)
--- a/servo/components/script/dom/document.rs
+++ b/servo/components/script/dom/document.rs
@@ -359,16 +359,18 @@ pub struct Document {
     /// See https://html.spec.whatwg.org/multipage/#form-owner
     form_id_listener_map: DomRefCell<HashMap<Atom, HashSet<Dom<Element>>>>,
     interactive_time: DomRefCell<InteractiveMetrics>,
     tti_window: DomRefCell<InteractiveWindow>,
     /// RAII canceller for Fetch
     canceller: FetchCanceller,
     /// https://html.spec.whatwg.org/multipage/#throw-on-dynamic-markup-insertion-counter
     throw_on_dynamic_markup_insertion_counter: Cell<u64>,
+    /// https://html.spec.whatwg.org/multipage/#page-showing
+    page_showing: Cell<bool>,
 }
 
 #[derive(JSTraceable, MallocSizeOf)]
 struct ImagesFilter;
 impl CollectionFilter for ImagesFilter {
     fn filter(&self, elem: &Element, _root: &Node) -> bool {
         elem.is::<HTMLImageElement>()
     }
@@ -1629,17 +1631,47 @@ impl Document {
                 if let Some(fragment) = document.url().fragment() {
                     document.check_and_scroll_fragment(fragment);
                 }
             }),
             self.window.upcast(),
         ).unwrap();
 
         // Step 8.
-        // TODO: pageshow event.
+        let document = Trusted::new(self);
+        if document.root().browsing_context().is_some() {
+            self.window.dom_manipulation_task_source().queue(
+                task!(fire_pageshow_event: move || {
+                    let document = document.root();
+                    let window = document.window();
+                    if document.page_showing.get() || !window.is_alive() {
+                        return;
+                    }
+
+                    document.page_showing.set(true);
+
+                    let event = PageTransitionEvent::new(
+                        window,
+                        atom!("pageshow"),
+                        false, // bubbles
+                        false, // cancelable
+                        false, // persisted
+                    );
+                    let event = event.upcast::<Event>();
+                    event.set_trusted(true);
+
+                    // FIXME(nox): Why are errors silenced here?
+                    let _ = window.upcast::<EventTarget>().dispatch_event_with_target(
+                        document.upcast(),
+                        &event,
+                    );
+                }),
+                self.window.upcast(),
+            ).unwrap();
+        }
 
         // Step 9.
         // TODO: pending application cache download process tasks.
 
         // Step 10.
         // TODO: printing steps.
 
         // Step 11.
@@ -2220,16 +2252,17 @@ impl Document {
             spurious_animation_frames: Cell::new(0),
             dom_count: Cell::new(1),
             fullscreen_element: MutNullableDom::new(None),
             form_id_listener_map: Default::default(),
             interactive_time: DomRefCell::new(interactive_time),
             tti_window: DomRefCell::new(InteractiveWindow::new()),
             canceller: canceller,
             throw_on_dynamic_markup_insertion_counter: Cell::new(0),
+            page_showing: Cell::new(false),
         }
     }
 
     // https://dom.spec.whatwg.org/#dom-document-document
     pub fn Constructor(window: &Window) -> Fallible<DomRoot<Document>> {
         let doc = window.Document();
         let docloader = DocumentLoader::new(&*doc.loader());
         Ok(Document::new(window,
--- a/servo/components/style/counter_style/mod.rs
+++ b/servo/components/style/counter_style/mod.rs
@@ -542,19 +542,18 @@ pub struct Fallback(pub CustomIdent);
 impl Parse for Fallback {
     fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
         parse_counter_style_name(input).map(Fallback)
     }
 }
 
 /// <https://drafts.csswg.org/css-counter-styles/#descdef-counter-style-symbols>
 #[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
-#[css(iterable)]
 #[derive(Clone, Debug, Eq, PartialEq, ToComputedValue, ToCss)]
-pub struct Symbols(pub Vec<Symbol>);
+pub struct Symbols(#[css(iterable)] pub Vec<Symbol>);
 
 impl Parse for Symbols {
     fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
         let mut symbols = Vec::new();
         loop {
             if let Ok(s) = input.try(|input| Symbol::parse(context, input)) {
                 symbols.push(s)
             } else {
--- a/servo/components/style/media_queries.rs
+++ b/servo/components/style/media_queries.rs
@@ -20,20 +20,21 @@ use values::CustomIdent;
 
 #[cfg(feature = "servo")]
 pub use servo::media_queries::{Device, Expression};
 #[cfg(feature = "gecko")]
 pub use gecko::media_queries::{Device, Expression};
 
 /// A type that encapsulates a media query list.
 #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
-#[css(comma, iterable)]
+#[css(comma)]
 #[derive(Clone, Debug, ToCss)]
 pub struct MediaList {
     /// The list of media queries.
+    #[css(iterable)]
     pub media_queries: Vec<MediaQuery>,
 }
 
 impl MediaList {
     /// Create an empty MediaList.
     pub fn empty() -> Self {
         MediaList { media_queries: vec![] }
     }
--- a/servo/components/style/properties/helpers.mako.rs
+++ b/servo/components/style/properties/helpers.mako.rs
@@ -121,26 +121,27 @@
 
             /// The computed value, effectively a list of single values.
             #[derive(Clone, Debug, MallocSizeOf, PartialEq)]
             % if need_animatable or animation_value_type == "ComputedValue":
             #[derive(Animate, ComputeSquaredDistance)]
             % endif
             % if not allow_empty:
             % if separator == "Comma":
-            #[css(comma, iterable)]
-            % else:
-            #[css(iterable)]
+            #[css(comma)]
             % endif
             #[derive(ToCss)]
             % endif
             pub struct T(
                 % if allow_empty and allow_empty != "NotInitial":
                 pub Vec<single_value::T>,
                 % else:
+                % if not allow_empty:
+                #[css(iterable)]
+                % endif
                 pub SmallVec<[single_value::T; 1]>,
                 % endif
             );
 
             % if need_animatable or animation_value_type == "ComputedValue":
                 use values::animated::{ToAnimatedZero};
 
                 impl ToAnimatedZero for T {
@@ -184,23 +185,26 @@
             }
         }
         % endif
 
         /// The specified value of ${name}.
         #[derive(Clone, Debug, MallocSizeOf, PartialEq)]
         % if not allow_empty:
         % if separator == "Comma":
-        #[css(comma, iterable)]
-        % else:
-        #[css(iterable)]
+        #[css(comma)]
         % endif
         #[derive(ToCss)]
         % endif
-        pub struct SpecifiedValue(pub Vec<single_value::SpecifiedValue>);
+        pub struct SpecifiedValue(
+            % if not allow_empty:
+            #[css(iterable)]
+            % endif
+            pub Vec<single_value::SpecifiedValue>,
+        );
 
         % if allow_empty:
         impl ToCss for SpecifiedValue {
             fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
             where
                 W: Write,
             {
                 let mut iter = self.0.iter();
--- a/servo/components/style/stylesheets/document_rule.rs
+++ b/servo/components/style/stylesheets/document_rule.rs
@@ -171,19 +171,19 @@ impl UrlMatchingFunction {
 
 /// A `@document` rule's condition.
 ///
 /// <https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#at-document>
 ///
 /// The `@document` rule's condition is written as a comma-separated list of
 /// URL matching functions, and the condition evaluates to true whenever any
 /// one of those functions evaluates to true.
-#[css(comma, iterable)]
+#[css(comma)]
 #[derive(Clone, Debug, ToCss)]
-pub struct DocumentCondition(Vec<UrlMatchingFunction>);
+pub struct DocumentCondition(#[css(iterable)] Vec<UrlMatchingFunction>);
 
 impl DocumentCondition {
     /// Parse a document condition.
     pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
         -> Result<Self, ParseError<'i>> {
         input.parse_comma_separated(|input| UrlMatchingFunction::parse(context, input))
              .map(DocumentCondition)
     }
--- a/servo/components/style/stylesheets/font_feature_values_rule.rs
+++ b/servo/components/style/stylesheets/font_feature_values_rule.rs
@@ -113,19 +113,18 @@ impl ToGeckoFontFeatureValues for PairVa
         array[0] = self.0 as u32;
         if let Some(second) = self.1 {
             array[1] = second as u32;
         };
     }
 }
 
 /// A @font-feature-values block declaration value that keeps a list of values.
-#[css(iterable)]
 #[derive(Clone, Debug, PartialEq, ToCss)]
-pub struct VectorValues(pub Vec<u32>);
+pub struct VectorValues(#[css(iterable)] pub Vec<u32>);
 
 impl Parse for VectorValues {
     fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>)
                      -> Result<VectorValues, ParseError<'i>> {
         let mut vec = vec![];
         loop {
             let location = input.current_source_location();
             match input.next() {
--- a/servo/components/style/stylesheets/keyframes_rule.rs
+++ b/servo/components/style/stylesheets/keyframes_rule.rs
@@ -140,19 +140,19 @@ impl KeyframePercentage {
         };
 
         Ok(percentage)
     }
 }
 
 /// A keyframes selector is a list of percentages or from/to symbols, which are
 /// converted at parse time to percentages.
-#[css(comma, iterable)]
+#[css(comma)]
 #[derive(Clone, Debug, Eq, PartialEq, ToCss)]
-pub struct KeyframeSelector(Vec<KeyframePercentage>);
+pub struct KeyframeSelector(#[css(iterable)] Vec<KeyframePercentage>);
 
 impl KeyframeSelector {
     /// Return the list of percentages this selector contains.
     #[inline]
     pub fn percentages(&self) -> &[KeyframePercentage] {
         &self.0
     }
 
--- a/servo/components/style/values/specified/box.rs
+++ b/servo/components/style/values/specified/box.rs
@@ -364,19 +364,19 @@ pub enum OverflowClipBox {
 /// Provides a rendering hint to the user agent,
 /// stating what kinds of changes the author expects
 /// to perform on the element
 ///
 /// <https://drafts.csswg.org/css-will-change/#will-change>
 pub enum WillChange {
     /// Expresses no particular intent
     Auto,
-    #[css(comma, iterable)]
     /// <custom-ident>
-    AnimateableFeatures(Box<[CustomIdent]>),
+    #[css(comma)]
+    AnimateableFeatures(#[css(iterable)] Box<[CustomIdent]>),
 }
 
 impl WillChange {
     #[inline]
     /// Get default value of `will-change` as `auto`
     pub fn auto() -> WillChange {
         WillChange::Auto
     }
--- a/servo/components/style/values/specified/counters.rs
+++ b/servo/components/style/values/specified/counters.rs
@@ -87,18 +87,17 @@ pub enum Content {
     /// `normal` reserved keyword.
     Normal,
     /// `none` reserved keyword.
     None,
     /// `-moz-alt-content`.
     #[cfg(feature = "gecko")]
     MozAltContent,
     /// Content items.
-    #[css(iterable)]
-    Items(Box<[ContentItem]>),
+    Items(#[css(iterable)] Box<[ContentItem]>),
 }
 
 /// Items for the `content` property.
 #[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToCss)]
 pub enum ContentItem {
     /// Literal string content.
     String(Box<str>),
     /// `counter(name, style)`.
--- a/servo/components/style/values/specified/font.rs
+++ b/servo/components/style/values/specified/font.rs
@@ -147,18 +147,18 @@ impl From<LengthOrPercentage> for FontSi
         FontSize::Length(other)
     }
 }
 
 /// Specifies a prioritized list of font family names or generic family names.
 #[derive(Clone, Debug, Eq, Hash, PartialEq, ToCss)]
 pub enum FontFamily {
     /// List of `font-family`
-    #[css(iterable, comma)]
-    Values(FontFamilyList),
+    #[css(comma)]
+    Values(#[css(iterable)] FontFamilyList),
     /// System font
     System(SystemFont),
 }
 
 impl FontFamily {
     /// Get `font-family` with system font
     pub fn system_font(f: SystemFont) -> Self {
         FontFamily::System(f)
@@ -713,21 +713,21 @@ bitflags! {
 
 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss)]
 /// Set of variant alternates
 pub enum VariantAlternates {
     /// Enables display of stylistic alternates
     #[css(function)]
     Stylistic(CustomIdent),
     /// Enables display with stylistic sets
-    #[css(comma, function, iterable)]
-    Styleset(Box<[CustomIdent]>),
+    #[css(comma, function)]
+    Styleset(#[css(iterable)] Box<[CustomIdent]>),
     /// Enables display of specific character variants
-    #[css(comma, function, iterable)]
-    CharacterVariant(Box<[CustomIdent]>),
+    #[css(comma, function)]
+    CharacterVariant(#[css(iterable)] Box<[CustomIdent]>),
     /// Enables display of swash glyphs
     #[css(function)]
     Swash(CustomIdent),
     /// Enables replacement of default glyphs with ornaments
     #[css(function)]
     Ornaments(CustomIdent),
     /// Enables display of alternate annotation forms
     #[css(function)]
--- a/servo/components/style_derive/to_css.rs
+++ b/servo/components/style_derive/to_css.rs
@@ -1,102 +1,33 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use cg;
+use cg::{self, WhereClause};
 use darling::util::Override;
-use quote::Tokens;
-use syn::{self, Ident};
-use synstructure;
+use quote::{ToTokens, Tokens};
+use syn::{self, Data};
+use synstructure::{BindingInfo, Structure, VariantInfo};
 
 pub fn derive(input: syn::DeriveInput) -> Tokens {
     let name = &input.ident;
-    let trait_path = parse_quote!(style_traits::ToCss);
+    let trait_path = parse_quote!(::style_traits::ToCss);
     let (impl_generics, ty_generics, mut where_clause) =
         cg::trait_parts(&input, &trait_path);
 
     let input_attrs = cg::parse_input_attrs::<CssInputAttrs>(&input);
-    let s = synstructure::Structure::new(&input);
+    if let Data::Enum(_) = input.data {
+        assert!(input_attrs.function.is_none(), "#[css(function)] is not allowed on enums");
+        assert!(!input_attrs.comma, "#[css(comma)] is not allowed on enums");
+    }
+    let s = Structure::new(&input);
 
     let match_body = s.each_variant(|variant| {
-        let bindings = variant.bindings();
-        let identifier = cg::to_css_identifier(variant.ast().ident.as_ref());
-        let ast = variant.ast();
-        let variant_attrs = cg::parse_variant_attrs::<CssVariantAttrs>(&ast);
-        let separator = if variant_attrs.comma { ", " } else { " " };
-
-        if variant_attrs.dimension {
-            assert_eq!(bindings.len(), 1);
-            assert!(
-                variant_attrs.function.is_none() && variant_attrs.keyword.is_none(),
-                "That makes no sense"
-            );
-        }
-
-        let mut expr = if let Some(keyword) = variant_attrs.keyword {
-            assert!(bindings.is_empty());
-            let keyword = keyword.to_string();
-            quote! {
-                ::std::fmt::Write::write_str(dest, #keyword)
-            }
-        } else if !bindings.is_empty() {
-            let mut expr = quote! {};
-            if variant_attrs.iterable {
-                assert_eq!(bindings.len(), 1);
-                let binding = &bindings[0];
-                expr = quote! {
-                    #expr
-
-                    for item in #binding.iter() {
-                        writer.item(&item)?;
-                    }
-                };
-            } else {
-                for binding in bindings {
-                    let attrs = cg::parse_field_attrs::<CssFieldAttrs>(&binding.ast());
-                    if attrs.skip {
-                        continue;
-                    }
-                    if !attrs.ignore_bound {
-                        where_clause.add_trait_bound(&binding.ast().ty);
-                    }
-                    expr = quote! {
-                        #expr
-                        writer.item(#binding)?;
-                    };
-                }
-            }
-
-            quote! {{
-                let mut writer = ::style_traits::values::SequenceWriter::new(dest, #separator);
-                #expr
-                Ok(())
-            }}
-        } else {
-            quote! {
-                ::std::fmt::Write::write_str(dest, #identifier)
-            }
-        };
-
-        if variant_attrs.dimension {
-            expr = quote! {
-                #expr?;
-                ::std::fmt::Write::write_str(dest, #identifier)
-            }
-        } else if let Some(function) = variant_attrs.function {
-            let mut identifier = function.explicit().map_or(identifier, |name| name.to_string());
-            identifier.push_str("(");
-            expr = quote! {
-                ::std::fmt::Write::write_str(dest, #identifier)?;
-                #expr?;
-                ::std::fmt::Write::write_str(dest, ")")
-            }
-        }
-        Some(expr)
+        derive_variant_arm(variant, &mut where_clause)
     });
 
     let mut impls = quote! {
         impl #impl_generics ::style_traits::ToCss for #name #ty_generics #where_clause {
             #[allow(unused_variables)]
             #[inline]
             fn to_css<W>(
                 &self,
@@ -123,37 +54,139 @@ pub fn derive(input: syn::DeriveInput) -
                 }
             }
         });
     }
 
     impls
 }
 
+fn derive_variant_arm(
+    variant: &VariantInfo,
+    where_clause: &mut WhereClause,
+) -> Tokens {
+    let bindings = variant.bindings();
+    let identifier = cg::to_css_identifier(variant.ast().ident.as_ref());
+    let ast = variant.ast();
+    let variant_attrs = cg::parse_variant_attrs::<CssVariantAttrs>(&ast);
+    let separator = if variant_attrs.comma { ", " } else { " " };
+
+    if variant_attrs.dimension {
+        assert_eq!(bindings.len(), 1);
+        assert!(
+            variant_attrs.function.is_none() && variant_attrs.keyword.is_none(),
+            "That makes no sense"
+        );
+    }
+
+    let mut expr = if let Some(keyword) = variant_attrs.keyword {
+        assert!(bindings.is_empty());
+        let keyword = keyword.to_string();
+        quote! {
+            ::std::fmt::Write::write_str(dest, #keyword)
+        }
+    } else if !bindings.is_empty() {
+        derive_variant_fields_expr(bindings, where_clause, separator)
+    } else {
+        quote! {
+            ::std::fmt::Write::write_str(dest, #identifier)
+        }
+    };
+
+    if variant_attrs.dimension {
+        expr = quote! {
+            #expr?;
+            ::std::fmt::Write::write_str(dest, #identifier)
+        }
+    } else if let Some(function) = variant_attrs.function {
+        let mut identifier = function.explicit().map_or(identifier, |name| name);
+        identifier.push_str("(");
+        expr = quote! {
+            ::std::fmt::Write::write_str(dest, #identifier)?;
+            #expr?;
+            ::std::fmt::Write::write_str(dest, ")")
+        }
+    }
+    expr
+}
+
+fn derive_variant_fields_expr(
+    bindings: &[BindingInfo],
+    where_clause: &mut WhereClause,
+    separator: &str,
+) -> Tokens {
+    let mut iter = bindings.iter().filter_map(|binding| {
+        let attrs = cg::parse_field_attrs::<CssFieldAttrs>(&binding.ast());
+        if attrs.skip {
+            return None;
+        }
+        Some((binding, attrs))
+    }).peekable();
+
+    let (first, attrs) = match iter.next() {
+        Some(pair) => pair,
+        None => return quote! { Ok(()) },
+    };
+    if !attrs.iterable && iter.peek().is_none() {
+        if !attrs.ignore_bound {
+            where_clause.add_trait_bound(&first.ast().ty);
+        }
+        return quote! { ::style_traits::ToCss::to_css(#first, dest) };
+    }
+
+    let mut expr = derive_single_field_expr(first, attrs, where_clause);
+    for (binding, attrs) in iter {
+        derive_single_field_expr(binding, attrs, where_clause).to_tokens(&mut expr)
+    }
+
+    quote! {{
+        let mut writer = ::style_traits::values::SequenceWriter::new(dest, #separator);
+        #expr
+        Ok(())
+    }}
+}
+
+fn derive_single_field_expr(
+    field: &BindingInfo,
+    attrs: CssFieldAttrs,
+    where_clause: &mut WhereClause,
+) -> Tokens {
+    if attrs.iterable {
+        quote! {
+            for item in #field.iter() {
+                writer.item(&item)?;
+            }
+        }
+    } else {
+        if !attrs.ignore_bound {
+            where_clause.add_trait_bound(&field.ast().ty);
+        }
+        quote! { writer.item(#field)?; }
+    }
+}
+
 #[darling(attributes(css), default)]
 #[derive(Default, FromDeriveInput)]
 struct CssInputAttrs {
     derive_debug: bool,
     // Here because structs variants are also their whole type definition.
-    function: Option<Override<Ident>>,
+    function: Option<Override<String>>,
     // Here because structs variants are also their whole type definition.
     comma: bool,
-    // Here because structs variants are also their whole type definition.
-    iterable: bool,
 }
 
 #[darling(attributes(css), default)]
 #[derive(Default, FromVariant)]
 pub struct CssVariantAttrs {
-    pub function: Option<Override<Ident>>,
-    pub iterable: bool,
+    pub function: Option<Override<String>>,
     pub comma: bool,
     pub dimension: bool,
     pub keyword: Option<String>,
     pub aliases: Option<String>,
 }
 
 #[darling(attributes(css), default)]
 #[derive(Default, FromField)]
 struct CssFieldAttrs {
     ignore_bound: bool,
+    iterable: bool,
     skip: bool,
 }
--- a/taskcluster/docker/firefox-snap/snapcraft.yaml.in
+++ b/taskcluster/docker/firefox-snap/snapcraft.yaml.in
@@ -31,20 +31,16 @@ apps:
       - x11
 
 plugs:
   browser-sandbox:
     interface: browser-support
     allow-sandbox: true
 
 parts:
-  desktop-gtk3:
-    prime:
-      - -usr/share/mime
-
   firefox:
     plugin: dump
     source: source
     stage-packages:
       - libxt6
       - libdbus-glib-1-2
       - libasound2
       - libpulse0
@@ -67,19 +63,14 @@ parts:
       update-desktop-database -v $SNAPCRAFT_PART_INSTALL/usr/share/applications
     build-packages:
       - desktop-file-utils
     build-attributes: [no-system-libraries]
 
   shared-mime-info:
     after: [xdg-open]
     plugin: nil
-    source: .
-    build-packages:
+    stage-packages:
       - shared-mime-info
     build-attributes: [no-system-libraries]
     install: |
       set -eux
-      mkdir -p $SNAPCRAFT_PART_INSTALL/usr/share/mime/packages
-      install -m 644 mime-handler.xml $SNAPCRAFT_PART_INSTALL/usr/share/mime/packages
       update-mime-database $SNAPCRAFT_PART_INSTALL/usr/share/mime
-    stage:
-      - usr/share/mime
--- a/toolkit/components/payments/content/paymentDialogWrapper.js
+++ b/toolkit/components/payments/content/paymentDialogWrapper.js
@@ -13,49 +13,49 @@ const paymentSrv = Cc["@mozilla.org/dom/
                      .getService(Ci.nsIPaymentRequestService);
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 ChromeUtils.defineModuleGetter(this, "MasterPassword",
                                "resource://formautofill/MasterPassword.jsm");
 
-XPCOMUtils.defineLazyGetter(this, "profileStorage", () => {
-  let profileStorage;
+XPCOMUtils.defineLazyGetter(this, "formAutofillStorage", () => {
+  let formAutofillStorage;
   try {
-    profileStorage = ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm", {})
-                                .profileStorage;
-    profileStorage.initialize();
+    formAutofillStorage = ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm", {})
+                                .formAutofillStorage;
+    formAutofillStorage.initialize();
   } catch (ex) {
-    profileStorage = null;
+    formAutofillStorage = null;
     Cu.reportError(ex);
   }
 
-  return profileStorage;
+  return formAutofillStorage;
 });
 
 var paymentDialogWrapper = {
   componentsLoaded: new Map(),
   frame: null,
   mm: null,
   request: null,
 
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsIObserver,
     Ci.nsISupportsWeakReference,
   ]),
 
   /**
-   * Note: This method is async because profileStorage plans to become async.
+   * Note: This method is async because formAutofillStorage plans to become async.
    *
    * @param {string} guid
    * @returns {nsIPaymentAddress}
    */
   async _convertProfileAddressToPaymentAddress(guid) {
-    let addressData = profileStorage.addresses.get(guid);
+    let addressData = formAutofillStorage.addresses.get(guid);
     if (!addressData) {
       throw new Error(`Shipping address not found: ${guid}`);
     }
 
     let address = this.createPaymentAddress({
       country: addressData.country,
       addressLines: addressData["street-address"].split("\n"),
       region: addressData["address-level1"],
@@ -72,17 +72,17 @@ var paymentDialogWrapper = {
   /**
    * @param {string} guid The GUID of the basic card record from storage.
    * @param {string} cardSecurityCode The associated card security code (CVV/CCV/etc.)
    * @throws if the user cancels entering their master password or an error decrypting
    * @returns {nsIBasicCardResponseData?} returns response data or null (if the
    *                                      master password dialog was cancelled);
    */
   async _convertProfileBasicCardToPaymentMethodData(guid, cardSecurityCode) {
-    let cardData = profileStorage.creditCards.get(guid);
+    let cardData = formAutofillStorage.creditCards.get(guid);
     if (!cardData) {
       throw new Error(`Basic card not found in storage: ${guid}`);
     }
 
     let cardNumber;
     try {
       cardNumber = await MasterPassword.decrypt(cardData["cc-number-encrypted"], true);
     } catch (ex) {
@@ -217,25 +217,25 @@ var paymentDialogWrapper = {
       this.componentsLoaded.set(componentName, component);
     }
 
     return component.createInstance(componentInterface);
   },
 
   fetchSavedAddresses() {
     let savedAddresses = {};
-    for (let address of profileStorage.addresses.getAll()) {
+    for (let address of formAutofillStorage.addresses.getAll()) {
       savedAddresses[address.guid] = address;
     }
     return savedAddresses;
   },
 
   fetchSavedPaymentCards() {
     let savedBasicCards = {};
-    for (let card of profileStorage.creditCards.getAll()) {
+    for (let card of formAutofillStorage.creditCards.getAll()) {
       savedBasicCards[card.guid] = card;
       // Filter out the encrypted card number since the dialog content is
       // considered untrusted and runs in a content process.
       delete card["cc-number-encrypted"];
     }
     return savedBasicCards;
   },
 
--- a/toolkit/components/payments/test/browser/browser_change_shipping.js
+++ b/toolkit/components/payments/test/browser/browser_change_shipping.js
@@ -1,25 +1,25 @@
 "use strict";
 
 add_task(async function setup_profiles() {
   let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
                                           (subject, data) => data == "add");
-  profileStorage.addresses.add(PTU.Addresses.TimBL);
+  formAutofillStorage.addresses.add(PTU.Addresses.TimBL);
   await onChanged;
 
   onChanged = TestUtils.topicObserved("formautofill-storage-changed",
                                       (subject, data) => data == "add");
-  profileStorage.addresses.add(PTU.Addresses.TimBL2);
+  formAutofillStorage.addresses.add(PTU.Addresses.TimBL2);
   await onChanged;
 
   onChanged = TestUtils.topicObserved("formautofill-storage-changed",
                                       (subject, data) => data == "add");
 
-  profileStorage.creditCards.add(PTU.BasicCards.JohnDoe);
+  formAutofillStorage.creditCards.add(PTU.BasicCards.JohnDoe);
   await onChanged;
 });
 
 add_task(async function test_change_shipping() {
   await BrowserTestUtils.withNewTab({
     gBrowser,
     url: BLANK_PAGE_URL,
   }, async browser => {
--- a/toolkit/components/payments/test/browser/browser_profile_storage.js
+++ b/toolkit/components/payments/test/browser/browser_profile_storage.js
@@ -4,34 +4,34 @@
 /* eslint-disable mozilla/no-cpows-in-tests */
 
 const methodData = [PTU.MethodData.basicCard];
 const details = PTU.Details.total60USD;
 
 add_task(async function test_initial_state() {
   let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
                                           (subject, data) => data == "add");
-  let address1GUID = profileStorage.addresses.add({
+  let address1GUID = formAutofillStorage.addresses.add({
     "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",
     "address-level1": "MA",
     "postal-code": "02139",
     country: "US",
     tel: "+16172535702",
     email: "timbl@w3.org",
   });
   await onChanged;
 
   onChanged = TestUtils.topicObserved("formautofill-storage-changed",
                                       (subject, data) => data == "add");
-  let card1GUID = profileStorage.creditCards.add({
+  let card1GUID = formAutofillStorage.creditCards.add({
     "cc-name": "John Doe",
     "cc-number": "1234567812345678",
     "cc-exp-month": 4,
     "cc-exp-year": 2028,
   });
   await onChanged;
 
   await BrowserTestUtils.withNewTab({
@@ -67,17 +67,17 @@ add_task(async function test_initial_sta
     }, {
       address1GUID,
       card1GUID,
     });
 
     let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
                                             (subject, data) => data == "add");
     info("adding an address");
-    let address2GUID = profileStorage.addresses.add({
+    let address2GUID = formAutofillStorage.addresses.add({
       "given-name": "John",
       "additional-name": "",
       "family-name": "Smith",
       "street-address": "331 E. Evelyn Ave.",
       "address-level2": "Mountain View",
       "address-level1": "CA",
       "postal-code": "94041",
       country: "US",
@@ -109,17 +109,17 @@ add_task(async function test_initial_sta
       address1GUID,
       address2GUID,
       card1GUID,
     });
 
     onChanged = TestUtils.topicObserved("formautofill-storage-changed",
                                         (subject, data) => data == "update");
     info("updating the credit expiration");
-    profileStorage.creditCards.update(card1GUID, {
+    formAutofillStorage.creditCards.update(card1GUID, {
       "cc-exp-month": 6,
       "cc-exp-year": 2029,
     }, true);
     await onChanged;
 
     await spawnPaymentDialogTask(frame, async function checkUpdate({
       address1GUID,
       address2GUID,
@@ -147,17 +147,17 @@ add_task(async function test_initial_sta
       address1GUID,
       address2GUID,
       card1GUID,
     });
 
     onChanged = TestUtils.topicObserved("formautofill-storage-changed",
                                         (subject, data) => data == "remove");
     info("removing the first address");
-    profileStorage.addresses.remove(address1GUID);
+    formAutofillStorage.addresses.remove(address1GUID);
     await onChanged;
 
     await spawnPaymentDialogTask(frame, async function checkRemove({
       address2GUID,
       card1GUID,
     }) {
       info("checkRemove");
       let contentWin = Cu.waiveXrays(content);
--- a/toolkit/components/payments/test/browser/browser_request_shipping.js
+++ b/toolkit/components/payments/test/browser/browser_request_shipping.js
@@ -6,17 +6,17 @@ add_task(async function setup() {
 
   let card = {
     "cc-exp-month": 1,
     "cc-exp-year": 9999,
     "cc-name": "John Doe",
     "cc-number": "999999999999",
   };
 
-  profileStorage.creditCards.add(card);
+  formAutofillStorage.creditCards.add(card);
   await onChanged;
 });
 
 add_task(async function test_request_shipping_present() {
   await BrowserTestUtils.withNewTab({
     gBrowser,
     url: BLANK_PAGE_URL,
   }, async browser => {
--- a/toolkit/components/payments/test/browser/browser_show_dialog.js
+++ b/toolkit/components/payments/test/browser/browser_show_dialog.js
@@ -51,29 +51,29 @@ add_task(async function test_show_comple
     "street-address": "32 Vassar Street\nMIT Room 32-G524",
     "address-level2": "Cambridge",
     "address-level1": "MA",
     "postal-code": "02139",
     country: "US",
     tel: "+16172535702",
     email: "timbl@example.org",
   };
-  profileStorage.addresses.add(address);
+  formAutofillStorage.addresses.add(address);
   await onChanged;
 
   onChanged = TestUtils.topicObserved("formautofill-storage-changed",
                                       (subject, data) => data == "add");
   let card = {
     "cc-exp-month": 1,
     "cc-exp-year": 9999,
     "cc-name": "John Doe",
     "cc-number": "999999999999",
   };
 
-  profileStorage.creditCards.add(card);
+  formAutofillStorage.creditCards.add(card);
   await onChanged;
 
   await BrowserTestUtils.withNewTab({
     gBrowser,
     url: BLANK_PAGE_URL,
   }, async browser => {
     let {win, frame} =
       await setupPaymentDialog(browser, {
--- a/toolkit/components/payments/test/browser/head.js
+++ b/toolkit/components/payments/test/browser/head.js
@@ -10,17 +10,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(
+const {formAutofillStorage} = ChromeUtils.import(
   "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()) {
@@ -129,27 +129,27 @@ function spawnTaskInNewDialog(requestId,
   return withNewDialogFrame(requestId, async function spawnTaskInNewDialog_tabTask(reqFrame) {
     await spawnPaymentDialogTask(reqFrame, contentTaskFn, args);
   });
 }
 
 async function addSampleAddressesAndBasicCard() {
   let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
                                           (subject, data) => data == "add");
-  profileStorage.addresses.add(PTU.Addresses.TimBL);
+  formAutofillStorage.addresses.add(PTU.Addresses.TimBL);
   await onChanged;
 
   onChanged = TestUtils.topicObserved("formautofill-storage-changed",
                                       (subject, data) => data == "add");
-  profileStorage.addresses.add(PTU.Addresses.TimBL2);
+  formAutofillStorage.addresses.add(PTU.Addresses.TimBL2);
   await onChanged;
 
   onChanged = TestUtils.topicObserved("formautofill-storage-changed",
                                       (subject, data) => data == "add");
-  profileStorage.creditCards.add(PTU.BasicCards.JohnDoe);
+  formAutofillStorage.creditCards.add(PTU.BasicCards.JohnDoe);
   await onChanged;
 }
 
 /**
  * Create a PaymentRequest object with the given parameters, then
  * run the given merchantTaskFn.
  *
  * @param {Object} browser
@@ -212,15 +212,15 @@ async function spawnInDialogForMerchantT
     let request = requests[0];
     ok(!!request.requestId, "Got a payment request with an ID");
 
     await spawnTaskInNewDialog(request.requestId, dialogTaskFn, taskArgs);
   });
 }
 
 add_task(async function setup_head() {
-  await profileStorage.initialize();
+  await formAutofillStorage.initialize();
   registerCleanupFunction(function cleanup() {
     paymentSrv.cleanup();
-    profileStorage.addresses._nukeAllRecords();
-    profileStorage.creditCards._nukeAllRecords();
+    formAutofillStorage.addresses._nukeAllRecords();
+    formAutofillStorage.creditCards._nukeAllRecords();
   });
 });
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -170,17 +170,17 @@
     "high": 10000,
     "n_buckets": 50,
     "description": "The length of time (in milliseconds) for the subsequent opens of AudioStream."
   },
   "AUDIOSTREAM_BACKEND_USED": {
     "record_in_processes": ["main", "content"],
     "alert_emails": ["padenot@mozilla.com", "kinetik@flim.org"],
     "bug_numbers": [1280630],
-    "expires_in_version": "60",
+    "expires_in_version": "65",
     "kind": "enumerated",
     "n_values": 16,
     "description": "The operating system audio back-end used when successfully opening an audio stream, or whether the failure occurred on the first try or not <https://dxr.mozilla.org/mozilla-central/search?q=AUDIOSTREAM_BACKEND_ID_STR>",
     "releaseChannelCollection": "opt-out"
   },
   "AUSHELPER_CPU_ERROR_CODE": {
     "record_in_processes": ["main", "content"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
@@ -6738,20 +6738,22 @@
     "record_in_processes": ["main", "content"],
     "alert_emails": ["dzeber@mozilla.com"],
     "expires_in_version": "63",
     "releaseChannelCollection": "opt-out",
     "kind": "categorical",
     "labels": [
       "enter",
       "enterSelection",
-      "click"
+      "click",
+      "arrowEnterSelection",
+      "tabEnterSelection"
     ],
     "bug_numbers": [1334615],
-    "description": "The input method the user used to select a result in the urlbar. 'enter' => The user hit the Enter key on the heuristic result at index 0. 'enterSelection' => The user chose a non-heuristic result and then hit the Enter key. 'click' => The user clicked a result with the mouse."
+    "description": "The input method the user used to select a result in the urlbar. 'enter' => The user hit the Enter key on the heuristic result at index 0. 'enterSelection' => The user chose a non-heuristic result (in exotic ways) and then hit the Enter key. 'click' => The user clicked a result with the mouse. 'arrowEnterSelection' => The user chose a non-heuristic result using arrow keys and then hit the Enter key. 'tabEnterSelection' => The user chose a non-heuristic result using tab at least once and then hit the Enter key."
   },
   "FX_SEARCHBAR_SELECTED_RESULT_METHOD": {
     "record_in_processes": ["main", "content"],
     "alert_emails": ["dzeber@mozilla.com"],
     "expires_in_version": "63",
     "releaseChannelCollection": "opt-out",
     "kind": "categorical",
     "labels": [
--- a/toolkit/components/viewsource/content/viewSource-content.js
+++ b/toolkit/components/viewsource/content/viewSource-content.js
@@ -32,22 +32,18 @@ var global = this;
 var ViewSourceContent = {
   /**
    * These are the messages that ViewSourceContent is prepared to listen
    * for. If you need ViewSourceContent to handle more messages, add them
    * here.
    */
   messages: [
     "ViewSource:LoadSource",
-    "ViewSource:LoadSourceDeprecated",
     "ViewSource:LoadSourceWithSelection",
     "ViewSource:GoToLine",
-    "ViewSource:ToggleWrapping",
-    "ViewSource:ToggleSyntaxHighlighting",
-    "ViewSource:SetCharacterSet",
   ],
 
   /**
    * When showing selection source, chrome will construct a page fragment to
    * show, and then instruct content to draw a selection after load.  This is
    * set true when there is a pending request to draw selection.
    */
   needsDrawSelection: false,
@@ -99,41 +95,27 @@ var ViewSourceContent = {
    * Anything added to the messages array will get handled here, and should
    * get dispatched to a specific function for the message name.
    */
   receiveMessage(msg) {
     if (!this.isViewSource && !this.isAboutBlank) {
       return;
     }
     let data = msg.data;
-    let objects = msg.objects;
     switch (msg.name) {
       case "ViewSource:LoadSource":
         this.viewSource(data.URL, data.outerWindowID, data.lineNumber,
                         data.shouldWrap);
         break;
-      case "ViewSource:LoadSourceDeprecated":
-        this.viewSourceDeprecated(data.URL, objects.pageDescriptor, data.lineNumber,
-                                  data.forcedCharSet);
-        break;
       case "ViewSource:LoadSourceWithSelection":
         this.viewSourceWithSelection(data.URL, data.drawSelection, data.baseURI);
         break;
       case "ViewSource:GoToLine":
         this.goToLine(data.lineNumber);
         break;
-      case "ViewSource:ToggleWrapping":
-        this.toggleWrapping();
-        break;
-      case "ViewSource:ToggleSyntaxHighlighting":
-        this.toggleSyntaxHighlighting();
-        break;
-      case "ViewSource:SetCharacterSet":
-        this.setCharacterSet(data.charset, data.doPageLoad);
-        break;
     }
   },
 
   /**
    * Any events should get handled here, and should get dispatched to
    * a specific function for the event type.
    */
   handleEvent(event) {
@@ -219,43 +201,16 @@ var ViewSourceContent = {
       forcedCharSet = utils.docCharsetIsForced ? doc.characterSet
                                                : null;
     }
 
     this.loadSource(URL, pageDescriptor, lineNumber, forcedCharSet);
   },
 
   /**
-   * Called when the parent is using the deprecated API for viewSource.xul.
-   * This function will throw if it's called on a remote browser.
-   *
-   * @param URL (required)
-   *        The URL string of the source to be shown.
-   * @param pageDescriptor (optional)
-   *        The currentDescriptor off of an nsIWebPageDescriptor, in the
-   *        event that the caller wants to try to load the source out of
-   *        the network cache.
-   * @param lineNumber (optional)
-   *        The line number to focus as soon as the source has finished
-   *        loading.
-   * @param forcedCharSet (optional)
-   *        The document character set to use instead of the default one.
-   */
-  viewSourceDeprecated(URL, pageDescriptor, lineNumber, forcedCharSet) {
-    // This should not be called if this frame script is running
-    // in a content process!
-    if (Services.appinfo.processType != Services.appinfo.PROCESS_TYPE_DEFAULT) {
-      throw new Error("ViewSource deprecated API should not be used with " +
-                      "remote browsers.");
-    }
-
-    this.loadSource(URL, pageDescriptor, lineNumber, forcedCharSet);
-  },
-
-  /**
    * Common utility function used by both the current and deprecated APIs
    * for loading source.
    *
    * @param URL (required)
    *        The URL string of the source to be shown.
    * @param pageDescriptor (optional)
    *        The currentDescriptor off of an nsIWebPageDescriptor, in the
    *        event that the caller wants to try to load the source out of
@@ -630,47 +585,16 @@ var ViewSourceContent = {
    */
   toggleSyntaxHighlighting() {
     let body = content.document.body;
     let state = body.classList.toggle("highlight");
     sendAsyncMessage("ViewSource:StoreSyntaxHighlighting", { state });
   },
 
   /**
-   * Called when the parent has changed the character set to view the
-   * source with.
-   *
-   * @param charset
-   *        The character set to use.
-   * @param doPageLoad
-   *        Whether or not we should reload the page ourselves with the
-   *        nsIWebPageDescriptor. Part of a workaround for bug 136322.
-   */
-  setCharacterSet(charset, doPageLoad) {
-    docShell.charset = charset;
-    if (doPageLoad) {
-      this.reload();
-    }
-  },
-
-  /**
-   * Reloads the content.
-   */
-  reload() {
-    let pageLoader = docShell.QueryInterface(Ci.nsIWebPageDescriptor);
-    try {
-      pageLoader.loadPage(pageLoader.currentDescriptor,
-                          Ci.nsIWebPageDescriptor.DISPLAY_NORMAL);
-    } catch (e) {
-      let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
-      webNav.reload(Ci.nsIWebNavigation.LOAD_FLAGS_NONE);
-    }
-  },
-
-  /**
    * Loads a view source selection showing the given view-source url and
    * highlight the selection.
    *
    * @param uri view-source uri to show
    * @param drawSelection true to highlight the selection
    * @param baseURI base URI of the original document
    */
   viewSourceWithSelection(uri, drawSelection, baseURI) {
--- a/toolkit/components/viewsource/content/viewSourceUtils.js
+++ b/toolkit/components/viewsource/content/viewSourceUtils.js
@@ -212,52 +212,37 @@ var gViewSourceUtils = {
           resolve(data);
         } else {
           // set up the progress listener with what we know so far
           this.viewSourceProgressListener.contentLoaded = false;
           this.viewSourceProgressListener.editor = editor;
           this.viewSourceProgressListener.resolve = resolve;
           this.viewSourceProgressListener.reject = reject;
           this.viewSourceProgressListener.data = data;
-          if (!data.pageDescriptor) {
-            // without a page descriptor, loadPage has no chance of working. download the file.
-            var file = this.getTemporaryFile(uri, data.doc, contentType);
-            this.viewSourceProgressListener.file = file;
 
-            var webBrowserPersist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
-              .createInstance(this.mnsIWebBrowserPersist);
-            // the default setting is to not decode. we need to decode.
-            webBrowserPersist.persistFlags = this.mnsIWebBrowserPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES;
-            webBrowserPersist.progressListener = this.viewSourceProgressListener;
-            let referrerPolicy = Ci.nsIHttpChannel.REFERRER_POLICY_NO_REFERRER;
-            webBrowserPersist.savePrivacyAwareURI(uri, null, null, referrerPolicy, null, null, file, data.isPrivate);
+          // without a page descriptor, loadPage has no chance of working. download the file.
+          var file = this.getTemporaryFile(uri, data.doc, contentType);
+          this.viewSourceProgressListener.file = file;
 
-            let helperService = Cc["@mozilla.org/uriloader/external-helper-app-service;1"]
-              .getService(Ci.nsPIExternalAppLauncher);
-            if (data.isPrivate) {
-              // register the file to be deleted when possible
-              helperService.deleteTemporaryPrivateFileWhenPossible(file);
-            } else {
-              // register the file to be deleted on app exit
-              helperService.deleteTemporaryFileOnExit(file);
-            }
+          var webBrowserPersist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
+            .createInstance(this.mnsIWebBrowserPersist);
+          // the default setting is to not decode. we need to decode.
+          webBrowserPersist.persistFlags = this.mnsIWebBrowserPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES;
+          webBrowserPersist.progressListener = this.viewSourceProgressListener;
+          let referrerPolicy = Ci.nsIHttpChannel.REFERRER_POLICY_NO_REFERRER;
+          webBrowserPersist.savePrivacyAwareURI(uri, null, null, referrerPolicy, null, null, file, data.isPrivate);
+
+          let helperService = Cc["@mozilla.org/uriloader/external-helper-app-service;1"]
+            .getService(Ci.nsPIExternalAppLauncher);
+          if (data.isPrivate) {
+            // register the file to be deleted when possible
+            helperService.deleteTemporaryPrivateFileWhenPossible(file);
           } else {
-            // we'll use nsIWebPageDescriptor to get the source because it may
-            // not have to refetch the file from the server
-            // XXXbz this is so broken...  This code doesn't set up this docshell
-            // at all correctly; if somehow the view-source stuff managed to
-            // execute script we'd be in big trouble here, I suspect.
-            var webShell = Cc["@mozilla.org/docshell;1"].createInstance();
-            webShell.QueryInterface(Ci.nsIBaseWindow).create();
-            this.viewSourceProgressListener.webShell = webShell;
-            var progress = webShell.QueryInterface(this.mnsIWebProgress);
-            progress.addProgressListener(this.viewSourceProgressListener,
-              this.mnsIWebProgress.NOTIFY_STATE_DOCUMENT);
-            var pageLoader = webShell.QueryInterface(this.mnsIWebPageDescriptor);
-            pageLoader.loadPage(data.pageDescriptor, this.mnsIWebPageDescriptor.DISPLAY_AS_SOURCE);
+            // register the file to be deleted on app exit
+            helperService.deleteTemporaryFileOnExit(file);
           }
         }
       } catch (ex) {
         // we failed loading it with the external editor.
         Cu.reportError(ex);
         reject(data);
       }
     });
--- a/tools/lint/eslint/modules.json
+++ b/tools/lint/eslint/modules.json
@@ -154,17 +154,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"],
-  "FormAutofillStorage.jsm": ["profileStorage"],
+  "FormAutofillStorage.jsm": ["formAutofillStorage"],
   "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"],