Merge autoland to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Wed, 06 Sep 2017 15:51:25 -0700
changeset 379190 d8e238b811d3
parent 379146 93dd2e456c0e (current diff)
parent 379189 7b74ef52385d (diff)
child 379268 023df6da913b
child 379410 df28182add1c
push id32451
push userkwierso@gmail.com
push dateWed, 06 Sep 2017 22:51:37 +0000
treeherdermozilla-central@d8e238b811d3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone57.0a1
first release with
nightly linux32
d8e238b811d3 / 57.0a1 / 20170907100318 / files
nightly linux64
d8e238b811d3 / 57.0a1 / 20170907100318 / files
nightly mac
d8e238b811d3 / 57.0a1 / 20170907100318 / files
nightly win32
d8e238b811d3 / 57.0a1 / 20170907100318 / files
nightly win64
d8e238b811d3 / 57.0a1 / 20170907100318 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge autoland to central, a=merge MozReview-Commit-ID: GQpNetIXsWP
browser/extensions/onboarding/test/browser/browser_onboarding_hide_all.js
media/mtransport/third_party/nICEr/src/util/mbslen.c
media/mtransport/third_party/nICEr/src/util/mbslen.h
taskcluster/ci/source-test/python-tests.yml
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1731,21 +1731,21 @@ pref("urlclassifier.downloadAllowTable",
 pref("urlclassifier.downloadBlockTable", "goog-badbinurl-proto");
 
 pref("browser.suppress_first_window_animation", true);
 
 // Preferences for Photon onboarding system extension
 pref("browser.onboarding.enabled", true);
 // Mark this as an upgraded profile so we don't offer the initial new user onboarding tour.
 pref("browser.onboarding.tourset-version", 2);
-pref("browser.onboarding.hidden", false);
 // On the Activity-Stream page, the snippet's position overlaps with our notification.
 // So use `browser.onboarding.notification.finished` to let the AS page know
 // if our notification is finished and safe to show their snippet.
 pref("browser.onboarding.notification.finished", false);
 pref("browser.onboarding.notification.mute-duration-on-first-session-ms", 300000); // 5 mins
 pref("browser.onboarding.notification.max-life-time-per-tour-ms", 432000000); // 5 days
+pref("browser.onboarding.notification.max-life-time-all-tours-ms", 1209600000); // 14 days
 pref("browser.onboarding.notification.max-prompt-count-per-tour", 8);
 pref("browser.onboarding.newtour", "performance,private,screenshots,addons,customize,default");
 pref("browser.onboarding.updatetour", "performance,library,screenshots,singlesearch,customize,sync");
 
 // Preference that allows individual users to disable Screenshots.
 pref("extensions.screenshots.disabled", false);
--- a/browser/base/content/moz.build
+++ b/browser/base/content/moz.build
@@ -90,17 +90,17 @@ with Files("test/tabcrashed/**"):
 
 with Files("test/tabs/**"):
     BUG_COMPONENT = ("Firefox", "Tabbed Browser")
 
 with Files("test/touch/**"):
     BUG_COMPONENT = ("Firefox", "General")
 
 with Files("test/urlbar/**"):
-    BUG_COMPONENT = ("Firefox", "Location Bar")
+    BUG_COMPONENT = ("Firefox", "Address Bar")
 
 with Files("test/webextensions/**"):
     BUG_COMPONENT = ("Toolkit", "WebExtensions: Untriaged")
 
 with Files("test/webrtc/**"):
     BUG_COMPONENT = ("Core", "WebRTC")
 
 with Files("aboutNetError.xhtml"):
--- a/browser/components/places/content/controller.js
+++ b/browser/components/places/content/controller.js
@@ -277,17 +277,17 @@ PlacesController.prototype = {
       break;
     case "placesCmd_new:separator":
       this.newSeparator().catch(Components.utils.reportError);
       break;
     case "placesCmd_show:info":
       this.showBookmarkPropertiesForSelection();
       break;
     case "placesCmd_moveBookmarks":
-      this.moveSelectedBookmarks();
+      this.moveSelectedBookmarks().catch(Components.utils.reportError);
       break;
     case "placesCmd_reload":
       this.reloadSelectedLivemark();
       break;
     case "placesCmd_sortBy:name":
       this.sortFolderByName().catch(Components.utils.reportError);
       break;
     case "placesCmd_createBookmark":
@@ -773,20 +773,48 @@ PlacesController.prototype = {
     let itemId = await PlacesUtils.promiseItemId(guid);
     // Select the new item.
     this._view.selectItems([itemId], false);
   },
 
   /**
    * Opens a dialog for moving the selected nodes.
    */
-  moveSelectedBookmarks: function PC_moveBookmarks() {
+  async moveSelectedBookmarks() {
+    let args = {
+      // The guid of the folder to move bookmarks to. This will only be
+      // set in the useAsyncTransactions case.
+      moveToGuid: null,
+      // nodes is passed to support !useAsyncTransactions.
+      nodes: this._view.selectedNodes,
+    };
     window.openDialog("chrome://browser/content/places/moveBookmarks.xul",
                       "", "chrome, modal",
-                      this._view.selectedNodes);
+                      args);
+
+    if (!args.moveToGuid) {
+      return;
+    }
+
+    let transactions = [];
+
+    for (let node of this._view.selectedNodes) {
+      // Nothing to do if the node is already under the selected folder.
+      if (node.parent.bookmarkGuid == args.moveToGuid) {
+        continue;
+      }
+      transactions.push(PlacesTransactions.Move({
+        guid: node.bookmarkGuid,
+        newParentGuid: args.moveToGuid,
+      }));
+    }
+
+    if (transactions.length) {
+      await PlacesTransactions.batch(transactions);
+    }
   },
 
   /**
    * Sort the selected folder by name
    */
   async sortFolderByName() {
     let itemId = PlacesUtils.getConcreteItemId(this._view.selectedNode);
     if (!PlacesUIUtils.useAsyncTransactions) {
--- a/browser/components/places/content/moveBookmarks.js
+++ b/browser/components/places/content/moveBookmarks.js
@@ -10,17 +10,17 @@ var gMoveBookmarksDialog = {
   get foldersTree() {
     if (!this._foldersTree)
       this._foldersTree = document.getElementById("foldersTree");
 
     return this._foldersTree;
   },
 
   init() {
-    this._nodes = window.arguments[0];
+    this._nodes = window.arguments[0].nodes;
 
     this.foldersTree.place =
       "place:excludeItems=1&excludeQueries=1&excludeReadOnlyFolders=1&folder=" +
       PlacesUIUtils.allBookmarksFolderId;
   },
 
   onOK: function MBD_onOK(aEvent) {
     let selectedNode = this.foldersTree.selectedNode;
@@ -40,26 +40,19 @@ var gMoveBookmarksDialog = {
       }
       if (transactions.length != 0) {
         let txn = new PlacesAggregatedTransaction("Move Items", transactions);
         PlacesUtils.transactionManager.doTransaction(txn);
       }
       return;
     }
 
-    PlacesTransactions.batch(async () => {
-      let newParentGuid = await PlacesUtils.promiseItemGuid(selectedFolderId);
-      for (let node of this._nodes) {
-        // Nothing to do if the node is already under the selected folder.
-        if (node.parent.itemId == selectedFolderId)
-          continue;
-        await PlacesTransactions.Move({ guid: node.bookmarkGuid,
-                                        newParentGuid }).transact();
-      }
-    }).catch(Components.utils.reportError);
+    // Async transactions must do the move in the caller to avoid going out of
+    // scope whilst the dialog is still closing.
+    window.arguments[0].moveToGuid = selectedNode.bookmarkGuid;
   },
 
   newFolder: function MBD_newFolder() {
     // The command is disabled when the tree is not focused
     this.foldersTree.focus();
     goDoCommand("placesCmd_new:folder");
   }
 };
--- a/browser/components/places/tests/browser/browser.ini
+++ b/browser/components/places/tests/browser/browser.ini
@@ -40,16 +40,17 @@ subsuite = clipboard
 [browser_library_commands.js]
 [browser_library_delete_tags.js]
 [browser_library_downloads.js]
 [browser_library_infoBox.js]
 [browser_library_left_pane_fixnames.js]
 [browser_library_left_pane_middleclick.js]
 [browser_library_left_pane_select_hierarchy.js]
 [browser_library_middleclick.js]
+[browser_library_move_bookmarks.js]
 [browser_library_open_leak.js]
 [browser_library_openFlatContainer.js]
 [browser_library_panel_leak.js]
 [browser_library_search.js]
 [browser_library_views_liveupdate.js]
 [browser_markPageAsFollowedLink.js]
 [browser_paste_into_tags.js]
 subsuite = clipboard
new file mode 100644
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_library_move_bookmarks.js
@@ -0,0 +1,79 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+/**
+ *  Test enabled commands in the left pane folder of the Library.
+ */
+
+registerCleanupFunction(async function() {
+  await PlacesUtils.bookmarks.eraseEverything();
+  await PlacesTestUtils.clearHistory();
+});
+
+add_task(async function test_moveBookmarks() {
+  let children = [{
+    title: "TestFolder",
+    type: PlacesUtils.bookmarks.TYPE_FOLDER,
+  }];
+
+  for (let i = 0; i < 10; i++) {
+    children.push({
+      title: `test${i}`,
+      url: `http://example.com/${i}`,
+    });
+  }
+
+  let bookmarks = await PlacesUtils.bookmarks.insertTree({
+    guid: PlacesUtils.bookmarks.unfiledGuid,
+    children
+  });
+
+  let folderId = await PlacesUtils.promiseItemId(bookmarks[0].guid);
+
+  let itemIds = [];
+  let promiseMoveNotifications = [];
+  for (let i = 0; i < 10; i++) {
+    // + 1 due to the folder being inserted first.
+    let guid = bookmarks[i + 1].guid;
+
+    itemIds.push(await PlacesUtils.promiseItemId(guid));
+    promiseMoveNotifications.push(PlacesTestUtils.waitForNotification(
+      "onItemMoved",
+      (itemId, parentId, oldIndex, newParentId, newIndex, itemType, itemGuid,
+       oldParentGuid, newParentGuid) =>
+        itemGuid == guid && newParentGuid == bookmarks[0].guid
+    ));
+  }
+
+  let library = await promiseLibrary("UnfiledBookmarks");
+
+  library.ContentTree.view.selectItems(itemIds);
+
+  await withBookmarksDialog(
+    false,
+    () => {
+      library.ContentTree.view._controller.doCommand("placesCmd_moveBookmarks");
+    },
+    async (dialogWin) => {
+      let tree = dialogWin.document.getElementById("foldersTree");
+
+      tree.selectItems([PlacesUtils.unfiledBookmarksFolderId], true);
+
+      tree.selectItems([folderId], true);
+
+      dialogWin.document.documentElement.acceptDialog();
+
+      info("Waiting for notifications of moves");
+      await Promise.all(promiseMoveNotifications);
+      Assert.ok(true, "should have completed all moves successfully");
+    },
+    null,
+    "chrome://browser/content/places/moveBookmarks.xul",
+    true
+  );
+
+  library.close();
+});
--- a/browser/components/places/tests/browser/head.js
+++ b/browser/components/places/tests/browser/head.js
@@ -272,24 +272,26 @@ function isToolbarVisible(aToolbar) {
  * @param openFn
  *        generator function causing the dialog to open
  * @param taskFn
  *        the task to execute once the dialog is open
  * @param closeFn
  *        A function to be used to wait for pending work when the dialog is
  *        closing. It is passed the dialog window handle and should return a promise.
  */
-var withBookmarksDialog = async function(autoCancel, openFn, taskFn, closeFn) {
+var withBookmarksDialog = async function(autoCancel, openFn, taskFn, closeFn,
+                                         dialogUrl = "chrome://browser/content/places/bookmarkProperties",
+                                         skipOverlayWait = false) {
   let closed = false;
   let dialogPromise = new Promise(resolve => {
     Services.ww.registerNotification(function winObserver(subject, topic, data) {
       if (topic == "domwindowopened") {
         let win = subject.QueryInterface(Ci.nsIDOMWindow);
         win.addEventListener("load", function() {
-          ok(win.location.href.startsWith("chrome://browser/content/places/bookmarkProperties"),
+          ok(win.location.href.startsWith(dialogUrl),
              "The bookmark properties dialog is open");
           // This is needed for the overlay.
           waitForFocus(() => {
             resolve(win);
           }, win);
         }, {once: true});
       } else if (topic == "domwindowclosed") {
         Services.ww.unregisterNotification(winObserver);
@@ -301,19 +303,21 @@ var withBookmarksDialog = async function
   info("withBookmarksDialog: opening the dialog");
   // The dialog might be modal and could block our events loop, so executeSoon.
   executeSoon(openFn);
 
   info("withBookmarksDialog: waiting for the dialog");
   let dialogWin = await dialogPromise;
 
   // Ensure overlay is loaded
-  info("waiting for the overlay to be loaded");
-  await waitForCondition(() => dialogWin.gEditItemOverlay.initialized,
-                         "EditItemOverlay should be initialized");
+  if (!skipOverlayWait) {
+    info("waiting for the overlay to be loaded");
+    await waitForCondition(() => dialogWin.gEditItemOverlay.initialized,
+                           "EditItemOverlay should be initialized");
+  }
 
   // Check the first textbox is focused.
   let doc = dialogWin.document;
   let elt = doc.querySelector("textbox:not([collapsed=true])");
   if (elt) {
     info("waiting for focus on the first textfield");
     await waitForCondition(() => doc.activeElement == elt.inputField,
                            "The first non collapsed textbox should have been focused");
@@ -324,20 +328,23 @@ var withBookmarksDialog = async function
   let closePromise = () => Promise.resolve();
   if (closeFn) {
     closePromise = closeFn(dialogWin);
   }
 
   try {
     await taskFn(dialogWin);
   } finally {
+    if (!closed && !autoCancel) {
+      // Give the dialog a little time to close itself in the manually closing
+      // case.
+      await BrowserTestUtils.waitForCondition(() => closed,
+        "The test should have closed the dialog!");
+    }
     if (!closed) {
-      if (!autoCancel) {
-        ok(false, "The test should have closed the dialog!");
-      }
       info("withBookmarksDialog: canceling the dialog");
 
       doc.documentElement.cancelDialog();
 
       await closePromise;
     }
   }
 };
--- a/browser/components/syncedtabs/sidebar.xhtml
+++ b/browser/components/syncedtabs/sidebar.xhtml
@@ -90,17 +90,17 @@
         </div>
       </div>
     </template>
 
     <div class="content-container">
       <!-- the non-scrollable header -->
       <div class="content-header">
         <div class="sidebar-search-container tabs-container sync-state">
-          <div class="search-box compact">
+          <div class="search-box">
             <div class="textbox-input-box">
               <input type="text" class="tabsFilter textbox-input" tabindex="1"/>
               <div class="textbox-search-icons">
                 <a class="textbox-search-clear"></a>
                 <a class="textbox-search-icon"></a>
               </div>
             </div>
           </div>
--- a/browser/extensions/formautofill/FormAutofillHandler.jsm
+++ b/browser/extensions/formautofill/FormAutofillHandler.jsm
@@ -554,16 +554,18 @@ FormAutofillHandler.prototype = {
         data[type].record[detail.fieldName] = value;
 
         if (detail.state == "AUTO_FILLED") {
           data[type].untouchedFields.push(detail.fieldName);
         }
       });
     });
 
+    this.normalizeAddress(data.address);
+
     if (data.address &&
         Object.values(data.address.record).filter(v => v).length <
         FormAutofillUtils.AUTOFILL_FIELDS_THRESHOLD) {
       log.debug("No address record saving since there are only",
                      Object.keys(data.address.record).length,
                      "usable fields");
       delete data.address;
     }
@@ -572,16 +574,46 @@ FormAutofillHandler.prototype = {
         !FormAutofillUtils.isCCNumber(data.creditCard.record["cc-number"]))) {
       log.debug("No credit card record saving since card number is invalid");
       delete data.creditCard;
     }
 
     return data;
   },
 
+  normalizeAddress(address) {
+    if (!address) {
+      return;
+    }
+
+    // Normalize Tel
+    FormAutofillUtils.compressTel(address.record);
+    if (address.record.tel) {
+      let allTelComponentsAreUntouched = Object.keys(address.record)
+        .filter(field => FormAutofillUtils.getCategoryFromFieldName(field) == "tel")
+        .every(field => address.untouchedFields.includes(field));
+      if (allTelComponentsAreUntouched) {
+        // No need to verify it if none of related fields are modified after autofilling.
+        if (!address.untouchedFields.includes("tel")) {
+          address.untouchedFields.push("tel");
+        }
+      } else {
+        let strippedNumber = address.record.tel.replace(/[\s\(\)-]/g, "");
+
+        // Remove "tel" if it contains invalid characters or the length of its
+        // number part isn't between 5 and 15.
+        // (The maximum length of a valid number in E.164 format is 15 digits
+        //  according to https://en.wikipedia.org/wiki/E.164 )
+        if (!/^(\+?)[\da-zA-Z]{5,15}$/.test(strippedNumber)) {
+          address.record.tel = "";
+        }
+      }
+    }
+  },
+
   async _decrypt(cipherText, reauth) {
     return new Promise((resolve) => {
       Services.cpmm.addMessageListener("FormAutofill:DecryptedString", function getResult(result) {
         Services.cpmm.removeMessageListener("FormAutofill:DecryptedString", getResult);
         resolve(result.data);
       });
 
       Services.cpmm.sendAsyncMessage("FormAutofill:GetDecryptedString", {cipherText, reauth});
--- a/browser/extensions/formautofill/FormAutofillUtils.jsm
+++ b/browser/extensions/formautofill/FormAutofillUtils.jsm
@@ -113,16 +113,42 @@ this.FormAutofillUtils = {
       return "";
     }
     return array
       .map(s => s ? s.trim() : "")
       .filter(s => s)
       .join(this.getAddressSeparator());
   },
 
+  /**
+   * In-place concatenate tel-related components into a single "tel" field and
+   * delete unnecessary fields.
+   * @param {object} address An address record.
+   */
+  compressTel(address) {
+    let telCountryCode = address["tel-country-code"] || "";
+    let telAreaCode = address["tel-area-code"] || "";
+
+    if (!address.tel) {
+      if (address["tel-national"]) {
+        address.tel = telCountryCode + address["tel-national"];
+      } else if (address["tel-local"]) {
+        address.tel = telCountryCode + telAreaCode + address["tel-local"];
+      } else if (address["tel-local-prefix"] && address["tel-local-suffix"]) {
+        address.tel = telCountryCode + telAreaCode + address["tel-local-prefix"] + address["tel-local-suffix"];
+      }
+    }
+
+    for (let field in address) {
+      if (field != "tel" && this.getCategoryFromFieldName(field) == "tel") {
+        delete address[field];
+      }
+    }
+  },
+
   fmtMaskedCreditCardLabel(maskedCCNum = "") {
     return {
       affix: "****",
       label: maskedCCNum.replace(/^\**/, ""),
     };
   },
 
   defineLazyLogGetter(scope, logPrefix) {
--- a/browser/extensions/formautofill/ProfileStorage.jsm
+++ b/browser/extensions/formautofill/ProfileStorage.jsm
@@ -1304,36 +1304,24 @@ class Addresses extends AutofillRecords 
     delete address["country-name"];
   }
 
   _normalizeTel(address) {
     if (!address.tel && TEL_COMPONENTS.every(c => !address[c])) {
       return;
     }
 
-    let region = address["tel-country-code"] || address.country || FormAutofillUtils.DEFAULT_COUNTRY_CODE;
-    let number;
+    FormAutofillUtils.compressTel(address);
 
-    if (address.tel) {
-      number = address.tel;
-    } else if (address["tel-national"]) {
-      number = address["tel-national"];
-    } else if (address["tel-local"]) {
-      number = (address["tel-area-code"] || "") + address["tel-local"];
-    } else if (address["tel-local-prefix"] && address["tel-local-suffix"]) {
-      number = (address["tel-area-code"] || "") + address["tel-local-prefix"] + address["tel-local-suffix"];
-    }
+    let possibleRegion = address.country || FormAutofillUtils.DEFAULT_COUNTRY_CODE;
+    let tel = PhoneNumber.Parse(address.tel, possibleRegion);
 
-    let tel = PhoneNumber.Parse(number, region);
     if (tel && tel.internationalNumber) {
       // Force to save numbers in E.164 format if parse success.
       address.tel = tel.internationalNumber;
-    } else if (!address.tel) {
-      // Save the original number anyway if "tel" is omitted.
-      address.tel = number;
     }
 
     TEL_COMPONENTS.forEach(c => delete address[c]);
   }
 
   /**
    * Merge new address into the specified address if mergeable.
    *
--- a/browser/extensions/formautofill/skin/shared/autocomplete-item.css
+++ b/browser/extensions/formautofill/skin/shared/autocomplete-item.css
@@ -15,17 +15,17 @@ xul|richlistitem[originaltype="autofill-
 }
 
 xul|richlistitem[originaltype="autofill-insecureWarning"] {
   border-bottom: 1px solid var(--panel-separator-color);
   background-color: var(--arrowpanel-dimmed);
 }
 
 .autofill-item-box {
-  --item-padding-vertical: 6px;
+  --item-padding-vertical: 7px;
   --item-padding-horizontal: 10px;
   --col-spacer: 7px;
   --item-width: calc(50% - (var(--col-spacer) / 2));
   --label-text-color: #262626;
   --comment-text-color: #646464;
   --warning-text-color: #646464;
   --option-btn-text-color: -moz-FieldText;
 
@@ -40,16 +40,17 @@ xul|richlistitem[originaltype="autofill-
 .autofill-item-box[size="small"] {
   --item-padding-vertical: 7px;
   --col-spacer: 0px;
   --row-spacer: 3px;
   --item-width: 100%;
 }
 
 .autofill-item-box:not([ac-image=""]) {
+  --item-padding-vertical: 6.5px;
   --comment-font-size: 11;
 }
 
 .autofill-footer,
 .autofill-footer[size="small"] {
   --item-width: 100%;
   --item-padding-vertical: 0;
   --item-padding-horizontal: 0;
@@ -122,16 +123,17 @@ xul|richlistitem[originaltype="autofill-
 }
 
 .autofill-item-box[size="small"] > .profile-comment-col {
   margin-top: var(--row-spacer);
   text-align: start;
 }
 
 .autofill-footer {
+  padding: 0;
   flex-direction: column;
 }
 
 .autofill-footer > .autofill-footer-row {
   display: flex;
   justify-content: center;
   align-items: center;
   width: var(--item-width);
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/test/unit/test_createRecords.js
@@ -0,0 +1,143 @@
+/*
+ * Test for the normalization of records created by FormAutofillHandler.
+ */
+
+"use strict";
+
+Cu.import("resource://formautofill/FormAutofillHandler.jsm");
+
+const TESTCASES = [
+  {
+    description: "Don't create address record if filled data is less than 3",
+    document: `<form>
+                <input id="given-name" autocomplete="given-name">
+                <input id="family-name" autocomplete="family-name">
+                <input id="country" autocomplete="country">
+               </form>`,
+    formValue: {
+      "given-name": "John",
+      "family-name": "Doe",
+    },
+    expectedRecord: {
+      address: undefined,
+    },
+  },
+  {
+    description: "\"tel\" related fields should be concatenated",
+    document: `<form>
+                <input id="given-name" autocomplete="given-name">
+                <input id="family-name" autocomplete="family-name">
+                <input id="tel-country-code" autocomplete="tel-country-code">
+                <input id="tel-national" autocomplete="tel-national">
+               </form>`,
+    formValue: {
+      "given-name": "John",
+      "family-name": "Doe",
+      "tel-country-code": "+1",
+      "tel-national": "1234567890",
+    },
+    expectedRecord: {
+      address: {
+        "given-name": "John",
+        "family-name": "Doe",
+        "tel": "+11234567890",
+      },
+    },
+  },
+  {
+    description: "\"tel\" should be removed if it's too short",
+    document: `<form>
+                <input id="given-name" autocomplete="given-name">
+                <input id="family-name" autocomplete="family-name">
+                <input id="organization" autocomplete="organization">
+                <input id="tel" autocomplete="tel-national">
+               </form>`,
+    formValue: {
+      "given-name": "John",
+      "family-name": "Doe",
+      "organization": "Mozilla",
+      "tel": "1234",
+    },
+    expectedRecord: {
+      address: {
+        "given-name": "John",
+        "family-name": "Doe",
+        "organization": "Mozilla",
+        "tel": "",
+      },
+    },
+  },
+  {
+    description: "\"tel\" should be removed if it's too long",
+    document: `<form>
+                <input id="given-name" autocomplete="given-name">
+                <input id="family-name" autocomplete="family-name">
+                <input id="organization" autocomplete="organization">
+                <input id="tel" autocomplete="tel-national">
+               </form>`,
+    formValue: {
+      "given-name": "John",
+      "family-name": "Doe",
+      "organization": "Mozilla",
+      "tel": "1234567890123456",
+    },
+    expectedRecord: {
+      address: {
+        "given-name": "John",
+        "family-name": "Doe",
+        "organization": "Mozilla",
+        "tel": "",
+      },
+    },
+  },
+  {
+    description: "\"tel\" should be removed if it contains invalid characters",
+    document: `<form>
+                <input id="given-name" autocomplete="given-name">
+                <input id="family-name" autocomplete="family-name">
+                <input id="organization" autocomplete="organization">
+                <input id="tel" autocomplete="tel-national">
+               </form>`,
+    formValue: {
+      "given-name": "John",
+      "family-name": "Doe",
+      "organization": "Mozilla",
+      "tel": "12345###!!!",
+    },
+    expectedRecord: {
+      address: {
+        "given-name": "John",
+        "family-name": "Doe",
+        "organization": "Mozilla",
+        "tel": "",
+      },
+    },
+  },
+];
+
+for (let testcase of TESTCASES) {
+  add_task(async function() {
+    do_print("Starting testcase: " + testcase.description);
+
+    let doc = MockDocument.createTestDocument("http://localhost:8080/test/", testcase.document);
+    let form = doc.querySelector("form");
+    let formLike = FormLikeFactory.createFromForm(form);
+    let handler = new FormAutofillHandler(formLike);
+
+    handler.collectFormFields();
+
+    for (let id in testcase.formValue) {
+      doc.getElementById(id).value = testcase.formValue[id];
+    }
+
+    let record = handler.createRecords();
+
+    for (let type in testcase.expectedRecord) {
+      if (!testcase.expectedRecord[type]) {
+        do_check_eq(record[type], undefined);
+      } else {
+        Assert.deepEqual(record[type].record, testcase.expectedRecord[type]);
+      }
+    }
+  });
+}
--- a/browser/extensions/formautofill/test/unit/test_onFormSubmitted.js
+++ b/browser/extensions/formautofill/test/unit/test_onFormSubmitted.js
@@ -388,17 +388,94 @@ const TESTCASES = [
             "tel": "1-650-903-0800",
             "email": "",
           },
           untouchedFields: [],
         },
       },
     },
   },
-
+  {
+    description: "Shouldn't save tel whose length is too short",
+    formValue: {
+      "street-addr": "331 E. Evelyn Avenue",
+      "address-level1": "CA",
+      "country": "US",
+      "tel": "1234",
+    },
+    expectedResult: {
+      formSubmission: true,
+      records: {
+        address: {
+          guid: null,
+          record: {
+            "street-address": "331 E. Evelyn Avenue",
+            "address-level1": "CA",
+            "address-level2": "",
+            "country": "US",
+            "tel": "",
+            "email": "",
+          },
+          untouchedFields: [],
+        },
+      },
+    },
+  },
+  {
+    description: "Shouldn't save tel whose length is too long",
+    formValue: {
+      "street-addr": "331 E. Evelyn Avenue",
+      "address-level1": "CA",
+      "country": "US",
+      "tel": "1234567890123456",
+    },
+    expectedResult: {
+      formSubmission: true,
+      records: {
+        address: {
+          guid: null,
+          record: {
+            "street-address": "331 E. Evelyn Avenue",
+            "address-level1": "CA",
+            "address-level2": "",
+            "country": "US",
+            "tel": "",
+            "email": "",
+          },
+          untouchedFields: [],
+        },
+      },
+    },
+  },
+  {
+    description: "Shouldn't save tel which contains invalid characters",
+    formValue: {
+      "street-addr": "331 E. Evelyn Avenue",
+      "address-level1": "CA",
+      "country": "US",
+      "tel": "12345###!!",
+    },
+    expectedResult: {
+      formSubmission: true,
+      records: {
+        address: {
+          guid: null,
+          record: {
+            "street-address": "331 E. Evelyn Avenue",
+            "address-level1": "CA",
+            "address-level2": "",
+            "country": "US",
+            "tel": "",
+            "email": "",
+          },
+          untouchedFields: [],
+        },
+      },
+    },
+  },
 ];
 
 add_task(async function handle_earlyformsubmit_event() {
   do_print("Starting testcase: Test an invalid form element");
   let fakeForm = MOCK_DOC.createElement("form");
   sinon.spy(FormAutofillContent, "_onFormSubmit");
 
   do_check_eq(FormAutofillContent.notify(fakeForm), true);
--- a/browser/extensions/formautofill/test/unit/xpcshell.ini
+++ b/browser/extensions/formautofill/test/unit/xpcshell.ini
@@ -15,16 +15,17 @@ support-files =
 [heuristics/third_party/test_QVC.js]
 [heuristics/third_party/test_Sears.js]
 [heuristics/third_party/test_Staples.js]
 [heuristics/third_party/test_Walmart.js]
 [test_activeStatus.js]
 [test_addressRecords.js]
 [test_autofillFormFields.js]
 [test_collectFormFields.js]
+[test_createRecords.js]
 [test_creditCardRecords.js]
 [test_extractLabelStrings.js]
 [test_findLabelElements.js]
 [test_getAdaptedProfiles.js]
 [test_getCategoriesFromFieldNames.js]
 [test_getFormInputDetails.js]
 [test_getInfo.js]
 [test_getRecords.js]
--- a/browser/extensions/onboarding/OnboardingTourType.jsm
+++ b/browser/extensions/onboarding/OnboardingTourType.jsm
@@ -27,17 +27,16 @@ var OnboardingTourType = {
     const TOURSET_VERSION = Services.prefs.getIntPref("browser.onboarding.tourset-version");
 
     if (!Services.prefs.prefHasUserValue(PREF_SEEN_TOURSET_VERSION)) {
       // User has never seen an onboarding tour, present the user with the new user tour.
       Services.prefs.setStringPref(PREF_TOUR_TYPE, "new");
     } else if (Services.prefs.getIntPref(PREF_SEEN_TOURSET_VERSION) < TOURSET_VERSION) {
       // show the update user tour when tour set version is larger than the seen tourset version
       Services.prefs.setStringPref(PREF_TOUR_TYPE, "update");
-      Services.prefs.setBoolPref("browser.onboarding.hidden", false);
       // Reset all the notification-related prefs because tours update.
       Services.prefs.setBoolPref("browser.onboarding.notification.finished", false);
       Services.prefs.clearUserPref("browser.onboarding.notification.prompt-count");
       Services.prefs.clearUserPref("browser.onboarding.notification.last-time-of-changing-tour-sec");
       Services.prefs.clearUserPref("browser.onboarding.notification.tour-ids-queue");
     }
     Services.prefs.setIntPref(PREF_SEEN_TOURSET_VERSION, TOURSET_VERSION);
   },
--- a/browser/extensions/onboarding/README.md
+++ b/browser/extensions/onboarding/README.md
@@ -3,17 +3,16 @@
 System addon to provide the onboarding overlay for user-friendly tours.
 
 ## How to show the onboarding tour
 
 Open `about:config` page and filter with `onboarding` keyword. Then set following preferences:
 
 ```
 browser.onboarding.disabled = false
-browser.onboarding.hidden = false
 browser.onboarding.tour-set = "new" // for new user tour, or "update" for update user tour
 ```
 And make sure the value of `browser.onboarding.tourset-verion` and `browser.onboarding.seen-tourset-verion` are the same.
 
 ## How to show the onboarding notification
 
 Besides above settings, notification will wait 5 minutes before showing the first notification on a new profile or the updated user profile (to not put too much information to the user at once).
 
--- a/browser/extensions/onboarding/bootstrap.js
+++ b/browser/extensions/onboarding/bootstrap.js
@@ -15,17 +15,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   "resource://gre/modules/FxAccounts.jsm");
 
 const {PREF_STRING, PREF_BOOL, PREF_INT} = Ci.nsIPrefBranch;
 
 const BROWSER_READY_NOTIFICATION = "browser-delayed-startup-finished";
 const BROWSER_SESSION_STORE_NOTIFICATION = "sessionstore-windows-restored";
 const PREF_WHITELIST = [
   ["browser.onboarding.enabled", PREF_BOOL],
-  ["browser.onboarding.hidden", PREF_BOOL],
   ["browser.onboarding.notification.finished", PREF_BOOL],
   ["browser.onboarding.notification.prompt-count", PREF_INT],
   ["browser.onboarding.notification.last-time-of-changing-tour-sec", PREF_INT],
   ["browser.onboarding.notification.tour-ids-queue", PREF_STRING],
 ];
 
 [
   "onboarding-tour-addons",
@@ -43,17 +42,17 @@ let waitingForBrowserReady = true;
 
 /**
  * Set pref. Why no `getPrefs` function is due to the priviledge level.
  * We cannot set prefs inside a framescript but can read.
  * For simplicity and effeciency, we still read prefs inside the framescript.
  *
  * @param {Array} prefs the array of prefs to set.
  *   The array element carrys info to set pref, should contain
- *   - {String} name the pref name, such as `browser.onboarding.hidden`
+ *   - {String} name the pref name
  *   - {*} value the value to set
  **/
 function setPrefs(prefs) {
   prefs.forEach(pref => {
     let prefObj = PREF_WHITELIST.find(([name, ]) => name == pref.name);
     if (!prefObj) {
       return;
     }
--- a/browser/extensions/onboarding/content/onboarding.css
+++ b/browser/extensions/onboarding/content/onboarding.css
@@ -85,16 +85,20 @@
   min-width: 100px;
   max-width: 140px;
   white-space: pre-line;
   margin-inline-start: 4px;
   margin-top: -10px;
   box-shadow: -2px 0 5px 0 rgba(74, 74, 79, 0.25);
 }
 
+#onboarding-overlay-button:dir(rtl)::after {
+  box-shadow: 2px 0 5px 0 rgba(74, 74, 79, 0.25);
+}
+
 #onboarding-overlay-dialog,
 .onboarding-hidden,
 #onboarding-tour-sync-page[data-login-state=logged-in] .show-on-logged-out,
 #onboarding-tour-sync-page[data-login-state=logged-out] .show-on-logged-in {
   display: none;
 }
 
 .onboarding-close-btn {
@@ -156,24 +160,23 @@
   margin-top: 40px;
   margin-bottom: 0;
   margin-inline-end: 0;
   margin-inline-start: 0;
   padding: 0;
 }
 
 #onboarding-overlay-dialog > footer {
-  grid-row: footer-start;
   grid-column: dialog-start / tour-end;
   font-size: 13px;
 }
 
-#onboarding-tour-hidden-checkbox {
+#onboarding-skip-tour-button {
   margin-inline-start: 27px;
-  margin-inline-end: 10px;
+  margin-bottom: 27px;
 }
 
 /* Onboarding tour list */
 #onboarding-tour-list {
   margin: 40px 0 0 0;
   padding: 0;
   margin-inline-start: 16px;
 }
@@ -315,16 +318,18 @@
 }
 
 .onboarding-tour-page.onboarding-no-button > .onboarding-tour-content {
   grid-row: tour-page-start / tour-page-end;
   grid-column: tour-content-start / tour-page-end;
 }
 
 .onboarding-tour-button-container {
+  /* Get higher z-index in order to ensure buttons within container are selectable */
+  z-index: 2;
   grid-row: tour-button-start / tour-page-end;
   grid-column: tour-content-start / tour-page-end;
 }
 
 .onboarding-tour-page.onboarding-no-button > .onboarding-tour-button-container {
   display: none;
   grid-row: tour-page-end;
   grid-column: tour-page-end;
@@ -341,24 +346,24 @@
   line-height: 16px;
   color: #fff;
   float: inline-end;
   margin-inline-end: 26px;
   margin-top: -32px;
 }
 
 /* Remove default dotted outline around buttons' text */
-.onboarding-tour-action-button::-moz-focus-inner,
+#onboarding-overlay button::-moz-focus-inner,
 #onboarding-overlay-button::-moz-focus-inner {
   border: 0;
 }
 
 /* Keyboard focus specific outline */
-.onboarding-tour-action-button:-moz-focusring,
-#onboarding-notification-action-btn:-moz-focusring,
+#onboarding-overlay button:-moz-focusring,
+.onboarding-action-button:-moz-focusring,
 #onboarding-tour-list .onboarding-tour-item:focus {
   outline: 2px solid rgba(0,149,221,0.5);
   outline-offset: 1px;
   -moz-outline-radius: 2px;
 }
 
 .onboarding-tour-action-button:hover:not([disabled])  {
   background: #003eaa;
@@ -547,35 +552,35 @@ a#onboarding-tour-screenshots-button:vis
 #onboarding-notification-tour-icon {
   min-width: 64px;
   height: 64px;
   background-size: 64px;
   background-repeat: no-repeat;
   background-image: url("chrome://branding/content/icon64.png");
 }
 
-#onboarding-notification-action-btn {
+.onboarding-action-button {
   background: #fbfbfb;
   /* With 1px border, could see a border in the high-constrast mode */
   border: 1px solid #c1c1c1;
   border-radius: 2px;
   padding: 10px 20px;
   font-size: 14px;
   font-weight: 600;
   line-height: 16px;
   color: #202340;
   min-width: 130px;
 }
 
-#onboarding-notification-action-btn:hover {
+.onboarding-action-button:hover {
   background-color: #ebebeb;
   cursor: pointer;
 }
 
-#onboarding-notification-action-btn:active {
+.onboarding-action-button:active {
   background-color: #dadada;
 }
 
 @media (min-resolution: 2dppx) {
   #onboarding-notification-tour-icon {
     background-image: url("chrome://branding/content/icon128.png");
   }
 }
--- a/browser/extensions/onboarding/content/onboarding.js
+++ b/browser/extensions/onboarding/content/onboarding.js
@@ -447,21 +447,16 @@ class Onboarding {
   }
 
   _initPrefObserver() {
     if (this._prefsObserved) {
       return;
     }
 
     this._prefsObserved = new Map();
-    this._prefsObserved.set("browser.onboarding.hidden", prefValue => {
-      if (prefValue) {
-        this.destroy();
-      }
-    });
     this._tours.forEach(tour => {
       let tourId = tour.id;
       this._prefsObserved.set(`browser.onboarding.tour.${tourId}.completed`, () => {
         this.markTourCompletionState(tourId);
       });
     });
     for (let [name, callback] of this._prefsObserved) {
       Services.prefs.addObserver(name, callback);
@@ -494,16 +489,21 @@ class Onboarding {
       ({ id, classList } = target.firstChild);
     }
 
     switch (id) {
       case "onboarding-overlay-button":
         this.showOverlay();
         this.gotoPage(this._firstUncompleteTour.id);
         break;
+      case "onboarding-skip-tour-button":
+        this.hideNotification();
+        this.hideOverlay();
+        this.skipTour();
+        break;
       case "onboarding-overlay-close-btn":
       // If the clicking target is directly on the outer-most overlay,
       // that means clicking outside the tour content area.
       // Let's toggle the overlay.
       case "onboarding-overlay":
         this.hideOverlay();
         break;
       case "onboarding-notification-close-btn":
@@ -661,20 +661,16 @@ class Onboarding {
       this._loadTours(this._tours);
     }
 
     this.hideNotification();
     this.toggleModal(this._overlay.classList.toggle("onboarding-opened"));
   }
 
   hideOverlay() {
-    let hiddenCheckbox = this._window.document.getElementById("onboarding-tour-hidden-checkbox");
-    if (hiddenCheckbox.checked) {
-      this.hide();
-    }
     this.toggleModal(this._overlay.classList.toggle("onboarding-opened"));
   }
 
   /**
    * Set modal dialog state and properties for accessibility purposes.
    * @param  {Boolean} opened  whether the dialog is opened or closed.
    */
   toggleModal(opened) {
@@ -807,45 +803,45 @@ class Onboarding {
     // When this is set to 0 on purpose, always judge as not the 1st session
     if (Services.prefs.getIntPref("browser.onboarding.notification.mute-duration-on-first-session-ms") === 0) {
       this._firstSession = false;
     }
 
     return this._firstSession;
   }
 
-  _muteNotificationOnFirstSession() {
+  _getLastTourChangeTime() {
+    return 1000 * Services.prefs.getIntPref("browser.onboarding.notification.last-time-of-changing-tour-sec", 0);
+  }
+
+  _muteNotificationOnFirstSession(lastTourChangeTime) {
     if (!this._isFirstSession) {
       return false;
     }
 
-    // Reuse the `last-time-of-changing-tour-sec` to save the time that
-    // we try to prompt on the 1st session.
-    let lastTime = 1000 * Services.prefs.getIntPref("browser.onboarding.notification.last-time-of-changing-tour-sec", 0);
-    if (lastTime <= 0) {
+    if (lastTourChangeTime <= 0) {
       sendMessageToChrome("set-prefs", [{
         name: "browser.onboarding.notification.last-time-of-changing-tour-sec",
         value: Math.floor(Date.now() / 1000)
       }]);
       return true;
     }
     let muteDuration = Services.prefs.getIntPref("browser.onboarding.notification.mute-duration-on-first-session-ms");
-    return Date.now() - lastTime <= muteDuration;
+    return Date.now() - lastTourChangeTime <= muteDuration;
   }
 
-  _isTimeForNextTourNotification() {
+  _isTimeForNextTourNotification(lastTourChangeTime) {
     let promptCount = Services.prefs.getIntPref("browser.onboarding.notification.prompt-count", 0);
     let maxCount = Services.prefs.getIntPref("browser.onboarding.notification.max-prompt-count-per-tour");
     if (promptCount >= maxCount) {
       return true;
     }
 
-    let lastTime = 1000 * Services.prefs.getIntPref("browser.onboarding.notification.last-time-of-changing-tour-sec", 0);
     let maxTime = Services.prefs.getIntPref("browser.onboarding.notification.max-life-time-per-tour-ms");
-    if (lastTime && Date.now() - lastTime >= maxTime) {
+    if (lastTourChangeTime && Date.now() - lastTourChangeTime >= maxTime) {
       return true;
     }
 
     return false;
   }
 
   _removeTourFromNotificationQueue(tourId) {
     let params = [];
@@ -887,27 +883,35 @@ class Onboarding {
     return queue ? queue.split(",") : [];
   }
 
   showNotification() {
     if (Services.prefs.getBoolPref("browser.onboarding.notification.finished", false)) {
       return;
     }
 
-    if (this._muteNotificationOnFirstSession()) {
+    let lastTime = this._getLastTourChangeTime();
+    if (this._muteNotificationOnFirstSession(lastTime)) {
       return;
     }
     // After the notification mute on the 1st session,
     // we don't want to show the speech bubble by default
     this._overlayIcon.classList.remove("onboarding-speech-bubble");
 
     let queue = this._getNotificationQueue();
+    let totalMaxTime = Services.prefs.getIntPref("browser.onboarding.notification.max-life-time-all-tours-ms");
+    if (lastTime && Date.now() - lastTime >= totalMaxTime) {
+      // Reach total max life time for all tour notifications.
+      // Clear the queue so that we would finish tour notifications below
+      queue = [];
+    }
+
     let startQueueLength = queue.length;
     // See if need to move on to the next tour
-    if (queue.length > 0 && this._isTimeForNextTourNotification()) {
+    if (queue.length > 0 && this._isTimeForNextTourNotification(lastTime)) {
       queue.shift();
     }
     // We don't want to prompt completed tour.
     while (queue.length > 0 && this.isTourCompleted(queue[0])) {
       queue.shift();
     }
 
     if (queue.length == 0) {
@@ -980,35 +984,31 @@ class Onboarding {
     // The security should be fine because this is not from an external input.
     footer.innerHTML = `
       <section id="onboarding-notification-message-section" role="presentation">
         <div id="onboarding-notification-tour-icon" role="presentation"></div>
         <div id="onboarding-notification-body" role="presentation">
           <h1 id="onboarding-notification-tour-title"></h1>
           <p id="onboarding-notification-tour-message"></p>
         </div>
-        <button id="onboarding-notification-action-btn"></button>
+        <button id="onboarding-notification-action-btn" class="onboarding-action-button"></button>
       </section>
       <button id="onboarding-notification-close-btn" class="onboarding-close-btn"></button>
     `;
 
     let closeBtn = footer.querySelector("#onboarding-notification-close-btn");
     closeBtn.setAttribute("title",
       this._bundle.GetStringFromName("onboarding.notification-close-button-tooltip"));
     return footer;
   }
 
-  hide() {
+  skipTour() {
     this.setToursCompleted(this._tours.map(tour => tour.id));
     sendMessageToChrome("set-prefs", [
       {
-        name: "browser.onboarding.hidden",
-        value: true
-      },
-      {
         name: "browser.onboarding.notification.finished",
         value: true
       }
     ]);
   }
 
   _renderOverlay() {
     let div = this._window.document.createElement("div");
@@ -1017,27 +1017,27 @@ class Onboarding {
     // The security should be fine because this is not from an external input.
     div.innerHTML = `
       <div role="dialog" tabindex="-1" aria-labelledby="onboarding-header">
         <header id="onboarding-header"></header>
         <nav>
           <ul id="onboarding-tour-list" role="tablist"></ul>
         </nav>
         <footer id="onboarding-footer">
-          <input type="checkbox" id="onboarding-tour-hidden-checkbox" /><label for="onboarding-tour-hidden-checkbox"></label>
+          <button id="onboarding-skip-tour-button" class="onboarding-action-button"></button>
         </footer>
         <button id="onboarding-overlay-close-btn" class="onboarding-close-btn"></button>
       </div>
     `;
 
     this._dialog = div.querySelector(`[role="dialog"]`);
     this._dialog.id = ONBOARDING_DIALOG_ID;
 
-    div.querySelector("label[for='onboarding-tour-hidden-checkbox']").textContent =
-      this._bundle.GetStringFromName("onboarding.hidden-checkbox-label-text");
+    div.querySelector("#onboarding-skip-tour-button").textContent =
+      this._bundle.GetStringFromName("onboarding.skip-tour-button-label");
     div.querySelector("#onboarding-header").textContent =
       this._bundle.GetStringFromName("onboarding.overlay-title2");
     let closeBtn = div.querySelector("#onboarding-overlay-close-btn");
     closeBtn.setAttribute("title",
       this._bundle.GetStringFromName("onboarding.overlay-close-button-tooltip"));
     return div;
   }
 
@@ -1141,25 +1141,23 @@ class Onboarding {
 
 // Load onboarding module only when we enable it.
 if (Services.prefs.getBoolPref("browser.onboarding.enabled", false)) {
   addEventListener("load", function onLoad(evt) {
     if (!content || evt.target != content.document) {
       return;
     }
 
-    if (!Services.prefs.getBoolPref("browser.onboarding.hidden", false)) {
-      let window = evt.target.defaultView;
-      let location = window.location.href;
-      if (location == ABOUT_NEWTAB_URL || location == ABOUT_HOME_URL) {
-        // We just want to run tests as quick as possible
-        // so in the automation test, we don't do `requestIdleCallback`.
-        if (Cu.isInAutomation) {
-          new Onboarding(window);
-          return;
-        }
-        window.requestIdleCallback(() => {
-          new Onboarding(window);
-        });
+    let window = evt.target.defaultView;
+    let location = window.location.href;
+    if (location == ABOUT_NEWTAB_URL || location == ABOUT_HOME_URL) {
+      // We just want to run tests as quick as possible
+      // so in the automation test, we don't do `requestIdleCallback`.
+      if (Cu.isInAutomation) {
+        new Onboarding(window);
+        return;
       }
+      window.requestIdleCallback(() => {
+        new Onboarding(window);
+      });
     }
   }, true);
 }
--- a/browser/extensions/onboarding/locales/en-US/onboarding.properties
+++ b/browser/extensions/onboarding/locales/en-US/onboarding.properties
@@ -1,14 +1,14 @@
 # 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/.
 # LOCALIZATION NOTE(onboarding.overlay-title2): This string will be used in the overlay title.
 onboarding.overlay-title2=Let’s get started
-onboarding.hidden-checkbox-label-text=Mark all as complete, and hide the tour
+onboarding.skip-tour-button-label=Skip Tour
 #LOCALIZATION NOTE(onboarding.button.learnMore): this string is used as a button label, displayed near the message, and shared across all the onboarding notifications.
 onboarding.button.learnMore=Learn More
 # LOCALIZATION NOTE(onboarding.overlay-icon-tooltip2): This string will be used
 # to show the tooltip alongside the notification icon in the overlay tour. %S is
 # brandShortName. The tooltip is designed to show in two lines. Please use \n to
 # do appropriate line breaking.
 onboarding.overlay-icon-tooltip2=New to %S?\nLet’s get started.
 # LOCALIZATION NOTE(onboarding.overlay-icon-tooltip-updated2): %S is
--- a/browser/extensions/onboarding/test/browser/browser.ini
+++ b/browser/extensions/onboarding/test/browser/browser.ini
@@ -1,16 +1,17 @@
 [DEFAULT]
 support-files =
   head.js
 
 [browser_onboarding_accessibility.js]
-[browser_onboarding_hide_all.js]
 [browser_onboarding_keyboard.js]
 skip-if = debug || os == "mac" # Full keyboard navigation on OSX only works if Full Keyboard Access setting is set to All Control in System Keyboard Preferences
 [browser_onboarding_notification.js]
 [browser_onboarding_notification_2.js]
 [browser_onboarding_notification_3.js]
 [browser_onboarding_notification_4.js]
+[browser_onboarding_notification_5.js]
 [browser_onboarding_notification_click_auto_complete_tour.js]
 [browser_onboarding_select_default_tour.js]
+[browser_onboarding_skip_tour.js]
 [browser_onboarding_tours.js]
 [browser_onboarding_tourset.js]
new file mode 100644
--- /dev/null
+++ b/browser/extensions/onboarding/test/browser/browser_onboarding_notification_5.js
@@ -0,0 +1,21 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_finish_tour_notifcations_after_total_max_life_time() {
+  resetOnboardingDefaultState();
+  skipMuteNotificationOnFirstSession();
+
+  let tab = await openTab(ABOUT_NEWTAB_URL);
+  await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
+  await promiseTourNotificationOpened(tab.linkedBrowser);
+
+  let totalMaxTime = Preferences.get("browser.onboarding.notification.max-life-time-all-tours-ms");
+  Preferences.set("browser.onboarding.notification.last-time-of-changing-tour-sec", Math.floor((Date.now() - totalMaxTime) / 1000));
+  let expectedPrefUpdate = promisePrefUpdated("browser.onboarding.notification.finished", true);
+  await reloadTab(tab);
+  await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
+  await expectedPrefUpdate;
+  await BrowserTestUtils.removeTab(tab);
+});
rename from browser/extensions/onboarding/test/browser/browser_onboarding_hide_all.js
rename to browser/extensions/onboarding/test/browser/browser_onboarding_skip_tour.js
--- a/browser/extensions/onboarding/test/browser/browser_onboarding_hide_all.js
+++ b/browser/extensions/onboarding/test/browser/browser_onboarding_skip_tour.js
@@ -1,84 +1,26 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
  "use strict";
 
-function assertOnboardingDestroyed(browser) {
-  return ContentTask.spawn(browser, {}, function() {
-    let expectedRemovals = [
-      "#onboarding-overlay",
-      "#onboarding-overlay-button"
-    ];
-    for (let selector of expectedRemovals) {
-      let removal = content.document.querySelector(selector);
-      ok(!removal, `Should remove ${selector} onboarding element`);
-    }
-  });
-}
-
-add_task(async function test_hide_onboarding_tours() {
+add_task(async function test_skip_onboarding_tours() {
   resetOnboardingDefaultState();
 
   let tourIds = TOUR_IDs;
   let expectedPrefUpdates = [
-    promisePrefUpdated("browser.onboarding.hidden", true),
     promisePrefUpdated("browser.onboarding.notification.finished", true)
   ];
   tourIds.forEach((id, idx) => expectedPrefUpdates.push(promisePrefUpdated(`browser.onboarding.tour.${id}.completed`, true)));
 
-  let tabs = [];
-  for (let url of URLs) {
-    let tab = await openTab(url);
-    await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
-    await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser);
-    await promiseOnboardingOverlayOpened(tab.linkedBrowser);
-    tabs.push(tab);
-  }
-
-  let doc = content.document;
-  await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-tour-hidden-checkbox", {}, gBrowser.selectedBrowser);
-  await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-close-btn", {}, gBrowser.selectedBrowser);
-  await Promise.all(expectedPrefUpdates);
-  ok(!doc.getElementById("onboarding-overlay-button"));
-
-  for (let i = tabs.length - 1; i >= 0; --i) {
-    let tab = tabs[i];
-    await assertOnboardingDestroyed(tab.linkedBrowser);
-    await BrowserTestUtils.removeTab(tab);
-  }
-});
-
-add_task(async function test_refresh_onboarding_tours_after_hide() {
-  resetOnboardingDefaultState();
+  let tab = await openTab(ABOUT_NEWTAB_URL);
+  await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
+  await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser);
+  await promiseOnboardingOverlayOpened(tab.linkedBrowser);
 
-  let tourIds = TOUR_IDs;
-  let expectedPrefUpdates = [
-    promisePrefUpdated("browser.onboarding.hidden", true),
-    promisePrefUpdated("browser.onboarding.notification.finished", true)
-  ];
-  tourIds.forEach((id, idx) => expectedPrefUpdates.push(promisePrefUpdated(`browser.onboarding.tour.${id}.completed`, true)));
-
-  let tabs = [];
-  for (let url of URLs) {
-    let tab = await openTab(url);
-    await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
-    await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser);
-    await promiseOnboardingOverlayOpened(tab.linkedBrowser);
-    tabs.push(tab);
-  }
+  let overlayClosedPromise = promiseOnboardingOverlayClosed(tab.linkedBrowser);
+  await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-skip-tour-button", {}, tab.linkedBrowser);
+  await overlayClosedPromise;
+  await Promise.all(expectedPrefUpdates);
 
-  await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-tour-hidden-checkbox", {}, gBrowser.selectedBrowser);
-  await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-close-btn", {}, gBrowser.selectedBrowser);
-  await Promise.all(expectedPrefUpdates);
-  ok(!content.document.getElementById("onboarding-overlay-button"), "should not show fox icon");
-
-  for (let i = tabs.length - 1; i >= 0; --i) {
-    let tab = tabs[i];
-    await reloadTab(tab);
-    await waitUntilWindowIdle(tab.linkedBrowser);
-    ok(!content.document.getElementById("onboarding-overlay-button"), "should not show fox icon after refresh");
-
-    await assertOnboardingDestroyed(tab.linkedBrowser);
-    await BrowserTestUtils.removeTab(tab);
-  }
+  await BrowserTestUtils.removeTab(tab);
 });
--- a/browser/extensions/onboarding/test/browser/head.js
+++ b/browser/extensions/onboarding/test/browser/head.js
@@ -24,20 +24,20 @@ const UPDATE_TOUR_IDs = [
 ];
 
 registerCleanupFunction(resetOnboardingDefaultState);
 
 function resetOnboardingDefaultState() {
   // All the prefs should be reset to the default states
   // and no need to revert back so we don't use `SpecialPowers.pushPrefEnv` here.
   Preferences.set("browser.onboarding.enabled", true);
-  Preferences.set("browser.onboarding.hidden", false);
   Preferences.set("browser.onboarding.notification.finished", false);
   Preferences.set("browser.onboarding.notification.mute-duration-on-first-session-ms", 300000);
   Preferences.set("browser.onboarding.notification.max-life-time-per-tour-ms", 432000000);
+  Preferences.set("browser.onboarding.notification.max-life-time-all-tours-ms", 1209600000);
   Preferences.set("browser.onboarding.notification.max-prompt-count-per-tour", 8);
   Preferences.reset("browser.onboarding.notification.last-time-of-changing-tour-sec");
   Preferences.reset("browser.onboarding.notification.prompt-count");
   Preferences.reset("browser.onboarding.notification.tour-ids-queue");
   TOUR_IDs.forEach(id => Preferences.reset(`browser.onboarding.tour.${id}.completed`));
   UPDATE_TOUR_IDs.forEach(id => Preferences.reset(`browser.onboarding.tour.${id}.completed`));
 }
 
--- a/browser/extensions/onboarding/test/unit/head.js
+++ b/browser/extensions/onboarding/test/unit/head.js
@@ -23,25 +23,22 @@ if (!extensionDir.exists()) {
 }
 Components.manager.addBootstrappedManifestLocation(extensionDir);
 
 const TOURSET_VERSION = 1;
 const NEXT_TOURSET_VERSION = 2;
 const PREF_TOUR_TYPE = "browser.onboarding.tour-type";
 const PREF_TOURSET_VERSION = "browser.onboarding.tourset-version";
 const PREF_SEEN_TOURSET_VERSION = "browser.onboarding.seen-tourset-version";
-const PREF_ONBOARDING_HIDDEN = "browser.onboarding.hidden";
 
 function resetOnboardingDefaultState() {
   // All the prefs should be reset to what prefs should looks like in a new user profile
-  Services.prefs.setBoolPref(PREF_ONBOARDING_HIDDEN, false);
   Services.prefs.setIntPref(PREF_TOURSET_VERSION, TOURSET_VERSION);
   Services.prefs.clearUserPref(PREF_SEEN_TOURSET_VERSION);
   Services.prefs.clearUserPref(PREF_TOUR_TYPE);
 }
 
 function resetOldProfileDefaultState() {
   // All the prefs should be reset to what prefs should looks like in a older new user profile
   Services.prefs.setIntPref(PREF_TOURSET_VERSION, TOURSET_VERSION);
   Services.prefs.setIntPref(PREF_SEEN_TOURSET_VERSION, 0);
-  Services.prefs.clearUserPref(PREF_ONBOARDING_HIDDEN);
   Services.prefs.clearUserPref(PREF_TOUR_TYPE);
 }
--- a/browser/extensions/onboarding/test/unit/test-onboarding-tour-type.js
+++ b/browser/extensions/onboarding/test/unit/test-onboarding-tour-type.js
@@ -11,89 +11,79 @@ add_task(async function() {
   resetOnboardingDefaultState();
   OnboardingTourType.check();
 
   do_check_eq(Preferences.get(PREF_TOUR_TYPE), "new", "should show the new user tour");
   do_check_eq(Preferences.get(PREF_TOURSET_VERSION), TOURSET_VERSION,
     "tourset version should not change");
   do_check_eq(Preferences.get(PREF_SEEN_TOURSET_VERSION), TOURSET_VERSION,
     "seen tourset version should be set as the tourset version");
-  do_check_eq(Preferences.get(PREF_ONBOARDING_HIDDEN), false, "should show the user tour");
 });
 
 add_task(async function() {
   do_print("Starting testcase: When New user restart the browser");
   resetOnboardingDefaultState();
   Preferences.set(PREF_TOUR_TYPE, "new");
   Preferences.set(PREF_SEEN_TOURSET_VERSION, TOURSET_VERSION);
-  Preferences.set(PREF_ONBOARDING_HIDDEN, false);
   OnboardingTourType.check();
 
   do_check_eq(Preferences.get(PREF_TOUR_TYPE), "new", "should show the new user tour");
   do_check_eq(Preferences.get(PREF_TOURSET_VERSION), TOURSET_VERSION),
     "tourset version should not change";
   do_check_eq(Preferences.get(PREF_SEEN_TOURSET_VERSION), TOURSET_VERSION,
     "seen tourset version should be set as the tourset version");
-  do_check_eq(Preferences.get(PREF_ONBOARDING_HIDDEN), false, "should show the user tour");
 });
 
 add_task(async function() {
   do_print("Starting testcase: When New User choosed to hide the overlay and restart the browser");
   resetOnboardingDefaultState();
   Preferences.set(PREF_TOUR_TYPE, "new");
   Preferences.set(PREF_SEEN_TOURSET_VERSION, TOURSET_VERSION);
-  Preferences.set(PREF_ONBOARDING_HIDDEN, true);
   OnboardingTourType.check();
 
   do_check_eq(Preferences.get(PREF_TOUR_TYPE), "new", "should show the new user tour");
   do_check_eq(Preferences.get(PREF_TOURSET_VERSION), TOURSET_VERSION),
     "tourset version should not change";
   do_check_eq(Preferences.get(PREF_SEEN_TOURSET_VERSION), TOURSET_VERSION,
     "seen tourset version should be set as the tourset version");
-  do_check_eq(Preferences.get(PREF_ONBOARDING_HIDDEN), true, "should hide the user tour");
 });
 
 add_task(async function() {
   do_print("Starting testcase: When New User updated to the next major version and restart the browser");
   resetOnboardingDefaultState();
   Preferences.set(PREF_TOURSET_VERSION, NEXT_TOURSET_VERSION);
   Preferences.set(PREF_TOUR_TYPE, "new");
   Preferences.set(PREF_SEEN_TOURSET_VERSION, TOURSET_VERSION);
-  Preferences.set(PREF_ONBOARDING_HIDDEN, false);
   OnboardingTourType.check();
 
   do_check_eq(Preferences.get(PREF_TOUR_TYPE), "update", "should show the update user tour");
   do_check_eq(Preferences.get(PREF_TOURSET_VERSION), NEXT_TOURSET_VERSION),
     "tourset version should not change";
   do_check_eq(Preferences.get(PREF_SEEN_TOURSET_VERSION), NEXT_TOURSET_VERSION,
     "seen tourset version should be set as the tourset version");
-  do_check_eq(Preferences.get(PREF_ONBOARDING_HIDDEN), false, "should show the user tour");
 });
 
 add_task(async function() {
   do_print("Starting testcase: When New User prefer hide the tour, then updated to the next major version and restart the browser");
   resetOnboardingDefaultState();
   Preferences.set(PREF_TOURSET_VERSION, NEXT_TOURSET_VERSION);
   Preferences.set(PREF_TOUR_TYPE, "new");
   Preferences.set(PREF_SEEN_TOURSET_VERSION, TOURSET_VERSION);
-  Preferences.set(PREF_ONBOARDING_HIDDEN, true);
   OnboardingTourType.check();
 
   do_check_eq(Preferences.get(PREF_TOUR_TYPE), "update", "should show the update user tour");
   do_check_eq(Preferences.get(PREF_TOURSET_VERSION), NEXT_TOURSET_VERSION),
     "tourset version should not change";
   do_check_eq(Preferences.get(PREF_SEEN_TOURSET_VERSION), NEXT_TOURSET_VERSION,
     "seen tourset version should be set as the tourset version");
-  do_check_eq(Preferences.get(PREF_ONBOARDING_HIDDEN), false, "should show the user tour");
 });
 
 add_task(async function() {
   do_print("Starting testcase: When User update from browser version < 56");
   resetOldProfileDefaultState();
   OnboardingTourType.check();
 
   do_check_eq(Preferences.get(PREF_TOUR_TYPE), "update", "should show the update user tour");
   do_check_eq(Preferences.get(PREF_TOURSET_VERSION), TOURSET_VERSION),
     "tourset version should not change";
   do_check_eq(Preferences.get(PREF_SEEN_TOURSET_VERSION), TOURSET_VERSION,
     "seen tourset version should be set as the tourset version");
-  do_check_eq(Preferences.get(PREF_ONBOARDING_HIDDEN), false, "should show the user tour");
 });
--- a/browser/themes/linux/syncedtabs/sidebar.css
+++ b/browser/themes/linux/syncedtabs/sidebar.css
@@ -2,17 +2,16 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 %include ../../shared/syncedtabs/sidebar.inc.css
 
 /* These styles are intended to mimic XUL trees and the XUL search box. */
 
 html {
-  border: 1px solid ThreeDShadow;
   background-color: -moz-Field;
   color: -moz-FieldText;
   box-sizing: border-box;
 }
 
 .item {
   padding-inline-end: 0;
 }
@@ -21,26 +20,17 @@ html {
   margin: 1px 0 0;
   margin-inline-end: 6px;
 }
 
 
 .search-box {
   -moz-appearance: textfield;
   cursor: text;
-  margin: 2px 4px;
-  border: 2px solid;
-  -moz-border-top-colors: ThreeDShadow ThreeDDarkShadow;
-  -moz-border-right-colors: ThreeDHighlight ThreeDLightShadow;
-  -moz-border-bottom-colors: ThreeDHighlight ThreeDLightShadow;
-  -moz-border-left-colors: ThreeDShadow ThreeDDarkShadow;
-  padding: 2px 2px 3px;
-  padding-inline-start: 4px;
-  background-color: -moz-Field;
-  color: -moz-FieldText;
+  margin: 4px;
 }
 
 .textbox-search-clear {
   background-image: url(moz-icon://stock/gtk-clear?size=menu);
   background-repeat: no-repeat;
   width: 16px;
   height: 16px;
 }
--- a/browser/themes/osx/syncedtabs/sidebar.css
+++ b/browser/themes/osx/syncedtabs/sidebar.css
@@ -92,26 +92,22 @@
     font-weight: 500;
   }
 
   .item.selected:focus > .item-title-container {
     color: #fff;
   }
 }
 
-.sidebar-search-container {
-  border-bottom: 1px solid #bdbdbd;
-}
-
 .search-box {
   -moz-appearance: searchfield;
   padding: 1px;
   font-size: 12px;
   cursor: text;
-  margin: 4px 8px 10px;
+  margin: 4px;
   border-width: 3px;
   border-style: solid;
   border-color: currentcolor;
   border-image: none;
   -moz-border-top-colors: transparent #888 #000;
   -moz-border-right-colors: transparent #FFF #000;
   -moz-border-bottom-colors: transparent #FFF #000;
   -moz-border-left-colors: transparent #888 #000;
--- a/browser/themes/shared/syncedtabs/sidebar.inc.css
+++ b/browser/themes/shared/syncedtabs/sidebar.inc.css
@@ -68,17 +68,16 @@ body {
   overflow: hidden;
   outline: none;
   color: -moz-FieldText;
 }
 
 .item.selected > .item-title-container {
   background-color: -moz-cellhighlight;
   color: -moz-cellhighlighttext;
-  font-weight: bold;
 }
 
 .item.selected:focus > .item-title-container {
   background-color: Highlight;
   color: HighlightText;
 }
 
 .client .item.tab > .item-title-container {
@@ -126,17 +125,17 @@ body {
   background-position: center;
 }
 
 .item-title-container {
   display: flex;
   flex-flow: row;
   overflow: hidden;
   flex-grow: 1;
-  padding: 1px 0px 1px 0px;
+  padding: 4px;
 }
 
 .item-title {
   flex-grow: 1;
   overflow: hidden;
   text-overflow: ellipsis;
   margin: 0px;
   line-height: 1.3;
--- a/browser/themes/windows/syncedtabs/sidebar.css
+++ b/browser/themes/windows/syncedtabs/sidebar.css
@@ -1,36 +1,32 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 %include ../../shared/syncedtabs/sidebar.inc.css
 
 /* These styles are intended to mimic XUL trees and the XUL search box. */
 
-html {
-  background-color: #EEF3FA;
-}
-
 .item {
   padding-inline-end: 0;
 }
 
 .item-title {
   margin: 1px 0 0;
 }
 
 .item-title {
   margin-inline-end: 6px;
 }
 
 .search-box {
   -moz-appearance: textfield;
   cursor: text;
-  margin: 2px 4px;
+  margin: 4px;
   padding: 2px 2px 3px;
   padding-inline-start: 4px;
   color: -moz-FieldText;
 }
 
 .textbox-search-icon {
   width: 16px;
   height: 16px;
@@ -89,19 +85,19 @@ html {
   background-repeat: no-repeat;
   background-position: center;
 }
 
 .item-twisty-container {
   background-size: contain;
   background-repeat: no-repeat;
   background-position: center;
-  padding-top: 5px;
   min-width: 9px; /* The image's width is 9 pixels */
   height: 9px;
+  margin: auto;
 }
 
 .item.client .item-twisty-container {
   background-image: url("chrome://global/skin/tree/twisty.svg#open");
 }
 
 .item.client.closed .item-twisty-container {
   background-image: url("chrome://global/skin/tree/twisty.svg#clsd");
--- a/caps/tests/mochitest/chrome.ini
+++ b/caps/tests/mochitest/chrome.ini
@@ -2,11 +2,10 @@
 skip-if = os == 'android'
 support-files =
   file_data.txt
   file_disableScript.html
   !/caps/tests/mochitest/file_data.txt
   !/caps/tests/mochitest/file_disableScript.html
 
 [test_bug995943.xul]
-skip-if = stylo && debug && os == 'linux' # bug 1384701
 [test_addonMayLoad.html]
 [test_disableScript.xul]
--- a/devtools/client/debugger/new/debugger.js
+++ b/devtools/client/debugger/new/debugger.js
@@ -15987,18 +15987,16 @@ const prefs = new PrefsHelper("devtools"
 
 
 const features = new PrefsHelper("devtools.debugger.features", {
   asyncStepping: ["Bool", "async-stepping", false]
 });
 /* harmony export (immutable) */ __webpack_exports__["features"] = features;
 
 
-debugger;
-
 if (prefs.debuggerPrefsSchemaVersion !== prefsSchemaVersion) {
   // clear pending Breakpoints
   prefs.pendingBreakpoints = {};
   prefs.debuggerPrefsSchemaVersion = prefsSchemaVersion;
 }
 
 
 /***/ }),
@@ -46288,9 +46286,9 @@ exports.default = (0, _reactRedux.connec
 /* 1173 */,
 /* 1174 */
 /***/ (function(module, exports) {
 
 module.exports = "<svg xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:cc=\"http://creativecommons.org/ns#\" xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\" xmlns:svg=\"http://www.w3.org/2000/svg\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 400 400\" xml:space=\"preserve\" id=\"svg2\" version=\"1.1\"><metadata id=\"metadata8\"><rdf:RDF><cc:Work rdf:about><dc:format>image/svg+xml</dc:format><dc:type rdf:resource=\"http://purl.org/dc/dcmitype/StillImage\"></dc></cc:Work></rdf:RDF></metadata><defs id=\"defs6\"></defs><g transform=\"matrix(1.3333333,0,0,-1.3333333,0,400)\" id=\"g10\"><g transform=\"translate(178.0626,235.0086)\" id=\"g12\"><path id=\"path14\" style=\"fill:#41b883;fill-opacity:1;fill-rule:nonzero;stroke:none\" d=\"M 0,0 -22.669,-39.264 -45.338,0 h -75.491 L -22.669,-170.017 75.491,0 Z\"></path></g><g transform=\"translate(178.0626,235.0086)\" id=\"g16\"><path id=\"path18\" style=\"fill:#34495e;fill-opacity:1;fill-rule:nonzero;stroke:none\" d=\"M 0,0 -22.669,-39.264 -45.338,0 H -81.565 L -22.669,-102.01 36.227,0 Z\"></path></g></g></svg>"
 
 /***/ })
 /******/ ]);
-});
\ No newline at end of file
+});
--- a/devtools/client/framework/source-map-url-service.js
+++ b/devtools/client/framework/source-map-url-service.js
@@ -68,16 +68,21 @@ SourceMapURLService.prototype.destroy = 
   Services.prefs.removeObserver(SOURCE_MAP_PREF, this._onPrefChanged);
   this._target = this._urls = this._subscriptions = null;
 };
 
 /**
  * A helper function that is called when a new source is available.
  */
 SourceMapURLService.prototype._onSourceUpdated = function (_, sourceEvent) {
+  // Maybe we were shut down while waiting.
+  if (!this._urls) {
+    return;
+  }
+
   let { source } = sourceEvent;
   let { generatedUrl, url, actor: id, sourceMapURL } = source;
 
   // |generatedUrl| comes from the actor and is extracted from the
   // source code by SpiderMonkey.
   let seenUrl = generatedUrl || url;
   this._urls.set(seenUrl, { id, url: seenUrl, sourceMapURL });
 };
--- a/docshell/test/moz.build
+++ b/docshell/test/moz.build
@@ -18,17 +18,17 @@ with Files('browser/*_bug655273*'):
 
 with Files('browser/*_bug852909*'):
     BUG_COMPONENT = ('Firefox', 'Menus')
 
 with Files('browser/*bug92473*'):
     BUG_COMPONENT = ('Core', 'Internationalization')
 
 with Files('browser/*loadDisallowInherit*'):
-    BUG_COMPONENT = ('Firefox', 'Location Bar')
+    BUG_COMPONENT = ('Firefox', 'Address Bar')
 
 with Files('browser/*tab_touch_events*'):
     BUG_COMPONENT = ('Core', 'DOM: Events')
 
 with Files('browser/*timelineMarkers*'):
     BUG_COMPONENT = ('Firefox', 'Developer Tools: Performance Tools (Profiler/Timeline)')
 
 with Files('browser/*ua_emulation*'):
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -1075,16 +1075,30 @@ class CGHeaders(CGWrapper):
                 # interface object as the proto of our interface object.
                 if iface.hasInterfaceObject():
                     parent = iface.parent
                     while parent and not parent.hasInterfaceObject():
                         parent = parent.parent
                     if parent:
                         ancestors.append(parent)
         interfaceDeps.extend(ancestors)
+
+        # Include parent interface headers needed for jsonifier code.
+        jsonInterfaceParents = []
+        for desc in descriptors:
+            if not desc.operations['Jsonifier']:
+                continue
+            parent = desc.interface.parent
+            while parent:
+                parentDesc = desc.getDescriptor(parent.identifier.name)
+                if parentDesc.operations['Jsonifier']:
+                    jsonInterfaceParents.append(parentDesc.interface)
+                parent = parent.parent
+        interfaceDeps.extend(jsonInterfaceParents)
+
         bindingIncludes = set(self.getDeclarationFilename(d) for d in interfaceDeps)
 
         # Grab all the implementation declaration files we need.
         implementationIncludes = set(d.headerFile for d in descriptors if d.needsHeaderInclude())
 
         # Grab the includes for checking hasInstance
         interfacesImplementingSelf = set()
         for d in descriptors:
--- a/dom/media/MediaResource.cpp
+++ b/dom/media/MediaResource.cpp
@@ -1015,80 +1015,87 @@ ChannelMediaResource::GetLength()
   return mCacheStream.GetLength();
 }
 
 // ChannelSuspendAgent
 
 bool
 ChannelSuspendAgent::Suspend()
 {
+  MOZ_ASSERT(NS_IsMainThread());
   SuspendInternal();
   return (++mSuspendCount == 1);
 }
 
 void
 ChannelSuspendAgent::SuspendInternal()
 {
+  MOZ_ASSERT(NS_IsMainThread());
   if (mChannel) {
     bool isPending = false;
     nsresult rv = mChannel->IsPending(&isPending);
     if (NS_SUCCEEDED(rv) && isPending && !mIsChannelSuspended) {
       mChannel->Suspend();
       mIsChannelSuspended = true;
     }
   }
 }
 
 bool
 ChannelSuspendAgent::Resume()
 {
+  MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(IsSuspended(), "Resume without suspend!");
   --mSuspendCount;
 
   if (mSuspendCount == 0) {
     if (mChannel && mIsChannelSuspended) {
       mChannel->Resume();
       mIsChannelSuspended = false;
     }
     return true;
   }
   return false;
 }
 
 void
 ChannelSuspendAgent::UpdateSuspendedStatusIfNeeded()
 {
+  MOZ_ASSERT(NS_IsMainThread());
   if (!mIsChannelSuspended && IsSuspended()) {
     SuspendInternal();
   }
 }
 
 void
 ChannelSuspendAgent::NotifyChannelOpened(nsIChannel* aChannel)
 {
+  MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aChannel);
   mChannel = aChannel;
 }
 
 void
 ChannelSuspendAgent::NotifyChannelClosing()
 {
+  MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mChannel);
   // Before close the channel, it need to be resumed to make sure its internal
   // state is correct. Besides, We need to suspend the channel after recreating.
   if (mIsChannelSuspended) {
     mChannel->Resume();
     mIsChannelSuspended = false;
   }
   mChannel = nullptr;
 }
 
 bool
 ChannelSuspendAgent::IsSuspended()
 {
+  MOZ_ASSERT(NS_IsMainThread());
   return (mSuspendCount > 0);
 }
 
 // FileMediaResource
 
 class FileMediaResource : public BaseMediaResource
 {
 public:
--- a/dom/media/MediaResource.h
+++ b/dom/media/MediaResource.h
@@ -367,17 +367,16 @@ protected:
 /**
  * This class is responsible for managing the suspend count and report suspend
  * status of channel.
  **/
 class ChannelSuspendAgent {
 public:
   explicit ChannelSuspendAgent(nsIChannel* aChannel)
   : mChannel(aChannel),
-    mSuspendCount(0),
     mIsChannelSuspended(false)
   {}
 
   // True when the channel has been suspended or needs to be suspended.
   bool IsSuspended();
 
   // Return true when the channel is logically suspended, i.e. the suspend
   // count goes from 0 to 1.
@@ -395,17 +394,17 @@ public:
 
   // Check whether we need to suspend the channel.
   void UpdateSuspendedStatusIfNeeded();
 private:
   // Only suspends channel but not changes the suspend count.
   void SuspendInternal();
 
   nsIChannel* mChannel;
-  Atomic<uint32_t> mSuspendCount;
+  uint32_t mSuspendCount = 0;
   bool mIsChannelSuspended;
 };
 
 /**
  * This is the MediaResource implementation that wraps Necko channels.
  * Much of its functionality is actually delegated to MediaCache via
  * an underlying MediaCacheStream.
  *
--- a/editor/libeditor/EditorBase.h
+++ b/editor/libeditor/EditorBase.h
@@ -967,26 +967,17 @@ public:
                                   nsIDOMNode* aEndContainer,
                                   int32_t aEndOffset);
 
   virtual already_AddRefed<dom::EventTarget> GetDOMEventTarget() = 0;
 
   /**
    * Fast non-refcounting editor root element accessor
    */
-  Element* GetRoot()
-  {
-    if (!mRootElement) {
-      // Let GetRootElement() do the work
-      nsCOMPtr<nsIDOMElement> root;
-      GetRootElement(getter_AddRefs(root));
-    }
-
-    return mRootElement;
-  }
+  Element* GetRoot() const { return mRootElement; }
 
   /**
    * Likewise, but gets the editor's root instead, which is different for HTML
    * editors.
    */
   virtual Element* GetEditorRoot();
 
   /**
--- a/layout/generic/nsPluginFrame.cpp
+++ b/layout/generic/nsPluginFrame.cpp
@@ -52,16 +52,17 @@
 #ifdef XP_WIN
 #include "gfxWindowsNativeDrawing.h"
 #include "gfxWindowsSurface.h"
 #endif
 
 #include "Layers.h"
 #include "ReadbackLayer.h"
 #include "ImageContainer.h"
+#include "mozilla/layers/WebRenderLayerManager.h"
 
 // accessibility support
 #ifdef ACCESSIBILITY
 #include "nsAccessibilityService.h"
 #endif
 
 #include "mozilla/Logging.h"
 
@@ -1037,16 +1038,30 @@ nsDisplayPlugin::GetOpaqueRegion(nsDispl
       // We can treat this as opaque
       result = bounds;
     }
   }
 
   return result;
 }
 
+bool
+nsDisplayPlugin::CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
+                                         const StackingContextHelper& aSc,
+                                         nsTArray<WebRenderParentCommand>& aParentCommands,
+                                         mozilla::layers::WebRenderLayerManager* aManager,
+                                         nsDisplayListBuilder* aDisplayListBuilder)
+{
+  return static_cast<nsPluginFrame*>(mFrame)->CreateWebRenderCommands(this,
+                                                                      aBuilder,
+                                                                      aSc,
+                                                                      aManager,
+                                                                      aDisplayListBuilder);
+}
+
 nsresult
 nsPluginFrame::PluginEventNotifier::Run() {
   nsCOMPtr<nsIObserverService> obsSvc =
     mozilla::services::GetObserverService();
   obsSvc->NotifyObservers(nullptr, "plugin-changed-event", mEventType.get());
   return NS_OK;
 }
 
@@ -1350,52 +1365,105 @@ public:
     return aLayerManager == mLayerManager;
   }
 
 private:
   nsPluginInstanceOwner* mInstanceOwner;
   RefPtr<LayerManager> mLayerManager;
 };
 
-already_AddRefed<Layer>
-nsPluginFrame::BuildLayer(nsDisplayListBuilder* aBuilder,
-                          LayerManager* aManager,
-                          nsDisplayItem* aItem,
-                          const ContainerLayerParameters& aContainerParameters)
+bool
+nsPluginFrame::GetBounds(nsDisplayItem* aItem, IntSize& aSize, gfxRect& aRect)
 {
   if (!mInstanceOwner)
-    return nullptr;
+    return false;
 
   NPWindow* window = nullptr;
   mInstanceOwner->GetWindow(window);
   if (!window)
-    return nullptr;
+    return false;
 
   if (window->width <= 0 || window->height <= 0)
-    return nullptr;
+    return false;
 
 #if defined(XP_MACOSX)
   // window is in "display pixels", but size needs to be in device pixels
   // window must be in "display pixels"
   double scaleFactor = 1.0;
   if (NS_FAILED(mInstanceOwner->GetContentsScaleFactor(&scaleFactor))) {
     scaleFactor = 1.0;
   }
 
   size_t intScaleFactor = ceil(scaleFactor);
 #else
   size_t intScaleFactor = 1;
 #endif
 
-  IntSize size(window->width * intScaleFactor, window->height * intScaleFactor);
+  aSize = IntSize(window->width * intScaleFactor, window->height * intScaleFactor);
 
   nsRect area = GetContentRectRelativeToSelf() + aItem->ToReferenceFrame();
-  gfxRect r = nsLayoutUtils::RectToGfxRect(area, PresContext()->AppUnitsPerDevPixel());
+  aRect = nsLayoutUtils::RectToGfxRect(area, PresContext()->AppUnitsPerDevPixel());
   // to provide crisper and faster drawing.
-  r.Round();
+  aRect.Round();
+
+  return true;
+}
+
+bool
+nsPluginFrame::CreateWebRenderCommands(nsDisplayItem* aItem,
+                                       mozilla::wr::DisplayListBuilder& aBuilder,
+                                       const StackingContextHelper& aSc,
+                                       mozilla::layers::WebRenderLayerManager* aManager,
+                                       nsDisplayListBuilder* aDisplayListBuilder)
+{
+  IntSize size;
+  gfxRect r;
+  if (!GetBounds(aItem, size, r)) {
+    return true;
+  }
+
+  RefPtr<ImageContainer> container;
+  // Image for Windowed plugins that support window capturing for scroll
+  // operations or async windowless rendering.
+  container = mInstanceOwner->GetImageContainer();
+  if (!container) {
+    // This can occur if our instance is gone or if the current plugin
+    // configuration does not require a backing image layer.
+    return true;
+  }
+
+#ifdef XP_MACOSX
+  if (!mInstanceOwner->UseAsyncRendering()) {
+    mInstanceOwner->DoCocoaEventDrawRect(r, nullptr);
+  }
+#endif
+
+  RefPtr<LayerManager> lm = aDisplayListBuilder->GetWidgetLayerManager();
+  if (!mDidCompositeObserver || !mDidCompositeObserver->IsValid(lm)) {
+    mDidCompositeObserver = MakeUnique<PluginFrameDidCompositeObserver>(mInstanceOwner, lm);
+  }
+  lm->AddDidCompositeObserver(mDidCompositeObserver.get());
+
+  LayerRect dest(r.x, r.y, size.width, size.height);
+  return aManager->PushImage(aItem, container, aBuilder, aSc, dest);
+}
+
+
+already_AddRefed<Layer>
+nsPluginFrame::BuildLayer(nsDisplayListBuilder* aBuilder,
+                          LayerManager* aManager,
+                          nsDisplayItem* aItem,
+                          const ContainerLayerParameters& aContainerParameters)
+{
+  IntSize size;
+  gfxRect r;
+  if (!GetBounds(aItem, size, r)) {
+    return nullptr;
+  }
+
   RefPtr<Layer> layer =
     (aManager->GetLayerBuilder()->GetLeafLayerFor(aBuilder, aItem));
 
   if (aItem->GetType() == DisplayItemType::TYPE_PLUGIN) {
     RefPtr<ImageContainer> container;
     // Image for Windowed plugins that support window capturing for scroll
     // operations or async windowless rendering.
     container = mInstanceOwner->GetImageContainer();
--- a/layout/generic/nsPluginFrame.h
+++ b/layout/generic/nsPluginFrame.h
@@ -12,16 +12,18 @@
 #include "mozilla/EventForwards.h"
 #include "mozilla/UniquePtr.h"
 #include "nsIObjectFrame.h"
 #include "nsFrame.h"
 #include "nsRegion.h"
 #include "nsDisplayList.h"
 #include "nsIReflowCallback.h"
 #include "Units.h"
+#include "mozilla/layers/StackingContextHelper.h"
+#include "mozilla/webrender/WebRenderAPI.h"
 
 #ifdef XP_WIN
 #include <windows.h> // For HWND :(
 // Undo the windows.h damage
 #undef GetMessage
 #undef CreateEvent
 #undef GetClassName
 #undef GetBinaryType
@@ -55,16 +57,19 @@ class nsPluginFrame final
 public:
   typedef mozilla::LayerState LayerState;
   typedef mozilla::LayoutDeviceIntPoint LayoutDeviceIntPoint;
   typedef mozilla::LayoutDeviceIntRect LayoutDeviceIntRect;
   typedef mozilla::LayoutDeviceIntRegion LayoutDeviceIntRegion;
   typedef mozilla::layers::Layer Layer;
   typedef mozilla::layers::LayerManager LayerManager;
   typedef mozilla::layers::ImageContainer ImageContainer;
+  typedef mozilla::layers::StackingContextHelper StackingContextHelper;
+  typedef mozilla::layers::WebRenderLayerManager WebRenderLayerManager;
+  typedef mozilla::layers::WebRenderParentCommand WebRenderParentCommand;
   typedef mozilla::ContainerLayerParameters ContainerLayerParameters;
 
   NS_DECL_FRAMEARENA_HELPERS(nsPluginFrame)
   NS_DECL_QUERYFRAME
 
   friend nsIFrame* NS_NewObjectFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
 
   virtual void Init(nsIContent*       aContent,
@@ -216,16 +221,21 @@ public:
   void HandleWheelEventAsDefaultAction(mozilla::WidgetWheelEvent* aEvent);
 
   /**
    * WantsToHandleWheelEventAsDefaultAction() returns true if the plugin
    * may want to handle wheel events as default action.
    */
   bool WantsToHandleWheelEventAsDefaultAction() const;
 
+  bool CreateWebRenderCommands(nsDisplayItem* aItem,
+                               mozilla::wr::DisplayListBuilder& aBuilder,
+                               const StackingContextHelper& aSc,
+                               mozilla::layers::WebRenderLayerManager* aManager,
+                               nsDisplayListBuilder* aDisplayListBuilder);
 protected:
   explicit nsPluginFrame(nsStyleContext* aContext);
   virtual ~nsPluginFrame();
 
   // NOTE:  This frame class does not inherit from |nsLeafFrame|, so
   // this is not a virtual method implementation.
   void GetDesiredSize(nsPresContext* aPresContext,
                       const ReflowInput& aReflowInput,
@@ -262,16 +272,17 @@ protected:
   void NotifyPluginReflowObservers();
 
   friend class nsPluginInstanceOwner;
   friend class nsDisplayPlugin;
   friend class PluginBackgroundSink;
 
   nsView* GetViewInternal() const override { return mOuterView; }
   void SetViewInternal(nsView* aView) override { mOuterView = aView; }
+  bool GetBounds(nsDisplayItem* aItem, mozilla::gfx::IntSize& aSize, gfxRect& aRect);
 
 private:
   // Registers the plugin for a geometry update, and requests a geometry
   // update. This caches the root pres context in
   // mRootPresContextRegisteredWith, so that we can be sure we unregister
   // from the right root prest context in UnregisterPluginForGeometryUpdates.
   void RegisterPluginForGeometryUpdates();
 
@@ -372,11 +383,17 @@ public:
 
   virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
                                    LayerManager* aManager,
                                    const ContainerLayerParameters& aParameters) override
   {
     return static_cast<nsPluginFrame*>(mFrame)->GetLayerState(aBuilder,
                                                               aManager);
   }
+
+  virtual bool CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
+                                       const StackingContextHelper& aSc,
+                                       nsTArray<WebRenderParentCommand>& aParentCommands,
+                                       mozilla::layers::WebRenderLayerManager* aManager,
+                                       nsDisplayListBuilder* aDisplayListBuilder) override;
 };
 
 #endif /* nsPluginFrame_h___ */
--- a/layout/reftests/table-background/reftest.list
+++ b/layout/reftests/table-background/reftest.list
@@ -1,14 +1,14 @@
 # these could be moved to crashtests
 != backgr_border-table-cell.html empty.html
 fuzzy-if(styloVsGecko,5,330) != backgr_border-table-column-group.html empty.html # Bug 1386543
 # This seems to be caused by bug 527825
 fuzzy-if(styloVsGecko,5,561) asserts-if(gtkWidget,0-12) != backgr_border-table-column.html empty.html # Bug 1386543
-asserts-if(gtkWidget,0-6) fuzzy-if(styloVsGecko&&winWidget,32,88) != backgr_border-table-quirks.html empty.html
+asserts-if(gtkWidget,0-6) fuzzy-if(styloVsGecko&&(winWidget||cocoaWidget),32,88) != backgr_border-table-quirks.html empty.html
 fuzzy-if(styloVsGecko,1,168) != backgr_border-table-row-group.html empty.html # Bug 1386543
 fuzzy-if(styloVsGecko,1,178) != backgr_border-table-row.html empty.html # Bug 1386543
 != backgr_border-table.html empty.html
 != backgr_fixed-bg.html empty.html
 != backgr_index.html empty.html
 != backgr_layers-hide.html empty.html
 fuzzy-if(styloVsGecko&&cocoaWidget,1,56781) != backgr_layers-opacity.html empty.html
 != backgr_layers-show.html empty.html
--- a/layout/style/test/mochitest.ini
+++ b/layout/style/test/mochitest.ini
@@ -257,16 +257,17 @@ skip-if = android_version == '18' #debug
 [test_parser_diagnostics_unprintables.html]
 [test_pixel_lengths.html]
 [test_pointer-events.html]
 [test_position_float_display.html]
 [test_position_sticky.html]
 [test_priority_preservation.html]
 [test_property_database.html]
 [test_property_syntax_errors.html]
+[test_pseudo_display_fixup.html]
 [test_pseudoelement_state.html]
 [test_pseudoelement_parsing.html]
 [test_redundant_font_download.html]
 support-files = redundant_font_download.sjs
 [test_reframe_pseudo_element.html]
 [test_rem_unit.html]
 [test_restyle_table_wrapper.html]
 [test_restyles_in_smil_animation.html]
new file mode 100644
--- /dev/null
+++ b/layout/style/test/test_pseudo_display_fixup.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test item blockification of pseudo-elements</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  #test {
+    display: flex;
+  }
+  #test::before, #test::after {
+    content: "test";
+    display: inline-block;
+    color: green;
+    /*
+     * NOTE(emilio): The transition rule is very intentional, to avoid testing
+     * the eagerly resolved style.
+     */
+    transition: color 1s ease;
+  }
+</style>
+<div id="test"></div>
+<script>
+test(function() {
+  document.body.offsetTop;
+  let test = document.getElementById("test");
+  assert_equals(getComputedStyle(test, "::before").display, "block");
+  assert_equals(getComputedStyle(test, "::after").display, "block");
+}, "::before and ::after pseudo-elements are blockified");
+</script>
--- a/layout/style/test/test_reframe_pseudo_element.html
+++ b/layout/style/test/test_reframe_pseudo_element.html
@@ -2,32 +2,45 @@
 <meta charset="utf-8">
 <title>
   Test for bug 1376352: We don't reframe all the time a replaced element that
   matches generated content rules.
 </title>
 <link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
 <script src="/tests/SimpleTest/SimpleTest.js"></script>
 <style>
+#flex::before,
 input::before {
   content: "Foo";
 }
 </style>
 <input type="text">
+<div id="flex"></div>
 <script>
 SimpleTest.waitForExplicitFinish();
 const utils = SpecialPowers.getDOMWindowUtils(window);
-document.documentElement.offsetTop;
-const input = document.querySelector('input');
 
-const previousConstructCount = utils.framesConstructed;
-const previousRestyleGeneration = utils.restyleGeneration;
+function testNoReframe(callback) {
+  document.documentElement.offsetTop;
+  const previousConstructCount = utils.framesConstructed;
+  const previousRestyleGeneration = utils.restyleGeneration;
+
+  callback();
 
-input.style.color = "blue";
+  document.documentElement.offsetTop;
+  isnot(previousRestyleGeneration, utils.restyleGeneration,
+        "We should have restyled");
+  is(previousConstructCount, utils.framesConstructed,
+     "We shouldn't have reframed");
+}
 
-document.documentElement.offsetTop;
-isnot(previousRestyleGeneration, utils.restyleGeneration,
-      "We should have restyled");
-is(previousConstructCount, utils.framesConstructed,
-   "We shouldn't have reframed");
+testNoReframe(function() {
+  const input = document.querySelector('input');
+  input.style.color = "blue";
+});
+
+testNoReframe(function() {
+  const flex = document.getElementById('flex');
+  flex.style.color = "blue";
+});
 
 SimpleTest.finish();
 </script>
--- a/media/mtransport/third_party/moz.build
+++ b/media/mtransport/third_party/moz.build
@@ -45,20 +45,16 @@ nICEr_non_unified_sources = [
     'nICEr/src/stun/stun_proc.c',
     'nICEr/src/stun/stun_server_ctx.c',
     'nICEr/src/stun/stun_util.c',
     'nICEr/src/stun/turn_client_ctx.c',
     'nICEr/src/util/cb_args.c',
     'nICEr/src/util/ice_util.c',
 ]
 
-# This file cannot be built in unified mode because it breaks Linux ASAN builds
-nICEr_non_unified_sources += [
-    'nICEr/src/util/mbslen.c',
-]
 nrappkit_non_unified_sources = [
     'nrappkit/src/log/r_log.c',
     'nrappkit/src/util/byteorder.c',
     'nrappkit/src/util/hex.c',
     'nrappkit/src/util/libekr/debug.c',
     'nrappkit/src/util/libekr/r_assoc.c',
     'nrappkit/src/util/libekr/r_crc32.c',
     'nrappkit/src/util/libekr/r_data.c',
--- a/media/mtransport/third_party/nICEr/IMPORT_FILES
+++ b/media/mtransport/third_party/nICEr/IMPORT_FILES
@@ -62,10 +62,8 @@
                 ./src/stun/turn_client_ctx.c
                 ./src/stun/turn_client_ctx.h
 
                 # Util
                 ./src/util/cb_args.c
                 ./src/util/cb_args.h
                 ./src/util/ice_util.c
                 ./src/util/ice_util.h
-                ./src/util/mbslen.c
-                ./src/util/mbslen.h
--- a/media/mtransport/third_party/nICEr/nicer.gyp
+++ b/media/mtransport/third_party/nICEr/nicer.gyp
@@ -114,18 +114,16 @@
                 "./src/stun/turn_client_ctx.c",
                 "./src/stun/turn_client_ctx.h",
 
                 # Util
                 "./src/util/cb_args.c",
                 "./src/util/cb_args.h",
                 "./src/util/ice_util.c",
                 "./src/util/ice_util.h",
-                "./src/util/mbslen.c",
-                "./src/util/mbslen.h",
 
 
           ],
 
           'defines' : [
               'SANITY_CHECKS',
               'USE_TURN',
               'USE_ICE',
--- a/media/mtransport/third_party/nICEr/src/stun/stun_codec.c
+++ b/media/mtransport/third_party/nICEr/src/stun/stun_codec.c
@@ -47,17 +47,16 @@ static char *RCSSTRING __UNUSED__="$Id: 
 #include <assert.h>
 #include <stddef.h>
 
 #include "nr_api.h"
 #include "stun.h"
 #include "byteorder.h"
 #include "r_crc32.h"
 #include "nr_crypto.h"
-#include "mbslen.h"
 
 #define NR_STUN_IPV4_FAMILY  0x01
 #define NR_STUN_IPV6_FAMILY  0x02
 
 #define SKIP_ATTRIBUTE_DECODE -1
 
 static int nr_stun_find_attr_info(UINT2 type, nr_stun_attr_info **info);
 
@@ -207,33 +206,56 @@ nr_stun_decode(int length, UCHAR *buf, i
    }
 
    memcpy(data, &buf[*offset], length);
    *offset += length;
 
    return 0;
 }
 
+/**
+ * The argument must be a non-null pointer to a zero-terminated string.
+ *
+ * If the argument is valid UTF-8, returns the number of code points in the
+ * string excluding the zero-terminator.
+ *
+ * If the argument is invalid UTF-8, returns a lower bound for the number of
+ * code points in the string. (If UTF-8 error handling was performed on the
+ * string, new REPLACEMENT CHARACTER code points could be introduced in
+ * a way that would increase the total number of code points compared to
+ * what this function counts.)
+ */
+size_t
+nr_count_utf8_code_points_without_validation(const char *s) {
+    size_t nchars = 0;
+    char c;
+    while ((c = *s)) {
+        if ((c & 0xC0) != 0x80) {
+            ++nchars;
+        }
+        ++s;
+    }
+    return nchars;
+}
+
 int
 nr_stun_attr_string_illegal(nr_stun_attr_info *attr_info, int len, void *data, int max_bytes, int max_chars)
 {
     int _status;
     char *s = data;
     size_t nchars;
 
     if (len > max_bytes) {
         r_log(NR_LOG_STUN, LOG_WARNING, "%s is too large: %d bytes", attr_info->name, len);
         ABORT(R_FAILED);
     }
 
     if (max_chars >= 0) {
-        if (mbslen(s, &nchars)) {
-            /* who knows what to do, just assume everything is working ok */
-        }
-        else if (nchars > max_chars) {
+        nchars = nr_count_utf8_code_points_without_validation(s);
+        if (nchars > max_chars) {
             r_log(NR_LOG_STUN, LOG_WARNING, "%s is too large: %zd characters", attr_info->name, nchars);
             ABORT(R_FAILED);
         }
     }
 
     _status = 0;
   abort:
     return _status;
--- a/media/mtransport/third_party/nICEr/src/stun/stun_codec.h
+++ b/media/mtransport/third_party/nICEr/src/stun/stun_codec.h
@@ -65,14 +65,14 @@ extern nr_stun_attr_codec nr_stun_attr_c
 extern nr_stun_attr_codec nr_stun_attr_codec_noop;
 extern nr_stun_attr_codec nr_stun_attr_codec_quoted_string;
 extern nr_stun_attr_codec nr_stun_attr_codec_string;
 extern nr_stun_attr_codec nr_stun_attr_codec_unknown_attributes;
 extern nr_stun_attr_codec nr_stun_attr_codec_xor_mapped_address;
 extern nr_stun_attr_codec nr_stun_attr_codec_xor_peer_address;
 extern nr_stun_attr_codec nr_stun_attr_codec_old_xor_mapped_address;
 
-
+size_t nr_count_utf8_code_points_without_validation(const char *s);
 int nr_stun_encode_message(nr_stun_message *msg);
 int nr_stun_decode_message(nr_stun_message *msg, int (*get_password)(void *arg, nr_stun_message *msg, Data **password), void *arg);
 
 #endif
 
deleted file mode 100644
--- a/media/mtransport/third_party/nICEr/src/util/mbslen.c
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
-Copyright (c) 2007, Adobe Systems, Incorporated
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
-* Redistributions of source code must retain the above copyright
-  notice, this list of conditions and the following disclaimer.
-
-* Redistributions in binary form must reproduce the above copyright
-  notice, this list of conditions and the following disclaimer in the
-  documentation and/or other materials provided with the distribution.
-
-* Neither the name of Adobe Systems, Network Resonance nor the names of its
-  contributors may be used to endorse or promote products derived from
-  this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-*/
-
-
-#ifdef LINUX
-#define _GNU_SOURCE 1
-#endif
-#include <string.h>
-
-#include <errno.h>
-#include <csi_platform.h>
-
-#include <assert.h>
-#include <locale.h>
-#include <stdlib.h>
-#include <wchar.h>
-#if defined(DARWIN) || defined(__DragonFly__) || defined(__FreeBSD__)
-#define HAVE_XLOCALE
-#endif
-
-#ifdef HAVE_XLOCALE
-#include <xlocale.h>
-#endif /* HAVE_XLOCALE */
-
-#include "nr_api.h"
-#include "mbslen.h"
-
-/* get number of characters in a mult-byte character string */
-int
-mbslen(const char *s, size_t *ncharsp)
-{
-#ifdef HAVE_XLOCALE
-    static locale_t loc = 0;
-    static int initialized = 0;
-#endif /* HAVE_XLOCALE */
-#ifdef WIN32
-    char *my_locale=0;
-    unsigned int i;
-#endif  /* WIN32 */
-    int _status;
-    size_t nbytes;
-    int nchars;
-    mbstate_t mbs;
-
-#ifdef HAVE_XLOCALE
-    if (! initialized) {
-        initialized = 1;
-        loc = newlocale(LC_CTYPE_MASK, "UTF-8", LC_GLOBAL_LOCALE);
-    }
-
-    if (loc == 0) {
-        /* unable to create the UTF-8 locale */
-        assert(loc != 0);  /* should never happen */
-#endif /* HAVE_XLOCALE */
-
-#ifdef WIN32
-    if (!setlocale(LC_CTYPE, 0))
-        ABORT(R_INTERNAL);
-
-    if (!(my_locale = r_strdup(setlocale(LC_CTYPE, 0))))
-        ABORT(R_NO_MEMORY);
-
-    for (i=0; i<strlen(my_locale); i++)
-        my_locale[i] = toupper(my_locale[i]);
-
-    if (!strstr(my_locale, "UTF-8") && !strstr(my_locale, "UTF8"))
-        ABORT(R_NOT_FOUND);
-#else
-    /* can't count UTF-8 characters with mbrlen if the locale isn't UTF-8 */
-    /* null-checking setlocale is required because Android */
-    char *locale = setlocale(LC_CTYPE, 0);
-    /* some systems use "utf8" instead of "UTF-8" like Fedora 17 */
-    if (!locale || (!strcasestr(locale, "UTF-8") && !strcasestr(locale, "UTF8")))
-        ABORT(R_NOT_FOUND);
-#endif
-
-#ifdef HAVE_XLOCALE
-    }
-#endif /* HAVE_XLOCALE */
-
-    memset(&mbs, 0, sizeof(mbs));
-    nchars = 0;
-
-#ifdef HAVE_XLOCALE
-    while (*s != '\0' && (nbytes = mbrlen_l(s, strlen(s), &mbs, loc)) != 0)
-#else
-    while (*s != '\0' && (nbytes = mbrlen(s, strlen(s), &mbs)) != 0)
-#endif /* HAVE_XLOCALE */
-    {
-        if (nbytes == (size_t)-1)   /* should never happen */ {
-            ABORT(R_INTERNAL);
-        }
-        if (nbytes == (size_t)-2)   /* encoding error */ {
-            ABORT(R_BAD_DATA);
-        }
-
-        s += nbytes;
-        ++nchars;
-    }
-
-    *ncharsp = nchars;
-
-    _status = 0;
-  abort:
-#ifdef WIN32
-    RFREE(my_locale);
-#endif
-    return _status;
-}
-
deleted file mode 100644
--- a/media/mtransport/third_party/nICEr/src/util/mbslen.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
-Copyright (c) 2007, Adobe Systems, Incorporated
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
-* Redistributions of source code must retain the above copyright
-  notice, this list of conditions and the following disclaimer.
-
-* Redistributions in binary form must reproduce the above copyright
-  notice, this list of conditions and the following disclaimer in the
-  documentation and/or other materials provided with the distribution.
-
-* Neither the name of Adobe Systems, Network Resonance nor the names of its
-  contributors may be used to endorse or promote products derived from
-  this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-*/
-
-
-
-#ifndef _mbslen_h
-#define _mbslen_h
-
-/* get number of characters in a mult-byte character string */
-int mbslen(const char *s, size_t *ncharsp);
-
-#endif
-
--- a/mobile/android/base/java/org/mozilla/gecko/home/TabMenuStripLayout.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/TabMenuStripLayout.java
@@ -65,17 +65,17 @@ class TabMenuStripLayout extends ThemedL
         tabContentStart = a.getDimensionPixelSize(R.styleable.TabMenuStrip_tabsMarginLeft, 0);
         activeTextColor = a.getColor(R.styleable.TabMenuStrip_activeTextColor,
                                      ResourcesCompat.getColor(getResources(), R.color.text_and_tabs_tray_grey, null));
         inactiveTextColor = a.getColorStateList(R.styleable.TabMenuStrip_inactiveTextColor);
         stripColor = a.getColorStateList(R.styleable.TabMenuStrip_stripColor);
         a.recycle();
 
         if (stripResId != -1) {
-            strip = getResources().getDrawable(stripResId);
+            strip = ResourcesCompat.getDrawable(getResources(), stripResId, null);
 
             if (stripColor != null) {
                 final int backgroundTintColor = stripColor.getColorForState(getDrawableState(), Color.TRANSPARENT);
                 DrawableCompat.setTint(strip, backgroundTintColor);
             }
         }
 
         setWillNotDraw(false);
@@ -292,16 +292,17 @@ class TabMenuStripLayout extends ThemedL
 
     @Override
     public void setPrivateMode(boolean isPrivate) {
         final boolean modeChanged = (isPrivateMode() != isPrivate);
 
         super.setPrivateMode(isPrivate);
 
         if (modeChanged && stripColor != null) {
+            strip = DrawableCompat.wrap(strip);
             final int backgroundTintColor = stripColor.getColorForState(getDrawableState(), Color.TRANSPARENT);
             DrawableCompat.setTint(strip, backgroundTintColor);
         }
     }
 
     private class ViewClickListener implements OnClickListener {
         private final int mIndex;
 
--- a/netwerk/test/gtest/TestStandardURL.cpp
+++ b/netwerk/test/gtest/TestStandardURL.cpp
@@ -211,24 +211,27 @@ MOZ_GTEST_BENCH(TestStandardURL, Normali
       ASSERT_EQ(NS_OK, Test_NormalizeIPv4(encHost3, result));
       nsAutoCString encHost4("111.159.123.220");
       ASSERT_EQ(NS_OK, Test_NormalizeIPv4(encHost4, result));
       nsAutoCString encHost5("1.160.204.200");
       ASSERT_EQ(NS_OK, Test_NormalizeIPv4(encHost5, result));
     }
 });
 
+// Bug 1394785 - ignore unstable test on OSX
+#ifndef XP_MACOSX
 // Note the five calls in the loop, so divide by 100k
 MOZ_GTEST_BENCH(TestStandardURL, NormalizePerfFails, [] {
     nsAutoCString result;
     for (int i = 0; i < 20000; i++) {
       nsAutoCString encHost("123.292.12.32");
       ASSERT_EQ(NS_ERROR_FAILURE, Test_NormalizeIPv4(encHost, result));
       nsAutoCString encHost2("83.62.12.0x13292");
       ASSERT_EQ(NS_ERROR_FAILURE, Test_NormalizeIPv4(encHost2, result));
       nsAutoCString encHost3("8.7.6.0xhello");
       ASSERT_EQ(NS_ERROR_FAILURE, Test_NormalizeIPv4(encHost3, result));
       nsAutoCString encHost4("111.159.notonmywatch.220");
       ASSERT_EQ(NS_ERROR_FAILURE, Test_NormalizeIPv4(encHost4, result));
       nsAutoCString encHost5("1.160.204.20f");
       ASSERT_EQ(NS_ERROR_FAILURE, Test_NormalizeIPv4(encHost5, result));
     }
 });
+#endif
--- a/python/mozboot/mozboot/mozillabuild.py
+++ b/python/mozboot/mozboot/mozillabuild.py
@@ -21,19 +21,19 @@ class MozillaBuildBootstrapper(BaseBoots
 
     def install_system_packages(self):
         self.install_rustup()
 
     def install_rustup(self):
         try:
             rustup_init = tempfile.gettempdir() + '/rustup-init.exe'
             self.http_download_and_save(
-                    'https://static.rust-lang.org/rustup/archive/0.2.0/i686-pc-windows-msvc/rustup-init.exe',
+                    'https://static.rust-lang.org/rustup/archive/1.6.0/i686-pc-windows-msvc/rustup-init.exe',
                     rustup_init,
-                    'a45ab7462b567dacddaf6e9e48bb43a1b9c1db4404ba77868f7d6fc685282a46')
+                    '9855b9f0b19fd83c056185e083b6b345982becc2f8c608aac14998a73bcc2937')
             self.run([rustup_init, '--no-modify-path', '--default-host',
                 'x86_64-pc-windows-msvc', '--default-toolchain', 'stable', '-y'])
             mozillabuild_dir = os.environ['MOZILLABUILD']
 
             with open(mozillabuild_dir + 'msys/etc/profile.d/profile-rustup.sh', 'wb') as f:
                 f.write('#!/bash/sh\n')
                 f.write('if test -n "$MOZILLABUILD"; then\n')
                 f.write('    WIN_HOME=$(command cd "$HOME" && pwd)\n')
--- a/servo/components/hashglobe/src/hash_map.rs
+++ b/servo/components/hashglobe/src/hash_map.rs
@@ -6,24 +6,24 @@
 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
 use self::Entry::*;
 use self::VacantEntryState::*;
 
-use borrow::Borrow;
-use cmp::max;
-use fmt::{self, Debug};
+use std::borrow::Borrow;
+use std::cmp::max;
+use std::fmt::{self, Debug};
 #[allow(deprecated)]
-use hash::{Hash, BuildHasher};
-use iter::FromIterator;
-use mem::{self, replace};
-use ops::{Deref, Index};
+use std::hash::{Hash, BuildHasher};
+use std::iter::FromIterator;
+use std::mem::{self, replace};
+use std::ops::{Deref, Index};
 
 use super::table::{self, Bucket, EmptyBucket, FullBucket, FullBucketMut, RawTable, SafeHash};
 use super::table::BucketState::{Empty, Full};
 
 use FailedAllocationError;
 
 const MIN_NONZERO_RAW_CAPACITY: usize = 32;     // must be a power of two
 
--- a/servo/components/hashglobe/src/hash_set.rs
+++ b/servo/components/hashglobe/src/hash_set.rs
@@ -3,21 +3,21 @@
 // http://rust-lang.org/COPYRIGHT.
 //
 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
-use borrow::Borrow;
-use fmt;
-use hash::{Hash, BuildHasher};
-use iter::{Chain, FromIterator};
-use ops::{BitOr, BitAnd, BitXor, Sub};
+use std::borrow::Borrow;
+use std::fmt;
+use std::hash::{Hash, BuildHasher};
+use std::iter::{Chain, FromIterator};
+use std::ops::{BitOr, BitAnd, BitXor, Sub};
 
 use super::Recover;
 use super::hash_map::{self, HashMap, Keys, RandomState};
 
 // Future Optimization (FIXME!)
 // =============================
 //
 // Iteration over zero sized values is a noop. There is no need
--- a/servo/components/hashglobe/src/lib.rs
+++ b/servo/components/hashglobe/src/lib.rs
@@ -3,18 +3,16 @@
 // http://rust-lang.org/COPYRIGHT.
 //
 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
-pub use std::*;
-
 extern crate heapsize;
 
 mod alloc;
 pub mod hash_map;
 pub mod hash_set;
 mod shim;
 mod table;
 
--- a/servo/components/hashglobe/src/table.rs
+++ b/servo/components/hashglobe/src/table.rs
@@ -4,23 +4,22 @@
 //
 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
 use alloc::{alloc, dealloc};
-use cmp;
-use hash::{BuildHasher, Hash, Hasher};
-use marker;
-use mem::{align_of, size_of};
-use mem;
-use ops::{Deref, DerefMut};
-use ptr;
+use std::cmp;
+use std::hash::{BuildHasher, Hash, Hasher};
+use std::marker;
+use std::mem::{self, align_of, size_of};
+use std::ops::{Deref, DerefMut};
+use std::ptr;
 use shim::{Unique, Shared};
 
 use self::BucketState::*;
 use FailedAllocationError;
 
 /// Integer type used for stored hash values.
 ///
 /// No more than bit_width(usize) bits are needed to select a bucket.
--- a/servo/components/style/gecko/wrapper.rs
+++ b/servo/components/style/gecko/wrapper.rs
@@ -1161,24 +1161,25 @@ impl<'le> TElement for GeckoElement<'le>
             // serves as an assertion that there are no outstanding borrows
             // when we destroy the data.
             debug_assert!({ let _ = data.borrow_mut(); true });
         }
     }
 
     #[inline]
     fn skip_root_and_item_based_display_fixup(&self) -> bool {
-        // We don't want to fix up display values of native anonymous content.
-        // Additionally, we want to skip root-based display fixup for document
-        // level native anonymous content subtree roots, since they're not
-        // really roots from the style fixup perspective.  Checking that we
-        // are NAC handles both cases.
-        self.is_native_anonymous() &&
-        (self.is_root_of_native_anonymous_subtree() ||
-         self.implemented_pseudo_element().is_some())
+        if !self.is_native_anonymous() {
+            return false;
+        }
+
+        if let Some(p) = self.implemented_pseudo_element() {
+            return p.skip_item_based_display_fixup();
+        }
+
+        self.is_root_of_native_anonymous_subtree()
     }
 
     unsafe fn set_selector_flags(&self, flags: ElementSelectorFlags) {
         debug_assert!(!flags.is_empty());
         self.set_flags(selector_flags_to_node_flags(flags));
     }
 
     fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool {
--- a/servo/components/style/invalidation/element/invalidation_map.rs
+++ b/servo/components/style/invalidation/element/invalidation_map.rs
@@ -10,17 +10,17 @@ use element_state::ElementState;
 use selector_map::{MaybeCaseInsensitiveHashMap, SelectorMap, SelectorMapEntry};
 use selector_parser::SelectorImpl;
 use selectors::attr::NamespaceConstraint;
 use selectors::parser::{Combinator, Component};
 use selectors::parser::{Selector, SelectorIter, SelectorMethods};
 use selectors::visitor::SelectorVisitor;
 use smallvec::SmallVec;
 #[cfg(feature = "gecko")]
-use stylesheets::{MallocEnclosingSizeOfFn, MallocSizeOfHash};
+use stylesheets::{MallocEnclosingSizeOfFn, MallocSizeOfFn, MallocSizeOfHash, MallocSizeOfVec};
 
 #[cfg(feature = "gecko")]
 /// Gets the element state relevant to the given `:dir` pseudo-class selector.
 pub fn dir_selector_to_state(s: &[u16]) -> ElementState {
     use element_state::{IN_LTR_STATE, IN_RTL_STATE};
 
     // Jump through some hoops to deal with our Box<[u16]> thing.
     const LTR: [u16; 4] = [b'l' as u16, b't' as u16, b'r' as u16, 0];
@@ -287,29 +287,39 @@ impl InvalidationMap {
             }
 
             index += 1; // Account for the combinator.
         }
     }
 
     /// Measures heap usage.
     #[cfg(feature = "gecko")]
-    pub fn malloc_size_of_children(&self, malloc_enclosing_size_of: MallocEnclosingSizeOfFn)
+    pub fn malloc_size_of_children(&self, malloc_size_of: MallocSizeOfFn,
+                                   malloc_enclosing_size_of: MallocEnclosingSizeOfFn)
                                    -> usize {
         // Currently we measure the HashMap storage, but not things pointed to
         // by keys and values.
         let mut n = 0;
 
         n += self.class_to_selector.malloc_shallow_size_of_hash(malloc_enclosing_size_of);
-        n += self.id_to_selector.malloc_shallow_size_of_hash(malloc_enclosing_size_of);
+        for (_, val) in self.class_to_selector.iter() {
+            n += val.malloc_shallow_size_of_vec(malloc_size_of);
+        }
 
-        n += self.state_affecting_selectors.malloc_size_of_children(malloc_enclosing_size_of);
+        n += self.id_to_selector.malloc_shallow_size_of_hash(malloc_enclosing_size_of);
+        for (_, val) in self.id_to_selector.iter() {
+            n += val.malloc_shallow_size_of_vec(malloc_size_of);
+        }
+
+        n += self.state_affecting_selectors.malloc_size_of_children(malloc_size_of,
+                                                                    malloc_enclosing_size_of);
 
         n += self.other_attribute_affecting_selectors.malloc_size_of_children(
-            malloc_enclosing_size_of);
+            malloc_size_of, malloc_enclosing_size_of);
+
         n
     }
 }
 
 /// A struct that collects invalidations for a given compound selector.
 struct CompoundSelectorDependencyCollector {
     /// The state this compound selector is affected by.
     state: ElementState,
--- a/servo/components/style/selector_map.rs
+++ b/servo/components/style/selector_map.rs
@@ -15,17 +15,17 @@ use pdqsort::sort_by;
 use precomputed_hash::PrecomputedHash;
 use rule_tree::CascadeLevel;
 use selector_parser::SelectorImpl;
 use selectors::matching::{matches_selector, MatchingContext, ElementSelectorFlags};
 use selectors::parser::{Component, Combinator, SelectorIter};
 use smallvec::{SmallVec, VecLike};
 use std::hash::{BuildHasherDefault, Hash, Hasher};
 #[cfg(feature = "gecko")]
-use stylesheets::{MallocEnclosingSizeOfFn, MallocSizeOfHash};
+use stylesheets::{MallocEnclosingSizeOfFn, MallocSizeOfFn, MallocSizeOfHash, MallocSizeOfVec};
 use stylist::Rule;
 
 /// A hasher implementation that doesn't hash anything, because it expects its
 /// input to be a suitable u32 hash.
 pub struct PrecomputedHasher {
     hash: Option<u32>,
 }
 
@@ -144,26 +144,41 @@ impl<T: 'static> SelectorMap<T> {
 
     /// Returns the number of entries.
     pub fn len(&self) -> usize {
         self.count
     }
 
     /// Measures heap usage.
     #[cfg(feature = "gecko")]
-    pub fn malloc_size_of_children(&self, malloc_enclosing_size_of: MallocEnclosingSizeOfFn)
+    pub fn malloc_size_of_children(&self, malloc_size_of: MallocSizeOfFn,
+                                   malloc_enclosing_size_of: MallocEnclosingSizeOfFn)
                                    -> usize {
-        // Currently we measure the HashMap storage, but not things pointed to
-        // by keys and values.
+        // Currently we measure the storage used by the HashMaps, and any
+        // heap-allocated SmallVec values, but not things pointed to by the T
+        // elements within the SmallVec values.
+
         let mut n = 0;
+
         n += self.id_hash.malloc_shallow_size_of_hash(malloc_enclosing_size_of);
+        for (_, val) in self.id_hash.iter() {
+            n += val.malloc_shallow_size_of_vec(malloc_size_of);
+        }
+
         n += self.class_hash.malloc_shallow_size_of_hash(malloc_enclosing_size_of);
+        for (_, val) in self.class_hash.iter() {
+            n += val.malloc_shallow_size_of_vec(malloc_size_of);
+        }
+
         n += self.local_name_hash.malloc_shallow_size_of_hash(malloc_enclosing_size_of);
+        for (_, val) in self.local_name_hash.iter() {
+            n += val.malloc_shallow_size_of_vec(malloc_size_of);
+        }
 
-        // We may measure other fields in the future if DMD says it's worth it.
+        n += self.other.malloc_shallow_size_of_vec(malloc_size_of);
 
         n
     }
 }
 
 impl SelectorMap<Rule> {
     /// Append to `rule_list` all Rules in `self` that match element.
     ///
--- a/servo/components/style/stylesheets/memory.rs
+++ b/servo/components/style/stylesheets/memory.rs
@@ -8,16 +8,17 @@
 use gecko_bindings::bindings::Gecko_HaveSeenPtr;
 #[cfg(feature = "gecko")]
 use gecko_bindings::structs::SeenPtrs;
 #[cfg(feature = "gecko")]
 use hash::HashMap;
 #[cfg(feature = "gecko")]
 use servo_arc::Arc;
 use shared_lock::SharedRwLockReadGuard;
+use smallvec::{Array, SmallVec};
 use std::collections::HashSet;
 use std::hash::{BuildHasher, Hash};
 use std::os::raw::c_void;
 
 /// Like gecko_bindings::structs::MallocSizeOf, but without the Option<>
 /// wrapper.
 ///
 /// Note that functions of this type should be called via do_malloc_size_of(),
@@ -150,16 +151,26 @@ pub trait MallocSizeOfVec {
 }
 
 impl<T> MallocSizeOfVec for Vec<T> {
     fn malloc_shallow_size_of_vec(&self, malloc_size_of: MallocSizeOfFn) -> usize {
         unsafe { do_malloc_size_of(malloc_size_of, self.as_ptr()) }
     }
 }
 
+impl<A: Array> MallocSizeOfVec for SmallVec<A> {
+    fn malloc_shallow_size_of_vec(&self, malloc_size_of: MallocSizeOfFn) -> usize {
+        if self.spilled() {
+            unsafe { do_malloc_size_of(malloc_size_of, self.as_ptr()) }
+        } else {
+            0
+        }
+    }
+}
+
 /// Trait for measuring the heap usage of a hash table.
 pub trait MallocSizeOfHash {
     /// Measure shallowly the size of the memory used within a hash table --
     /// anything pointer to by the keys and values must be measured separately,
     /// using iteration.
     fn malloc_shallow_size_of_hash(&self, malloc_enclosing_size_of: MallocEnclosingSizeOfFn)
                                    -> usize;
 }
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -1951,37 +1951,38 @@ impl CascadeData {
     }
 
     /// Measures heap usage.
     #[cfg(feature = "gecko")]
     pub fn malloc_add_size_of_children(&self, malloc_size_of: MallocSizeOfFn,
                                        malloc_enclosing_size_of: MallocEnclosingSizeOfFn,
                                        sizes: &mut ServoStyleSetSizes) {
         sizes.mStylistElementAndPseudosMaps +=
-            self.element_map.malloc_size_of_children(malloc_enclosing_size_of);
+            self.element_map.malloc_size_of_children(malloc_size_of, malloc_enclosing_size_of);
 
         for elem in self.pseudos_map.iter() {
             if let Some(ref elem) = *elem {
                 sizes.mStylistElementAndPseudosMaps +=
                     elem.malloc_shallow_size_of_box(malloc_size_of) +
-                    elem.malloc_size_of_children(malloc_enclosing_size_of)
+                    elem.malloc_size_of_children(malloc_size_of, malloc_enclosing_size_of)
             }
         }
 
         sizes.mStylistOther +=
             self.animations.malloc_shallow_size_of_hash(malloc_enclosing_size_of);
         for val in self.animations.values() {
             sizes.mStylistOther += val.malloc_size_of_children(malloc_size_of);
         }
 
         sizes.mStylistInvalidationMap +=
-            self.invalidation_map.malloc_size_of_children(malloc_enclosing_size_of);
+            self.invalidation_map.malloc_size_of_children(malloc_size_of, malloc_enclosing_size_of);
 
         sizes.mStylistRevalidationSelectors +=
-            self.selectors_for_cache_revalidation.malloc_size_of_children(malloc_enclosing_size_of);
+            self.selectors_for_cache_revalidation.malloc_size_of_children(malloc_size_of,
+                                                                          malloc_enclosing_size_of);
 
         sizes.mStylistOther +=
             self.effective_media_query_results.malloc_size_of_children(malloc_enclosing_size_of);
     }
 }
 
 impl Default for CascadeData {
     fn default() -> Self {
--- a/taskcluster/ci/source-test/doc.yml
+++ b/taskcluster/ci/source-test/doc.yml
@@ -1,9 +1,9 @@
-doc-generate:
+generate:
     description: Generate the Sphinx documentation
     platform: lint/opt
     treeherder:
         symbol: tc(Doc)
         kind: test
         tier: 1
     worker-type: aws-provisioner-v1/gecko-t-linux-xlarge
     worker:
@@ -20,17 +20,17 @@ doc-generate:
             ./mach doc --outdir docs-out --no-open --archive
         sparse-profile: sphinx-docs
     when:
         files-changed:
             - '**/*.py'
             - '**/*.rst'
             - 'tools/docs/**'
 
-doc-upload:
+upload:
     description: Generate and upload the Sphinx documentation
     platform: lint/opt
     treeherder:
         symbol: tc(DocUp)
         kind: test
         tier: 3
     run-on-projects: [mozilla-central]
     worker-type: aws-provisioner-v1/gecko-t-linux-xlarge
--- a/taskcluster/ci/source-test/kind.yml
+++ b/taskcluster/ci/source-test/kind.yml
@@ -9,17 +9,17 @@ transforms:
    - taskgraph.transforms.job:transforms
    - taskgraph.transforms.task:transforms
 
 jobs-from:
    - cram.yml
    - doc.yml
    - mocha.yml
    - mozlint.yml
-   - python-tests.yml
+   - python.yml
    - webidl.yml
 
 # This is used by run-task based tasks to lookup which build task it
 # should depend on based on its own platform.
 dependent-build-platforms:
    linux64-asan/opt:
       label: build-linux64-asan/opt
       target-name: target.tar.bz2
--- a/taskcluster/ci/source-test/mozlint.yml
+++ b/taskcluster/ci/source-test/mozlint.yml
@@ -1,9 +1,9 @@
-mozlint-eslint:
+eslint:
     description: JS lint check
     platform: lint/opt
     treeherder:
         symbol: ES
         kind: test
         tier: 1
     worker-type: aws-provisioner-v1/gecko-t-linux-xlarge
     worker:
@@ -31,17 +31,37 @@ mozlint-eslint:
             - '**/*eslintrc*'
             # The plugin implementing custom checks.
             - 'tools/lint/eslint/eslint-plugin-mozilla/**'
             - 'tools/lint/eslint/eslint-plugin-spidermonkey-js/**'
             # Other misc lint related files.
             - 'python/mozlint/**'
             - 'tools/lint/**'
 
-mozlint-py-flake8:
+py-compat:
+    description: lint for python 2/3 compatibility issues
+    platform: lint/opt
+    treeherder:
+        symbol: py-compat
+        kind: test
+        tier: 1
+    worker-type: aws-provisioner-v1/gecko-t-linux-xlarge
+    worker:
+        docker-image: {in-tree: "lint"}
+        max-run-time: 1800
+    run:
+        using: mach
+        mach: lint -l py2 -l py3 -f treeherder
+    when:
+        files-changed:
+            - '**/*.py'
+            - 'python/mozlint/**'
+            - 'tools/lint/**'
+
+py-flake8:
     description: flake8 run over the gecko codebase
     platform: lint/opt
     treeherder:
         symbol: f8
         kind: test
         tier: 1
     worker-type: aws-provisioner-v1/gecko-t-linux-xlarge
     worker:
@@ -52,58 +72,16 @@ mozlint-py-flake8:
         mach: lint -l flake8 -f treeherder
     when:
         files-changed:
             - '**/*.py'
             - '**/.flake8'
             - 'python/mozlint/**'
             - 'tools/lint/**'
 
-mozlint-yaml:
-    description: yamllint run over the gecko codebase
-    platform: lint/opt
-    treeherder:
-        symbol: yaml
-        kind: test
-        tier: 1
-    worker-type: aws-provisioner-v1/gecko-t-linux-xlarge
-    worker:
-        docker-image: {in-tree: "lint"}
-        max-run-time: 1800
-    run:
-        using: mach
-        mach: lint -l yaml -f treeherder
-    when:
-        files-changed:
-            - '**/*.yml'
-            - '**/*.yaml'
-            - '**/.ymllint'
-            - 'python/mozlint/**'
-            - 'tools/lint/**'
-
-mozlint-py-compat:
-    description: lint for python 2/3 compatibility issues
-    platform: lint/opt
-    treeherder:
-        symbol: py-compat
-        kind: test
-        tier: 1
-    worker-type: aws-provisioner-v1/gecko-t-linux-xlarge
-    worker:
-        docker-image: {in-tree: "lint"}
-        max-run-time: 1800
-    run:
-        using: mach
-        mach: lint -l py2 -l py3 -f treeherder
-    when:
-        files-changed:
-            - '**/*.py'
-            - 'python/mozlint/**'
-            - 'tools/lint/**'
-
 wptlint-gecko:
     description: web-platform-tests linter
     platform: lint/opt
     treeherder:
         symbol: W
         kind: test
         tier: 1
     worker-type: aws-provisioner-v1/gecko-t-linux-xlarge
@@ -116,8 +94,30 @@ wptlint-gecko:
     when:
         files-changed:
             - 'testing/web-platform/tests/**'
             - 'testing/web-platform/mozilla/tests/**'
             - 'testing/web-platform/meta/MANIFEST.json'
             - 'testing/web-platform/mozilla/meta/MANIFEST.json'
             - 'python/mozlint/**'
             - 'tools/lint/**'
+
+yaml:
+    description: yamllint run over the gecko codebase
+    platform: lint/opt
+    treeherder:
+        symbol: yaml
+        kind: test
+        tier: 1
+    worker-type: aws-provisioner-v1/gecko-t-linux-xlarge
+    worker:
+        docker-image: {in-tree: "lint"}
+        max-run-time: 1800
+    run:
+        using: mach
+        mach: lint -l yaml -f treeherder
+    when:
+        files-changed:
+            - '**/*.yml'
+            - '**/*.yaml'
+            - '**/.ymllint'
+            - 'python/mozlint/**'
+            - 'tools/lint/**'
rename from taskcluster/ci/source-test/python-tests.yml
rename to taskcluster/ci/source-test/python.yml
--- a/taskcluster/ci/source-test/webidl.yml
+++ b/taskcluster/ci/source-test/webidl.yml
@@ -1,9 +1,9 @@
-webidl-test:
+test:
     description: WebIDL parser tests
     platform: lint/opt
     treeherder:
         symbol: Wp
         kind: test
         tier: 1
     worker-type: aws-provisioner-v1/gecko-t-linux-xlarge
     worker:
--- a/taskcluster/taskgraph/loader/transform.py
+++ b/taskcluster/taskgraph/loader/transform.py
@@ -1,16 +1,15 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 import logging
-import itertools
 
 from ..util.templates import merge
 from ..util.yaml import load_yaml
 
 logger = logging.getLogger(__name__)
 
 
 def loader(kind, path, config, params, loaded_tasks):
@@ -28,21 +27,25 @@ def loader(kind, path, config, params, l
     kind.  More complex defaults should be implemented with custom
     transforms.
 
     Other kind implementations can use a different loader function to
     produce inputs and hand them to `transform_inputs`.
     """
     def jobs():
         defaults = config.get('job-defaults')
-        jobs = config.get('jobs', {}).iteritems()
-        jobs_from = itertools.chain.from_iterable(
-            load_yaml(path, filename).iteritems()
-            for filename in config.get('jobs-from', {}))
-        for name, job in itertools.chain(jobs, jobs_from):
+        for name, job in config.get('jobs', {}).iteritems():
             if defaults:
                 job = merge(defaults, job)
+            job['job-from'] = 'kind.yml'
             yield name, job
 
+        for filename in config.get('jobs-from', []):
+            for name, job in load_yaml(path, filename).iteritems():
+                if defaults:
+                    job = merge(defaults, job)
+                job['job-from'] = filename
+                yield name, job
+
     for name, job in jobs():
         job['name'] = name
         logger.debug("Generating tasks for {} {}".format(kind, name))
         yield job
--- a/taskcluster/taskgraph/transforms/job/__init__.py
+++ b/taskcluster/taskgraph/transforms/job/__init__.py
@@ -43,16 +43,17 @@ job_description_schema = Schema({
     Optional('name'): basestring,
     Optional('label'): basestring,
 
     # the following fields are passed directly through to the task description,
     # possibly modified by the run implementation.  See
     # taskcluster/taskgraph/transforms/task.py for the schema details.
     Required('description'): task_description_schema['description'],
     Optional('attributes'): task_description_schema['attributes'],
+    Optional('job-from'): task_description_schema['job-from'],
     Optional('dependencies'): task_description_schema['dependencies'],
     Optional('expires-after'): task_description_schema['expires-after'],
     Optional('routes'): task_description_schema['routes'],
     Optional('scopes'): task_description_schema['scopes'],
     Optional('tags'): task_description_schema['tags'],
     Optional('extra'): task_description_schema['extra'],
     Optional('treeherder'): task_description_schema['treeherder'],
     Optional('index'): task_description_schema['index'],
--- a/taskcluster/taskgraph/transforms/push_apk.py
+++ b/taskcluster/taskgraph/transforms/push_apk.py
@@ -22,16 +22,17 @@ from voluptuous import Required
 transforms = TransformSequence()
 
 push_apk_description_schema = Schema({
     # the dependent task (object) for this beetmover job, used to inform beetmover.
     Required('dependent-tasks'): object,
     Required('name'): basestring,
     Required('label'): basestring,
     Required('description'): basestring,
+    Required('job-from'): basestring,
     Required('attributes'): object,
     Required('treeherder'): object,
     Required('run-on-projects'): list,
     Required('worker-type'): basestring,
     Required('worker'): object,
     Required('scopes'): None,
     Required('deadline-after'): basestring,
 })
--- a/taskcluster/taskgraph/transforms/push_apk_breakpoint.py
+++ b/taskcluster/taskgraph/transforms/push_apk_breakpoint.py
@@ -20,16 +20,17 @@ from voluptuous import Required
 transforms = TransformSequence()
 
 push_apk_breakpoint_description_schema = Schema({
     # the dependent task (object) for this beetmover job, used to inform beetmover.
     Required('dependent-tasks'): object,
     Required('name'): basestring,
     Required('label'): basestring,
     Required('description'): basestring,
+    Required('job-from'): basestring,
     Required('attributes'): object,
     Required('worker-type'): None,
     Required('worker'): object,
     Required('treeherder'): object,
     Required('run-on-projects'): list,
     Required('deadline-after'): basestring,
 })
 
--- a/taskcluster/taskgraph/transforms/source_test.py
+++ b/taskcluster/taskgraph/transforms/source_test.py
@@ -5,16 +5,17 @@
 Source-test jobs can run on multiple platforms.  These transforms allow jobs
 with either `platform` or a list of `platforms`, and set the appropriate
 treeherder configuration and attributes for that platform.
 """
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 import copy
+import os
 
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.transforms.job import job_description_schema
 from taskgraph.util.attributes import keymatch
 from taskgraph.util.schema import (
     validate_schema,
     resolve_keyed_by,
 )
@@ -63,19 +64,23 @@ transforms = TransformSequence()
 @transforms.add
 def validate(config, jobs):
     for job in jobs:
         yield validate_schema(source_test_description_schema, job,
                               "In job {!r}:".format(job['name']))
 
 
 @transforms.add
-def set_job_try_name(config, jobs):
+def set_job_name(config, jobs):
     for job in jobs:
         job.setdefault('attributes', {}).setdefault('job_try_name', job['name'])
+
+        if 'job-from' in job and job['job-from'] != 'kind.yml':
+            from_name = os.path.splitext(job['job-from'])[0]
+            job['name'] = '{}-{}'.format(from_name, job['name'])
         yield job
 
 
 @transforms.add
 def expand_platforms(config, jobs):
     for job in jobs:
         if isinstance(job['platform'], basestring):
             yield job
--- a/taskcluster/taskgraph/transforms/task.py
+++ b/taskcluster/taskgraph/transforms/task.py
@@ -50,16 +50,19 @@ task_description_schema = Schema({
     Required('label'): basestring,
 
     # description of the task (for metadata)
     Required('description'): basestring,
 
     # attributes for this task
     Optional('attributes'): {basestring: object},
 
+    # relative path (from config.path) to the file task was defined in
+    Optional('job-from'): basestring,
+
     # dependencies of this task, keyed by name; these are passed through
     # verbatim and subject to the interpretation of the Task's get_dependencies
     # method.
     Optional('dependencies'): {basestring: object},
 
     # expiration and deadline times, relative to task creation, with units
     # (e.g., "14 days").  Defaults are set based on the project.
     Optional('expires-after'): basestring,
--- a/testing/geckodriver/src/capabilities.rs
+++ b/testing/geckodriver/src/capabilities.rs
@@ -154,16 +154,23 @@ impl<'a> BrowserCapabilities for Firefox
         Ok(true)
     }
 
     fn validate_custom(&self, name: &str,  value: &Json) -> WebDriverResult<()> {
         if !name.starts_with("moz:") {
             return Ok(())
         }
         match name {
+            "moz:webdriverClick" => {
+                if !value.is_boolean() {
+                    return Err(WebDriverError::new(
+                        ErrorStatus::InvalidArgument,
+                        "moz:webdriverClick is not a boolean"));
+                }
+            }
             "moz:firefoxOptions" => {
                 let data = try_opt!(value.as_object(),
                                     ErrorStatus::InvalidArgument,
                                     "moz:firefoxOptions is not an object");
                 for (key, value) in data.iter() {
                     match &**key {
                         "binary" => {
                             if !value.is_string() {
--- a/testing/marionette/addon.js
+++ b/testing/marionette/addon.js
@@ -25,16 +25,53 @@ addon.Errors = {
   [-5]: "ERROR_SIGNEDSTATE_REQUIRED: The addon must be signed and isn't.",
 };
 
 function lookupError(code) {
   let msg = addon.Errors[code];
   return new UnknownError(msg);
 }
 
+async function installAddon(file) {
+  let install = await AddonManager.getInstallForFile(file);
+
+  return new Promise((resolve, reject) => {
+    if (install.error != 0) {
+      reject(new UnknownError(lookupError(install.error)));
+    }
+
+    let addonId = install.addon.id;
+
+    let success = install => {
+      if (install.addon.id === addonId) {
+        install.removeListener(listener);
+        resolve(install.addon);
+      }
+    };
+
+    let fail = install => {
+      if (install.addon.id === addonId) {
+        install.removeListener(listener);
+        reject(new UnknownError(lookupError(install.error)));
+      }
+    };
+
+    let listener = {
+      onDownloadCancelled: fail,
+      onDownloadFailed: fail,
+      onInstallCancelled: fail,
+      onInstallFailed: fail,
+      onInstallEnded: success,
+    };
+
+    install.addListener(listener);
+    install.install();
+  });
+}
+
 /**
  * Install a Firefox addon.
  *
  * If the addon is restartless, it can be used right away.  Otherwise a
  * restart is required.
  *
  * Temporary addons will automatically be uninstalled on shutdown and
  * do not need to be signed, though they must be restartless.
@@ -45,61 +82,61 @@ function lookupError(code) {
  *     True to install the addon temporarily, false (default) otherwise.
  *
  * @return {Promise.<string>}
  *     Addon ID.
  *
  * @throws {UnknownError}
  *     If there is a problem installing the addon.
  */
-addon.install = function(path, temporary = false) {
-  return new Promise((resolve, reject) => {
-    let file = new FileUtils.File(path);
-
-    let listener = {
-      onInstallEnded(install, addon) {
-        resolve(addon.id);
-      },
-
-      onInstallFailed(install) {
-        reject(lookupError(install.error));
-      },
+addon.install = async function(path, temporary = false) {
+  let file = new FileUtils.File(path);
+  let addon;
 
-      onInstalled(addon) {
-        AddonManager.removeAddonListener(listener);
-        resolve(addon.id);
-      },
-    };
+  try {
+    if (temporary) {
+      addon = await AddonManager.installTemporaryAddon(file);
+    } else {
+      addon = await installAddon(file);
+    }
+  } catch (e) {
+    throw new UnknownError(
+        `Could not install add-on at '${path}': ${e.message}`);
+  }
 
-    if (!temporary) {
-      AddonManager.getInstallForFile(file, function(aInstall) {
-        if (aInstall.error !== 0) {
-          reject(lookupError(aInstall.error));
-        }
-        aInstall.addListener(listener);
-        aInstall.install();
-      });
-    } else {
-      AddonManager.addAddonListener(listener);
-      AddonManager.installTemporaryAddon(file);
-    }
-  });
+  return addon.id;
 };
 
 /**
  * Uninstall a Firefox addon.
  *
  * If the addon is restartless it will be uninstalled right away.
  * Otherwise, Firefox must be restarted for the change to take effect.
  *
  * @param {string} id
  *     ID of the addon to uninstall.
  *
  * @return {Promise}
+ *
+ * @throws {UnknownError}
+ *     If there is a problem uninstalling the addon.
  */
-addon.uninstall = function(id) {
-  return new Promise(resolve => {
-    AddonManager.getAddonByID(id, function(addon) {
-      addon.uninstall();
-      resolve();
-    });
+addon.uninstall = async function(id) {
+  return AddonManager.getAddonByID(id).then(addon => {
+    let listener = {
+      onOperationCancelled: addon => {
+        if (addon.id === id) {
+          AddonManager.removeAddonListener(listener);
+          throw new UnknownError(`Uninstall of ${id} has been canceled`);
+        }
+      },
+      onUninstalled: addon => {
+        if (addon.id === id) {
+          AddonManager.removeAddonListener(listener);
+          Promise.resolve();
+        }
+      },
+    };
+
+    AddonManager.addAddonListener(listener);
+    addon.uninstall();
   });
 };
--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -668,22 +668,21 @@ GeckoDriver.prototype.listeningPromise =
  *   are implicitly trusted on navigation for the duration of the session.
  *
  *  <dt><code>timeouts</code> (Timeouts object)
  *  <dd>Describes the timeouts imposed on certian session operations.
  *
  *  <dt><code>proxy</code> (Proxy object)
  *  <dd>Defines the proxy configuration.
  *
- *  <dt><code>specificationLevel</code> (number)
- *  <dd>If set to 1, a WebDriver conforming <i>WebDriver::ElementClick</i>
- *   implementation will be used.
- *
  *  <dt><code>moz:accessibilityChecks</code> (boolean)
  *  <dd>Run a11y checks when clicking elements.
+ *
+ *  <dt><code>moz:webdriverClick</code> (boolean)
+ *  <dd>Use a WebDriver conforming <i>WebDriver::ElementClick</i>.
  * </dl>
  *
  * <h4>Timeouts object</h4>
  *
  * <dl>
  *  <dt><code>script</code> (number)
  *  <dd>Determines when to interrupt a script that is being evaluates.
  *
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_addons.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_addons.py
@@ -1,58 +1,93 @@
 # 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/.
 
 import os
 
 from marionette_driver.addons import Addons, AddonInstallException
-from marionette_harness import MarionetteTestCase, skip
+from marionette_harness import MarionetteTestCase
 
 
 here = os.path.abspath(os.path.dirname(__file__))
 
 
 class TestAddons(MarionetteTestCase):
 
     def setUp(self):
-        MarionetteTestCase.setUp(self)
+        super(TestAddons, self).setUp()
+
         self.addons = Addons(self.marionette)
+        self.preinstalled_addons = self.all_addon_ids
+
+    def tearDown(self):
+        self.reset_addons()
+
+        super(TestAddons, self).tearDown()
 
     @property
     def all_addon_ids(self):
-        with self.marionette.using_context('chrome'):
+        with self.marionette.using_context("chrome"):
             addons = self.marionette.execute_async_script("""
               Components.utils.import("resource://gre/modules/AddonManager.jsm");
-              AddonManager.getAllAddons(function(addons){
-                let ids = addons.map(function(x) {
-                  return x.id;
-                });
+              AddonManager.getAllAddons(function(addons) {
+                let ids = addons.map(x => x.id);
                 marionetteScriptFinished(ids);
               });
             """)
 
-        return addons
+        return set(addons)
 
-    def test_install_and_remove_temporary_unsigned_addon(self):
-        addon_path = os.path.join(here, 'webextension-unsigned.xpi')
+    def reset_addons(self):
+        with self.marionette.using_context("chrome"):
+            for addon in (self.all_addon_ids - self.preinstalled_addons):
+                addon_id = self.marionette.execute_async_script("""
+                  Components.utils.import("resource://gre/modules/AddonManager.jsm");
+                  return new Promise(resolve => {
+                    AddonManager.getAddonByID(arguments[0], function(addon) {
+                      addon.uninstall();
+                      marionetteScriptFinished(addon.id);
+                    });
+                  });
+                """, script_args=(addon,))
+                self.assertEqual(addon_id, addon,
+                                 msg="Failed to uninstall {}".format(addon))
+
+    def test_temporary_install_and_remove_unsigned_addon(self):
+        addon_path = os.path.join(here, "webextension-unsigned.xpi")
 
         addon_id = self.addons.install(addon_path, temp=True)
         self.assertIn(addon_id, self.all_addon_ids)
+        self.assertEqual(addon_id, "{d3e7c1f1-2e35-4a49-89fe-9f46eb8abf0a}")
 
         self.addons.uninstall(addon_id)
         self.assertNotIn(addon_id, self.all_addon_ids)
 
-    def test_install_unsigned_addon(self):
-        addon_path = os.path.join(here, 'webextension-unsigned.xpi')
+    def test_temporary_install_invalid_addon(self):
+        addon_path = os.path.join(here, "webextension-invalid.xpi")
+
+        with self.assertRaises(AddonInstallException):
+            self.addons.install(addon_path, temp=True)
+        self.assertNotIn("{d3e7c1f1-2e35-4a49-89fe-9f46eb8abf0a}", self.all_addon_ids)
+
+    def test_install_and_remove_signed_addon(self):
+        addon_path = os.path.join(here, "webextension-signed.xpi")
+
+        addon_id = self.addons.install(addon_path)
+        self.assertIn(addon_id, self.all_addon_ids)
+        self.assertEqual(addon_id, "{d3e7c1f1-2e35-4a49-89fe-9f46eb8abf0a}")
+
+        self.addons.uninstall(addon_id)
+        self.assertNotIn(addon_id, self.all_addon_ids)
+
+    def test_install_invalid_addon(self):
+        addon_path = os.path.join(here, "webextension-invalid.xpi")
 
         with self.assertRaises(AddonInstallException):
             self.addons.install(addon_path)
-
-    @skip("Need to get the test extension signed")
-    def test_install_and_remove_signed_addon(self):
-        addon_path = os.path.join(here, 'mn-restartless-signed.xpi')
+        self.assertNotIn("{d3e7c1f1-2e35-4a49-89fe-9f46eb8abf0a}", self.all_addon_ids)
 
-        addon_id = self.addons.install(addon_path)
-        self.assertIn(addon_id, self.all_addon_ids)
+    def test_install_unsigned_addon_fails(self):
+        addon_path = os.path.join(here, "webextension-unsigned.xpi")
 
-        self.addons.uninstall(addon_id)
-        self.assertNotIn(addon_id, self.all_addon_ids)
+        with self.assertRaises(AddonInstallException):
+            self.addons.install(addon_path)
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_capabilities.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_capabilities.py
@@ -62,24 +62,24 @@ class TestCapabilities(MarionetteTestCas
                 current_profile = self.marionette.instance.runner.device.app_ctx.remote_profile
             else:
                 current_profile = convert_path(self.marionette.instance.runner.profile.profile)
             self.assertEqual(convert_path(str(self.caps["moz:profile"])), current_profile)
             self.assertEqual(convert_path(str(self.marionette.profile)), current_profile)
 
         self.assertIn("moz:accessibilityChecks", self.caps)
         self.assertFalse(self.caps["moz:accessibilityChecks"])
-        self.assertIn("specificationLevel", self.caps)
-        self.assertEqual(self.caps["specificationLevel"], 0)
+        self.assertIn("moz:webdriverClick", self.caps)
+        self.assertEqual(self.caps["moz:webdriverClick"], False)
 
-    def test_set_specification_level(self):
+    def test_set_webdriver_click(self):
         self.marionette.delete_session()
-        self.marionette.start_session({"specificationLevel": 2})
+        self.marionette.start_session({"moz:webdriverClick": True})
         caps = self.marionette.session_capabilities
-        self.assertEqual(2, caps["specificationLevel"])
+        self.assertEqual(True, caps["moz:webdriverClick"])
 
     def test_we_get_valid_uuid4_when_creating_a_session(self):
         self.assertNotIn("{", self.marionette.session_id,
                          "Session ID has {{}} in it: {}".format(
                              self.marionette.session_id))
 
 
 class TestCapabilityMatching(MarionetteTestCase):
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_click.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_click.py
@@ -127,17 +127,17 @@ class TestLegacyClick(MarionetteTestCase
 class TestClick(TestLegacyClick):
     """Uses WebDriver specification compatible element interactability
     checks.
     """
 
     def setUp(self):
         TestLegacyClick.setUp(self)
         self.marionette.delete_session()
-        self.marionette.start_session({"specificationLevel": 1})
+        self.marionette.start_session({"moz:webdriverClick": True})
 
     def test_click_element_obscured_by_absolute_positioned_element(self):
         self.marionette.navigate(obscured_overlay)
         overlay = self.marionette.find_element(By.ID, "overlay")
         obscured = self.marionette.find_element(By.ID, "obscured")
 
         overlay.click()
         with self.assertRaises(errors.ElementClickInterceptedException):
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..bd1177462ea7ba987f2b31c3f9ff9b200edb5e90
GIT binary patch
literal 295
zc$^FHW@Zs#U|`^2h^i9z-jTV)vK+`;0mQrvG7Pzid6{Xc#U*-K#rb)mA)E}%yBp?4
z&H>`m3T_5QmamKq3}EfK-Hv>R6a-wq&q*@MHaxm$+r2cdW<@2{))MC(yBF!*{k!LT
zohXCE%aaw%Ukt^$7`dJ|wdd)iZ~htbN9sSjb^cHFtA#%{^l2`A!2316ZZ}s&`saxK
zmv7GtxBhfhWBHTL<D0K6Nw+-P_w>z|*6E#M<^j$b7hHt(1H2iT<d|`}UxEPyfKFs+
dXaup)e8URy4Vq5^yjj^G+87yvf%HDGApkgdUgrP+
--- a/testing/marionette/listener.js
+++ b/testing/marionette/listener.js
@@ -1363,17 +1363,17 @@ function clickElement(msg) {
     if (target === "_blank") {
       loadEventExpected = false;
     }
 
     loadListener.navigate(() => {
       return interaction.clickElement(
           seenEls.get(id),
           capabilities.get("moz:accessibilityChecks"),
-          capabilities.get("specificationLevel") >= 1
+          capabilities.get("moz:webdriverClick")
       );
     }, commandID, pageTimeout, loadEventExpected, true);
 
   } catch (e) {
     sendError(e, commandID);
   }
 }
 
--- a/testing/marionette/session.js
+++ b/testing/marionette/session.js
@@ -346,21 +346,21 @@ session.Capabilities = class extends Map
       ["acceptInsecureCerts", false],
       ["timeouts", new session.Timeouts()],
       ["proxy", new session.Proxy()],
 
       // features
       ["rotatable", appinfo.name == "B2G"],
 
       // proprietary
-      ["specificationLevel", 0],
+      ["moz:accessibilityChecks", false],
+      ["moz:headless", Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo).isHeadless],
       ["moz:processID", Services.appinfo.processID],
       ["moz:profile", maybeProfile()],
-      ["moz:accessibilityChecks", false],
-      ["moz:headless", Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo).isHeadless],
+      ["moz:webdriverClick", false],
     ]);
   }
 
   /**
    * @param {string} key
    *     Capability key.
    * @param {(string|number|boolean)} value
    *     JSON-safe capability value.
@@ -437,19 +437,19 @@ session.Capabilities = class extends Map
           matched.set("proxy", proxy);
           break;
 
         case "timeouts":
           let timeouts = session.Timeouts.fromJSON(v);
           matched.set("timeouts", timeouts);
           break;
 
-        case "specificationLevel":
-          assert.positiveInteger(v);
-          matched.set("specificationLevel", v);
+        case "moz:webdriverClick":
+          assert.boolean(v);
+          matched.set("moz:webdriverClick", v);
           break;
 
         case "moz:accessibilityChecks":
           assert.boolean(v);
           matched.set("moz:accessibilityChecks", v);
           break;
       }
     }
--- a/testing/marionette/test_session.js
+++ b/testing/marionette/test_session.js
@@ -305,20 +305,20 @@ add_test(function test_Capabilities_ctor
   ok(caps.has("platformVersion"));
   equal(session.PageLoadStrategy.Normal, caps.get("pageLoadStrategy"));
   equal(false, caps.get("acceptInsecureCerts"));
   ok(caps.get("timeouts") instanceof session.Timeouts);
   ok(caps.get("proxy") instanceof session.Proxy);
 
   ok(caps.has("rotatable"));
 
-  equal(0, caps.get("specificationLevel"));
+  equal(false, caps.get("moz:accessibilityChecks"));
   ok(caps.has("moz:processID"));
   ok(caps.has("moz:profile"));
-  equal(false, caps.get("moz:accessibilityChecks"));
+  equal(false, caps.get("moz:webdriverClick"));
 
   run_next_test();
 });
 
 add_test(function test_Capabilities_toString() {
   equal("[object session.Capabilities]", new session.Capabilities().toString());
 
   run_next_test();
@@ -334,20 +334,20 @@ add_test(function test_Capabilities_toJS
   equal(caps.get("platformVersion"), json.platformVersion);
   equal(caps.get("pageLoadStrategy"), json.pageLoadStrategy);
   equal(caps.get("acceptInsecureCerts"), json.acceptInsecureCerts);
   deepEqual(caps.get("timeouts").toJSON(), json.timeouts);
   equal(undefined, json.proxy);
 
   equal(caps.get("rotatable"), json.rotatable);
 
-  equal(caps.get("specificationLevel"), json.specificationLevel);
+  equal(caps.get("moz:accessibilityChecks"), json["moz:accessibilityChecks"]);
   equal(caps.get("moz:processID"), json["moz:processID"]);
   equal(caps.get("moz:profile"), json["moz:profile"]);
-  equal(caps.get("moz:accessibilityChecks"), json["moz:accessibilityChecks"]);
+  equal(caps.get("moz:webdriverClick"), json["moz:webdriverClick"]);
 
   run_next_test();
 });
 
 add_test(function test_Capabilities_fromJSON() {
   const {fromJSON} = session.Capabilities;
 
   // plain
@@ -376,21 +376,21 @@ add_test(function test_Capabilities_from
   let proxyConfig = {proxyType: "manual"};
   caps = fromJSON({proxy: proxyConfig});
   equal("manual", caps.get("proxy").proxyType);
 
   let timeoutsConfig = {implicit: 123};
   caps = fromJSON({timeouts: timeoutsConfig});
   equal(123, caps.get("timeouts").implicit);
 
-  equal(0, caps.get("specificationLevel"));
-  caps = fromJSON({specificationLevel: 123});
-  equal(123, caps.get("specificationLevel"));
-  Assert.throws(() => fromJSON({specificationLevel: "foo"}));
-  Assert.throws(() => fromJSON({specificationLevel: -1}));
+  equal(false, caps.get("moz:webdriverClick"));
+  caps = fromJSON({"moz:webdriverClick": true});
+  equal(true, caps.get("moz:webdriverClick"));
+  Assert.throws(() => fromJSON({"moz:webdriverClick": "foo"}));
+  Assert.throws(() => fromJSON({"moz:webdriverClick": 1}));
 
   caps = fromJSON({"moz:accessibilityChecks": true});
   equal(true, caps.get("moz:accessibilityChecks"));
   caps = fromJSON({"moz:accessibilityChecks": false});
   equal(false, caps.get("moz:accessibilityChecks"));
   Assert.throws(() => fromJSON({"moz:accessibilityChecks": "foo"}));
 
   run_next_test();
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/cssom/getComputedStyle-pseudo.html.ini
@@ -0,0 +1,6 @@
+[getComputedStyle-pseudo.html]
+  type: testharness
+  bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1396844
+  [Item-based blockification of nonexistent pseudo-elements]
+    expected:
+      if stylo: FAIL
--- a/testing/web-platform/tests/cssom/getComputedStyle-pseudo.html
+++ b/testing/web-platform/tests/cssom/getComputedStyle-pseudo.html
@@ -12,33 +12,43 @@
 #contents {
   display: contents;
   border: 10px solid red;
 }
 
 #test::before,
 #test::after,
 #contents::before,
-#contents::after {
+#contents::after,
+#flex::before,
+#flex::after {
   content: " ";
   width: 50%;
   height: 10px;
   display: block;
 }
 #none {
   display: none;
 }
 #none::before,
 #none::after {
   content: "Foo";
 }
+#flex {
+  display: flex;
+}
+#flex-no-pseudo {
+  display: flex;
+}
 </style>
 <div id="test">
   <div id="contents"></div>
   <div id="none"></div>
+  <div id="flex"></div>
+  <div id="flex-no-pseudo"></div>
 </div>
 <script>
 test(function() {
   var div = document.getElementById('test');
   [":before", ":after"].forEach(function(pseudo) {
     assert_equals(getComputedStyle(div, pseudo).width, "50px");
   });
 }, "Resolution of width is correct for ::before and ::after pseudo-elements");
@@ -59,9 +69,23 @@ test(function() {
 }, "Resolution of nonexistent pseudo-element styles");
 test(function() {
   var none = document.getElementById('none');
   [":before", ":after"].forEach(function(pseudo) {
     assert_equals(getComputedStyle(none, pseudo).content, "\"Foo\"",
                   "Pseudo-styles of display: none elements should be correct");
   });
 }, "Resolution of pseudo-element styles in display: none elements");
+test(function() {
+  var flex = document.getElementById('flex');
+  [":before", ":after"].forEach(function(pseudo) {
+    assert_equals(getComputedStyle(flex, pseudo).display, "block",
+                  "Pseudo-styles of display: flex elements should get blockified");
+  });
+}, "Item-based blockification of pseudo-elements");
+test(function() {
+  var flexNoPseudo = document.getElementById('flex-no-pseudo');
+  [":before", ":after"].forEach(function(pseudo) {
+    assert_equals(getComputedStyle(flexNoPseudo, pseudo).display, "block",
+                  "Pseudo-styles of display: flex elements should get blockified");
+  });
+}, "Item-based blockification of nonexistent pseudo-elements");
 </script>
--- a/toolkit/components/extensions/ExtensionChild.jsm
+++ b/toolkit/components/extensions/ExtensionChild.jsm
@@ -81,30 +81,35 @@ function injectAPI(source, dest) {
  * A finalization witness helper that wraps a sendMessage response and
  * guarantees to either get the promise resolved, or rejected when the
  * wrapped promise goes out of scope.
  *
  * Holding a reference to a returned StrongPromise doesn't prevent the
  * wrapped promise from being garbage collected.
  */
 const StrongPromise = {
-  wrap(promise, id) {
+  wrap(promise, channelId, location) {
     return new Promise((resolve, reject) => {
-      const witness = finalizationService.make("extensions-sendMessage-witness", id);
+      const tag = `${channelId}|${location}`;
+      const witness = finalizationService.make("extensions-sendMessage-witness", tag);
       promise.then(value => {
         witness.forget();
         resolve(value);
       }, error => {
         witness.forget();
         reject(error);
       });
     });
   },
-  observe(subject, topic, id) {
-    MessageChannel.abortChannel(id, {message: "Response handle went out of scope"});
+  observe(subject, topic, tag) {
+    const pos = tag.indexOf("|");
+    const channel = tag.substr(0, pos);
+    const location = tag.substr(pos + 1);
+    const message = `Promised response from onMessage listener at ${location} went out of scope`;
+    MessageChannel.abortChannel(channel, {message});
   },
 };
 Services.obs.addObserver(StrongPromise, "extensions-sendMessage-witness");
 
 /**
  * Abstraction for a Port object in the extension API.
  *
  * @param {BaseContext} context The context that owns this port.
@@ -375,16 +380,18 @@ class Messenger {
 
   sendNativeMessage(messageManager, msg, recipient, responseCallback) {
     msg = NativeApp.encodeMessage(this.context, msg);
     return this.sendMessage(messageManager, msg, recipient, responseCallback);
   }
 
   _onMessage(name, filter) {
     return new EventManager(this.context, name, fire => {
+      const [location] = new this.context.cloneScope.Error().stack.split("\n", 1);
+
       let listener = {
         messageFilterPermissive: this.optionalFilter,
         messageFilterStrict: this.filter,
 
         filterMessage: (sender, recipient) => {
           // Ignore the message if it was sent by this Messenger.
           return (sender.contextId !== this.context.contextId &&
                   filter(sender, recipient));
@@ -411,19 +418,19 @@ class Messenger {
           sendResponse = Cu.exportFunction(sendResponse, this.context.cloneScope);
 
           // Note: We intentionally do not use runSafe here so that any
           // errors are propagated to the message sender.
           let result = fire.raw(message, sender, sendResponse);
           message = null;
 
           if (result instanceof this.context.cloneScope.Promise) {
-            return StrongPromise.wrap(result, channelId);
+            return StrongPromise.wrap(result, channelId, location);
           } else if (result === true) {
-            return StrongPromise.wrap(promise, channelId);
+            return StrongPromise.wrap(promise, channelId, location);
           }
           return response;
         },
       };
 
       MessageChannel.addListener(this.messageManagers, "Extension:Message", listener);
       return () => {
         MessageChannel.removeListener(this.messageManagers, "Extension:Message", listener);
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/ext-clipboard.js
@@ -0,0 +1,84 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+XPCOMUtils.defineLazyServiceGetter(this, "imgTools",
+                                   "@mozilla.org/image/tools;1", "imgITools");
+
+const ArrayBufferInputStream = Components.Constructor(
+    "@mozilla.org/io/arraybuffer-input-stream;1", "nsIArrayBufferInputStream");
+const SupportsInterfacePointer = Components.Constructor(
+    "@mozilla.org/supports-interface-pointer;1", "nsISupportsInterfacePointer");
+const Transferable = Components.Constructor(
+    "@mozilla.org/widget/transferable;1", "nsITransferable");
+
+this.clipboard = class extends ExtensionAPI {
+  getAPI(context) {
+    return {
+      clipboard: {
+        async setImageData(imageData, imageType) {
+          if (AppConstants.platform == "android") {
+            return Promise.reject({message: "Writing images to the clipboard is not supported on Android"});
+          }
+          let mimeType = `image/${imageType}`;
+          let input = new ArrayBufferInputStream();
+          input.setData(imageData, 0, imageData.byteLength);
+
+          let container;
+          try {
+            container = imgTools.decodeImage(input, mimeType);
+          } catch (e) {
+            return Promise.reject({message: `Data is not a valid ${imageType} image`});
+          }
+
+          // Other applications can only access the copied image once the data
+          // is exported via the platform-specific clipboard APIs:
+          // nsClipboard::SelectionGetEvent (widget/gtk/nsClipboard.cpp)
+          // nsClipboard::PasteDictFromTransferable (widget/cocoa/nsClipboard.mm)
+          // nsDataObj::GetDib (widget/windows/nsDataObj.cpp)
+          //
+          // The common protocol for exporting a nsITransferable as an image is:
+          // - Use nsITransferable::GetTransferData to fetch the stored data.
+          // - QI a nsISupportsInterfacePointer and get the underlying pointer.
+          // - QI imgIContainer on the pointer.
+          // - Convert the image to the native clipboard format.
+          //
+          // Below we create a nsITransferable in the above format.
+          let imgPtr = new SupportsInterfacePointer();
+          imgPtr.data = container;
+          let transferable = new Transferable();
+          transferable.init(null);
+          transferable.addDataFlavor(mimeType);
+
+          // Internal consumers expect the image data to be stored as a
+          // nsIInputStream. On Linux and Windows, pasted data is directly
+          // retrieved from the system's native clipboard, and made available
+          // as a nsIInputStream.
+          //
+          // On macOS, nsClipboard::GetNativeClipboardData (nsClipboard.mm) uses
+          // a cached copy of nsITransferable if available, e.g. when the copy
+          // was initiated by the same browser instance. Consequently, the
+          // transferable still holds a nsISupportsInterfacePointer pointer
+          // instead of a nsIInputStream, and logic that assumes the data to be
+          // a nsIInputStream instance fails.
+          // For example HTMLEditor::InsertObject (HTMLEditorDataTransfer.cpp)
+          // and DataTransferItem::FillInExternalData (DataTransferItem.cpp).
+          //
+          // As a work-around, we force nsClipboard::GetNativeClipboardData to
+          // ignore the cached image data, by passing zero as the length
+          // parameter to transferable.setTransferData. When the length is zero,
+          // nsITransferable::GetTransferData will return NS_ERROR_FAILURE and
+          // conveniently nsClipboard::GetNativeClipboardData will then fall
+          // back to retrieving the data directly from the system's clipboard.
+          //
+          // Note that the length itself is not really used if the data is not
+          // a string type, so the actual value does not matter.
+          transferable.setTransferData(mimeType, imgPtr, 0);
+
+          Services.clipboard.setData(
+            transferable, null, Services.clipboard.kGlobalClipboard);
+        },
+      },
+    };
+  }
+};
--- a/toolkit/components/extensions/ext-toolkit.json
+++ b/toolkit/components/extensions/ext-toolkit.json
@@ -19,16 +19,24 @@
   "browserSettings": {
     "url": "chrome://extensions/content/ext-browserSettings.js",
     "schema": "chrome://extensions/content/schemas/browser_settings.json",
     "scopes": ["addon_parent"],
     "paths": [
       ["browserSettings"]
     ]
   },
+  "clipboard": {
+    "url": "chrome://extensions/content/ext-clipboard.js",
+    "schema": "chrome://extensions/content/schemas/clipboard.json",
+    "scopes": ["addon_parent"],
+    "paths": [
+      ["clipboard"]
+    ]
+  },
   "contextualIdentities": {
     "url": "chrome://extensions/content/ext-contextualIdentities.js",
     "schema": "chrome://extensions/content/schemas/contextual_identities.json",
     "scopes": ["addon_parent"],
     "events": ["startup"],
     "permissions": ["contextualIdentities"],
     "paths": [
       ["contextualIdentities"]
--- a/toolkit/components/extensions/jar.mn
+++ b/toolkit/components/extensions/jar.mn
@@ -5,16 +5,17 @@
 toolkit.jar:
 % content extensions %content/extensions/
     content/extensions/dummy.xul
     content/extensions/ext-alarms.js
     content/extensions/ext-backgroundPage.js
     content/extensions/ext-browser-content.js
     content/extensions/ext-browserSettings.js
     content/extensions/ext-contextualIdentities.js
+    content/extensions/ext-clipboard.js
     content/extensions/ext-cookies.js
     content/extensions/ext-downloads.js
     content/extensions/ext-extension.js
     content/extensions/ext-i18n.js
     content/extensions/ext-idle.js
     content/extensions/ext-management.js
     content/extensions/ext-notifications.js
     content/extensions/ext-permissions.js
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/schemas/clipboard.json
@@ -0,0 +1,30 @@
+[
+  {
+    "namespace": "clipboard",
+    "description": "Offers the ability to write to the clipboard. Reading is not supported because the clipboard can already be read through the standard web platform APIs.",
+    "permissions": ["clipboardWrite"],
+    "functions": [
+      {
+        "name": "setImageData",
+        "type": "function",
+        "description": "Copy an image to the clipboard. The image is re-encoded before it is written to the clipboard. If the image is invalid, the clipboard is not modified.",
+        "async": true,
+        "parameters": [
+          {
+            "type": "object",
+            "isInstanceOf": "ArrayBuffer",
+            "additionalProperties": true,
+            "name": "imageData",
+            "description": "The image data to be copied."
+          },
+          {
+            "type": "string",
+            "name": "imageType",
+            "enum": ["jpeg", "png"],
+            "description": "The type of imageData."
+          }
+        ]
+      }
+    ]
+  }
+]
--- a/toolkit/components/extensions/schemas/jar.mn
+++ b/toolkit/components/extensions/schemas/jar.mn
@@ -1,16 +1,17 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 toolkit.jar:
 % content extensions %content/extensions/
     content/extensions/schemas/alarms.json
     content/extensions/schemas/browser_settings.json
+    content/extensions/schemas/clipboard.json
     content/extensions/schemas/contextual_identities.json
     content/extensions/schemas/cookies.json
     content/extensions/schemas/downloads.json
     content/extensions/schemas/events.json
     content/extensions/schemas/experiments.json
     content/extensions/schemas/extension.json
     content/extensions/schemas/extension_types.json
     content/extensions/schemas/extension_protocol_handlers.json
--- a/toolkit/components/extensions/test/mochitest/mochitest-common.ini
+++ b/toolkit/components/extensions/test/mochitest/mochitest-common.ini
@@ -53,16 +53,17 @@ support-files =
   lorem.html.gz^headers^
   return_headers.sjs
   slow_response.sjs
   webrequest_worker.js
   !/toolkit/components/passwordmgr/test/authenticate.sjs
   !/dom/tests/mochitest/geolocation/network_geolocation.sjs
 
 [test_ext_clipboard.html]
+[test_ext_clipboard_image.html]
 # skip-if = # disabled test case with_permission_allow_copy, see inline comment.
 [test_ext_inIncognitoContext_window.html]
 skip-if = os == 'android' # Android does not support multiple windows.
 [test_ext_geturl.html]
 [test_ext_background_canvas.html]
 [test_ext_content_security_policy.html]
 [test_ext_contentscript_api_injection.html]
 [test_ext_contentscript_async_loading.html]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/test_ext_clipboard_image.html
@@ -0,0 +1,263 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Clipboard permissions tests</title>
+  <script src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
+  <script src="head.js"></script>
+  <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+
+<script>
+"use strict";
+/**
+ * This cannot be a xpcshell test, because:
+ * - On Android, copyString of nsIClipboardHelper segfaults because
+ *   widget/android/nsClipboard.cpp calls java::Clipboard::SetText, which is
+ *   unavailable in xpcshell.
+ * - On Windows, the clipboard is unavailable to xpcshell.
+ */
+
+function resetClipboard() {
+  SpecialPowers.clipboardCopyString(
+    "This is the default value of the clipboard in the test.");
+}
+
+async function checkClipboardHasTestImage(imageType) {
+  async function backgroundScript(imageType) {
+    async function verifyImage(img) {
+      // Checks whether the image is a 1x1 red image.
+      browser.test.assertEq(1, img.naturalWidth, "image width should match");
+      browser.test.assertEq(1, img.naturalHeight, "image height should match");
+
+      let canvas = document.createElement("canvas");
+      canvas.width = 1;
+      canvas.height = 1;
+      let ctx = canvas.getContext("2d");
+      ctx.drawImage(img, 0, 0);  // Draw without scaling.
+      let [r, g, b, a] = ctx.getImageData(0, 0, 1, 1).data;
+      let expectedColor;
+      if (imageType === "png") {
+        expectedColor = [255, 0, 0];
+      } else if (imageType === "jpeg") {
+        expectedColor = [254, 0, 0];
+      }
+      let {os} = await browser.runtime.getPlatformInfo();
+      if (os === "mac") {
+        // Due to https://bugzil.la/1396587, the pasted image differs from the
+        // original/expected image.
+        // Once that bug is fixed, this whole macOS-only branch can be removed.
+        if (imageType === "png") {
+          expectedColor = [255, 38, 0];
+        } else if (imageType === "jpeg") {
+          expectedColor = [255, 38, 0];
+        }
+      }
+      browser.test.assertEq(expectedColor[0], r, "pixel should be red");
+      browser.test.assertEq(expectedColor[1], g, "pixel should not contain green");
+      browser.test.assertEq(expectedColor[2], b, "pixel should not contain blue");
+      browser.test.assertEq(255, a, "pixel should be opaque");
+    }
+
+    let editable = document.body;
+    editable.contentEditable = true;
+    let file;
+    await new Promise(resolve => {
+      document.addEventListener("paste", function(event) {
+        browser.test.assertEq(1, event.clipboardData.types.length, "expected one type");
+        browser.test.assertEq("Files", event.clipboardData.types[0], "expected type");
+        browser.test.assertEq(1, event.clipboardData.files.length, "expected one file");
+
+        // After returning from the paste event, event.clipboardData is cleaned, so we
+        // have to store the file in a separate variable.
+        file = event.clipboardData.files[0];
+        resolve();
+      }, {once: true});
+
+      document.execCommand("paste");  // requires clipboardWrite permission.
+    });
+
+    // When image data is copied, its first frame is decoded and exported to the
+    // clipboard. The pasted result is always an unanimated PNG file, regardless
+    // of the input.
+    browser.test.assertEq("image/png", file.type, "expected file.type");
+
+    // event.files[0] should be an accurate representation of the input image.
+    {
+      let img = new Image();
+      await new Promise((resolve, reject) => {
+        img.onload = resolve;
+        img.onerror = () => reject(new Error(`Failed to load image ${img.src} of size ${file.size}`));
+        img.src = URL.createObjectURL(file);
+      });
+
+      await verifyImage(img);
+    }
+
+    // This confirms that an image was put on the clipboard.
+    // In contrast, when document.execCommand('copy') + clipboardData.setData
+    // is used, then the 'paste' event will also have the image data (as tested
+    // above), but the contentEditable area will be empty.
+    {
+      let imgs = editable.querySelectorAll("img");
+      browser.test.assertEq(1, imgs.length, "should have pasted one image");
+      await verifyImage(imgs[0]);
+    }
+    browser.test.sendMessage("tested image on clipboard");
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    background: `(${backgroundScript})("${imageType}");`,
+    manifest: {
+      permissions: ["clipboardRead"],
+    },
+  });
+  await extension.startup();
+  await extension.awaitMessage("tested image on clipboard");
+  await extension.unload();
+}
+
+add_task(async function test_without_clipboard_permission() {
+  let extension = ExtensionTestUtils.loadExtension({
+    background() {
+      browser.test.assertEq(undefined, browser.clipboard,
+        "clipboard API requires the clipboardWrite permission.");
+      browser.test.notifyPass();
+    },
+    manifest: {
+      permissions: ["clipboardRead"],
+    },
+  });
+  await extension.startup();
+  await extension.awaitFinish();
+  await extension.unload();
+});
+
+add_task(async function test_copy_png() {
+  if (AppConstants.platform === "android") {
+    return;  // Android does not support images on the clipboard.
+  }
+  let extension = ExtensionTestUtils.loadExtension({
+    async background() {
+      // A 1x1 red PNG image.
+      let b64data = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAABlBMVEX/AAD///9BHTQRAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAACklEQVQImWNgAAAAAgAB9HFkpgAAAABJRU5ErkJggg==";
+      let imageData = Uint8Array.from(atob(b64data), c => c.charCodeAt(0)).buffer;
+      await browser.clipboard.setImageData(imageData, "png");
+      browser.test.sendMessage("Called setImageData with PNG");
+    },
+    manifest: {
+      permissions: ["clipboardWrite"],
+    },
+  });
+
+  resetClipboard();
+
+  await extension.startup();
+  await extension.awaitMessage("Called setImageData with PNG");
+  await extension.unload();
+
+  await checkClipboardHasTestImage("png");
+});
+
+add_task(async function test_copy_jpeg() {
+  if (AppConstants.platform === "android") {
+    return;  // Android does not support images on the clipboard.
+  }
+  let extension = ExtensionTestUtils.loadExtension({
+    async background() {
+      // A 1x1 red JPEG image, created using: convert xc:red red.jpg.
+      // JPEG is lossy, and the red pixel value is actually #FE0000 instead of
+      // #FF0000 (also seen using: convert red.jpg text:-).
+      let b64data = "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAABAAEDAREAAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAACP/EABQQAQAAAAAAAAAAAAAAAAAAAAD/xAAVAQEBAAAAAAAAAAAAAAAAAAAHCf/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/ADoDFU3/2Q==";
+      let imageData = Uint8Array.from(atob(b64data), c => c.charCodeAt(0)).buffer;
+      await browser.clipboard.setImageData(imageData, "jpeg");
+      browser.test.sendMessage("Called setImageData with JPEG");
+    },
+    manifest: {
+      permissions: ["clipboardWrite"],
+    },
+  });
+
+  resetClipboard();
+
+  await extension.startup();
+  await extension.awaitMessage("Called setImageData with JPEG");
+  await extension.unload();
+
+  await checkClipboardHasTestImage("jpeg");
+});
+
+add_task(async function test_copy_invalid_image() {
+  if (AppConstants.platform === "android") {
+    // Android does not support images on the clipboard.
+    return;
+  }
+  let extension = ExtensionTestUtils.loadExtension({
+    async background() {
+      // This is a PNG image.
+      let b64data = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAABlBMVEX/AAD///9BHTQRAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAACklEQVQImWNgAAAAAgAB9HFkpgAAAABJRU5ErkJggg==";
+      let pngImageData = Uint8Array.from(atob(b64data), c => c.charCodeAt(0)).buffer;
+      await browser.test.assertRejects(
+        browser.clipboard.setImageData(pngImageData, "jpeg"),
+        "Data is not a valid jpeg image",
+        "Image data that is not valid for the given type should be rejected.");
+      browser.test.sendMessage("finished invalid image");
+    },
+    manifest: {
+      permissions: ["clipboardWrite"],
+    },
+  });
+
+  await extension.startup();
+  await extension.awaitMessage("finished invalid image");
+  await extension.unload();
+});
+
+add_task(async function test_copy_invalid_image_type() {
+  let extension = ExtensionTestUtils.loadExtension({
+    async background() {
+      // setImageData expects "png" or "jpeg", but we pass "image/png" here.
+      browser.test.assertThrows(
+        () => { browser.clipboard.setImageData(new ArrayBuffer(0), "image/png"); },
+        "Type error for parameter imageType (Invalid enumeration value \"image/png\") for clipboard.setImageData.",
+        "An invalid type for setImageData should be rejected.");
+      browser.test.sendMessage("finished invalid type");
+    },
+    manifest: {
+      permissions: ["clipboardWrite"],
+    },
+  });
+
+  await extension.startup();
+  await extension.awaitMessage("finished invalid type");
+  await extension.unload();
+});
+
+if (AppConstants.platform === "android") {
+  add_task(async function test_setImageData_unsupported_on_android() {
+    let extension = ExtensionTestUtils.loadExtension({
+      async background() {
+        // Android does not support images on the clipboard,
+        // so it should not try to decode an image but fail immediately.
+        await browser.test.assertRejects(
+          browser.clipboard.setImageData(new ArrayBuffer(0), "png"),
+          "Writing images to the clipboard is not supported on Android",
+          "Should get an error when setImageData is called on Android.");
+        browser.test.sendMessage("finished unsupported setImageData");
+      },
+      manifest: {
+        permissions: ["clipboardWrite"],
+      },
+    });
+
+    await extension.startup();
+    await extension.awaitMessage("finished unsupported setImageData");
+    await extension.unload();
+  });
+}
+
+</script>
+</body>
+</html>
--- a/toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage.js
@@ -150,20 +150,20 @@ add_task(async function sendMessageRespo
   function page() {
     browser.test.onMessage.addListener(msg => {
       browser.runtime.sendMessage(msg)
         .then(response => {
           if (response) {
             browser.test.log(`Got response: ${response}`);
             browser.test.sendMessage(response);
           }
-        }, error => {
-          browser.test.assertEq(error.message,
-            "Response handle went out of scope",
-            "The promise rejected with the correct error");
+        }, ({message}) => {
+          browser.test.assertTrue(
+            /at background@moz-extension:\/\/[\w-]+\/%7B[\w-]+%7D\.js:4:\d went out/.test(message),
+            `Promise rejected with the correct error message: ${message}`);
           browser.test.sendMessage("rejected");
         }
       );
     });
     browser.test.sendMessage("ready");
   }
 
   let extension = ExtensionTestUtils.loadExtension({
--- a/widget/cocoa/nsPrintOptionsX.mm
+++ b/widget/cocoa/nsPrintOptionsX.mm
@@ -139,21 +139,16 @@ nsPrintOptionsX::SerializeToPrintData(ns
   data->printReversed() = [[dict objectForKey: NSPrintReversePageOrder] boolValue];
   data->pagesAcross() = [[dict objectForKey: NSPrintPagesAcross] unsignedShortValue];
   data->pagesDown() = [[dict objectForKey: NSPrintPagesDown] unsignedShortValue];
   data->detailedErrorReporting() = [[dict objectForKey: NSPrintDetailedErrorReporting] boolValue];
   data->addHeaderAndFooter() = [[dict objectForKey: NSPrintHeaderAndFooter] boolValue];
   data->fileNameExtensionHidden() =
     [[dict objectForKey: NSPrintJobSavingFileNameExtensionHidden] boolValue];
 
-  bool printSelectionOnly = [[dict objectForKey: NSPrintSelectionOnly] boolValue];
-  aSettings->SetPrintOptions(nsIPrintSettings::kEnableSelectionRB,
-                             printSelectionOnly);
-  aSettings->GetPrintOptionsBits(&data->optionFlags());
-
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsPrintOptionsX::DeserializeToPrintSettings(const PrintData& data,
                                             nsIPrintSettings* settings)
 {
   nsresult rv = nsPrintOptions::DeserializeToPrintSettings(data, settings);
--- a/widget/gtk/nsLookAndFeel.cpp
+++ b/widget/gtk/nsLookAndFeel.cpp
@@ -24,59 +24,50 @@
 #include "nsStyleConsts.h"
 #include "gfxFontConstants.h"
 #include "WidgetUtils.h"
 
 #include <dlfcn.h>
 
 #include "mozilla/gfx/2D.h"
 
-#if MOZ_WIDGET_GTK != 2
 #include <cairo-gobject.h>
 #include "WidgetStyleCache.h"
 #include "prenv.h"
-#endif
 
 using mozilla::LookAndFeel;
 
 #define GDK_COLOR_TO_NS_RGB(c) \
     ((nscolor) NS_RGB(c.red>>8, c.green>>8, c.blue>>8))
 #define GDK_RGBA_TO_NS_RGBA(c) \
     ((nscolor) NS_RGBA((int)((c).red*255), (int)((c).green*255), \
                        (int)((c).blue*255), (int)((c).alpha*255)))
 
 #if !GTK_CHECK_VERSION(3,12,0)
 #define GTK_STATE_FLAG_LINK (static_cast<GtkStateFlags>(1 << 9))
 #endif
 
 nsLookAndFeel::nsLookAndFeel()
     : nsXPLookAndFeel(),
-#if (MOZ_WIDGET_GTK == 2)
-      mStyle(nullptr),
-#endif
       mDefaultFontCached(false), mButtonFontCached(false),
       mFieldFontCached(false), mMenuFontCached(false),
       mInitialized(false)
 {
 }
 
 void
 nsLookAndFeel::NativeInit()
 {
     EnsureInit();
 }
 
 nsLookAndFeel::~nsLookAndFeel()
 {
-#if (MOZ_WIDGET_GTK == 2)
-    g_object_unref(mStyle);
-#endif
 }
 
-#if MOZ_WIDGET_GTK != 2
 // Modifies color |*aDest| as if a pattern of color |aSource| was painted with
 // CAIRO_OPERATOR_OVER to a surface with color |*aDest|.
 static void
 ApplyColorOver(const GdkRGBA& aSource, GdkRGBA* aDest) {
     gdouble sourceCoef = aSource.alpha;
     gdouble destCoef = aDest->alpha * (1.0 - sourceCoef);
     gdouble resultAlpha = sourceCoef + destCoef;
     if (resultAlpha != 0.0) { // don't divide by zero
@@ -223,52 +214,29 @@ GetBorderColors(GtkStyleContext* aContex
                 nscolor* aLightColor, nscolor* aDarkColor)
 {
     GdkRGBA lightColor, darkColor;
     bool ret = GetBorderColors(aContext, &lightColor, &darkColor);
     *aLightColor = GDK_RGBA_TO_NS_RGBA(lightColor);
     *aDarkColor = GDK_RGBA_TO_NS_RGBA(darkColor);
     return ret;
 }
-#endif
 
 nsresult
 nsLookAndFeel::NativeGetColor(ColorID aID, nscolor& aColor)
 {
     EnsureInit();
 
-#if (MOZ_WIDGET_GTK == 3)
     GdkRGBA gdk_color;
-#endif
     nsresult res = NS_OK;
 
     switch (aID) {
         // These colors don't seem to be used for anything anymore in Mozilla
         // (except here at least TextSelectBackground and TextSelectForeground)
         // The CSS2 colors below are used.
-#if (MOZ_WIDGET_GTK == 2)
-    case eColorID_WindowBackground:
-        aColor = GDK_COLOR_TO_NS_RGB(mStyle->base[GTK_STATE_NORMAL]);
-        break;
-    case eColorID_WindowForeground:
-        aColor = GDK_COLOR_TO_NS_RGB(mStyle->text[GTK_STATE_NORMAL]);
-        break;
-    case eColorID_WidgetBackground:
-        aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_NORMAL]);
-        break;
-    case eColorID_WidgetForeground:
-        aColor = GDK_COLOR_TO_NS_RGB(mStyle->fg[GTK_STATE_NORMAL]);
-        break;
-    case eColorID_WidgetSelectBackground:
-        aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_SELECTED]);
-        break;
-    case eColorID_WidgetSelectForeground:
-        aColor = GDK_COLOR_TO_NS_RGB(mStyle->fg[GTK_STATE_SELECTED]);
-        break;
-#else
     case eColorID_WindowBackground:
     case eColorID_WidgetBackground:
     case eColorID_TextBackground:
     case eColorID_activecaption: // active window caption background
     case eColorID_appworkspace: // MDI background color
     case eColorID_background: // desktop background
     case eColorID_window:
     case eColorID_windowframe:
@@ -298,45 +266,22 @@ nsLookAndFeel::NativeGetColor(ColorID aI
     case eColorID_TextSelectForeground:
     case eColorID_IMESelectedRawTextForeground:
     case eColorID_IMESelectedConvertedTextForeground:
     case eColorID_highlighttext:
     case eColorID__moz_cellhighlighttext:
     case eColorID__moz_html_cellhighlighttext:
         aColor = sTextSelectedText;
         break;
-#endif
     case eColorID_Widget3DHighlight:
         aColor = NS_RGB(0xa0,0xa0,0xa0);
         break;
     case eColorID_Widget3DShadow:
         aColor = NS_RGB(0x40,0x40,0x40);
         break;
-#if (MOZ_WIDGET_GTK == 2)
-    case eColorID_TextBackground:
-        // not used?
-        aColor = GDK_COLOR_TO_NS_RGB(mStyle->base[GTK_STATE_NORMAL]);
-        break;
-    case eColorID_TextForeground: 
-        // not used?
-        aColor = GDK_COLOR_TO_NS_RGB(mStyle->text[GTK_STATE_NORMAL]);
-        break;
-    case eColorID_TextSelectBackground:
-    case eColorID_IMESelectedRawTextBackground:
-    case eColorID_IMESelectedConvertedTextBackground:
-        // still used
-        aColor = GDK_COLOR_TO_NS_RGB(mStyle->base[GTK_STATE_SELECTED]);
-        break;
-    case eColorID_TextSelectForeground:
-    case eColorID_IMESelectedRawTextForeground:
-    case eColorID_IMESelectedConvertedTextForeground:
-        // still used
-        aColor = GDK_COLOR_TO_NS_RGB(mStyle->text[GTK_STATE_SELECTED]);
-        break;
-#endif
     case eColorID_IMERawInputBackground:
     case eColorID_IMEConvertedTextBackground:
         aColor = NS_TRANSPARENT;
         break;
     case eColorID_IMERawInputForeground:
     case eColorID_IMEConvertedTextForeground:
         aColor = NS_SAME_AS_FOREGROUND_COLOR;
         break;
@@ -347,63 +292,16 @@ nsLookAndFeel::NativeGetColor(ColorID aI
     case eColorID_IMESelectedRawTextUnderline:
     case eColorID_IMESelectedConvertedTextUnderline:
         aColor = NS_TRANSPARENT;
         break;
     case eColorID_SpellCheckerUnderline:
       aColor = NS_RGB(0xff, 0, 0);
       break;
 
-#if (MOZ_WIDGET_GTK == 2)
-        // css2  http://www.w3.org/TR/REC-CSS2/ui.html#system-colors
-    case eColorID_activeborder:
-        // active window border
-        aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_NORMAL]);
-        break;
-    case eColorID_activecaption:
-        // active window caption background
-        aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_NORMAL]);
-        break;
-    case eColorID_appworkspace:
-        // MDI background color
-        aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_NORMAL]);
-        break;
-    case eColorID_background:
-        // desktop background
-        aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_NORMAL]);
-        break;
-    case eColorID_captiontext:
-        // text in active window caption, size box, and scrollbar arrow box (!)
-        aColor = GDK_COLOR_TO_NS_RGB(mStyle->fg[GTK_STATE_NORMAL]);
-        break;
-    case eColorID_graytext:
-        // disabled text in windows, menus, etc.
-        aColor = GDK_COLOR_TO_NS_RGB(mStyle->fg[GTK_STATE_INSENSITIVE]);
-        break;
-    case eColorID_highlight:
-        // background of selected item
-        aColor = GDK_COLOR_TO_NS_RGB(mStyle->base[GTK_STATE_SELECTED]);
-        break;
-    case eColorID_highlighttext:
-        // text of selected item
-        aColor = GDK_COLOR_TO_NS_RGB(mStyle->text[GTK_STATE_SELECTED]);
-        break;
-    case eColorID_inactiveborder:
-        // inactive window border
-        aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_NORMAL]);
-        break;
-    case eColorID_inactivecaption:
-        // inactive window caption
-        aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_INSENSITIVE]);
-        break;
-    case eColorID_inactivecaptiontext:
-        // text in inactive window caption
-        aColor = GDK_COLOR_TO_NS_RGB(mStyle->fg[GTK_STATE_INSENSITIVE]);
-        break;
-#else
         // css2  http://www.w3.org/TR/REC-CSS2/ui.html#system-colors
     case eColorID_activeborder: {
         // active window border
         GtkStyleContext *style = ClaimStyleContext(MOZ_GTK_WINDOW);
         gtk_style_context_get_border_color(style,
                                            GTK_STATE_FLAG_NORMAL, &gdk_color);
         aColor = GDK_RGBA_TO_NS_RGBA(gdk_color);
         ReleaseStyleContext(style);
@@ -428,17 +326,16 @@ nsLookAndFeel::NativeGetColor(ColorID aI
         GtkStyleContext *style = ClaimStyleContext(MOZ_GTK_WINDOW);
         gtk_style_context_get_background_color(style,
                                                GTK_STATE_FLAG_INSENSITIVE, 
                                                &gdk_color);
         aColor = GDK_RGBA_TO_NS_RGBA(gdk_color);
         ReleaseStyleContext(style);
         break;
     }
-#endif
     case eColorID_infobackground:
         // tooltip background color
         aColor = sInfoBackground;
         break;
     case eColorID_infotext:
         // tooltip text color
         aColor = sInfoText;
         break;
@@ -447,34 +344,26 @@ nsLookAndFeel::NativeGetColor(ColorID aI
         aColor = sMenuBackground;
         break;
     case eColorID_menutext:
         // menu text
         aColor = sMenuText;
         break;
     case eColorID_scrollbar:
         // scrollbar gray area
-#if (MOZ_WIDGET_GTK == 2)
-        aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_ACTIVE]);
-#else
         aColor = sMozScrollbar;
-#endif
         break;
 
     case eColorID_threedlightshadow:
         // 3-D highlighted inner edge color
         // always same as background in GTK code
     case eColorID_threedface:
     case eColorID_buttonface:
         // 3-D face color
-#if (MOZ_WIDGET_GTK == 3)
         aColor = sMozWindowBackground;
-#else
-        aColor = sButtonBackground;
-#endif
         break;
 
     case eColorID_buttontext:
         // text on push buttons
         aColor = sButtonText;
         break;
 
     case eColorID_buttonhighlight:
@@ -486,66 +375,16 @@ nsLookAndFeel::NativeGetColor(ColorID aI
 
     case eColorID_buttonshadow:
         // 3-D shadow edge color
     case eColorID_threedshadow:
         // 3-D shadow inner edge color
         aColor = sFrameInnerDarkBorder;
         break;
 
-#if (MOZ_WIDGET_GTK == 2)
-    case eColorID_threeddarkshadow:
-        // 3-D shadow outer edge color
-        aColor = GDK_COLOR_TO_NS_RGB(mStyle->black);
-        break;
-
-    case eColorID_window:
-    case eColorID_windowframe:
-        aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_NORMAL]);
-        break;
-
-    case eColorID_windowtext:
-        aColor = GDK_COLOR_TO_NS_RGB(mStyle->fg[GTK_STATE_NORMAL]);
-        break;
-
-    case eColorID__moz_eventreerow:
-    case eColorID__moz_field:
-        aColor = GDK_COLOR_TO_NS_RGB(mStyle->base[GTK_STATE_NORMAL]);
-        break;
-    case eColorID__moz_fieldtext:
-        aColor = GDK_COLOR_TO_NS_RGB(mStyle->text[GTK_STATE_NORMAL]);
-        break;
-    case eColorID__moz_dialog:
-        aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_NORMAL]);
-        break;
-    case eColorID__moz_dialogtext:
-        aColor = GDK_COLOR_TO_NS_RGB(mStyle->fg[GTK_STATE_NORMAL]);
-        break;
-    case eColorID__moz_dragtargetzone:
-        aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_SELECTED]);
-        break; 
-    case eColorID__moz_buttondefault:
-        // default button border color
-        aColor = GDK_COLOR_TO_NS_RGB(mStyle->black);
-        break;
-    case eColorID__moz_buttonhoverface:
-        aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_PRELIGHT]);
-        break;
-    case eColorID__moz_buttonhovertext:
-        aColor = GDK_COLOR_TO_NS_RGB(mStyle->fg[GTK_STATE_PRELIGHT]);
-        break;
-    case eColorID__moz_cellhighlight:
-    case eColorID__moz_html_cellhighlight:
-        aColor = GDK_COLOR_TO_NS_RGB(mStyle->base[GTK_STATE_ACTIVE]);
-        break;
-    case eColorID__moz_cellhighlighttext:
-    case eColorID__moz_html_cellhighlighttext:
-        aColor = GDK_COLOR_TO_NS_RGB(mStyle->text[GTK_STATE_ACTIVE]);
-        break;
-#else
     case eColorID_threeddarkshadow:
         // Hardcode to black
         aColor = NS_RGB(0x00,0x00,0x00);
         break;
 
     case eColorID__moz_eventreerow:
     case eColorID__moz_field:
         aColor = sMozFieldBackground;
@@ -569,81 +408,50 @@ nsLookAndFeel::NativeGetColor(ColorID aI
                                                &gdk_color);
         aColor = GDK_RGBA_TO_NS_RGBA(gdk_color);
         ReleaseStyleContext(style);
         break;
     }
     case eColorID__moz_buttonhovertext:
         aColor = sButtonHoverText;
         break;
-#endif
     case eColorID__moz_menuhover:
         aColor = sMenuHover;
         break;
     case eColorID__moz_menuhovertext:
         aColor = sMenuHoverText;
         break;
     case eColorID__moz_oddtreerow:
         aColor = sOddCellBackground;
         break;
     case eColorID__moz_nativehyperlinktext:
         aColor = sNativeHyperLinkText;
         break;
     case eColorID__moz_comboboxtext:
         aColor = sComboBoxText;
         break;
-#if (MOZ_WIDGET_GTK == 2)
-    case eColorID__moz_combobox:
-        aColor = sComboBoxBackground;
-        break;
-#endif
     case eColorID__moz_menubartext:
         aColor = sMenuBarText;
         break;
     case eColorID__moz_menubarhovertext:
         aColor = sMenuBarHoverText;
         break;
     case eColorID__moz_gtk_info_bar_text:
-#if (MOZ_WIDGET_GTK == 3)
         aColor = sInfoBarText;
-#else
-        aColor = sInfoText;
-#endif
         break;
     default:
         /* default color is BLACK */
         aColor = 0;
         res    = NS_ERROR_FAILURE;
         break;
     }
 
     return res;
 }
 
-#if (MOZ_WIDGET_GTK == 2)
-static void darken_gdk_color(GdkColor *src, GdkColor *dest)
-{
-    gdouble red;
-    gdouble green;
-    gdouble blue;
-
-    red = (gdouble) src->red / 65535.0;
-    green = (gdouble) src->green / 65535.0;
-    blue = (gdouble) src->blue / 65535.0;
-
-    red *= 0.93;
-    green *= 0.93;
-    blue *= 0.93;
-
-    dest->red = red * 65535.0;
-    dest->green = green * 65535.0;
-    dest->blue = blue * 65535.0;
-}
-#endif
-
 static int32_t CheckWidgetStyle(GtkWidget* aWidget, const char* aStyle, int32_t aResult) {
     gboolean value = FALSE;
     gtk_widget_style_get(aWidget, aStyle, &value, nullptr);
     return value ? aResult : 0;
 }
 
 static int32_t ConvertGTKStepperStyleToMozillaScrollArrowStyle(GtkWidget* aWidget)
 {
@@ -821,24 +629,18 @@ nsLookAndFeel::GetIntImpl(IntID aID, int
     case eIntID_WindowsClassic:
     case eIntID_WindowsDefaultTheme:
     case eIntID_WindowsThemeIdentifier:
     case eIntID_OperatingSystemVersionIdentifier:
         aResult = 0;
         res = NS_ERROR_NOT_IMPLEMENTED;
         break;
     case eIntID_TouchEnabled:
-#if MOZ_WIDGET_GTK == 3
         aResult = mozilla::widget::WidgetUtils::IsTouchDeviceSupportPresent();
         break;
-#else
-        aResult = 0;
-        res = NS_ERROR_NOT_IMPLEMENTED;
-#endif
-        break;
     case eIntID_MacGraphiteTheme:
         aResult = 0;
         res = NS_ERROR_NOT_IMPLEMENTED;
         break;
     case eIntID_AlertNotificationOrigin:
         aResult = NS_ALERT_TOP;
         break;
     case eIntID_IMERawInputUnderlineStyle:
@@ -900,34 +702,30 @@ nsLookAndFeel::GetFloatImpl(FloatID aID,
     default:
         aResult = -1.0;
         res = NS_ERROR_FAILURE;
     }
     return res;
 }
 
 static void
-GetSystemFontInfo(GtkWidget *aWidget,
+GetSystemFontInfo(GtkStyleContext *aStyle,
                   nsString *aFontName,
                   gfxFontStyle *aFontStyle)
 {
-    GtkSettings *settings = gtk_widget_get_settings(aWidget);
-
     aFontStyle->style       = NS_FONT_STYLE_NORMAL;
 
-    gchar *fontname;
-    g_object_get(settings, "gtk-font-name", &fontname, nullptr);
-
+    // As in
+    // https://git.gnome.org/browse/gtk+/tree/gtk/gtkwidget.c?h=3.22.19#n10333
     PangoFontDescription *desc;
-    desc = pango_font_description_from_string(fontname);
+    gtk_style_context_get(aStyle, gtk_style_context_get_state(aStyle),
+                          "font", &desc, nullptr);
 
     aFontStyle->systemFont = true;
 
-    g_free(fontname);
-
     NS_NAMED_LITERAL_STRING(quote, "\"");
     NS_ConvertUTF8toUTF16 family(pango_font_description_get_family(desc));
     *aFontName = quote + family + quote;
 
     aFontStyle->weight = pango_font_description_get_weight(desc);
 
     // FIXME: Set aFontStyle->stretch correctly!
     aFontStyle->stretch = NS_FONT_STRETCH_NORMAL;
@@ -948,214 +746,72 @@ GetSystemFontInfo(GtkWidget *aWidget,
 
     // |size| is now pixels
 
     aFontStyle->size = size;
 
     pango_font_description_free(desc);
 }
 
-static void
-GetSystemFontInfo(LookAndFeel::FontID aID,
-                  nsString *aFontName,
-                  gfxFontStyle *aFontStyle)
-{
-    if (aID == LookAndFeel::eFont_Widget) {
-        GtkWidget *label = gtk_label_new("M");
-        GtkWidget *parent = gtk_fixed_new();
-        GtkWidget *window = gtk_window_new(GTK_WINDOW_POPUP);
-
-        gtk_container_add(GTK_CONTAINER(parent), label);
-        gtk_container_add(GTK_CONTAINER(window), parent);
-
-        gtk_widget_ensure_style(label);
-        GetSystemFontInfo(label, aFontName, aFontStyle);
-        gtk_widget_destroy(window);  // no unref, windows are different
-
-    } else if (aID == LookAndFeel::eFont_Button) {
-        GtkWidget *label = gtk_label_new("M");
-        GtkWidget *parent = gtk_fixed_new();
-        GtkWidget *button = gtk_button_new();
-        GtkWidget *window = gtk_window_new(GTK_WINDOW_POPUP);
-
-        gtk_container_add(GTK_CONTAINER(button), label);
-        gtk_container_add(GTK_CONTAINER(parent), button);
-        gtk_container_add(GTK_CONTAINER(window), parent);
-
-        gtk_widget_ensure_style(label);
-        GetSystemFontInfo(label, aFontName, aFontStyle);
-        gtk_widget_destroy(window);  // no unref, windows are different
-
-    } else if (aID == LookAndFeel::eFont_Field) {
-        GtkWidget *entry = gtk_entry_new();
-        GtkWidget *parent = gtk_fixed_new();
-        GtkWidget *window = gtk_window_new(GTK_WINDOW_POPUP);
-
-        gtk_container_add(GTK_CONTAINER(parent), entry);
-        gtk_container_add(GTK_CONTAINER(window), parent);
-
-        gtk_widget_ensure_style(entry);
-        GetSystemFontInfo(entry, aFontName, aFontStyle);
-        gtk_widget_destroy(window);  // no unref, windows are different
-
-    } else {
-        MOZ_ASSERT(aID == LookAndFeel::eFont_Menu, "unexpected font ID");
-        GtkWidget *accel_label = gtk_accel_label_new("M");
-        GtkWidget *menuitem = gtk_menu_item_new();
-        GtkWidget *menu = gtk_menu_new();
-        g_object_ref_sink(menu);
-
-        gtk_container_add(GTK_CONTAINER(menuitem), accel_label);
-        gtk_menu_shell_append((GtkMenuShell *)GTK_MENU(menu), menuitem);
-
-        gtk_widget_ensure_style(accel_label);
-        GetSystemFontInfo(accel_label, aFontName, aFontStyle);
-        g_object_unref(menu);
-    }
-}
-
 bool
 nsLookAndFeel::GetFontImpl(FontID aID, nsString& aFontName,
                            gfxFontStyle& aFontStyle,
                            float aDevPixPerCSSPixel)
 {
-  nsString *cachedFontName = nullptr;
-  gfxFontStyle *cachedFontStyle = nullptr;
-  bool *isCached = nullptr;
-
   switch (aID) {
     case eFont_Menu:         // css2
     case eFont_PullDownMenu: // css3
-      cachedFontName = &mMenuFontName;
-      cachedFontStyle = &mMenuFontStyle;
-      isCached = &mMenuFontCached;
-      aID = eFont_Menu;
-      break;
+      aFontName = mMenuFontName;
+      aFontStyle = mMenuFontStyle;
+      return true;
 
     case eFont_Field:        // css3
     case eFont_List:         // css3
-      cachedFontName = &mFieldFontName;
-      cachedFontStyle = &mFieldFontStyle;
-      isCached = &mFieldFontCached;
-      aID = eFont_Field;
-      break;
+      aFontName = mFieldFontName;
+      aFontStyle = mFieldFontStyle;
+      return true;
 
     case eFont_Button:       // css3
-      cachedFontName = &mButtonFontName;
-      cachedFontStyle = &mButtonFontStyle;
-      isCached = &mButtonFontCached;
-      break;
+      aFontName = mButtonFontName;
+      aFontStyle = mButtonFontStyle;
+      return true;
 
     case eFont_Caption:      // css2
     case eFont_Icon:         // css2
     case eFont_MessageBox:   // css2
     case eFont_SmallCaption: // css2
     case eFont_StatusBar:    // css2
     case eFont_Window:       // css3
     case eFont_Document:     // css3
     case eFont_Workspace:    // css3
     case eFont_Desktop:      // css3
     case eFont_Info:         // css3
     case eFont_Dialog:       // css3
     case eFont_Tooltips:     // moz
     case eFont_Widget:       // moz
-      cachedFontName = &mDefaultFontName;
-      cachedFontStyle = &mDefaultFontStyle;
-      isCached = &mDefaultFontCached;
-      aID = eFont_Widget;
-      break;
+    default:
+      aFontName = mDefaultFontName;
+      aFontStyle = mDefaultFontStyle;
+      return true;
   }
-
-  if (!*isCached) {
-    GetSystemFontInfo(aID, cachedFontName, cachedFontStyle);
-    *isCached = true;
-  }
-
-  aFontName = *cachedFontName;
-  aFontStyle = *cachedFontStyle;
-  return true;
 }
 
 void
 nsLookAndFeel::EnsureInit()
 {
     GdkColor colorValue;
     GdkColor *colorValuePtr;
 
     if (mInitialized)
         return;
     mInitialized = true;
 
     // gtk does non threadsafe refcounting
     MOZ_ASSERT(NS_IsMainThread());
 
-#if (MOZ_WIDGET_GTK == 2)
-    NS_ASSERTION(!mStyle, "already initialized");
-    // GtkInvisibles come with a refcount that is not floating
-    // (since their initialization code calls g_object_ref_sink) and
-    // their destroy code releases that reference (which means they
-    // have to be explicitly destroyed, since calling unref enough
-    // to cause destruction would lead to *another* unref).
-    // However, this combination means that it's actually still ok
-    // to use the normal pattern, which is to g_object_ref_sink
-    // after construction, and then destroy *and* unref when we're
-    // done.  (Though we could skip the g_object_ref_sink and the
-    // corresponding g_object_unref, but that's particular to
-    // GtkInvisibles and GtkWindows.)
-    GtkWidget *widget = gtk_invisible_new();
-    g_object_ref_sink(widget); // effectively g_object_ref (see above)
-
-    gtk_widget_ensure_style(widget);
-    mStyle = gtk_style_copy(gtk_widget_get_style(widget));
-
-    gtk_widget_destroy(widget);
-    g_object_unref(widget);
-        
-    // tooltip foreground and background
-    GtkStyle *style = gtk_rc_get_style_by_paths(gtk_settings_get_default(),
-                                                "gtk-tooltips", "GtkWindow",
-                                                GTK_TYPE_WINDOW);
-    if (style) {
-        sInfoBackground = GDK_COLOR_TO_NS_RGB(style->bg[GTK_STATE_NORMAL]);
-        sInfoText = GDK_COLOR_TO_NS_RGB(style->fg[GTK_STATE_NORMAL]);
-    }
-
-    // menu foreground & menu background
-    GtkWidget *accel_label = gtk_accel_label_new("M");
-    GtkWidget *menuitem = gtk_menu_item_new();
-    GtkWidget *menu = gtk_menu_new();
-
-    g_object_ref_sink(menu);
-
-    gtk_container_add(GTK_CONTAINER(menuitem), accel_label);
-    gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
-
-    gtk_widget_set_style(accel_label, nullptr);
-    gtk_widget_set_style(menu, nullptr);
-    gtk_widget_realize(menu);
-    gtk_widget_realize(accel_label);
-
-    style = gtk_widget_get_style(accel_label);
-    if (style) {
-        sMenuText = GDK_COLOR_TO_NS_RGB(style->fg[GTK_STATE_NORMAL]);
-    }
-
-    style = gtk_widget_get_style(menu);
-    if (style) {
-        sMenuBackground = GDK_COLOR_TO_NS_RGB(style->bg[GTK_STATE_NORMAL]);
-    }
-    
-    style = gtk_widget_get_style(menuitem);
-    if (style) {
-        sMenuHover = GDK_COLOR_TO_NS_RGB(style->bg[GTK_STATE_PRELIGHT]);
-        sMenuHoverText = GDK_COLOR_TO_NS_RGB(style->fg[GTK_STATE_PRELIGHT]);
-    }
-
-    g_object_unref(menu);
-#else
     GdkRGBA color;
     GtkStyleContext *style;
 
     // Gtk manages a screen's CSS in the settings object so we
     // ask Gtk to create it explicitly. Otherwise we may end up
     // with wrong color theme, see Bug 972382
     GtkSettings *settings = gtk_settings_get_for_screen(gdk_screen_get_default());
 
@@ -1190,45 +846,61 @@ nsLookAndFeel::EnsureInit()
         nsAutoCString contentThemeName;
         mozilla::Preferences::GetCString("widget.content.gtk-theme-override",
                                          contentThemeName);
         if (!contentThemeName.IsEmpty()) {
             g_object_set(settings, "gtk-theme-name", contentThemeName.get(), nullptr);
         }
     }
 
+    // The label is not added to a parent widget, but shared for constructing
+    // different style contexts.  The node hierarchy is constructed only on
+    // the label style context.
+    GtkWidget *labelWidget = gtk_label_new("M");
+    g_object_ref_sink(labelWidget);
+
     // Scrollbar colors
     style = ClaimStyleContext(MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL);
     gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
     sMozScrollbar = GDK_RGBA_TO_NS_RGBA(color);
     ReleaseStyleContext(style);
 
     // Window colors
     style = ClaimStyleContext(MOZ_GTK_WINDOW);
     gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
     sMozWindowBackground = GDK_RGBA_TO_NS_RGBA(color);
     gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
     sMozWindowText = GDK_RGBA_TO_NS_RGBA(color);
     ReleaseStyleContext(style);
+    style = ClaimStyleContext(MOZ_GTK_WINDOW_CONTAINER);
+    {
+        GtkStyleContext* labelStyle = CreateStyleForWidget(labelWidget, style);
+        GetSystemFontInfo(labelStyle, &mDefaultFontName, &mDefaultFontStyle);
+        g_object_unref(labelStyle);
+    }
+    ReleaseStyleContext(style);
 
     // tooltip foreground and background
     style = ClaimStyleContext(MOZ_GTK_TOOLTIP);
     gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
     sInfoBackground = GDK_RGBA_TO_NS_RGBA(color);
     ReleaseStyleContext(style);
 
     style = ClaimStyleContext(MOZ_GTK_TOOLTIP_BOX_LABEL);
     gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
     sInfoText = GDK_RGBA_TO_NS_RGBA(color);
     ReleaseStyleContext(style);
 
     style = ClaimStyleContext(MOZ_GTK_MENUITEM);
     {
         GtkStyleContext* accelStyle =
             CreateStyleForWidget(gtk_accel_label_new("M"), style);
+
+        GetSystemFontInfo(accelStyle, &mMenuFontName, &mMenuFontStyle);
+
         gtk_style_context_get_color(accelStyle, GTK_STATE_FLAG_NORMAL, &color);
         sMenuText = GDK_RGBA_TO_NS_RGBA(color);
         gtk_style_context_get_color(accelStyle, GTK_STATE_FLAG_INSENSITIVE, &color);
         sMenuTextInactive = GDK_RGBA_TO_NS_RGBA(color);
         g_object_unref(accelStyle);
     }
     ReleaseStyleContext(style);
 
@@ -1238,123 +910,34 @@ nsLookAndFeel::EnsureInit()
     ReleaseStyleContext(style);
 
     style = ClaimStyleContext(MOZ_GTK_MENUITEM);
     gtk_style_context_get_background_color(style, GTK_STATE_FLAG_PRELIGHT, &color);
     sMenuHover = GDK_RGBA_TO_NS_RGBA(color);
     gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color);
     sMenuHoverText = GDK_RGBA_TO_NS_RGBA(color);
     ReleaseStyleContext(style);
-#endif
 
-    // button styles
     GtkWidget *parent = gtk_fixed_new();
-    GtkWidget *button = gtk_button_new();
-    GtkWidget *label = gtk_label_new("M");
-#if (MOZ_WIDGET_GTK == 2)
-    GtkWidget *combobox = gtk_combo_box_new();
-    GtkWidget *comboboxLabel = gtk_label_new("M");
-    gtk_container_add(GTK_CONTAINER(combobox), comboboxLabel);
-#endif
     GtkWidget *window = gtk_window_new(GTK_WINDOW_POPUP);
     GtkWidget *treeView = gtk_tree_view_new();
     GtkWidget *linkButton = gtk_link_button_new("http://example.com/");
     GtkWidget *menuBar = gtk_menu_bar_new();
     GtkWidget *menuBarItem = gtk_menu_item_new();
     GtkWidget *entry = gtk_entry_new();
     GtkWidget *textView = gtk_text_view_new();
 
-    gtk_container_add(GTK_CONTAINER(button), label);
-    gtk_container_add(GTK_CONTAINER(parent), button);
     gtk_container_add(GTK_CONTAINER(parent), treeView);
     gtk_container_add(GTK_CONTAINER(parent), linkButton);
-#if (MOZ_WIDGET_GTK == 2)
-    gtk_container_add(GTK_CONTAINER(parent), combobox);
-#endif
     gtk_container_add(GTK_CONTAINER(parent), menuBar);
     gtk_menu_shell_append(GTK_MENU_SHELL(menuBar), menuBarItem);
     gtk_container_add(GTK_CONTAINER(window), parent);
     gtk_container_add(GTK_CONTAINER(parent), entry);
     gtk_container_add(GTK_CONTAINER(parent), textView);
     
-#if (MOZ_WIDGET_GTK == 2)
-    gtk_widget_set_style(button, nullptr);
-    gtk_widget_set_style(label, nullptr);
-    gtk_widget_set_style(treeView, nullptr);
-    gtk_widget_set_style(linkButton, nullptr);
-    gtk_widget_set_style(combobox, nullptr);
-    gtk_widget_set_style(comboboxLabel, nullptr);
-    gtk_widget_set_style(menuBar, nullptr);
-    gtk_widget_set_style(entry, nullptr);
-
-    gtk_widget_realize(button);
-    gtk_widget_realize(label);
-    gtk_widget_realize(treeView);
-    gtk_widget_realize(linkButton);
-    gtk_widget_realize(combobox);
-    gtk_widget_realize(comboboxLabel);
-    gtk_widget_realize(menuBar);
-    gtk_widget_realize(entry);
-
-    style = gtk_widget_get_style(label);
-    if (style) {
-        sButtonText = GDK_COLOR_TO_NS_RGB(style->fg[GTK_STATE_NORMAL]);
-    }
-
-    style = gtk_widget_get_style(comboboxLabel);
-    if (style) {
-        sComboBoxText = GDK_COLOR_TO_NS_RGB(style->fg[GTK_STATE_NORMAL]);
-    }
-    style = gtk_widget_get_style(combobox);
-    if (style) {
-        sComboBoxBackground = GDK_COLOR_TO_NS_RGB(style->bg[GTK_STATE_NORMAL]);
-    }
-
-    style = gtk_widget_get_style(menuBar);
-    if (style) {
-        sMenuBarText = GDK_COLOR_TO_NS_RGB(style->fg[GTK_STATE_NORMAL]);
-        sMenuBarHoverText = GDK_COLOR_TO_NS_RGB(style->fg[GTK_STATE_SELECTED]);
-    }
-
-    // GTK's guide to fancy odd row background colors:
-    // 1) Check if a theme explicitly defines an odd row color
-    // 2) If not, check if it defines an even row color, and darken it
-    //    slightly by a hardcoded value (gtkstyle.c)
-    // 3) If neither are defined, take the base background color and
-    //    darken that by a hardcoded value
-    colorValuePtr = nullptr;
-    gtk_widget_style_get(treeView,
-                         "odd-row-color", &colorValuePtr,
-                         nullptr);
-
-    if (colorValuePtr) {
-        colorValue = *colorValuePtr;
-    } else {
-        gtk_widget_style_get(treeView,
-                             "even-row-color", &colorValuePtr,
-                             nullptr);
-        if (colorValuePtr)
-            darken_gdk_color(colorValuePtr, &colorValue);
-        else
-            darken_gdk_color(&treeView->style->base[GTK_STATE_NORMAL], &colorValue);
-    }
-
-    sOddCellBackground = GDK_COLOR_TO_NS_RGB(colorValue);
-    if (colorValuePtr)
-        gdk_color_free(colorValuePtr);
-
-    style = gtk_widget_get_style(button);
-    if (style) {
-        sButtonBackground = GDK_COLOR_TO_NS_RGB(style->bg[GTK_STATE_NORMAL]);
-        sFrameOuterLightBorder =
-            GDK_COLOR_TO_NS_RGB(style->light[GTK_STATE_NORMAL]);
-        sFrameInnerDarkBorder =
-            GDK_COLOR_TO_NS_RGB(style->dark[GTK_STATE_NORMAL]);
-    }
-#else
     // Text colors
     GdkRGBA bgColor;
     // If the text window background is translucent, then the background of
     // the textview root node is visible.
     style = ClaimStyleContext(MOZ_GTK_TEXT_VIEW);
     gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL,
                                            &bgColor);
     ReleaseStyleContext(style);
@@ -1376,18 +959,20 @@ nsLookAndFeel::EnsureInit()
         static_cast<GtkStateFlags>(GTK_STATE_FLAG_FOCUSED|GTK_STATE_FLAG_SELECTED),
         &color);
     sTextSelectedText = GDK_RGBA_TO_NS_RGBA(color);
     ReleaseStyleContext(style);
 
     // Button text color
     style = ClaimStyleContext(MOZ_GTK_BUTTON);
     {
-        GtkStyleContext* labelStyle =
-            CreateStyleForWidget(gtk_label_new("M"), style);
+        GtkStyleContext* labelStyle = CreateStyleForWidget(labelWidget, style);
+
+        GetSystemFontInfo(labelStyle, &mButtonFontName, &mButtonFontStyle);
+
         gtk_style_context_get_color(labelStyle, GTK_STATE_FLAG_NORMAL, &color);
         sButtonText = GDK_RGBA_TO_NS_RGBA(color);
         gtk_style_context_get_color(labelStyle, GTK_STATE_FLAG_PRELIGHT, &color);
         sButtonHoverText = GDK_RGBA_TO_NS_RGBA(color);
         g_object_unref(labelStyle);
     }
     ReleaseStyleContext(style);
 
@@ -1441,42 +1026,38 @@ nsLookAndFeel::EnsureInit()
     GtkWidget* infoBarContent = gtk_info_bar_get_content_area(GTK_INFO_BAR(infoBar));
     GtkWidget* infoBarLabel = gtk_label_new(nullptr);
     gtk_container_add(GTK_CONTAINER(parent), infoBar);
     gtk_container_add(GTK_CONTAINER(infoBarContent), infoBarLabel);
     style = gtk_widget_get_style_context(infoBarLabel);
     gtk_style_context_add_class(style, GTK_STYLE_CLASS_INFO);
     gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
     sInfoBarText = GDK_RGBA_TO_NS_RGBA(color);
-#endif
     // Some themes have a unified menu bar, and support window dragging on it
     gboolean supports_menubar_drag = FALSE;
     GParamSpec *param_spec =
         gtk_widget_class_find_style_property(GTK_WIDGET_GET_CLASS(menuBar),
                                              "window-dragging");
     if (param_spec) {
         if (g_type_is_a(G_PARAM_SPEC_VALUE_TYPE(param_spec), G_TYPE_BOOLEAN)) {
             gtk_widget_style_get(menuBar,
                                  "window-dragging", &supports_menubar_drag,
                                  nullptr);
         }
     }
     sMenuSupportsDrag = supports_menubar_drag;
 
-#if (MOZ_WIDGET_GTK == 3)
     if (gtk_check_version(3, 12, 0) == nullptr) {
         // TODO: It returns wrong color for themes which
         // sets link color for GtkLabel only as we query
         // GtkLinkButton style here.
         style = gtk_widget_get_style_context(linkButton);
         gtk_style_context_get_color(style, GTK_STATE_FLAG_LINK, &color);
         sNativeHyperLinkText = GDK_RGBA_TO_NS_RGBA(color);
-    } else
-#endif
-    {
+    } else {
         colorValuePtr = nullptr;
         gtk_widget_style_get(linkButton, "link-color", &colorValuePtr, nullptr);
         if (colorValuePtr) {
             colorValue = *colorValuePtr; // we can't pass deref pointers to GDK_COLOR_TO_NS_RGB
             sNativeHyperLinkText = GDK_COLOR_TO_NS_RGB(colorValue);
             gdk_color_free(colorValuePtr);
         } else {
             sNativeHyperLinkText = NS_RGB(0x00,0x00,0xEE);
@@ -1488,17 +1069,21 @@ nsLookAndFeel::EnsureInit()
     g_object_get (entry, "invisible-char", &value, nullptr);
     sInvisibleCharacter = char16_t(value);
 
     // caret styles
     gtk_widget_style_get(entry,
                          "cursor-aspect-ratio", &sCaretRatio,
                          nullptr);
 
+    GetSystemFontInfo(gtk_widget_get_style_context(entry),
+                      &mFieldFontName, &mFieldFontStyle);
+
     gtk_widget_destroy(window);
+    g_object_unref(labelWidget);
 }
 
 // virtual
 char16_t
 nsLookAndFeel::GetPasswordCharacterImpl()
 {
     EnsureInit();
     return sInvisibleCharacter;
@@ -1510,20 +1095,15 @@ nsLookAndFeel::RefreshImpl()
     nsXPLookAndFeel::RefreshImpl();
     moz_gtk_refresh();
 
     mDefaultFontCached = false;
     mButtonFontCached = false;
     mFieldFontCached = false;
     mMenuFontCached = false;
 
-#if (MOZ_WIDGET_GTK == 2)
-    g_object_unref(mStyle);
-    mStyle = nullptr;
-#endif
-
     mInitialized = false;
 }
 
 bool
 nsLookAndFeel::GetEchoPasswordImpl() {
     return false;
 }
--- a/widget/gtk/nsLookAndFeel.h
+++ b/widget/gtk/nsLookAndFeel.h
@@ -28,19 +28,16 @@ public:
                              gfxFontStyle& aFontStyle,
                              float aDevPixPerCSSPixel);
 
     virtual void RefreshImpl();
     virtual char16_t GetPasswordCharacterImpl();
     virtual bool GetEchoPasswordImpl();
 
 protected:
-#if (MOZ_WIDGET_GTK == 2)
-    struct _GtkStyle *mStyle;
-#endif
 
     // Cached fonts
     bool mDefaultFontCached;
     bool mButtonFontCached;
     bool mFieldFontCached;
     bool mMenuFontCached;
     nsString mDefaultFontName;
     nsString mButtonFontName;
@@ -58,33 +55,30 @@ protected:
     nscolor sMenuBarText;
     nscolor sMenuBarHoverText;
     nscolor sMenuText;
     nscolor sMenuTextInactive;
     nscolor sMenuHover;
     nscolor sMenuHoverText;
     nscolor sButtonText;
     nscolor sButtonHoverText;
-    nscolor sButtonBackground;
     nscolor sFrameOuterLightBorder;
     nscolor sFrameInnerDarkBorder;
     nscolor sOddCellBackground;
     nscolor sNativeHyperLinkText;
     nscolor sComboBoxText;
     nscolor sComboBoxBackground;
     nscolor sMozFieldText;
     nscolor sMozFieldBackground;
     nscolor sMozWindowText;
     nscolor sMozWindowBackground;
     nscolor sTextSelectedText;
     nscolor sTextSelectedBackground;
     nscolor sMozScrollbar;
-#if (MOZ_WIDGET_GTK == 3)
     nscolor sInfoBarText;
-#endif
     char16_t sInvisibleCharacter;
     float   sCaretRatio;
     bool    sMenuSupportsDrag;
     bool    mInitialized;
 
     void EnsureInit();
 };