Merge autoland to m-c, a=merge
authorPhil Ringnalda <philringnalda@gmail.com>
Tue, 04 Jul 2017 20:30:34 -0700
changeset 367335 0893f6685e154aaa3252ed091abb16a2feb2806d
parent 367327 acbd9a64c0fa4e1980c7732735d4f4c2761ca4c9 (current diff)
parent 367334 dfa61b6f9e93cb08f6e268c4a942acd73c1a042f (diff)
child 367336 6cb10c35c61743e1899fd4dcdeca0562f0ca740d
child 367362 89bde072ff1ec5f9539617b224e61e0787808898
push id92189
push userphilringnalda@gmail.com
push dateWed, 05 Jul 2017 03:32:26 +0000
treeherdermozilla-inbound@6cb10c35c617 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone56.0a1
first release with
nightly linux32
0893f6685e15 / 56.0a1 / 20170705100248 / files
nightly linux64
0893f6685e15 / 56.0a1 / 20170705100248 / files
nightly mac
0893f6685e15 / 56.0a1 / 20170705100303 / files
nightly win32
0893f6685e15 / 56.0a1 / 20170705030206 / files
nightly win64
0893f6685e15 / 56.0a1 / 20170705030206 / 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 m-c, a=merge MozReview-Commit-ID: 3KyMJNBoJpp
--- a/addon-sdk/source/lib/sdk/places/host/host-tags.js
+++ b/addon-sdk/source/lib/sdk/places/host/host-tags.js
@@ -32,28 +32,30 @@ const EVENT_MAP = {
 
 function tag (message) {
   let data = message.data;
   let resData = {
     id: message.id,
     event: message.event
   };
 
-  resData.data = taggingService.tagURI(newURI(data.url), data.tags);
+  if (data.tags && data.tags.length > 0)
+    resData.data = taggingService.tagURI(newURI(data.url), data.tags);
   respond(resData);
 }
 
 function untag (message) {
   let data = message.data;
   let resData = {
     id: message.id,
     event: message.event
   };
 
-  resData.data = taggingService.untagURI(newURI(data.url), data.tags);
+  if (!data.tags || data.tags.length > 0)
+    resData.data = taggingService.untagURI(newURI(data.url), data.tags);
   respond(resData);
 }
 
 function getURLsByTag (message) {
   let data = message.data;
   let resData = {
     id: message.id,
     event: message.event
--- a/browser/base/content/test/performance/browser_appmenu_reflows.js
+++ b/browser/base/content/test/performance/browser_appmenu_reflows.js
@@ -71,17 +71,42 @@ const EXPECTED_APPMENU_OPEN_REFLOWS = [
   [
     "handleEvent@resource:///modules/PanelMultiView.jsm",
     "openPopup@chrome://global/content/bindings/popup.xml",
   ],
 ];
 
 const EXPECTED_APPMENU_SUBVIEW_REFLOWS = [
   /**
-   * Nothing here! Please don't add anything new!
+   * The synced tabs view has labels that are multiline. Because of bugs in
+   * XUL layout relating to multiline text in scrollable containers, we need
+   * to manually read their height in order to ensure container heights are
+   * correct. Unfortunately this requires 2 sync reflows.
+   *
+   * If we add more views where this is necessary, we may need to duplicate
+   * these expected reflows further.
+   *
+   * Because the test dirties the frame tree by manipulating margins,
+   * getBoundingClientRect() in the descriptionHeightWorkaround code
+   * seems to sometimes fire multiple times. Bug 1363361 will change how the
+   * test dirties the frametree, after which this (2 hits in that method)
+   * should become deterministic and we can re-enable the subview testing
+   * for the remotetabs subview (this is bug 1376822). In the meantime,
+   * that subview only is excluded from this test.
+  [
+    "descriptionHeightWorkaround@resource:///modules/PanelMultiView.jsm",
+    "onTransitionEnd@resource:///modules/PanelMultiView.jsm",
+  ],
+  [
+    "descriptionHeightWorkaround@resource:///modules/PanelMultiView.jsm",
+    "onTransitionEnd@resource:///modules/PanelMultiView.jsm",
+  ],
+   */
+  /**
+   * Please don't add anything new!
    */
 ];
 
 add_task(async function() {
   await ensureNoPreloadedBrowser();
 
   await SpecialPowers.pushPrefEnv({
     set: [["browser.photon.structure.enabled", true]],
@@ -105,18 +130,27 @@ add_task(async function() {
     // exhausted, we go back up a level.
     async function openSubViewsRecursively(currentView) {
       let navButtons = Array.from(currentView.querySelectorAll(".subviewbutton-nav"));
       if (!navButtons) {
         return;
       }
 
       for (let button of navButtons) {
+        // We skip the remote tabs subview, see the comments above
+        // in EXPECTED_APPMENU_SUBVIEW_REFLOWS. bug 1376822 tracks
+        // re-enabling this.
+        if (button.id == "appMenu-library-remotetabs-button") {
+          info("Skipping " + button.id);
+          continue;
+        }
+        info("Click " + button.id);
         button.click();
         await BrowserTestUtils.waitForEvent(PanelUI.panel, "ViewShown");
+        info("Shown " + PanelUI.multiView.instance._currentSubView.id);
         // Unfortunately, I can't find a better accessor to the current
         // subview, so I have to reach the PanelMultiView instance
         // here.
         await openSubViewsRecursively(PanelUI.multiView.instance._currentSubView);
         PanelUI.multiView.goBack();
         await BrowserTestUtils.waitForEvent(PanelUI.panel, "ViewShown");
       }
     }
--- a/browser/components/controlcenter/content/panel.inc.xul
+++ b/browser/components/controlcenter/content/panel.inc.xul
@@ -11,20 +11,19 @@
        orient="vertical">
 
   <broadcasterset>
     <broadcaster id="identity-popup-mcb-learn-more" class="text-link plain" value="&identity.learnMore;"/>
     <broadcaster id="identity-popup-insecure-login-forms-learn-more" class="text-link plain" value="&identity.learnMore;"/>
   </broadcasterset>
 
   <panelmultiview id="identity-popup-multiView"
-                  mainViewId="identity-popup-mainView"
-                  descriptionheightworkaround="true">
-    <panelview id="identity-popup-mainView" flex="1">
-
+                  mainViewId="identity-popup-mainView">
+    <panelview id="identity-popup-mainView" flex="1"
+               descriptionheightworkaround="true">
       <!-- Security Section -->
       <hbox id="identity-popup-security" class="identity-popup-section">
         <vbox id="identity-popup-security-content" flex="1">
           <label class="plain">
             <label class="identity-popup-headline identity-popup-host"></label>
             <label class="identity-popup-headline identity-popup-hostless" crop="end"/>
           </label>
           <description class="identity-popup-connection-not-secure"
@@ -92,17 +91,18 @@
           <vbox id="identity-popup-permission-list"/>
           <description id="identity-popup-permission-reload-hint">&identity.permissionsReloadHint;</description>
           <description id="identity-popup-permission-empty-hint">&identity.permissionsEmpty;</description>
         </vbox>
       </hbox>
     </panelview>
 
     <!-- Security SubView -->
-    <panelview id="identity-popup-securityView">
+    <panelview id="identity-popup-securityView"
+               descriptionheightworkaround="true">
       <vbox id="identity-popup-securityView-header">
         <label class="plain">
           <label class="identity-popup-headline identity-popup-host"></label>
           <label class="identity-popup-headline identity-popup-hostless" crop="end"/>
         </label>
         <description class="identity-popup-connection-not-secure"
                      when-connection="not-secure secure-cert-user-overridden">&identity.connectionNotSecure;</description>
         <description class="identity-popup-connection-secure"
--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -438,16 +438,18 @@ const CustomizableWidgets = [
           }
           if (paginationInfo && paginationInfo.clientId == client.id) {
             this._appendClient(client, fragment, paginationInfo.maxTabs);
           } else {
             this._appendClient(client, fragment);
           }
         }
         this._tabsList.appendChild(fragment);
+        let panelView = this._tabsList.closest("panelview");
+        panelView.panelMultiView.descriptionHeightWorkaround(panelView);
       }).catch(err => {
         Cu.reportError(err);
       }).then(() => {
         // an observer for tests.
         Services.obs.notifyObservers(null, "synced-tabs-menu:test:tabs-updated");
       });
     },
     _clearTabList() {
--- a/browser/components/customizableui/PanelMultiView.jsm
+++ b/browser/components/customizableui/PanelMultiView.jsm
@@ -1030,39 +1030,51 @@ this.PanelMultiView = class {
       let bounds = dwu.getBoundsWithoutFlushing(button);
       return bounds.width > 0 && bounds.height > 0;
     });
   }
 
   /**
    * If the main view or a subview contains wrapping elements, the attribute
    * "descriptionheightworkaround" should be set on the view to force all the
-   * "description" or wrapping toolbarbutton elements to a fixed height.
-   * If the attribute is set and the visibility, contents, or width of any of
-   * these elements changes, this function should be called to refresh the
-   * calculated heights.
+   * wrapping "description", "label" or "toolbarbutton" elements to a fixed
+   * height. If the attribute is set and the visibility, contents, or width
+   * of any of these elements changes, this function should be called to
+   * refresh the calculated heights.
    *
    * This may trigger a synchronous layout.
    *
    * @param viewNode
    *        Indicates the node to scan for descendant elements. This is the main
    *        view if omitted.
    */
   descriptionHeightWorkaround(viewNode = this._mainView) {
-    if (!this.node.hasAttribute("descriptionheightworkaround")) {
+    if (!viewNode.hasAttribute("descriptionheightworkaround")) {
       // This view does not require the workaround.
       return;
     }
 
     // We batch DOM changes together in order to reduce synchronous layouts.
     // First we reset any change we may have made previously. The first time
     // this is called, and in the best case scenario, this has no effect.
     let items = [];
-    for (let element of viewNode.querySelectorAll(
-         "description:not([hidden]):not([value]),toolbarbutton[wrap]:not([hidden])")) {
+    // Non-hidden <label> or <description> elements that also aren't empty
+    // and also don't have a value attribute can be multiline (if their
+    // text content is long enough).
+    let isMultiline = ":not(:-moz-any([hidden],[value],:empty))";
+    let selector = [
+      "description" + isMultiline,
+      "label" + isMultiline,
+      "toolbarbutton[wrap]:not([hidden])",
+    ].join(",");
+    for (let element of viewNode.querySelectorAll(selector)) {
+      // Ignore items in hidden containers.
+      if (element.closest("[hidden]")) {
+        continue;
+      }
       // Take the label for toolbarbuttons; it only exists on those elements.
       element = element.labelElement || element;
 
       let bounds = element.getBoundingClientRect();
       let previous = this._multiLineElementsMap.get(element);
       // We don't need to (re-)apply the workaround for invisible elements or
       // on elements we've seen before and haven't changed in the meantime.
       if (!bounds.width || !bounds.height ||
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -6,17 +6,18 @@
        role="group"
        type="arrow"
        hidden="true"
        flip="slide"
        position="bottomcenter topright"
        noautofocus="true">
   <panelmultiview id="PanelUI-multiView" mainViewId="PanelUI-mainView"
                   viewCacheId="appMenu-viewCache">
-    <panelview id="PanelUI-mainView" context="customizationPanelContextMenu">
+    <panelview id="PanelUI-mainView" context="customizationPanelContextMenu"
+               descriptionheightworkaround="true">
       <vbox id="PanelUI-contents-scroller">
         <vbox id="PanelUI-contents" class="panelUI-grid"/>
       </vbox>
 
       <footer id="PanelUI-footer">
         <vbox id="PanelUI-footer-addons"></vbox>
         <toolbarbutton class="panel-banner-item"
                        label-update-available="&updateAvailable.panelUI.label;"
@@ -104,17 +105,18 @@
         <vbox id="PanelUI-historyItems" tooltip="bhTooltip"/>
       </vbox>
       <toolbarbutton id="PanelUI-historyMore"
                      class="panel-subview-footer subviewbutton"
                      label="&appMenuHistory.showAll.label;"
                      oncommand="PlacesCommandHook.showPlacesOrganizer('History'); CustomizableUI.hidePanelForNode(this);"/>
     </panelview>
 
-    <panelview id="PanelUI-remotetabs" flex="1" class="PanelUI-subView">
+    <panelview id="PanelUI-remotetabs" flex="1" class="PanelUI-subView"
+               descriptionheightworkaround="true">
       <label value="&appMenuRemoteTabs.label;" class="panel-subview-header"/>
       <vbox class="panel-subview-body">
         <!-- this widget has 3 boxes in the body, but only 1 is ever visible -->
         <!-- When Sync is ready to sync -->
         <vbox id="PanelUI-remotetabs-main" observes="sync-syncnow-state">
           <vbox id="PanelUI-remotetabs-buttons">
             <toolbarbutton id="PanelUI-remotetabs-view-sidebar"
                            class="subviewbutton subviewbutton-iconic"
@@ -316,17 +318,18 @@
         <vbox>
           <label id="PanelUI-characterEncodingView-autodetect-label"/>
           <vbox id="PanelUI-characterEncodingView-autodetect"
                 class="PanelUI-characterEncodingView-list"/>
         </vbox>
       </vbox>
     </panelview>
 
-    <panelview id="PanelUI-panicView" flex="1">
+    <panelview id="PanelUI-panicView" flex="1"
+               descriptionheightworkaround="true">
       <vbox class="panel-subview-body">
         <hbox id="PanelUI-panic-timeframe">
           <image id="PanelUI-panic-timeframe-icon" alt=""/>
           <vbox flex="1">
             <hbox id="PanelUI-panic-header">
               <image id="PanelUI-panic-timeframe-icon-small" alt=""/>
               <description id="PanelUI-panic-mainDesc" flex="1">&panicButton.view.mainTimeframeDesc;</description>
             </hbox>
@@ -531,19 +534,19 @@
        class="cui-widget-panel"
        role="group"
        type="arrow"
        hidden="true"
        flip="slide"
        position="bottomcenter topright"
        noautofocus="true">
   <photonpanelmultiview id="appMenu-multiView" mainViewId="appMenu-mainView"
-                        descriptionheightworkaround="true"
                         viewCacheId="appMenu-viewCache">
-    <panelview id="appMenu-mainView" class="PanelUI-subView">
+    <panelview id="appMenu-mainView" class="PanelUI-subView"
+               descriptionheightworkaround="true">
       <vbox class="panel-subview-body">
         <vbox id="appMenu-addon-banners"/>
         <toolbarbutton class="panel-banner-item"
                        label-update-available="&updateAvailable.panelUI.label;"
                        label-update-manual="&updateManual.panelUI.label;"
                        label-update-restart="&updateRestart.panelUI.label2;"
                        oncommand="PanelUI._onBannerItemSelected(event)"
                        wrap="true"
--- a/browser/components/downloads/content/downloadsOverlay.xul
+++ b/browser/components/downloads/content/downloadsOverlay.xul
@@ -102,18 +102,17 @@
                   label="&cmd.removeFromHistory.label;"
                   accesskey="&cmd.removeFromHistory.accesskey;"/>
         <menuitem command="downloadsCmd_clearList"
                   label="&cmd.clearList2.label;"
                   accesskey="&cmd.clearList2.accesskey;"/>
       </menupopup>
 
       <panelmultiview id="downloadsPanel-multiView"
-                      mainViewId="downloadsPanel-mainView"
-                      descriptionheightworkaround="true">
+                      mainViewId="downloadsPanel-mainView">
 
         <panelview id="downloadsPanel-mainView">
           <vbox class="panel-view-body-unscrollable">
             <richlistbox id="downloadsListBox"
                          context="downloadsContextMenu"
                          onmouseover="DownloadsView.onDownloadMouseOver(event);"
                          onmouseout="DownloadsView.onDownloadMouseOut(event);"
                          oncontextmenu="DownloadsView.onDownloadContextMenu(event);"
@@ -152,17 +151,18 @@
                         accesskey="&downloadsHistory.accesskey;"
                         flex="1"
                         oncommand="DownloadsPanel.showDownloadsHistory();"/>
               </hbox>
             </stack>
           </vbox>
         </panelview>
 
-        <panelview id="downloadsPanel-blockedSubview">
+        <panelview id="downloadsPanel-blockedSubview"
+                   descriptionheightworkaround="true">
           <vbox class="panel-view-body-unscrollable">
             <description id="downloadsPanel-blockedSubview-title"/>
             <description id="downloadsPanel-blockedSubview-details1"/>
             <description id="downloadsPanel-blockedSubview-details2"/>
           </vbox>
           <hbox id="downloadsPanel-blockedSubview-buttons"
                 class="downloadsPanelFooter"
                 align="stretch">
--- a/browser/components/places/content/editBookmarkOverlay.js
+++ b/browser/components/places/content/editBookmarkOverlay.js
@@ -506,17 +506,20 @@ var gEditItemOverlay = {
     // Optimize the trivial cases (which are actually the most common).
     if (inputTags.length == 0 && aCurrentTags.length == 0)
       return { newTags: [], removedTags: [] };
     if (inputTags.length == 0)
       return { newTags: [], removedTags: aCurrentTags };
     if (aCurrentTags.length == 0)
       return { newTags: inputTags, removedTags: [] };
 
-    let removedTags = aCurrentTags.filter(t => !inputTags.includes(t));
+    // Do not remove tags that may be reinserted with a different
+    // case, since the tagging service may handle those more efficiently.
+    let lcInputTags = inputTags.map(t => t.toLowerCase());
+    let removedTags = aCurrentTags.filter(t => !lcInputTags.includes(t.toLowerCase()));
     let newTags = inputTags.filter(t => !aCurrentTags.includes(t));
     return { removedTags, newTags };
   },
 
   // Adds and removes tags for one or more uris.
   async _setTagsFromTagsInputField(aCurrentTags, aURIs) {
     let { removedTags, newTags } = this._getTagsChanges(aCurrentTags);
     if (removedTags.length + newTags.length == 0)
@@ -532,24 +535,24 @@ var gEditItemOverlay = {
       }
 
       PlacesUtils.transactionManager.doTransaction(
         new PlacesAggregatedTransaction("Update tags", txns));
       return true;
     }
 
     let setTags = async function() {
+      if (removedTags.length > 0) {
+        await PlacesTransactions.Untag({ urls: aURIs, tags: removedTags })
+                                .transact();
+      }
       if (newTags.length > 0) {
         await PlacesTransactions.Tag({ urls: aURIs, tags: newTags })
                                 .transact();
       }
-      if (removedTags.length > 0) {
-        await PlacesTransactions.Untag({ urls: aURIs, tags: removedTags })
-                          .transact();
-      }
     };
 
     // Only in the library info-pane it's safe (and necessary) to batch these.
     // TODO bug 1093030: cleanup this mess when the bookmarksProperties dialog
     // and star UI code don't "run a batch in the background".
     if (window.document.documentElement.id == "places")
       PlacesTransactions.batch(setTags).catch(Components.utils.reportError);
     else
--- a/browser/components/places/tests/browser/browser_bookmarkProperties_editTagContainer.js
+++ b/browser/components/places/tests/browser/browser_bookmarkProperties_editTagContainer.js
@@ -26,34 +26,34 @@ add_task(async function() {
   let fooTag = tagsContainer.getChild(0);
   let tagNode = fooTag;
   tree.selectNode(fooTag);
   Assert.equal(tagNode.title, "tag1", "tagNode title is correct");
 
   Assert.ok(tree.controller.isCommandEnabled("placesCmd_show:info"),
             "'placesCmd_show:info' on current selected node is enabled");
 
-  let promiseTitleResetNotification = promiseBookmarksNotification(
+  let promiseTitleResetNotification = PlacesTestUtils.waitForNotification(
       "onItemChanged", (itemId, prop, isAnno, val) => prop == "title" && val == "tag1");
 
   await withBookmarksDialog(
     true,
     function openDialog() {
       tree.controller.doCommand("placesCmd_show:info");
     },
     async function test(dialogWin) {
       // Check that the dialog is not read-only.
       Assert.ok(!dialogWin.gEditItemOverlay.readOnly, "Dialog should not be read-only");
 
       // Check that name picker is not read only
       let namepicker = dialogWin.document.getElementById("editBMPanel_namePicker");
       Assert.ok(!namepicker.readOnly, "Name field should not be read-only");
       Assert.equal(namepicker.value, "tag1", "Node title is correct");
 
-      let promiseTitleChangeNotification = promiseBookmarksNotification(
+      let promiseTitleChangeNotification = PlacesTestUtils.waitForNotification(
           "onItemChanged", (itemId, prop, isAnno, val) => prop == "title" && val == "tag2");
 
       fillBookmarkTextField("editBMPanel_namePicker", "tag2", dialogWin);
 
       await promiseTitleChangeNotification;
 
       Assert.equal(namepicker.value, "tag2", "Node title has been properly edited");
 
--- a/browser/components/places/tests/browser/browser_bookmarksProperties.js
+++ b/browser/components/places/tests/browser/browser_bookmarksProperties.js
@@ -393,28 +393,26 @@ function open_properties_dialog() {
        "We have a places node selected: " + tree.selectedNode.title);
 
     // Wait for the Properties dialog.
     function windowObserver(aSubject, aTopic, aData) {
       if (aTopic != "domwindowopened")
         return;
       ww.unregisterNotification(windowObserver);
       let observerWindow = aSubject.QueryInterface(Ci.nsIDOMWindow);
-      waitForFocus(() => {
-        // Windows has been loaded, execute our test now.
-        executeSoon(function() {
-          // Ensure overlay is loaded
-          ok(observerWindow.gEditItemOverlay.initialized, "EditItemOverlay is initialized");
-          gCurrentTest.window = observerWindow;
-          try {
-            gCurrentTest.run();
-          } catch (ex) {
-            ok(false, "An error occured during test run: " + ex.message);
-          }
-        });
+      waitForFocus(async () => {
+        // Ensure overlay is loaded
+        await BrowserTestUtils.waitForCondition(
+          () => observerWindow.gEditItemOverlay.initialized, "EditItemOverlay is initialized");
+        gCurrentTest.window = observerWindow;
+        try {
+          gCurrentTest.run();
+        } catch (ex) {
+          ok(false, "An error occured during test run: " + ex.message);
+        }
       }, observerWindow);
     }
     ww.registerNotification(windowObserver);
 
     var command = null;
     switch (gCurrentTest.action) {
       case ACTION_EDIT:
         command = "placesCmd_show:info";
--- a/browser/components/places/tests/chrome/test_bug485100-change-case-loses-tag.xul
+++ b/browser/components/places/tests/chrome/test_bug485100-change-case-loses-tag.xul
@@ -23,27 +23,26 @@
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         title="485100: Exchanging a letter of a tag name with its big/small equivalent removes tag from bookmark"
         onload="runTest();">
 
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
   <script type="application/javascript"
           src="chrome://browser/content/places/editBookmarkOverlay.js"/>
+  <script type="application/javascript" src="head.js" />
 
   <body xmlns="http://www.w3.org/1999/xhtml" />
 
   <vbox id="editBookmarkPanelContent"/>
 
   <script type="application/javascript">
   <![CDATA[
-
     function runTest() {
       SimpleTest.waitForExplicitFinish();
-
       (async function() {
         let testTag = "foo";
         let testTagUpper = "Foo";
         let testURI = Services.io.newURI("http://www.example.com/");
 
         // Add a bookmark.
         let bm = await PlacesUtils.bookmarks.insert({
           parentGuid: PlacesUtils.bookmarks.toolbarGuid,
@@ -55,24 +54,33 @@
 
         // Init panel
         ok(gEditItemOverlay, "gEditItemOverlay is in context");
         let node = await PlacesUIUtils.promiseNodeLikeFromFetchInfo(bm);
         gEditItemOverlay.initPanel({ node });
 
         // add a tag
         document.getElementById("editBMPanel_tagsField").value = testTag;
+        let promiseNotification = PlacesTestUtils.waitForNotification(
+          "onItemChanged", (id, property) => property == "tags");
         gEditItemOverlay.onTagsFieldChange();
+        await promiseNotification;
 
         // test that the tag has been added in the backend
         is(PlacesUtils.tagging.getTagsForURI(testURI)[0], testTag, "tags match");
 
         // change the tag
         document.getElementById("editBMPanel_tagsField").value = testTagUpper;
+        // The old sync API doesn't notify a tags change, and fixing it would be
+        // quite complex, so we just wait for a title change until tags are
+        // refactored.
+        promiseNotification = PlacesTestUtils.waitForNotification(
+          "onItemChanged", (id, property) => property == "title");
         gEditItemOverlay.onTagsFieldChange();
+        await promiseNotification;
 
         // test that the tag has been added in the backend
         is(PlacesUtils.tagging.getTagsForURI(testURI)[0], testTagUpper, "tags match");
 
         // Cleanup.
         PlacesUtils.tagging.untagURI(testURI, [testTag]);
         await PlacesUtils.bookmarks.remove(bm.guid);
       })().then(() => SimpleTest.finish());
--- a/browser/components/places/tests/chrome/test_bug631374_tags_selector_scroll.xul
+++ b/browser/components/places/tests/chrome/test_bug631374_tags_selector_scroll.xul
@@ -22,55 +22,49 @@
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         title="Bug 631374 - Editing tags in the selector scrolls up the listbox"
         onload="runTest();">
 
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
   <script type="application/javascript"
           src="chrome://browser/content/places/editBookmarkOverlay.js"/>
+  <script type="application/javascript" src="head.js" />
 
   <body xmlns="http://www.w3.org/1999/xhtml" />
 
   <vbox id="editBookmarkPanelContent"/>
 
   <script type="application/javascript">
   <![CDATA[
-
      /**
       * This test checks that editing tags doesn't scroll the tags selector
       * listbox to wrong positions.
       */
 
     function runTest() {
       SimpleTest.waitForExplicitFinish();
-
       (async function() {
-        let bs = PlacesUtils.bookmarks;
-
+        await PlacesUtils.bookmarks.eraseEverything();
         let tags = ["a", "b", "c", "d", "e", "f", "g",
                     "h", "i", "l", "m", "n", "o", "p"];
 
         // Add a bookmark and tag it.
         let uri1 = Services.io.newURI("http://www1.mozilla.org/");
-        let bm1 = await bs.insert({
-          parentGuid: bs.toolbarGuid,
-          index: bs.DEFAULT_INDEX,
-          type: bs.TYPE_BOOKMARK,
+        let bm1 = await PlacesUtils.bookmarks.insert({
+          parentGuid: PlacesUtils.bookmarks.toolbarGuid,
           title: "mozilla",
           url: uri1.spec
         });
         PlacesUtils.tagging.tagURI(uri1, tags);
 
         // Add a second bookmark so that tags won't disappear when unchecked.
         let uri2 = Services.io.newURI("http://www2.mozilla.org/");
-        let bm2 = await bs.insert({
-          parentGuid: bs.toolbarGuid,
-          index: bs.DEFAULT_INDEX,
-          type: bs.TYPE_BOOKMARK,
+        let bm2 = await PlacesUtils.bookmarks.insert({
+          parentGuid: PlacesUtils.bookmarks.toolbarGuid,
           title: "mozilla",
           url: uri2.spec
         });
         PlacesUtils.tagging.tagURI(uri2, tags);
 
         // Init panel.
         ok(gEditItemOverlay, "gEditItemOverlay is in context");
         let node1 = await PlacesUIUtils.promiseNodeLikeFromFetchInfo(bm1);
@@ -88,49 +82,58 @@
 
           tagsSelector.ensureElementIsVisible(listItem);
           let visibleIndex = tagsSelector.getIndexOfFirstVisibleRow();
 
           ok(listItem.checked, "Item is checked " + i);
           let selectedTag = listItem.label;
 
           // Uncheck the tag.
+          let promiseNotification = PlacesTestUtils.waitForNotification(
+            "onItemChanged", (id, property) => property == "tags");
           listItem.checked = false;
+          await promiseNotification;
           is(visibleIndex, tagsSelector.getIndexOfFirstVisibleRow(),
              "Scroll position did not change");
 
           // The listbox is rebuilt, so we have to get the new element.
           let newItem = tagsSelector.selectedItem;
           isnot(newItem, null, "Valid new listItem found");
           ok(!newItem.checked, "New listItem is unchecked " + i);
           is(newItem.label, selectedTag, "Correct tag is still selected");
 
           // Check the tag.
+          promiseNotification = PlacesTestUtils.waitForNotification(
+            "onItemChanged", (id, property) => property == "tags");
           newItem.checked = true;
+          await promiseNotification;
           is(visibleIndex, tagsSelector.getIndexOfFirstVisibleRow(),
              "Scroll position did not change");
         }
 
         // Remove the second bookmark, then nuke some of the tags.
-        await bs.remove(bm2.guid);
+        await PlacesUtils.bookmarks.remove(bm2);
 
         // Doing this backwords tests more interesting paths.
         for (let i = tags.length - 1; i >= 0 ; i -= 2) {
           tagsSelector.selectedIndex = i;
           let listItem = tagsSelector.selectedItem;
           isnot(listItem, null, "Valid listItem found");
 
           tagsSelector.ensureElementIsVisible(listItem);
           let firstVisibleTag = tags[tagsSelector.getIndexOfFirstVisibleRow()];
 
           ok(listItem.checked, "Item is checked " + i);
           let selectedTag = listItem.label;
 
           // Uncheck the tag.
+          let promiseNotification = PlacesTestUtils.waitForNotification(
+            "onItemChanged", (id, property) => property == "tags");
           listItem.checked = false;
+          await promiseNotification;
 
           // Ensure the first visible tag is still visible in the list.
           let firstVisibleIndex = tagsSelector.getIndexOfFirstVisibleRow();
           let lastVisibleIndex = firstVisibleIndex + tagsSelector.getNumberOfVisibleRows() -1;
           let expectedTagIndex = tags.indexOf(firstVisibleTag);
           ok(expectedTagIndex >= firstVisibleIndex &&
              expectedTagIndex <= lastVisibleIndex,
              "Scroll position is correct");
@@ -140,31 +143,29 @@
           isnot(newItem, null, "Valid new listItem found");
           ok(newItem.checked, "New listItem is checked " + i);
           is(tagsSelector.selectedItem.label,
              tags[Math.min(i + 1, tags.length - 2)],
              "The next tag is now selected");
         }
 
         // Cleanup.
-        await bs.remove(bm1.guid);
-      })().then(SimpleTest.finish).catch(alert);
+        await PlacesUtils.bookmarks.remove(bm1);
+      })().catch(ex => ok(false, "test failed: " + ex)).then(SimpleTest.finish);
     }
 
     function openTagSelector() {
       // Wait for the tags selector to be open.
       let promise = new Promise(resolve => {
         let row = document.getElementById("editBMPanel_tagsSelectorRow");
         row.addEventListener("DOMAttrModified", function onAttrModified() {
           row.removeEventListener("DOMAttrModified", onAttrModified);
           resolve();
         });
       });
-
       // Open the tags selector.
       document.getElementById("editBMPanel_tagsSelectorExpander").doCommand();
-
       return promise;
     }
   ]]>
   </script>
 
 </window>
--- a/browser/components/preferences/in-content-new/findInPage.js
+++ b/browser/components/preferences/in-content-new/findInPage.js
@@ -408,50 +408,53 @@ var gSearchResultsPane = {
    * @param String query
    *    Word or words that are being searched for
    */
   createSearchTooltip(anchorNode, query) {
     let searchTooltip = anchorNode.ownerDocument.createElement("span");
     searchTooltip.setAttribute("class", "search-tooltip");
     searchTooltip.textContent = query;
 
-    anchorNode.setAttribute("data-has-tooltip", "true");
+    // Set tooltipNode property to track corresponded tooltip node.
+    anchorNode.tooltipNode = searchTooltip;
     anchorNode.parentElement.classList.add("search-tooltip-parent");
     anchorNode.parentElement.appendChild(searchTooltip);
 
     this.calculateTooltipPosition(anchorNode);
   },
 
   calculateTooltipPosition(anchorNode) {
-    let searchTooltip = anchorNode.parentElement.querySelector(":scope > .search-tooltip");
-
+    let searchTooltip = anchorNode.tooltipNode;
     // In order to get the up-to-date position of each of the nodes that we're
     // putting tooltips on, we have to flush layout intentionally, and that
     // this is the result of a XUL limitation (bug 1363730).
     let anchorRect = anchorNode.getBoundingClientRect();
     let tooltipRect = searchTooltip.getBoundingClientRect();
     let parentRect = anchorNode.parentElement.getBoundingClientRect();
 
-    let offSet = (anchorRect.width / 2) - (tooltipRect.width / 2);
+    let offSetLeft = (anchorRect.width / 2) - (tooltipRect.width / 2);
     let relativeOffset = anchorRect.left - parentRect.left;
-    offSet += relativeOffset > 0 ? relativeOffset : 0;
+    offSetLeft += relativeOffset > 0 ? relativeOffset : 0;
+    // 20.5 is reserved for tooltip position
+    let offSetTop = anchorRect.top - parentRect.top - 20.5;
 
-    searchTooltip.style.setProperty("left", `${offSet}px`);
+    searchTooltip.style.setProperty("left", `${offSetLeft}px`);
+    searchTooltip.style.setProperty("top", `${offSetTop}px`);
   },
 
   /**
    * Remove all search tooltips that were created.
    */
   removeAllSearchTooltips() {
     let searchTooltips = Array.from(document.querySelectorAll(".search-tooltip"));
     for (let searchTooltip of searchTooltips) {
       searchTooltip.parentElement.classList.remove("search-tooltip-parent");
       searchTooltip.remove();
     }
-    this.listSearchTooltips.forEach((anchorNode) => anchorNode.removeAttribute("data-has-tooltip"));
+    this.listSearchTooltips.forEach((anchorNode) => anchorNode.tooltipNode.remove());
     this.listSearchTooltips.clear();
   },
 
   /**
    * Remove all indicators on menuitem.
    */
   removeAllSearchMenuitemIndicators() {
     this.listSearchMenuitemIndicators.forEach((node) => node.removeAttribute("indicator"));
--- a/browser/components/preferences/in-content-new/subdialogs.js
+++ b/browser/components/preferences/in-content-new/subdialogs.js
@@ -333,26 +333,22 @@ SubDialog.prototype = {
     }
 
     this._trapFocus();
 
     // Search within main document and highlight matched keyword.
     gSearchResultsPane.searchWithinNode(this._titleElement, gSearchResultsPane.query);
 
     // Search within sub-dialog document and highlight matched keyword.
-    let subDialogsChildren = this._frame.contentDocument
-      .querySelectorAll(":scope > *:not([data-hidden-from-search])");
-
-    for (let i = 0; i < subDialogsChildren.length; i++) {
-      gSearchResultsPane.searchWithinNode(subDialogsChildren[i], gSearchResultsPane.query);
-    }
+    gSearchResultsPane.searchWithinNode(this._frame.contentDocument.firstElementChild,
+      gSearchResultsPane.query);
 
     // Creating tooltips for all the instances found
     for (let node of gSearchResultsPane.listSearchTooltips) {
-      if (!node.getAttribute("data-has-tooltip")) {
+      if (!node.tooltipNode) {
         gSearchResultsPane.createSearchTooltip(node, gSearchResultsPane.query);
       }
     }
   },
 
   _onResize(mutations) {
     let frame = this._frame;
     // The width and height styles are needed for the initial
--- a/browser/extensions/formautofill/FormAutofillContent.jsm
+++ b/browser/extensions/formautofill/FormAutofillContent.jsm
@@ -493,24 +493,42 @@ var FormAutofillContent = {
     if (!field || !(field instanceof Ci.nsIDOMHTMLInputElement)) {
       return;
     }
 
     formFillController.markAsAutofillField(field);
   },
 
   _previewProfile(doc) {
-    let selectedIndex = ProfileAutocomplete._getSelectedIndex(doc.ownerGlobal);
+    let docWin = doc.ownerGlobal;
+    let selectedIndex = ProfileAutocomplete._getSelectedIndex(docWin);
     let lastAutoCompleteResult = ProfileAutocomplete.getProfileAutoCompleteResult();
+    let focusedInput = formFillController.focusedInput;
+    let mm = this._messageManagerFromWindow(docWin);
 
     if (selectedIndex === -1 ||
+        !focusedInput ||
         !lastAutoCompleteResult ||
         lastAutoCompleteResult.getStyleAt(selectedIndex) != "autofill-profile") {
+      mm.sendAsyncMessage("FormAutofill:UpdateWarningMessage", {});
+
       ProfileAutocomplete._clearProfilePreview();
     } else {
+      let focusedInputDetails = this.getInputDetails(focusedInput);
+      let profile = JSON.parse(lastAutoCompleteResult.getCommentAt(selectedIndex));
+      let allFieldNames = FormAutofillContent.getAllFieldNames(focusedInput);
+      let profileFields = allFieldNames.filter(fieldName => !!profile[fieldName]);
+
+      let focusedCategory = FormAutofillUtils.getCategoryFromFieldName(focusedInputDetails.fieldName);
+      let categories = FormAutofillUtils.getCategoriesFromFieldNames(profileFields);
+      mm.sendAsyncMessage("FormAutofill:UpdateWarningMessage", {
+        focusedCategory,
+        categories,
+      });
+
       ProfileAutocomplete._previewSelectedProfile(selectedIndex);
     }
   },
 
   _messageManagerFromWindow(win) {
     return win.QueryInterface(Ci.nsIInterfaceRequestor)
               .getInterface(Ci.nsIWebNavigation)
               .QueryInterface(Ci.nsIDocShell)
--- a/browser/extensions/formautofill/FormAutofillUtils.jsm
+++ b/browser/extensions/formautofill/FormAutofillUtils.jsm
@@ -40,25 +40,29 @@ this.FormAutofillUtils = {
   isAddressField(fieldName) {
     return !!this._fieldNameInfo[fieldName] && !this.isCreditCardField(fieldName);
   },
 
   isCreditCardField(fieldName) {
     return this._fieldNameInfo[fieldName] == "creditCard";
   },
 
+  getCategoryFromFieldName(fieldName) {
+    return this._fieldNameInfo[fieldName];
+  },
+
   getCategoriesFromFieldNames(fieldNames) {
     let categories = new Set();
     for (let fieldName of fieldNames) {
-      let info = this._fieldNameInfo[fieldName];
+      let info = this.getCategoryFromFieldName(fieldName);
       if (info) {
         categories.add(info);
       }
     }
-    return categories;
+    return Array.from(categories);
   },
 
   getAddressSeparator() {
     // The separator should be based on the L10N address format, and using a
     // white space is a temporary solution.
     return " ";
   },
 
--- a/browser/extensions/formautofill/ProfileAutoCompleteResult.jsm
+++ b/browser/extensions/formautofill/ProfileAutoCompleteResult.jsm
@@ -37,16 +37,17 @@ this.ProfileAutoCompleteResult = functio
   this._popupLabels = this._generateLabels(this._focusedFieldName,
                                            this._allFieldNames,
                                            this._matchingProfiles);
   // Add an empty result entry for footer. Its content will come from
   // the footer binding, so don't assign any value to it.
   this._popupLabels.push({
     primary: "",
     secondary: "",
+    categories: FormAutofillUtils.getCategoriesFromFieldNames(allFieldNames),
   });
 };
 
 ProfileAutoCompleteResult.prototype = {
 
   // The user's query string
   searchString: "",
 
--- a/browser/extensions/formautofill/content/formautofill.xml
+++ b/browser/extensions/formautofill/content/formautofill.xml
@@ -53,17 +53,17 @@
       <method name="_onOverflow">
         <body></body>
       </method>
 
       <method name="_onUnderflow">
         <body></body>
       </method>
 
-      <method name="_adjustProfileItemLayout">
+      <method name="_adjustAutofillItemLayout">
         <body>
         <![CDATA[
           let outerBoxRect = this.parentNode.getBoundingClientRect();
 
           // Make item fit in popup as XUL box could not constrain
           // item's width
           this._itemBox.style.width = outerBoxRect.width + "px";
           // Use two-lines layout when width is smaller than 150px
@@ -119,72 +119,147 @@
           let {AutoCompletePopup} = Cu.import("resource://gre/modules/AutoCompletePopup.jsm", {});
 
           AutoCompletePopup.sendMessageToBrowser("FormAutofill:PreviewProfile");
 
           return val;
         ]]></setter>
       </property>
 
-
       <method name="_adjustAcItem">
         <body>
         <![CDATA[
-          this._adjustProfileItemLayout();
+          this._adjustAutofillItemLayout();
           this.setAttribute("formautofillattached", "true");
 
           let {primary, secondary} = JSON.parse(this.getAttribute("ac-value"));
 
           this._label.textContent = primary;
           this._comment.textContent = secondary;
         ]]>
         </body>
       </method>
     </implementation>
   </binding>
 
   <binding id="autocomplete-profile-listitem-footer" extends="chrome://formautofill/content/formautofill.xml#autocomplete-profile-listitem-base">
     <xbl:content xmlns="http://www.w3.org/1999/xhtml">
       <div anonid="autofill-footer" class="autofill-item-box autofill-footer">
+        <div anonid="autofill-warning" class="autofill-footer-row autofill-warning">
+        </div>
+        <div anonid="autofill-option-button" class="autofill-footer-row autofill-option-button">
+        </div>
       </div>
     </xbl:content>
 
     <handlers>
       <handler event="click" button="0"><![CDATA[
         window.openPreferences("panePrivacy", {origin: "autofillFooter"});
       ]]></handler>
     </handlers>
 
     <implementation implements="nsIDOMXULSelectControlItemElement">
       <constructor>
         <![CDATA[
           this._itemBox = document.getAnonymousElementByAttribute(
             this, "anonid", "autofill-footer"
           );
+          this._optionButton = document.getAnonymousElementByAttribute(
+            this, "anonid", "autofill-option-button"
+          );
+          this._warningTextBox = document.getAnonymousElementByAttribute(
+            this, "anonid", "autofill-warning"
+          );
+
+          this.allFieldCategories = JSON.parse(this.getAttribute("ac-value")).categories;
+
+          /**
+           * Update the text on the footer.
+           *
+           * @private
+           * @param {string|string[]} categories
+           *        A list of categories that used to generate the message.
+           * @param {boolean} hasExtraCategories
+           *        Used to determine if it has the extra categories other than the focued category. If
+           *        the value is true, we show "Also fill ...", otherwise, show "Fill ..." only.
+           */
+          this._updateText = (categories = this.allFieldCategories, hasExtraCategories = true) => {
+            let warningTextTmplKey = hasExtraCategories ? "phishingWarningMessage" : "phishingWarningMessage2";
+            let sep = this._stringBundle.GetStringFromName("fieldNameSeparator");
+            let categoriesText = categories.map(this._stringBundle.GetStringFromName).join(sep);
+
+            this._warningTextBox.textContent = this._stringBundle.formatStringFromName(warningTextTmplKey,
+              [categoriesText], 1);
+            this.parentNode.parentNode.adjustHeight();
+          };
+
+          /**
+           * A handler for updating warning message once selectedIndex has been changed.
+           *
+           * There're three different states of warning message:
+           * 1. None of addresses were selected: We show all the categories in the form.
+           * 2. An address was selested: Show the additional categories that will also be filled.
+           * 3. An address was selected, but the focused category is the same as the only all categories: Only show
+           * the exact category that we're going to fill in.
+           *
+           * @private
+           * @param {string} focusedCategory
+           *        The category that the focused input's field belongs to.
+           * @param {string[]} categories
+           *        The categories of all the fields contained in the selected address.
+           */
+          this._updateWarningMsgHandler = ({data: {focusedCategory, categories}} = {data: {}}) => {
+            let hasSelectedAddress = focusedCategory && categories;
+            // If the length of categories is 1, that means all the fillable fields are in the same
+            // category. We will change the way to inform user according to this flag.
+            let hasExtraCategories = hasSelectedAddress && categories.length > 1;
+            if (!hasSelectedAddress) {
+              this._updateText();
+              return;
+            }
+
+            let showCategories = hasExtraCategories ?
+                                 categories.filter(category => category != focusedCategory) :
+                                 [focusedCategory];
+            this._updateText(showCategories, hasExtraCategories);
+          };
 
           this._adjustAcItem();
+          this._updateText();
         ]]>
       </constructor>
 
+      <method name="_onCollapse">
+        <body>
+        <![CDATA[
+          /* global messageManager */
+
+          messageManager.removeMessageListener("FormAutofill:UpdateWarningMessage", this._updateWarningMsgHandler);
+        ]]>
+        </body>
+      </method>
+
       <method name="_adjustAcItem">
         <body>
         <![CDATA[
           /* global Cu */
-          this._adjustProfileItemLayout();
+          this._adjustAutofillItemLayout();
           this.setAttribute("formautofillattached", "true");
 
           let {AppConstants} = Cu.import("resource://gre/modules/AppConstants.jsm", {});
-          let footerTextBundleKey = AppConstants.platform == "macosx" ?
+          let buttonTextBundleKey = AppConstants.platform == "macosx" ?
             "autocompleteFooterOptionOSX" : "autocompleteFooterOption";
           // If the popup shows up with small layout, we should use short string to
           // have a better fit in the box.
           if (this._itemBox.getAttribute("size") == "small") {
-            footerTextBundleKey += "Short";
+            buttonTextBundleKey += "Short";
           }
-          let footerText = this._stringBundle.GetStringFromName(footerTextBundleKey);
-          this._itemBox.textContent = footerText;
+          let buttonText = this._stringBundle.GetStringFromName(buttonTextBundleKey);
+          this._optionButton.textContent = buttonText;
+
+          messageManager.addMessageListener("FormAutofill:UpdateWarningMessage", this._updateWarningMsgHandler);
         ]]>
         </body>
       </method>
     </implementation>
   </binding>
 
 </bindings>
--- a/browser/extensions/formautofill/locale/en-US/formautofill.properties
+++ b/browser/extensions/formautofill/locale/en-US/formautofill.properties
@@ -13,8 +13,21 @@ changeAutofillOptionsOSX = Change Form A
 updateAddressMessage = Would you like to update your address with this new information?
 createAddressLabel = Create New Address
 updateAddressLabel = Update Address
 openAutofillMessagePanel = Open Form Autofill message panel
 autocompleteFooterOption = Form Autofill Options
 autocompleteFooterOptionShort = Options
 autocompleteFooterOptionOSX = Form Autofill Preferences
 autocompleteFooterOptionOSXShort = Preferences
+address = address
+name = name
+organization = company
+tel = phone
+email = email
+# LOCALIZATION NOTE (fieldNameSeparator): This is used as a separator between categories.
+fieldNameSeparator = ,\u0020
+# LOCALIZATION NOTE (phishingWarningMessage, phishingWarningMessage2): The warning
+# text that is displayed for informing users what categories are about to be filled.
+# "%S" will be replaced with a list generated from the pre-defined categories.
+# The text would be e.g. Also fill company, phone, email
+phishingWarningMessage = Also fill %S
+phishingWarningMessage2 = Fill %S
--- a/browser/extensions/formautofill/skin/linux/autocomplete-item.css
+++ b/browser/extensions/formautofill/skin/linux/autocomplete-item.css
@@ -8,11 +8,15 @@
 .autofill-item-box > .profile-item-col > .profile-label {
   font-size: .84em;
 }
 
 .autofill-item-box > .profile-item-col > .profile-comment {
   font-size: .7em;
 }
 
-.autofill-footer {
+.autofill-footer > .autofill-warning {
+  font-size: .7em;
+}
+
+.autofill-footer > .autofill-option-button {
   font-size: .77em;
 }
--- a/browser/extensions/formautofill/skin/osx/autocomplete-item.css
+++ b/browser/extensions/formautofill/skin/osx/autocomplete-item.css
@@ -7,8 +7,12 @@
 
 .autofill-item-box > .profile-item-col > .profile-label {
   font-size: 1.09em;
 }
 
 .autofill-item-box > .profile-item-col > .profile-comment {
   font-size: .9em;
 }
+
+.autofill-footer > .autofill-warning {
+  font-size: .9em;
+}
--- a/browser/extensions/formautofill/skin/shared/autocomplete-item.css
+++ b/browser/extensions/formautofill/skin/shared/autocomplete-item.css
@@ -5,17 +5,17 @@
 @namespace url("http://www.w3.org/1999/xhtml");
 @namespace xul url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
 
 
 xul|richlistitem[originaltype="autofill-profile"][selected="true"] > .autofill-item-box {
   background-color: #F2F2F2;
 }
 
-xul|richlistitem[originaltype="autofill-footer"][selected="true"] > .autofill-footer {
+xul|richlistitem[originaltype="autofill-footer"][selected="true"] > .autofill-item-box > .autofill-option-button {
   background-color: #DCDCDE;
 }
 
 .autofill-item-box {
   --item-padding-vertical: 6px;
   --item-padding-horizontal: 10px;
   --col-spacer: 7px;
   --item-width: calc(50% - (var(--col-spacer) / 2));
@@ -24,17 +24,19 @@ xul|richlistitem[originaltype="autofill-
 
 .autofill-item-box[size="small"] {
   --item-padding-vertical: 7px;
   --col-spacer: 0px;
   --row-spacer: 3px;
   --item-width: 100%;
 }
 
-.autofill-footer {
+.autofill-footer,
+.autofill-footer[size="small"] {
+  --item-width: 100%;
   --item-padding-vertical: 0;
   --item-padding-horizontal: 0;
 }
 
 .autofill-item-box {
   box-sizing: border-box;
   margin: 0;
   border-bottom: 1px solid rgba(38,38,38,.15);
@@ -76,12 +78,30 @@ xul|richlistitem[originaltype="autofill-
 }
 
 .autofill-item-box[size="small"] > .profile-comment-col {
   margin-top: var(--row-spacer);
   text-align: start;
 }
 
 .autofill-footer {
+  flex-direction: column;
+}
+
+.autofill-footer > .autofill-footer-row {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  width: var(--item-width);
+}
+
+.autofill-footer > .autofill-warning {
+  padding: 2.5px 0;
+  color: #989898;
+  text-align: center;
+  background-color: rgba(248,232,28,.2);
+  border-bottom: 1px solid rgba(38,38,38,.15);
+}
+
+.autofill-footer > .autofill-option-button {
   height: 41px;
   background-color: #EDEDED;
-  justify-content: center;
 }
--- a/browser/extensions/formautofill/skin/windows/autocomplete-item.css
+++ b/browser/extensions/formautofill/skin/windows/autocomplete-item.css
@@ -5,17 +5,21 @@
 @namespace url("http://www.w3.org/1999/xhtml");
 @namespace xul url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
 
 
 .autofill-item-box > .profile-item-col > .profile-comment {
   font-size: .83em;
 }
 
-.autofill-footer {
+.autofill-footer > .autofill-warning {
+  font-size: .83em;
+}
+
+.autofill-footer > .autofill-option-button {
   font-size: .91em;
 }
 
 @media (-moz-windows-default-theme: 0) {
   xul|richlistitem[originaltype="autofill-profile"][selected="true"] > .autofill-item-box {
     background-color: Highlight;
   }
 }
--- a/browser/extensions/formautofill/test/browser/browser_autocomplete_footer.js
+++ b/browser/extensions/formautofill/test/browser/browser_autocomplete_footer.js
@@ -1,60 +1,87 @@
 "use strict";
 
 const URL = BASE_URL + "autocomplete_basic.html";
 const PRIVACY_PREF_URL = "about:preferences#privacy";
 
+async function expectWarningText(browser, expectedText) {
+  const {autoCompletePopup: {richlistbox: itemsBox}} = browser;
+  const warningBox = itemsBox.querySelector(".autocomplete-richlistitem:last-child")._warningTextBox;
+
+  await BrowserTestUtils.waitForCondition(() => {
+    return warningBox.textContent == expectedText;
+  }, `Waiting for expected warning text: ${expectedText}, Got ${warningBox.textContent}`);
+  ok(true, `Got expected warning text: ${expectedText}`);
+}
+
 add_task(async function setup_storage() {
   await saveAddress(TEST_ADDRESS_1);
   await saveAddress(TEST_ADDRESS_2);
   await saveAddress(TEST_ADDRESS_3);
 });
 
 add_task(async function test_click_on_footer() {
   await BrowserTestUtils.withNewTab({gBrowser, url: URL}, async function(browser) {
     const {autoCompletePopup, autoCompletePopup: {richlistbox: itemsBox}} = browser;
 
-    await ContentTask.spawn(browser, {}, async function() {
-      content.document.getElementById("organization").focus();
-    });
-    await sleep(2000);
-    await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
-    await expectPopupOpen(browser);
-
+    await openPopupOn(browser, "#organization");
     // Click on the footer
-    const listItemElems = itemsBox.querySelectorAll(".autocomplete-richlistitem");
+    const optionButton = itemsBox.querySelector(".autocomplete-richlistitem:last-child")._optionButton;
     const prefTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, PRIVACY_PREF_URL);
-    await EventUtils.synthesizeMouseAtCenter(listItemElems[listItemElems.length - 1], {});
+    await EventUtils.synthesizeMouseAtCenter(optionButton, {});
     await BrowserTestUtils.removeTab(await prefTabPromise);
     ok(true, "Tab: preferences#privacy was successfully opened by clicking on the footer");
 
     // Ensure the popup is closed before entering the next test.
     await ContentTask.spawn(browser, {}, async function() {
       content.document.getElementById("organization").blur();
     });
     await BrowserTestUtils.waitForCondition(() => !autoCompletePopup.popupOpen);
   });
 });
 
 add_task(async function test_press_enter_on_footer() {
   await BrowserTestUtils.withNewTab({gBrowser, url: URL}, async function(browser) {
-    const {autoCompletePopup: {richlistbox: itemsBox}} = browser;
+    const {autoCompletePopup, autoCompletePopup: {richlistbox: itemsBox}} = browser;
 
-    await ContentTask.spawn(browser, {}, async function() {
-      const input = content.document.getElementById("organization");
-      input.focus();
-    });
-    await sleep(2000);
-    await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
-    await expectPopupOpen(browser);
-
+    await openPopupOn(browser, "#organization");
     // Navigate to the footer and press enter.
     const listItemElems = itemsBox.querySelectorAll(".autocomplete-richlistitem");
     const prefTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, PRIVACY_PREF_URL);
     for (let i = 0; i < listItemElems.length; i++) {
       await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
     }
     await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
     await BrowserTestUtils.removeTab(await prefTabPromise);
     ok(true, "Tab: preferences#privacy was successfully opened by pressing enter on the footer");
+
+    // Ensure the popup is closed before entering the next test.
+    await ContentTask.spawn(browser, {}, async function() {
+      content.document.getElementById("organization").blur();
+    });
+    await BrowserTestUtils.waitForCondition(() => !autoCompletePopup.popupOpen);
   });
 });
+
+add_task(async function test_phishing_warning() {
+  await BrowserTestUtils.withNewTab({gBrowser, url: URL}, async function(browser) {
+    const {autoCompletePopup, autoCompletePopup: {richlistbox: itemsBox}} = browser;
+
+    await openPopupOn(browser, "#street-address");
+    const warningBox = itemsBox.querySelector(".autocomplete-richlistitem:last-child")._warningTextBox;
+    ok(warningBox, "Got phishing warning box");
+    await expectWarningText(browser, "Also fill company, address, phone, email");
+    await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+    await expectWarningText(browser, "Also fill company, phone, email");
+    await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+    await expectWarningText(browser, "Fill address");
+    await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+    await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+    await expectWarningText(browser, "Also fill company, address, phone, email");
+
+    // Ensure the popup is closed before entering the next test.
+    await ContentTask.spawn(browser, {}, async function() {
+      content.document.getElementById("street-address").blur();
+    });
+    await BrowserTestUtils.waitForCondition(() => !autoCompletePopup.popupOpen);
+  });
+});
--- a/browser/extensions/formautofill/test/browser/head.js
+++ b/browser/extensions/formautofill/test/browser/head.js
@@ -1,11 +1,12 @@
 /* exported MANAGE_PROFILES_DIALOG_URL, EDIT_PROFILE_DIALOG_URL, BASE_URL,
             TEST_ADDRESS_1, TEST_ADDRESS_2, TEST_ADDRESS_3,
-            sleep, expectPopupOpen, getAddresses, saveAddress, removeAddresses */
+            sleep, expectPopupOpen, openPopupOn,
+            getAddresses, saveAddress, removeAddresses */
 
 "use strict";
 
 const MANAGE_PROFILES_DIALOG_URL = "chrome://formautofill/content/manageProfiles.xhtml";
 const EDIT_PROFILE_DIALOG_URL = "chrome://formautofill/content/editProfile.xhtml";
 const BASE_URL = "http://mochi.test:8888/browser/browser/extensions/formautofill/test/browser/";
 
 const TEST_ADDRESS_1 = {
@@ -45,16 +46,26 @@ async function expectPopupOpen(browser) 
     return [...listItemElems].every(item => {
       return (item.getAttribute("originaltype") == "autofill-profile" ||
              item.getAttribute("originaltype") == "autofill-footer") &&
              item.hasAttribute("formautofillattached");
     });
   });
 }
 
+async function openPopupOn(browser, selector) {
+  /* eslint no-shadow: ["error", { "allow": ["selector"] }] */
+  await ContentTask.spawn(browser, {selector}, async function({selector}) {
+    content.document.querySelector(selector).focus();
+  });
+  await sleep(2000);
+  await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+  await expectPopupOpen(browser);
+}
+
 function getAddresses() {
   return new Promise(resolve => {
     Services.cpmm.addMessageListener("FormAutofill:Addresses", function getResult(result) {
       Services.cpmm.removeMessageListener("FormAutofill:Addresses", getResult);
       resolve(result.data);
     });
     Services.cpmm.sendAsyncMessage("FormAutofill:GetAddresses", {});
   });
--- a/browser/themes/shared/identity-block/identity-block.inc.css
+++ b/browser/themes/shared/identity-block/identity-block.inc.css
@@ -43,40 +43,42 @@
 
 #identity-icon-labels:-moz-locale-dir(ltr) {
   padding-left: 2px;
 }
 #identity-icon-labels:-moz-locale-dir(rtl) {
   padding-right: 2px;
 }
 %ifdef MOZ_PHOTON_THEME
-#identity-box:not(.chromeUI) {
+#identity-box:not(.chromeUI):not(.extensionPage) {
   --urlbar-separator-color: transparent;
 }
 #urlbar[pageproxystate=valid] > #identity-box.verifiedIdentity {
   --urlbar-separator-color: rgba(18, 188, 0, .5);
 }
 %endif
 
 #identity-box {
   padding-inline-end: 2px;
   margin-inline-end: 2px;
 }
 
 #urlbar[pageproxystate=valid] > #identity-box.verifiedIdentity,
 #urlbar[pageproxystate=valid] > #identity-box.chromeUI,
+#urlbar[pageproxystate=valid] > #identity-box.extensionPage,
 #urlbar-display-box {
   margin-inline-end: 4px;
   border-inline-end: 1px solid var(--urlbar-separator-color);
   border-image: linear-gradient(transparent 15%, var(--urlbar-separator-color) 15%, var(--urlbar-separator-color) 85%, transparent 85%);
   border-image-slice: 1;
 }
 
 #urlbar[pageproxystate=valid] > #identity-box.verifiedIdentity,
-#urlbar[pageproxystate=valid] > #identity-box.chromeUI {
+#urlbar[pageproxystate=valid] > #identity-box.chromeUI,
+#urlbar[pageproxystate=valid] > #identity-box.extensionPage {
   padding-inline-end: 4px;
 }
 
 #urlbar-display-box {
   padding-inline-start: 4px;
   border-inline-start: 1px solid var(--urlbar-separator-color);
   border-image: linear-gradient(transparent 15%, var(--urlbar-separator-color) 15%, var(--urlbar-separator-color) 85%, transparent 85%);
   border-image-slice: 1;
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -618,24 +618,24 @@ groupbox {
   color: var(--in-content-category-text);
   text-decoration: none;
 }
 
 .search-tooltip {
   font-size: 1.25rem;
   position: absolute;
   padding: 0 10px;
-  bottom: 100%;
   background-color: #ffe900;
   border: 1px solid #d7b600;
+  -moz-user-select: none;
 }
 
 .search-tooltip:hover,
 .search-tooltip:hover::before {
-  filter: opacity(10%);
+  opacity: .1;
 }
 
 .search-tooltip::before {
   position: absolute;
   content: "";
   border: 7px solid transparent;
   border-top-color: #d7b600;
   top: 100%;
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -124,48 +124,58 @@
   skin/classic/browser/bookmark-hollow.svg            (../shared/icons/bookmark-hollow.svg)
 #ifndef MOZ_PHOTON_THEME
   skin/classic/browser/bookmarksMenu.svg              (../shared/icons/bookmarksMenu.svg)
 #else
   skin/classic/browser/bookmark-star-on-tray.svg      (../shared/icons/bookmark-star-on-tray.svg)
 #endif
   skin/classic/browser/characterEncoding.svg          (../shared/icons/characterEncoding.svg)
   skin/classic/browser/chevron.svg                    (../shared/icons/chevron.svg)
+#ifdef MOZ_PHOTON_ANIMATIONS
   skin/classic/browser/chevron-animation.svg          (../shared/icons/chevron-animation.svg)
+#endif
   skin/classic/browser/check.svg                      (../shared/icons/check.svg)
   skin/classic/browser/containers.svg                 (../shared/icons/containers.svg)
   skin/classic/browser/customize.svg                  (../shared/icons/customize.svg)
   skin/classic/browser/developer.svg                  (../shared/icons/developer.svg)
   skin/classic/browser/device-mobile.svg              (../shared/icons/device-mobile.svg)
+#ifdef MOZ_PHOTON_THEME
   skin/classic/browser/device-desktop.svg             (../shared/icons/device-desktop.svg)
+#endif
 #ifndef MOZ_PHOTON_THEME
   skin/classic/browser/download.svg                   (../shared/icons/download.svg)
 #endif
   skin/classic/browser/edit-copy.svg                  (../shared/icons/edit-copy.svg)
   skin/classic/browser/edit-cut.svg                   (../shared/icons/edit-cut.svg)
   skin/classic/browser/edit-paste.svg                 (../shared/icons/edit-paste.svg)
+#ifdef MOZ_PHOTON_THEME
   skin/classic/browser/email-link.svg                 (../shared/icons/email-link.svg)
+#endif
   skin/classic/browser/feed.svg                       (../shared/icons/feed.svg)
   skin/classic/browser/find.svg                       (../shared/icons/find.svg)
   skin/classic/browser/forget.svg                     (../shared/icons/forget.svg)
   skin/classic/browser/forward.svg                    (../shared/icons/forward.svg)
   skin/classic/browser/fullscreen.svg                 (../shared/icons/fullscreen.svg)
   skin/classic/browser/fullscreen-enter.svg           (../shared/icons/fullscreen-enter.svg)
   skin/classic/browser/fullscreen-exit.svg            (../shared/icons/fullscreen-exit.svg)
   skin/classic/browser/help.svg                       (../shared/icons/help.svg)
   skin/classic/browser/history.svg                    (../shared/icons/history.svg)
   skin/classic/browser/home.svg                       (../shared/icons/home.svg)
   skin/classic/browser/library.svg                    (../shared/icons/library.svg)
+#ifdef MOZ_PHOTON_THEME
   skin/classic/browser/link.svg                       (../shared/icons/link.svg)
+#endif
   skin/classic/browser/mail.svg                       (../shared/icons/mail.svg)
   skin/classic/browser/menu.svg                       (../shared/icons/menu.svg)
   skin/classic/browser/new-tab.svg                    (../shared/icons/new-tab.svg)
   skin/classic/browser/new-window.svg                 (../shared/icons/new-window.svg)
   skin/classic/browser/open.svg                       (../shared/icons/open.svg)
+#ifdef MOZ_PHOTON_THEME
   skin/classic/browser/page-action.svg                (../shared/icons/page-action.svg)
+#endif
   skin/classic/browser/print.svg                      (../shared/icons/print.svg)
   skin/classic/browser/privateBrowsing.svg            (../shared/icons/privateBrowsing.svg)
   skin/classic/browser/quit.svg                       (../shared/icons/quit.svg)
   skin/classic/browser/reload.svg                     (../shared/icons/reload.svg)
   skin/classic/browser/save.svg                       (../shared/icons/save.svg)
   skin/classic/browser/settings.svg                   (../shared/icons/settings.svg)
   skin/classic/browser/share.svg                      (../shared/icons/share.svg)
   skin/classic/browser/sidebars.svg                   (../shared/icons/sidebars.svg)
@@ -189,17 +199,19 @@
   skin/classic/browser/search-indicator-badge-add@2x.png       (../shared/search/search-indicator-badge-add@2x.png)
   skin/classic/browser/search-indicator-magnifying-glass.svg   (../shared/search/search-indicator-magnifying-glass.svg)
   skin/classic/browser/search-arrow-go.svg                     (../shared/search/search-arrow-go.svg)
   skin/classic/browser/gear.svg                                (../shared/search/gear.svg)
   skin/classic/browser/sidebar/close.svg                       (../shared/sidebar/close.svg)
   skin/classic/browser/tabbrowser/connecting.png               (../shared/tabbrowser/connecting.png)
   skin/classic/browser/tabbrowser/connecting@2x.png            (../shared/tabbrowser/connecting@2x.png)
   skin/classic/browser/tabbrowser/crashed.svg                  (../shared/tabbrowser/crashed.svg)
+#ifdef MOZ_PHOTON_THEME
   skin/classic/browser/tabbrowser/indicator-tab-attention.svg  (../shared/tabbrowser/indicator-tab-attention.svg)
+#endif
   skin/classic/browser/tabbrowser/pendingpaint.png             (../shared/tabbrowser/pendingpaint.png)
   skin/classic/browser/tabbrowser/tab-audio-playing.svg        (../shared/tabbrowser/tab-audio-playing.svg)
   skin/classic/browser/tabbrowser/tab-audio-muted.svg          (../shared/tabbrowser/tab-audio-muted.svg)
   skin/classic/browser/tabbrowser/tab-audio-blocked.svg        (../shared/tabbrowser/tab-audio-blocked.svg)
   skin/classic/browser/tabbrowser/tab-audio-small.svg          (../shared/tabbrowser/tab-audio-small.svg)
   skin/classic/browser/tabbrowser/tab-overflow-indicator.png   (../shared/tabbrowser/tab-overflow-indicator.png)
   skin/classic/browser/toolbarbutton-dropdown-arrow.png        (../shared/toolbarbutton-dropdown-arrow.png)
   skin/classic/browser/toolbarbutton-dropdown-arrow-inverted.png (../shared/toolbarbutton-dropdown-arrow-inverted.png)
--- a/devtools/client/netmonitor/src/assets/styles/netmonitor.css
+++ b/devtools/client/netmonitor/src/assets/styles/netmonitor.css
@@ -736,20 +736,16 @@ body,
 }
 
 .theme-firebug .request-list-item:not(.selected):hover {
   background: #EFEFEF;
 }
 
 /* Network details panel toggle */
 
-.network-details-panel-toggle[disabled] {
-  display: none;
-}
-
 .network-details-panel-toggle:dir(ltr)::before,
 .network-details-panel-toggle.pane-collapsed:dir(rtl)::before {
   background-image: var(--theme-pane-collapse-image);
 }
 
 .network-details-panel-toggle.pane-collapsed:dir(ltr)::before,
 .network-details-panel-toggle:dir(rtl)::before {
   background-image: var(--theme-pane-expand-image);
--- a/image/ImageFactory.cpp
+++ b/image/ImageFactory.cpp
@@ -91,17 +91,18 @@ ImageFactory::CreateImage(nsIRequest* aR
 
   // Compute the image's initialization flags.
   uint32_t imageFlags = ComputeImageFlags(aURI, aMimeType, aIsMultiPart);
 
 #ifdef DEBUG
   // Record the image load for startup performance testing.
   if (NS_IsMainThread()) {
     nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
-    if (NS_WARN_IF(obs)) {
+    NS_WARNING_ASSERTION(obs, "Can't get an observer service handle");
+    if (obs) {
       nsAutoCString spec;
       aURI->GetSpec(spec);
       obs->NotifyObservers(nullptr, "image-loading", NS_ConvertUTF8toUTF16(spec).get());
     }
   }
 #endif
 
   // Select the type of image to create based on MIME type.
--- a/image/RasterImage.cpp
+++ b/image/RasterImage.cpp
@@ -1394,17 +1394,18 @@ RasterImage::DrawInternal(DrawableSurfac
   gfxContextMatrixAutoSaveRestore saveMatrix(aContext);
   ImageRegion region(aRegion);
   bool frameIsFinished = aSurface->IsFinished();
 
 #ifdef DEBUG
   // Record the image drawing for startup performance testing.
   if (NS_IsMainThread()) {
     nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
-    if (NS_WARN_IF(obs)) {
+    NS_WARNING_ASSERTION(obs, "Can't get an observer service handle");
+    if (obs) {
       nsCOMPtr<nsIURI> imageURI = mURI->ToIURI();
       nsAutoCString spec;
       imageURI->GetSpec(spec);
       obs->NotifyObservers(nullptr, "image-drawing", NS_ConvertUTF8toUTF16(spec).get());
     }
   }
 #endif
 
--- a/image/VectorImage.cpp
+++ b/image/VectorImage.cpp
@@ -1024,17 +1024,18 @@ VectorImage::Show(gfxDrawable* aDrawable
                              SurfaceFormat::B8G8R8A8,
                              aParams.samplingFilter,
                              aParams.flags, aParams.opacity);
 
 #ifdef DEBUG
   // Record the image drawing for startup performance testing.
   if (NS_IsMainThread()) {
     nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
-    if (NS_WARN_IF(obs)) {
+    NS_WARNING_ASSERTION(obs, "Can't get an observer service handle");
+    if (obs) {
       nsCOMPtr<nsIURI> imageURI = mURI->ToIURI();
       nsAutoCString spec;
       imageURI->GetSpec(spec);
       obs->NotifyObservers(nullptr, "image-drawing", NS_ConvertUTF8toUTF16(spec).get());
     }
   }
 #endif
 
--- a/js/xpconnect/wrappers/XrayWrapper.cpp
+++ b/js/xpconnect/wrappers/XrayWrapper.cpp
@@ -208,21 +208,21 @@ const JSClass XrayTraits::HolderClass = 
     "XrayHolder", JSCLASS_HAS_RESERVED_SLOTS(HOLDER_SHARED_SLOT_COUNT)
 };
 
 const JSClass JSXrayTraits::HolderClass = {
     "JSXrayHolder", JSCLASS_HAS_RESERVED_SLOTS(SLOT_COUNT)
 };
 
 bool
-OpaqueXrayTraits::resolveOwnProperty(JSContext* cx, const Wrapper& jsWrapper, HandleObject wrapper,
+OpaqueXrayTraits::resolveOwnProperty(JSContext* cx, HandleObject wrapper, HandleObject target,
                                      HandleObject holder, HandleId id,
                                      MutableHandle<PropertyDescriptor> desc)
 {
-    bool ok = XrayTraits::resolveOwnProperty(cx, jsWrapper, wrapper, holder, id, desc);
+    bool ok = XrayTraits::resolveOwnProperty(cx, wrapper, target, holder, id, desc);
     if (!ok || desc.object())
         return ok;
 
     return ReportWrapperDenial(cx, id, WrapperDenialForXray, "object is not safely Xrayable");
 }
 
 bool
 ReportWrapperDenial(JSContext* cx, HandleId id, WrapperDenialType type, const char* reason)
@@ -513,40 +513,39 @@ ShouldResolveStaticProperties(JSProtoKey
     // have issues.  In particular, some of them grab state off the
     // global of the RegExp constructor that describes the last regexp
     // evaluation in that global, which is not a useful thing to do
     // over Xrays.
     return key != JSProto_RegExp;
 }
 
 bool
-JSXrayTraits::resolveOwnProperty(JSContext* cx, const Wrapper& jsWrapper,
-                                 HandleObject wrapper, HandleObject holder,
+JSXrayTraits::resolveOwnProperty(JSContext* cx, HandleObject wrapper,
+                                 HandleObject target, HandleObject holder,
                                  HandleId id,
                                  MutableHandle<PropertyDescriptor> desc)
 {
     // Call the common code.
-    bool ok = XrayTraits::resolveOwnProperty(cx, jsWrapper, wrapper, holder,
+    bool ok = XrayTraits::resolveOwnProperty(cx, wrapper, target, holder,
                                              id, desc);
     if (!ok || desc.object())
         return ok;
 
     // The non-HasPrototypes semantics implemented by traditional Xrays are kind
     // of broken with respect to |own|-ness and the holder. The common code
     // muddles through by only checking the holder for non-|own| lookups, but
     // that doesn't work for us. So we do an explicit holder check here, and hope
     // that this mess gets fixed up soon.
     if (!JS_GetOwnPropertyDescriptorById(cx, holder, id, desc))
         return false;
     if (desc.object()) {
         desc.object().set(wrapper);
         return true;
     }
 
-    RootedObject target(cx, getTargetObject(wrapper));
     JSProtoKey key = getProtoKey(holder);
     if (!isPrototype(holder)) {
         // For Object and Array instances, we expose some properties from the
         // underlying object, but only after filtering them carefully.
         //
         // Note that, as far as JS observables go, Arrays are just Objects with
         // a different prototype and a magic (own, non-configurable) |.length| that
         // serves as a non-tight upper bound on |own| indexed properties. So while
@@ -1500,22 +1499,21 @@ wrappedJSObject_getter(JSContext* cx, un
     }
 
     args.rval().setObject(*wrapper);
 
     return WrapperFactory::WaiveXrayAndWrap(cx, args.rval());
 }
 
 bool
-XrayTraits::resolveOwnProperty(JSContext* cx, const Wrapper& jsWrapper,
-                               HandleObject wrapper, HandleObject holder, HandleId id,
+XrayTraits::resolveOwnProperty(JSContext* cx, HandleObject wrapper, HandleObject target,
+                               HandleObject holder, HandleId id,
                                MutableHandle<PropertyDescriptor> desc)
 {
     desc.object().set(nullptr);
-    RootedObject target(cx, getTargetObject(wrapper));
     RootedObject expando(cx);
     if (!getExpandoObject(cx, target, wrapper, &expando))
         return false;
 
     // Check for expando properties first. Note that the expando object lives
     // in the target compartment.
     bool found = false;
     if (expando) {
@@ -1572,23 +1570,23 @@ XrayTraits::resolveOwnProperty(JSContext
         desc.object().set(wrapper);
         return true;
     }
 
     return true;
 }
 
 bool
-XPCWrappedNativeXrayTraits::resolveOwnProperty(JSContext* cx, const Wrapper& jsWrapper,
-                                               HandleObject wrapper, HandleObject holder,
+XPCWrappedNativeXrayTraits::resolveOwnProperty(JSContext* cx, HandleObject wrapper,
+                                               HandleObject target, HandleObject holder,
                                                HandleId id,
                                                MutableHandle<PropertyDescriptor> desc)
 {
     // Call the common code.
-    bool ok = XrayTraits::resolveOwnProperty(cx, jsWrapper, wrapper, holder,
+    bool ok = XrayTraits::resolveOwnProperty(cx, wrapper, target, holder,
                                              id, desc);
     if (!ok || desc.object())
         return ok;
 
     // Xray wrappers don't use the regular wrapper hierarchy, so we should be
     // in the wrapper's compartment here, not the wrappee.
     MOZ_ASSERT(js::IsObjectInContextCompartment(wrapper, cx));
 
@@ -1678,22 +1676,22 @@ XPCWrappedNativeXrayTraits::construct(JS
         }
     }
 
     return true;
 
 }
 
 bool
-DOMXrayTraits::resolveOwnProperty(JSContext* cx, const Wrapper& jsWrapper, HandleObject wrapper,
+DOMXrayTraits::resolveOwnProperty(JSContext* cx, HandleObject wrapper, HandleObject target,
                                   HandleObject holder, HandleId id,
                                   MutableHandle<PropertyDescriptor> desc)
 {
     // Call the common code.
-    bool ok = XrayTraits::resolveOwnProperty(cx, jsWrapper, wrapper, holder, id, desc);
+    bool ok = XrayTraits::resolveOwnProperty(cx, wrapper, target, holder, id, desc);
     if (!ok || desc.object())
         return ok;
 
     // Check for indexed access on a window.
     uint32_t index = GetArrayIndexFromId(cx, id);
     if (IsArrayIndex(index)) {
         nsGlobalWindow* win = AsWindow(cx, wrapper);
         // Note: As() unwraps outer windows to get to the inner window.
@@ -1717,19 +1715,18 @@ DOMXrayTraits::resolveOwnProperty(JSCont
 
     if (!JS_GetOwnPropertyDescriptorById(cx, holder, id, desc))
         return false;
     if (desc.object()) {
         desc.object().set(wrapper);
         return true;
     }
 
-    RootedObject obj(cx, getTargetObject(wrapper));
     bool cacheOnHolder;
-    if (!XrayResolveOwnProperty(cx, wrapper, obj, id, desc, cacheOnHolder))
+    if (!XrayResolveOwnProperty(cx, wrapper, target, id, desc, cacheOnHolder))
         return false;
 
     MOZ_ASSERT(!desc.object() || desc.object() == wrapper, "What did we resolve this on?");
 
     if (!desc.object() || !cacheOnHolder)
         return true;
 
     return JS_DefinePropertyById(cx, holder, id, desc) &&
@@ -1890,24 +1887,24 @@ GetNativePropertiesObject(JSContext* cx,
 }
 
 bool
 HasNativeProperty(JSContext* cx, HandleObject wrapper, HandleId id, bool* hasProp)
 {
     MOZ_ASSERT(WrapperFactory::IsXrayWrapper(wrapper));
     XrayTraits* traits = GetXrayTraits(wrapper);
     MOZ_ASSERT(traits);
+    RootedObject target(cx, XrayTraits::getTargetObject(wrapper));
     RootedObject holder(cx, traits->ensureHolder(cx, wrapper));
     NS_ENSURE_TRUE(holder, false);
     *hasProp = false;
     Rooted<PropertyDescriptor> desc(cx);
-    const Wrapper* handler = Wrapper::wrapperHandler(wrapper);
 
     // Try resolveOwnProperty.
-    if (!traits->resolveOwnProperty(cx, *handler, wrapper, holder, id, &desc))
+    if (!traits->resolveOwnProperty(cx, wrapper, target, holder, id, &desc))
         return false;
     if (desc.object()) {
         *hasProp = true;
         return true;
     }
 
     // Try the holder.
     bool found = false;
@@ -2006,16 +2003,17 @@ XrayWrapper<Base, Traits>::isExtensible(
 template <typename Base, typename Traits>
 bool
 XrayWrapper<Base, Traits>::getPropertyDescriptor(JSContext* cx, HandleObject wrapper, HandleId id,
                                                  JS::MutableHandle<PropertyDescriptor> desc)
                                                  const
 {
     assertEnteredPolicy(cx, wrapper, id, BaseProxyHandler::GET | BaseProxyHandler::SET |
                                          BaseProxyHandler::GET_PROPERTY_DESCRIPTOR);
+    RootedObject target(cx, XrayTraits::getTargetObject(wrapper));
     RootedObject holder(cx, Traits::singleton.ensureHolder(cx, wrapper));
 
     if (!holder)
         return false;
 
     // Ordering is important here.
     //
     // We first need to call resolveOwnProperty, even before checking the holder,
@@ -2031,17 +2029,17 @@ XrayWrapper<Base, Traits>::getPropertyDe
     // supposed to be dynamic. This means that we have to first check the result
     // of resolveOwnProperty, and _then_, if that comes up blank, check the
     // holder for any cached native properties.
     //
     // Finally, we call resolveNativeProperty, which checks non-own properties,
     // and unconditionally caches what it finds on the holder.
 
     // Check resolveOwnProperty.
-    if (!Traits::singleton.resolveOwnProperty(cx, *this, wrapper, holder, id, desc))
+    if (!Traits::singleton.resolveOwnProperty(cx, wrapper, target, holder, id, desc))
         return false;
 
     // Check the holder.
     if (!desc.object() && !JS_GetOwnPropertyDescriptorById(cx, holder, id, desc))
         return false;
     if (desc.object()) {
         desc.object().set(wrapper);
         return true;
@@ -2094,19 +2092,20 @@ XrayWrapper<Base, Traits>::getPropertyDe
 template <typename Base, typename Traits>
 bool
 XrayWrapper<Base, Traits>::getOwnPropertyDescriptor(JSContext* cx, HandleObject wrapper, HandleId id,
                                                     JS::MutableHandle<PropertyDescriptor> desc)
                                                     const
 {
     assertEnteredPolicy(cx, wrapper, id, BaseProxyHandler::GET | BaseProxyHandler::SET |
                                          BaseProxyHandler::GET_PROPERTY_DESCRIPTOR);
+    RootedObject target(cx, XrayTraits::getTargetObject(wrapper));
     RootedObject holder(cx, Traits::singleton.ensureHolder(cx, wrapper));
 
-    if (!Traits::singleton.resolveOwnProperty(cx, *this, wrapper, holder, id, desc))
+    if (!Traits::singleton.resolveOwnProperty(cx, wrapper, target, holder, id, desc))
         return false;
     if (desc.object())
         desc.object().set(wrapper);
     return true;
 }
 
 // Consider what happens when chrome does |xray.expando = xray.wrappedJSObject|.
 //
--- a/js/xpconnect/wrappers/XrayWrapper.h
+++ b/js/xpconnect/wrappers/XrayWrapper.h
@@ -72,18 +72,18 @@ public:
 
     virtual bool resolveNativeProperty(JSContext* cx, JS::HandleObject wrapper,
                                        JS::HandleObject holder, JS::HandleId id,
                                        JS::MutableHandle<JS::PropertyDescriptor> desc) = 0;
     // NB: resolveOwnProperty may decide whether or not to cache what it finds
     // on the holder. If the result is not cached, the lookup will happen afresh
     // for each access, which is the right thing for things like dynamic NodeList
     // properties.
-    virtual bool resolveOwnProperty(JSContext* cx, const js::Wrapper& jsWrapper,
-                                    JS::HandleObject wrapper, JS::HandleObject holder,
+    virtual bool resolveOwnProperty(JSContext* cx, JS::HandleObject wrapper,
+                                    JS::HandleObject target, JS::HandleObject holder,
                                     JS::HandleId id, JS::MutableHandle<JS::PropertyDescriptor> desc);
 
     bool delete_(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id,
                  JS::ObjectOpResult& result) {
         return result.succeed();
     }
 
     static bool getBuiltinClass(JSContext* cx, JS::HandleObject wrapper, const js::Wrapper& baseInstance,
@@ -145,17 +145,17 @@ public:
         HasPrototype = 0
     };
 
     static const XrayType Type = XrayForWrappedNative;
 
     virtual bool resolveNativeProperty(JSContext* cx, JS::HandleObject wrapper,
                                        JS::HandleObject holder, JS::HandleId id,
                                        JS::MutableHandle<JS::PropertyDescriptor> desc) override;
-    virtual bool resolveOwnProperty(JSContext* cx, const js::Wrapper& jsWrapper, JS::HandleObject wrapper,
+    virtual bool resolveOwnProperty(JSContext* cx, JS::HandleObject wrapper, JS::HandleObject target,
                                     JS::HandleObject holder, JS::HandleId id,
                                     JS::MutableHandle<JS::PropertyDescriptor> desc) override;
     bool defineProperty(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id,
                         JS::Handle<JS::PropertyDescriptor> desc,
                         JS::Handle<JS::PropertyDescriptor> existingDesc,
                         JS::ObjectOpResult& result, bool* defined)
     {
         *defined = false;
@@ -199,17 +199,17 @@ public:
         // these Xrays are really own properties, either of the instance object or
         // of the prototypes.
         // FIXME https://bugzilla.mozilla.org/show_bug.cgi?id=1072482
         //       This should really be:
         // MOZ_CRASH("resolveNativeProperty hook should never be called with HasPrototype = 1");
         //       but we can't do that yet because XrayUtils::HasNativeProperty calls this.
         return true;
     }
-    virtual bool resolveOwnProperty(JSContext* cx, const js::Wrapper& jsWrapper, JS::HandleObject wrapper,
+    virtual bool resolveOwnProperty(JSContext* cx, JS::HandleObject wrapper, JS::HandleObject target,
                                     JS::HandleObject holder, JS::HandleId id,
                                     JS::MutableHandle<JS::PropertyDescriptor> desc) override;
 
     bool delete_(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, JS::ObjectOpResult& result);
 
     bool defineProperty(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id,
                         JS::Handle<JS::PropertyDescriptor> desc,
                         JS::Handle<JS::PropertyDescriptor> existingDesc,
@@ -246,17 +246,17 @@ public:
 
     virtual bool resolveNativeProperty(JSContext* cx, JS::HandleObject wrapper,
                                        JS::HandleObject holder, JS::HandleId id,
                                        JS::MutableHandle<JS::PropertyDescriptor> desc) override
     {
         MOZ_CRASH("resolveNativeProperty hook should never be called with HasPrototype = 1");
     }
 
-    virtual bool resolveOwnProperty(JSContext* cx, const js::Wrapper& jsWrapper, JS::HandleObject wrapper,
+    virtual bool resolveOwnProperty(JSContext* cx, JS::HandleObject wrapper, JS::HandleObject target,
                                     JS::HandleObject holder, JS::HandleId id,
                                     JS::MutableHandle<JS::PropertyDescriptor> desc) override;
 
     bool delete_(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, JS::ObjectOpResult& result);
 
     bool defineProperty(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id,
                         JS::Handle<JS::PropertyDescriptor> desc,
                         JS::Handle<JS::PropertyDescriptor> existingDesc,
@@ -363,17 +363,17 @@ public:
 
     virtual bool resolveNativeProperty(JSContext* cx, JS::HandleObject wrapper,
                                        JS::HandleObject holder, JS::HandleId id,
                                        JS::MutableHandle<JS::PropertyDescriptor> desc) override
     {
         MOZ_CRASH("resolveNativeProperty hook should never be called with HasPrototype = 1");
     }
 
-    virtual bool resolveOwnProperty(JSContext* cx, const js::Wrapper& jsWrapper, JS::HandleObject wrapper,
+    virtual bool resolveOwnProperty(JSContext* cx, JS::HandleObject wrapper, JS::HandleObject target,
                                     JS::HandleObject holder, JS::HandleId id,
                                     JS::MutableHandle<JS::PropertyDescriptor> desc) override;
 
     bool defineProperty(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id,
                         JS::Handle<JS::PropertyDescriptor> desc,
                         JS::Handle<JS::PropertyDescriptor> existingDesc,
                         JS::ObjectOpResult& result, bool* defined)
     {
--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -956,28 +956,76 @@ ServoRestyleManager::ContentStateChanged
 
   ServoElementSnapshot& snapshot = SnapshotFor(aElement);
   EventStates previousState = aElement->StyleState() ^ aChangedBits;
   snapshot.AddState(previousState);
 
   if (Element* parent = aElement->GetFlattenedTreeParentElementForStyle()) {
     parent->NoteDirtyDescendantsForServo();
   }
-  PostRestyleEvent(aElement, restyleHint, changeHint);
+
+  if (restyleHint || changeHint) {
+    Servo_NoteExplicitHints(aElement, restyleHint, changeHint);
+  }
+}
+
+static inline bool
+AttributeInfluencesOtherPseudoClassState(const Element& aElement,
+                                         const nsIAtom* aAttribute)
+{
+  // We must record some state for :-moz-browser-frame and
+  // :-moz-table-border-nonzero.
+  if (aAttribute == nsGkAtoms::mozbrowser) {
+    return aElement.IsAnyOfHTMLElements(nsGkAtoms::iframe, nsGkAtoms::frame);
+  }
+
+  if (aAttribute == nsGkAtoms::border) {
+    return aElement.IsHTMLElement(nsGkAtoms::table);
+  }
+
+  return false;
 }
 
 static inline bool
-AttributeInfluencesOtherPseudoClassState(Element* aElement, nsIAtom* aAttribute)
+NeedToRecordAttrChange(const ServoStyleSet& aStyleSet,
+                       const Element& aElement,
+                       int32_t aNameSpaceID,
+                       nsIAtom* aAttribute,
+                       bool* aInfluencesOtherPseudoClassState)
 {
-  // We must record some state for :-moz-browser-frame and
-  // :-moz-table-border-nonzero.
-  return (aAttribute == nsGkAtoms::mozbrowser &&
-          aElement->IsAnyOfHTMLElements(nsGkAtoms::iframe, nsGkAtoms::frame)) ||
-         (aAttribute == nsGkAtoms::border &&
-          aElement->IsHTMLElement(nsGkAtoms::table));
+  *aInfluencesOtherPseudoClassState =
+    AttributeInfluencesOtherPseudoClassState(aElement, aAttribute);
+
+  // If the attribute influences one of the pseudo-classes that are backed by
+  // attributes, we just record it.
+  if (*aInfluencesOtherPseudoClassState) {
+    return true;
+  }
+
+  // We assume that id and class attributes are used in class/id selectors, and
+  // thus record them.
+  //
+  // TODO(emilio): We keep a filter of the ids in use somewhere in the StyleSet,
+  // presumably we could try to filter the old and new id, but it's not clear
+  // it's worth it.
+  if (aNameSpaceID == kNameSpaceID_None &&
+      (aAttribute == nsGkAtoms::id || aAttribute == nsGkAtoms::_class)) {
+    return true;
+  }
+
+  // We always record lang="", even though we force a subtree restyle when it
+  // changes, since it can change how its siblings match :lang(..) due to
+  // selectors like :lang(..) + div.
+  if (aAttribute == nsGkAtoms::lang) {
+    return true;
+  }
+
+  // Otherwise, just record the attribute change if a selector in the page may
+  // reference it from an attribute selector.
+  return aStyleSet.MightHaveAttributeDependency(aElement, aAttribute);
 }
 
 void
 ServoRestyleManager::AttributeWillChange(Element* aElement,
                                          int32_t aNameSpaceID,
                                          nsIAtom* aAttribute, int32_t aModType,
                                          const nsAttrValue* aNewValue)
 {
@@ -997,71 +1045,78 @@ ServoRestyleManager::TakeSnapshotForAttr
                                                     nsIAtom* aAttribute)
 {
   MOZ_ASSERT(!mInStyleRefresh);
 
   if (!aElement->HasServoData()) {
     return;
   }
 
-  bool influencesOtherPseudoClassState =
-    AttributeInfluencesOtherPseudoClassState(aElement, aAttribute);
-
-  if (!influencesOtherPseudoClassState &&
-      !((aNameSpaceID == kNameSpaceID_None &&
-         (aAttribute == nsGkAtoms::id ||
-          aAttribute == nsGkAtoms::_class)) ||
-        aAttribute == nsGkAtoms::lang ||
-        StyleSet()->MightHaveAttributeDependency(*aElement, aAttribute))) {
+  bool influencesOtherPseudoClassState;
+  if (!NeedToRecordAttrChange(*StyleSet(),
+                              *aElement,
+                              aNameSpaceID,
+                              aAttribute,
+                              &influencesOtherPseudoClassState)) {
     return;
   }
 
   ServoElementSnapshot& snapshot = SnapshotFor(aElement);
   snapshot.AddAttrs(aElement, aNameSpaceID, aAttribute);
 
   if (influencesOtherPseudoClassState) {
     snapshot.AddOtherPseudoClassState(aElement);
   }
 
   if (Element* parent = aElement->GetFlattenedTreeParentElementForStyle()) {
     parent->NoteDirtyDescendantsForServo();
   }
 }
 
+// For some attribute changes we must restyle the whole subtree:
+//
+// * <td> is affected by the cellpadding on its ancestor table
+// * lang="" and xml:lang="" can affect all descendants due to :lang()
+//
+static inline bool
+AttributeChangeRequiresSubtreeRestyle(const Element& aElement, nsIAtom* aAttr)
+{
+  if (aAttr == nsGkAtoms::cellpadding) {
+    return aElement.IsHTMLElement(nsGkAtoms::table);
+  }
+
+  return aAttr == nsGkAtoms::lang;
+}
+
 void
 ServoRestyleManager::AttributeChanged(Element* aElement, int32_t aNameSpaceID,
                                       nsIAtom* aAttribute, int32_t aModType,
                                       const nsAttrValue* aOldValue)
 {
   MOZ_ASSERT(!mInStyleRefresh);
 
-  nsIFrame* primaryFrame = aElement->GetPrimaryFrame();
-  if (primaryFrame) {
+  if (nsIFrame* primaryFrame = aElement->GetPrimaryFrame()) {
     primaryFrame->AttributeChanged(aNameSpaceID, aAttribute, aModType);
   }
 
-  nsChangeHint hint = aElement->GetAttributeChangeHint(aAttribute, aModType);
-  if (hint) {
-    PostRestyleEvent(aElement, nsRestyleHint(0), hint);
-  }
+  auto changeHint = nsChangeHint(0);
+  auto restyleHint = nsRestyleHint(0);
+
+  changeHint |= aElement->GetAttributeChangeHint(aAttribute, aModType);
 
   if (aAttribute == nsGkAtoms::style) {
-    PostRestyleEvent(aElement, eRestyle_StyleAttribute, nsChangeHint(0));
+    restyleHint |= eRestyle_StyleAttribute;
+  } else if (AttributeChangeRequiresSubtreeRestyle(*aElement, aAttribute)) {
+    restyleHint |= eRestyle_Subtree;
+  } else if (aElement->IsAttributeMapped(aAttribute)) {
+    restyleHint |= eRestyle_Self;
   }
 
-  // For some attribute changes we must restyle the whole subtree:
-  //
-  // * <td> is affected by the cellpadding on its ancestor table
-  // * lang="" and xml:lang="" can affect all descendants due to :lang()
-  if ((aAttribute == nsGkAtoms::cellpadding &&
-       aElement->IsHTMLElement(nsGkAtoms::table)) ||
-      aAttribute == nsGkAtoms::lang) {
-    PostRestyleEvent(aElement, eRestyle_Subtree, nsChangeHint(0));
-  } else if (aElement->IsAttributeMapped(aAttribute)) {
-    Servo_NoteExplicitHints(aElement, eRestyle_Self, nsChangeHint(0));
+  if (restyleHint || changeHint) {
+    Servo_NoteExplicitHints(aElement, restyleHint, changeHint);
   }
 }
 
 nsresult
 ServoRestyleManager::ReparentStyleContext(nsIFrame* aFrame)
 {
   NS_WARNING("stylo: ServoRestyleManager::ReparentStyleContext not implemented");
   return NS_OK;
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -837,16 +837,29 @@ nsPresContext::UpdateAfterPreferencesCha
 nsresult
 nsPresContext::Init(nsDeviceContext* aDeviceContext)
 {
   NS_ASSERTION(!mInitialized, "attempt to reinit pres context");
   NS_ENSURE_ARG(aDeviceContext);
 
   mDeviceContext = aDeviceContext;
 
+  // In certain rare cases (such as changing page mode), we tear down layout
+  // state and re-initialize a new prescontext for a document. Given that we
+  // hang style state off the DOM, we detect that re-initialization case and
+  // lazily drop the servo data. We don't do this eagerly during layout teardown
+  // because that would incur an extra whole-tree traversal that's unnecessary
+  // most of the time.
+  if (mDocument->IsStyledByServo()) {
+    Element* root = mDocument->GetRootElement();
+    if (root && root->HasServoData()) {
+      ServoRestyleManager::ClearServoDataFromSubtree(root);
+    }
+  }
+
   if (mDeviceContext->SetFullZoom(mFullZoom))
     mDeviceContext->FlushFontCache();
   mCurAppUnitsPerDevPixel = AppUnitsPerDevPixel();
 
   mEventManager = new mozilla::EventStateManager();
 
   mEffectCompositor = new mozilla::EffectCompositor(this);
   mTransitionManager = new nsTransitionManager(this);
--- a/layout/style/ServoBindingList.h
+++ b/layout/style/ServoBindingList.h
@@ -152,16 +152,19 @@ SERVO_BINDING_FUNC(Servo_StyleRule_GetSe
 SERVO_BINDING_FUNC(Servo_StyleRule_GetSelectorTextAtIndex, void,
                    RawServoStyleRuleBorrowed rule, uint32_t index,
                    nsAString* result)
 SERVO_BINDING_FUNC(Servo_StyleRule_GetSpecificityAtIndex, void,
                    RawServoStyleRuleBorrowed rule, uint32_t index,
                    uint64_t* specificity)
 SERVO_BINDING_FUNC(Servo_StyleRule_GetSelectorCount, void,
                    RawServoStyleRuleBorrowed rule, uint32_t* count)
+SERVO_BINDING_FUNC(Servo_StyleRule_SelectorMatchesElement, bool,
+                   RawServoStyleRuleBorrowed, RawGeckoElementBorrowed,
+                   uint32_t index, mozilla::CSSPseudoElementType pseudo_type)
 SERVO_BINDING_FUNC(Servo_ImportRule_GetHref, void,
                    RawServoImportRuleBorrowed rule, nsAString* result)
 SERVO_BINDING_FUNC(Servo_ImportRule_GetSheet,
                    const mozilla::ServoStyleSheet*,
                    RawServoImportRuleBorrowed rule)
 SERVO_BINDING_FUNC(Servo_Keyframe_GetKeyText, void,
                    RawServoKeyframeBorrowed keyframe, nsAString* result)
 // Returns whether it successfully changes the key text.
--- a/layout/style/ServoStyleRule.cpp
+++ b/layout/style/ServoStyleRule.cpp
@@ -274,13 +274,20 @@ ServoStyleRule::GetSpecificity(uint32_t 
 }
 
 nsresult
 ServoStyleRule::SelectorMatchesElement(Element* aElement,
                                        uint32_t aSelectorIndex,
                                        const nsAString& aPseudo,
                                        bool* aMatches)
 {
-  // TODO Bug 1370502
+  nsCOMPtr<nsIAtom> pseudoElt = NS_Atomize(aPseudo);
+  const CSSPseudoElementType pseudoType =
+    nsCSSPseudoElements::GetPseudoType(pseudoElt,
+                                       CSSEnabledState::eIgnoreEnabledState);
+  *aMatches = Servo_StyleRule_SelectorMatchesElement(mRawRule,
+                                                     aElement,
+                                                     aSelectorIndex,
+                                                     pseudoType);
   return NS_OK;
 }
 
 } // namespace mozilla
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -77,100 +77,27 @@ ServoStyleSet::Init(nsPresContext* aPres
       Servo_StyleSet_AppendStyleSheet(mRawSet.get(), sheet);
     }
   }
 
   // No need to Servo_StyleSet_FlushStyleSheets because we just created the
   // mRawSet, so there was nothing to flush.
 }
 
-// Traverses the given frame tree, calling ClearServoDataFromSubtree on
-// any NAC that is found.
-static void
-ClearServoDataFromNAC(nsIFrame* aFrame)
-{
-  nsIAnonymousContentCreator* ac = do_QueryFrame(aFrame);
-  if (ac) {
-    nsTArray<nsIContent*> nodes;
-    ac->AppendAnonymousContentTo(nodes, 0);
-    for (nsIContent* node : nodes) {
-      if (node->IsElement()) {
-        ServoRestyleManager::ClearServoDataFromSubtree(node->AsElement());
-      }
-    }
-  }
-
-  nsIFrame::ChildListIterator lists(aFrame);
-  for (; !lists.IsDone(); lists.Next()) {
-    for (nsIFrame* child : lists.CurrentList()) {
-      ClearServoDataFromNAC(child);
-    }
-  }
-}
-
 void
 ServoStyleSet::BeginShutdown()
 {
   nsIDocument* doc = mPresContext->Document();
 
   // Remove the style rule map from document's observer and drop it.
   if (mStyleRuleMap) {
     doc->RemoveObserver(mStyleRuleMap);
     doc->CSSLoader()->RemoveObserver(mStyleRuleMap);
     mStyleRuleMap = nullptr;
   }
-
-  // It's important to do this before mRawSet is released, since that will cause
-  // a RuleTree GC, which needs to happen after we have dropped all of the
-  // document's strong references to RuleNodes.  We also need to do it here,
-  // in BeginShutdown, and not in Shutdown, since Shutdown happens after the
-  // frame tree has been destroyed, but before the script runners that delete
-  // native anonymous content (which also could be holding on the RuleNodes)
-  // have run.  By clearing style here, before the frame tree is destroyed,
-  // the AllChildrenIterator will find the anonymous content.
-  //
-  // Note that this is pretty bad for performance; we should find a way to
-  // get by with the ServoNodeDatas being dropped as part of the document
-  // going away.
-  DocumentStyleRootIterator iter(doc);
-  while (Element* root = iter.GetNextStyleRoot()) {
-    ServoRestyleManager::ClearServoDataFromSubtree(root);
-  }
-
-  // We can also have some cloned canvas custom content stored in the document
-  // (as done in nsCanvasFrame::DestroyFrom), due to bug 1348480, when we create
-  // the clone (wastefully) during PresShell destruction.  Clear data from that
-  // clone.
-  for (RefPtr<AnonymousContent>& ac : doc->GetAnonymousContents()) {
-    ServoRestyleManager::ClearServoDataFromSubtree(ac->GetContentNode());
-  }
-
-  // Also look for any NAC created by position:fixed replicated frames in a
-  // print or print preview presentation.
-  if (nsIPresShell* shell = doc->GetShell()) {
-    if (nsIFrame* pageSeq = shell->FrameConstructor()->GetPageSequenceFrame()) {
-      auto iter = pageSeq->PrincipalChildList().begin();
-      if (*iter) {
-        ++iter;  // skip past first page
-        while (nsIFrame* page = *iter) {
-          MOZ_ASSERT(page->IsPageFrame());
-
-          // The position:fixed replicated frames live on the PageContent frame.
-          nsIFrame* pageContent = page->PrincipalChildList().FirstChild();
-          MOZ_ASSERT(pageContent && pageContent->IsPageContentFrame());
-
-          for (nsIFrame* f : pageContent->GetChildList(nsIFrame::kFixedList)) {
-            ClearServoDataFromNAC(f);
-          }
-
-          ++iter;
-        }
-      }
-    }
-  }
 }
 
 void
 ServoStyleSet::Shutdown()
 {
   // Make sure we drop our cached style contexts before the presshell arena
   // starts going away.
   ClearNonInheritingStyleContexts();
@@ -1420,22 +1347,23 @@ ServoStyleSet::StyleRuleMap()
     doc->AddObserver(mStyleRuleMap);
     doc->CSSLoader()->AddObserver(mStyleRuleMap);
   }
   return mStyleRuleMap;
 }
 
 bool
 ServoStyleSet::MightHaveAttributeDependency(const Element& aElement,
-                                            nsIAtom* aAttribute)
+                                            nsIAtom* aAttribute) const
 {
   return Servo_StyleSet_MightHaveAttributeDependency(
       mRawSet.get(), &aElement, aAttribute);
 }
 
 bool
-ServoStyleSet::HasStateDependency(const Element& aElement, EventStates aState)
+ServoStyleSet::HasStateDependency(const Element& aElement,
+                                  EventStates aState) const
 {
   return Servo_StyleSet_HasStateDependency(
       mRawSet.get(), &aElement, aState.ServoValue());
 }
 
 ServoStyleSet* ServoStyleSet::sInServoTraversal = nullptr;
--- a/layout/style/ServoStyleSet.h
+++ b/layout/style/ServoStyleSet.h
@@ -454,27 +454,28 @@ public:
    * Returns true if a modification to an an attribute with the specified
    * local name might require us to restyle the element.
    *
    * This function allows us to skip taking a an attribute snapshot when
    * the modified attribute doesn't appear in an attribute selector in
    * a style sheet.
    */
   bool MightHaveAttributeDependency(const dom::Element& aElement,
-                                    nsIAtom* aAttribute);
+                                    nsIAtom* aAttribute) const;
 
   /**
    * Returns true if a change in event state on an element might require
    * us to restyle the element.
    *
    * This function allows us to skip taking a state snapshot when
    * the changed state isn't depended upon by any pseudo-class selectors
    * in a style sheet.
    */
-  bool HasStateDependency(const dom::Element& aElement, EventStates aState);
+  bool HasStateDependency(const dom::Element& aElement,
+                          EventStates aState) const;
 
 private:
   // On construction, sets sInServoTraversal to the given ServoStyleSet.
   // On destruction, clears sInServoTraversal and calls RunPostTraversalTasks.
   class MOZ_STACK_CLASS AutoSetInServoTraversal
   {
   public:
     explicit AutoSetInServoTraversal(ServoStyleSet* aSet)
--- a/media/libstagefright/binding/MP4Metadata.cpp
+++ b/media/libstagefright/binding/MP4Metadata.cpp
@@ -824,16 +824,20 @@ MP4MetadataRust::GetNumberTracks(mozilla
 
   uint32_t total = 0;
   for (uint32_t i = 0; i < tracks; ++i) {
     mp4parse_track_info track_info;
     rv = mp4parse_get_track_info(mRustParser.get(), i, &track_info);
     if (rv != mp4parse_status_OK) {
       continue;
     }
+    // JPEG 'video' decoder is not supported in media stack yet.
+    if (track_info.codec == mp4parse_codec::mp4parse_codec_JPEG) {
+      continue;
+    }
     if (TrackTypeEqual(aType, track_info.track_type)) {
         total += 1;
     }
   }
 
   return {NS_OK, total};
 }
 
@@ -896,16 +900,17 @@ MP4MetadataRust::GetTrackInfo(mozilla::T
     case mp4parse_codec_UNKNOWN: codec_string = "unknown"; break;
     case mp4parse_codec_AAC: codec_string = "aac"; break;
     case mp4parse_codec_OPUS: codec_string = "opus"; break;
     case mp4parse_codec_FLAC: codec_string = "flac"; break;
     case mp4parse_codec_AVC: codec_string = "h.264"; break;
     case mp4parse_codec_VP9: codec_string = "vp9"; break;
     case mp4parse_codec_MP3: codec_string = "mp3"; break;
     case mp4parse_codec_MP4V: codec_string = "mp4v"; break;
+    case mp4parse_codec_JPEG: codec_string = "jpeg"; break;
     case mp4parse_codec_AC3: codec_string = "ac-3"; break;
     case mp4parse_codec_EC3: codec_string = "ec-3"; break;
   }
   MOZ_LOG(sLog, LogLevel::Debug, ("track codec %s (%u)\n",
         codec_string, info.codec));
 #endif
 
   // This specialization interface is crazy.
--- a/media/webrtc/trunk/webrtc/modules/audio_processing/utility/ooura_fft.cc
+++ b/media/webrtc/trunk/webrtc/modules/audio_processing/utility/ooura_fft.cc
@@ -16,17 +16,17 @@
  *  All changes are covered by the WebRTC license and IP grant:
  *  Use of this source code is governed by a BSD-style license
  *  that can be found in the LICENSE file in the root of the source
  *  tree. An additional intellectual property rights grant can be found
  *  in the file PATENTS.  All contributing project authors may
  *  be found in the AUTHORS file in the root of the source tree.
  */
 
-#include "webrtc/modules/audio_processing//utility/ooura_fft.h"
+#include "webrtc/modules/audio_processing/utility/ooura_fft.h"
 
 #include <math.h>
 
 #include "webrtc/modules/audio_processing/utility/ooura_fft_tables_common.h"
 #include "webrtc/system_wrappers/include/cpu_features_wrapper.h"
 #include "webrtc/typedefs.h"
 
 namespace webrtc {
--- a/media/webrtc/trunk/webrtc/modules/audio_processing/utility/ooura_fft_sse2.cc
+++ b/media/webrtc/trunk/webrtc/modules/audio_processing/utility/ooura_fft_sse2.cc
@@ -3,17 +3,17 @@
  *
  *  Use of this source code is governed by a BSD-style license
  *  that can be found in the LICENSE file in the root of the source
  *  tree. An additional intellectual property rights grant can be found
  *  in the file PATENTS.  All contributing project authors may
  *  be found in the AUTHORS file in the root of the source tree.
  */
 
-#include "webrtc/modules/audio_processing//utility/ooura_fft.h"
+#include "webrtc/modules/audio_processing/utility/ooura_fft.h"
 
 #include <emmintrin.h>
 
 #include "webrtc/modules/audio_processing/utility/ooura_fft_tables_common.h"
 #include "webrtc/modules/audio_processing/utility/ooura_fft_tables_neon_sse2.h"
 
 namespace webrtc {
 
--- a/memory/build/mozmemory_wrap.c
+++ b/memory/build/mozmemory_wrap.c
@@ -63,17 +63,16 @@ mozmem_malloc_impl(_ZdaPvRKSt9nothrow_t)
 }
 #endif
 
 /* strndup and strdup may be defined as macros in string.h, which would
  * clash with the definitions below. */
 #undef strndup
 #undef strdup
 
-#ifndef XP_DARWIN
 MOZ_MEMORY_API char *
 strndup_impl(const char *src, size_t len)
 {
   char* dst = (char*) malloc_impl(len + 1);
   if (dst) {
     strncpy(dst, src, len);
     dst[len] = '\0';
   }
@@ -81,17 +80,16 @@ strndup_impl(const char *src, size_t len
 }
 
 MOZ_MEMORY_API char *
 strdup_impl(const char *src)
 {
   size_t len = strlen(src);
   return strndup_impl(src, len);
 }
-#endif /* XP_DARWIN */
 
 #ifdef ANDROID
 #include <stdarg.h>
 #include <stdio.h>
 
 MOZ_MEMORY_API int
 vasprintf_impl(char **str, const char *fmt, va_list ap)
 {
--- a/memory/build/mozmemory_wrap.h
+++ b/memory/build/mozmemory_wrap.h
@@ -48,21 +48,20 @@
  *   specific functions are left unprefixed. All these functions are however
  *   aliased when exporting them, such that the resulting mozglue.dll exports
  *   them unprefixed (see $(topsrcdir)/mozglue/build/mozglue.def.in). The
  *   prefixed malloc implementation and duplication functions are not
  *   exported.
  *
  * - On MacOSX, the system libc has a zone allocator, which allows us to
  *   hook custom malloc implementation functions without exporting them.
- *   The malloc implementation functions are all prefixed with "je_" and used
- *   this way from the custom zone allocator. They are not exported.
- *   Duplication functions are not included, since they will call the custom
- *   zone allocator anyways. Jemalloc-specific functions are also left
- *   unprefixed.
+ *   However, since we want things in Firefox to skip the system zone
+ *   allocator, the malloc implementation functions are all exported
+ *   unprefixed, as well as duplication functions.
+ *   Jemalloc-specific functions are also left unprefixed.
  *
  * - On Android and Gonk, all functions are left unprefixed. Additionally,
  *   C++ allocation functions (operator new/delete) are also exported and
  *   unprefixed.
  *
  * - On other systems (mostly Linux), all functions are left unprefixed.
  *
  * Only Android and Gonk add C++ allocation functions.
@@ -129,17 +128,17 @@
 #endif
 
 #ifdef MOZ_MEMORY_IMPL
 #  if defined(MOZ_JEMALLOC_IMPL) && defined(MOZ_REPLACE_MALLOC)
 #    define mozmem_malloc_impl(a)     je_ ## a
 #    define mozmem_jemalloc_impl(a)   je_ ## a
 #  else
 #    define MOZ_JEMALLOC_API MOZ_EXTERN_C MFBT_API
-#    if (defined(XP_WIN) || defined(XP_DARWIN))
+#    if defined(XP_WIN)
 #      if defined(MOZ_REPLACE_MALLOC)
 #        define mozmem_malloc_impl(a)   a ## _impl
 #      else
 #        define mozmem_malloc_impl(a)   je_ ## a
 #      endif
 #    else
 #      define MOZ_MEMORY_API MOZ_EXTERN_C MFBT_API
 #      if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
--- a/mobile/android/app/build.gradle
+++ b/mobile/android/app/build.gradle
@@ -80,32 +80,30 @@ android {
     //
     // The "skin" flavor dimension distinguishes between different user interfaces.  We sometimes
     // want to develop significant new user interface pieces in-tree that don't ship (even in the
     // Nightly channel) while under development.  A new "skin" flavour allows us to develop such
     // pieces in Gradle without changing the mainline configuration.
     flavorDimensions "audience", "skin"
 
     productFlavors {
-        // For API 21+ - with multi dex, this will be faster for local development.
+        // For API 21+ - with pre-dexing, this will be faster for local development.
         local {
             dimension "audience"
 
-            // For multi dex, setting `minSdkVersion 21` allows the Android gradle plugin to
+            // For pre-dexing, setting `minSdkVersion 21` allows the Android gradle plugin to
             // pre-DEX each module and produce an APK that can be tested on
             // Android Lollipop without time consuming DEX merging processes.
             minSdkVersion 21
             dexOptions {
                 preDexLibraries true
-                multiDexEnabled true
             }
         }
-        // For API < 21 - does not support multi dex because local development
-        // is slow in that case. Most builds will not require multi dex so this
-        // should not be an issue.
+        // For API < 21 - does not support pre-dexing because local development
+        // is slow in that case.
         localOld {
             dimension "audience"
         }
 
         // Automation builds.  We use "official" rather than "automation" to drive these builds down
         // the list of configurations that Android Studio offers, thereby making it _not_ the
         // default.  This avoids a common issue with "omni.ja" not being packed into the default APK
         // built and deployed by Android Studio.
@@ -263,17 +261,17 @@ dependencies {
     if (mozconfig.substs.MOZ_ANDROID_GCM) {
         compile "com.google.android.gms:play-services-basement:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
         compile "com.google.android.gms:play-services-base:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
         compile "com.google.android.gms:play-services-gcm:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
         compile "com.google.android.gms:play-services-measurement:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
     }
 
     // Include LeakCanary in most gradle based builds. LeakCanary adds about 5k methods, so we disable
-    // it for the (non-proguarded, non-multidex) localOld builds to allow space for other libraries.
+    // it for the (non-proguarded, non-predex) localOld builds to allow space for other libraries.
     // Gradle based tests include the no-op version.  Mach based builds only include the no-op version
     // of this library.
     // It doesn't seem like there is a non-trivial way to be conditional on 'localOld', so instead we explicitly
     // define a version of leakcanary for every flavor:
     localCompile 'com.squareup.leakcanary:leakcanary-android:1.4-beta1'
     localOldCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta1'
     officialCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta1'
     officialCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta1'
--- a/mobile/android/base/java/org/mozilla/gecko/bookmarks/BookmarkEditFragment.java
+++ b/mobile/android/base/java/org/mozilla/gecko/bookmarks/BookmarkEditFragment.java
@@ -164,16 +164,20 @@ public class BookmarkEditFragment extend
 
         folderText.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
                 if (bookmark == null) {
                     return;
                 }
 
+                // When coming back from SelectFolderFragment, we update view with data stored in `bookmark`,
+                // so before navigating, we have to save current title from nameText into `bookmark`.
+                bookmark.title = nameText.getText().toString();
+
                 final SelectFolderFragment dialog = SelectFolderFragment.newInstance(bookmark.parentId, bookmark.id);
                 dialog.setTargetFragment(BookmarkEditFragment.this, 0);
                 dialog.show(getActivity().getSupportFragmentManager(), "select-bookmark-folder");
             }
         });
 
         return view;
     }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/HardwareCodecCapabilityUtils.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/HardwareCodecCapabilityUtils.java
@@ -42,17 +42,18 @@ public final class HardwareCodecCapabili
   private static final String[] adaptivePlaybackBlacklist =
   {
     "GT-I9300",         // S3 (I9300 / I9300I)
     "SCH-I535",         // S3
     "SGH-M919",         // S4
     "GT-I9505",         // S4
     "GT-I9515",         // S4
     "SGH-I337",         // S4
-    "SAMSUNG-SGH-I337"  // S4
+    "SAMSUNG-SGH-I337", // S4
+    "LG-D605"           // LG Optimus L9 II
   };
 
   @WrapForJNI
   public static boolean findDecoderCodecInfoForMimeType(String aMimeType) {
     int numCodecs = 0;
     try {
       numCodecs = MediaCodecList.getCodecCount();
     } catch (final RuntimeException e) {
--- a/mozglue/misc/TimeStamp_windows.cpp
+++ b/mozglue/misc/TimeStamp_windows.cpp
@@ -473,17 +473,17 @@ HasStableTSC()
                 sizeof(cpuInfo.cpuString))) {
     return false;
   }
 
   int regs[4];
 
   // detect if the Advanced Power Management feature is supported
   __cpuid(regs, 0x80000000);
-  if (regs[0] < 0x80000007) {
+  if ((unsigned int)regs[0] < 0x80000007) {
     // XXX should we return true here?  If there is no APM there may be
     // no way how TSC can run out of sync among cores.
     return false;
   }
 
   __cpuid(regs, 0x80000007);
   // if bit 8 is set than TSC will run at a constant rate
   // in all ACPI P-states, C-states and T-states
--- a/parser/html/javasrc/MetaScanner.java
+++ b/parser/html/javasrc/MetaScanner.java
@@ -1,48 +1,48 @@
 /*
  * Copyright (c) 2007 Henri Sivonen
  * Copyright (c) 2008-2015 Mozilla Foundation
  *
- * Permission is hereby granted, free of charge, to any person obtaining a 
- * copy of this software and associated documentation files (the "Software"), 
- * to deal in the Software without restriction, including without limitation 
- * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
- * and/or sell copies of the Software, and to permit persons to whom the 
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
  * Software is furnished to do so, subject to the following conditions:
  *
- * The above copyright notice and this permission notice shall be included in 
+ * The above copyright notice and this permission notice shall be included in
  * all copies or substantial portions of the Software.
  *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  * DEALINGS IN THE SOFTWARE.
  */
 
 package nu.validator.htmlparser.impl;
 
 import java.io.IOException;
 
+import org.xml.sax.SAXException;
+
 import nu.validator.htmlparser.annotation.Auto;
 import nu.validator.htmlparser.annotation.Inline;
 import nu.validator.htmlparser.common.ByteReadable;
 
-import org.xml.sax.SAXException;
-
 public abstract class MetaScanner {
 
     /**
      * Constant for "charset".
      */
     private static final char[] CHARSET = { 'h', 'a', 'r', 's', 'e', 't' };
-    
+
     /**
      * Constant for "content".
      */
     private static final char[] CONTENT = { 'o', 'n', 't', 'e', 'n', 't' };
 
     /**
      * Constant for "http-equiv".
      */
@@ -53,23 +53,23 @@ public abstract class MetaScanner {
      * Constant for "content-type".
      */
     private static final char[] CONTENT_TYPE = { 'c', 'o', 'n', 't', 'e', 'n',
             't', '-', 't', 'y', 'p', 'e' };
 
     private static final int NO = 0;
 
     private static final int M = 1;
-    
+
     private static final int E = 2;
-    
+
     private static final int T = 3;
 
     private static final int A = 4;
-    
+
     private static final int DATA = 0;
 
     private static final int TAG_OPEN = 1;
 
     private static final int SCAN_UNTIL_GT = 2;
 
     private static final int TAG_NAME = 3;
 
@@ -85,52 +85,52 @@ public abstract class MetaScanner {
 
     private static final int ATTRIBUTE_VALUE_SINGLE_QUOTED = 9;
 
     private static final int ATTRIBUTE_VALUE_UNQUOTED = 10;
 
     private static final int AFTER_ATTRIBUTE_VALUE_QUOTED = 11;
 
     private static final int MARKUP_DECLARATION_OPEN = 13;
-    
+
     private static final int MARKUP_DECLARATION_HYPHEN = 14;
 
     private static final int COMMENT_START = 15;
 
     private static final int COMMENT_START_DASH = 16;
 
     private static final int COMMENT = 17;
 
     private static final int COMMENT_END_DASH = 18;
 
     private static final int COMMENT_END = 19;
-    
+
     private static final int SELF_CLOSING_START_TAG = 20;
-    
+
     private static final int HTTP_EQUIV_NOT_SEEN = 0;
-    
+
     private static final int HTTP_EQUIV_CONTENT_TYPE = 1;
 
     private static final int HTTP_EQUIV_OTHER = 2;
 
     /**
      * The data source.
      */
     protected ByteReadable readable;
-    
+
     /**
      * The state of the state machine that recognizes the tag name "meta".
      */
     private int metaState = NO;
 
     /**
      * The current position in recognizing the attribute name "content".
      */
     private int contentIndex = Integer.MAX_VALUE;
-    
+
     /**
      * The current position in recognizing the attribute name "charset".
      */
     private int charsetIndex = Integer.MAX_VALUE;
 
     /**
      * The current position in recognizing the attribute name "http-equive".
      */
@@ -150,23 +150,23 @@ public abstract class MetaScanner {
      * The currently filled length of strBuf.
      */
     private int strBufLen;
 
     /**
      * Accumulation buffer for attribute values.
      */
     private @Auto char[] strBuf;
-    
+
     private String content;
-    
+
     private String charset;
-    
+
     private int httpEquivState;
-    
+
     // CPPONLY: private TreeBuilder treeBuilder;
 
     public MetaScanner(
         // CPPONLY: TreeBuilder tb
     ) {
         this.readable = null;
         this.metaState = NO;
         this.contentIndex = Integer.MAX_VALUE;
@@ -175,28 +175,29 @@ public abstract class MetaScanner {
         this.contentTypeIndex = Integer.MAX_VALUE;
         this.stateSave = DATA;
         this.strBufLen = 0;
         this.strBuf = new char[36];
         this.content = null;
         this.charset = null;
         this.httpEquivState = HTTP_EQUIV_NOT_SEEN;
         // CPPONLY: this.treeBuilder = tb;
+        // CPPONLY: this.mEncoding = null;
     }
-    
+
     @SuppressWarnings("unused") private void destructor() {
         Portability.releaseString(content);
         Portability.releaseString(charset);
     }
 
     // [NOCPP[
-    
+
     /**
      * Reads a byte from the data source.
-     * 
+     *
      * -1 means end.
      * @return
      * @throws IOException
      */
     protected int read() throws IOException {
         return readable.readByte();
     }
 
@@ -238,17 +239,17 @@ public abstract class MetaScanner {
                         switch (c) {
                             case -1:
                                 break stateloop;
                             case 'm':
                             case 'M':
                                 metaState = M;
                                 state = MetaScanner.TAG_NAME;
                                 break tagopenloop;
-                                // continue stateloop;                                
+                                // continue stateloop;
                             case '!':
                                 state = MetaScanner.MARKUP_DECLARATION_OPEN;
                                 continue stateloop;
                             case '?':
                             case '/':
                                 state = MetaScanner.SCAN_UNTIL_GT;
                                 continue stateloop;
                             case '>':
@@ -345,25 +346,25 @@ public abstract class MetaScanner {
                                 continue stateloop;
                             case 'c':
                             case 'C':
                                 contentIndex = 0;
                                 charsetIndex = 0;
                                 httpEquivIndex = Integer.MAX_VALUE;
                                 contentTypeIndex = Integer.MAX_VALUE;
                                 state = MetaScanner.ATTRIBUTE_NAME;
-                                break beforeattributenameloop;                                
+                                break beforeattributenameloop;
                             case 'h':
                             case 'H':
                                 contentIndex = Integer.MAX_VALUE;
                                 charsetIndex = Integer.MAX_VALUE;
                                 httpEquivIndex = 0;
                                 contentTypeIndex = Integer.MAX_VALUE;
                                 state = MetaScanner.ATTRIBUTE_NAME;
-                                break beforeattributenameloop;                                
+                                break beforeattributenameloop;
                             default:
                                 contentIndex = Integer.MAX_VALUE;
                                 charsetIndex = Integer.MAX_VALUE;
                                 httpEquivIndex = Integer.MAX_VALUE;
                                 contentTypeIndex = Integer.MAX_VALUE;
                                 state = MetaScanner.ATTRIBUTE_NAME;
                                 break beforeattributenameloop;
                             // continue stateloop;
@@ -411,17 +412,17 @@ public abstract class MetaScanner {
                                         ++charsetIndex;
                                     } else {
                                         charsetIndex = Integer.MAX_VALUE;
                                     }
                                     if (httpEquivIndex < HTTP_EQUIV.length && c == HTTP_EQUIV[httpEquivIndex]) {
                                         ++httpEquivIndex;
                                     } else {
                                         httpEquivIndex = Integer.MAX_VALUE;
-                                    }                                    
+                                    }
                                 }
                                 continue;
                         }
                     }
                     // FALLTHRU DON'T REORDER
                 case BEFORE_ATTRIBUTE_VALUE:
                     beforeattributevalueloop: for (;;) {
                         c = read();
@@ -818,17 +819,17 @@ public abstract class MetaScanner {
         boolean stop = handleTagInner();
         Portability.releaseString(content);
         content = null;
         Portability.releaseString(charset);
         charset = null;
         httpEquivState = HTTP_EQUIV_NOT_SEEN;
         return stop;
     }
-    
+
     private boolean handleTagInner() throws SAXException {
         if (charset != null && tryCharset(charset)) {
                 return true;
         }
         if (content != null && httpEquivState == HTTP_EQUIV_CONTENT_TYPE) {
             String extract = TreeBuilder.extractCharsetFromContent(content
                 // CPPONLY: , treeBuilder
             );
@@ -839,16 +840,16 @@ public abstract class MetaScanner {
             Portability.releaseString(extract);
             return success;
         }
         return false;
     }
 
     /**
      * Tries to switch to an encoding.
-     * 
+     *
      * @param encoding
      * @return <code>true</code> if successful
      * @throws SAXException
      */
     protected abstract boolean tryCharset(String encoding) throws SAXException;
-    
+
 }
--- a/parser/html/nsHtml5MetaScanner.cpp
+++ b/parser/html/nsHtml5MetaScanner.cpp
@@ -1,28 +1,28 @@
 /*
  * Copyright (c) 2007 Henri Sivonen
  * Copyright (c) 2008-2015 Mozilla Foundation
  *
- * Permission is hereby granted, free of charge, to any person obtaining a 
- * copy of this software and associated documentation files (the "Software"), 
- * to deal in the Software without restriction, including without limitation 
- * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
- * and/or sell copies of the Software, and to permit persons to whom the 
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
  * Software is furnished to do so, subject to the following conditions:
  *
- * The above copyright notice and this permission notice shall be included in 
+ * The above copyright notice and this permission notice shall be included in
  * all copies or substantial portions of the Software.
  *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  * DEALINGS IN THE SOFTWARE.
  */
 
 /*
  * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
  * Please edit MetaScanner.java instead and regenerate.
  */
 
--- a/parser/html/nsHtml5MetaScanner.h
+++ b/parser/html/nsHtml5MetaScanner.h
@@ -1,28 +1,28 @@
 /*
  * Copyright (c) 2007 Henri Sivonen
  * Copyright (c) 2008-2015 Mozilla Foundation
  *
- * Permission is hereby granted, free of charge, to any person obtaining a 
- * copy of this software and associated documentation files (the "Software"), 
- * to deal in the Software without restriction, including without limitation 
- * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
- * and/or sell copies of the Software, and to permit persons to whom the 
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
  * Software is furnished to do so, subject to the following conditions:
  *
- * The above copyright notice and this permission notice shall be included in 
+ * The above copyright notice and this permission notice shall be included in
  * all copies or substantial portions of the Software.
  *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  * DEALINGS IN THE SOFTWARE.
  */
 
 /*
  * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
  * Please edit MetaScanner.java instead and regenerate.
  */
 
--- a/parser/html/nsHtml5TreeBuilder.h
+++ b/parser/html/nsHtml5TreeBuilder.h
@@ -399,20 +399,18 @@ class nsHtml5TreeBuilder : public nsAHtm
     int32_t findInListOfActiveFormattingElementsContainsBetweenEndAndLastMarker(nsIAtom* name);
     void maybeForgetEarlierDuplicateFormattingElement(nsIAtom* name, nsHtml5HtmlAttributes* attributes);
     int32_t findLastOrRoot(nsIAtom* name);
     int32_t findLastOrRoot(int32_t group);
     bool addAttributesToBody(nsHtml5HtmlAttributes* attributes);
     void addAttributesToHtml(nsHtml5HtmlAttributes* attributes);
     void pushHeadPointerOntoStack();
     void reconstructTheActiveFormattingElements();
-
   public:
     void notifyUnusedStackNode(int32_t idxInStackNodes);
-
   private:
     nsHtml5StackNode* getUnusedStackNode();
     nsHtml5StackNode* createStackNode(int32_t flags,
                                       int32_t ns,
                                       nsIAtom* name,
                                       nsIContentHandle* node,
                                       nsIAtom* popName,
                                       nsHtml5HtmlAttributes* attributes);
--- a/python/mozbuild/mozbuild/backend/configenvironment.py
+++ b/python/mozbuild/mozbuild/backend/configenvironment.py
@@ -139,24 +139,24 @@ class ConfigEnvironment(object):
             self.import_prefix = self.dll_prefix
             self.import_suffix = self.dll_suffix
 
         global_defines = [name for name in self.defines
             if not name in self.non_global_defines]
         self.substs['ACDEFINES'] = ' '.join(['-D%s=%s' % (name,
             shell_quote(self.defines[name]).replace('$', '$$'))
             for name in sorted(global_defines)])
-        def serialize(obj):
+        def serialize(name, obj):
             if isinstance(obj, StringTypes):
                 return obj
             if isinstance(obj, Iterable):
                 return ' '.join(obj)
-            raise Exception('Unhandled type %s', type(obj))
+            raise Exception('Unhandled type %s for %s', type(obj), str(name))
         self.substs['ALLSUBSTS'] = '\n'.join(sorted(['%s = %s' % (name,
-            serialize(self.substs[name])) for name in self.substs if self.substs[name]]))
+            serialize(name, self.substs[name])) for name in self.substs if self.substs[name]]))
         self.substs['ALLEMPTYSUBSTS'] = '\n'.join(sorted(['%s =' % name
             for name in self.substs if not self.substs[name]]))
 
         self.substs = ReadOnlyDict(self.substs)
 
         self.external_source_dir = None
         external = self.substs.get('EXTERNAL_SOURCE_DIR', '')
         if external:
--- a/python/mozbuild/mozbuild/vendor_rust.py
+++ b/python/mozbuild/mozbuild/vendor_rust.py
@@ -268,22 +268,27 @@ license file's hash.
         # Once we require a new enough cargo to switch to workspaces, we can
         # just do this once on the workspace root crate.
         crates_and_roots = (
             ('gkrust', 'toolkit/library/rust'),
             ('gkrust-gtest', 'toolkit/library/gtest/rust'),
             ('mozjs_sys', 'js/src'),
             ('geckodriver', 'testing/geckodriver'),
         )
+
+        lockfiles = []
         for (lib, crate_root) in crates_and_roots:
             path = mozpath.join(self.topsrcdir, crate_root)
             # We use check_call instead of mozprocess to ensure errors are displayed.
             # We do an |update -p| here to regenerate the Cargo.lock file with minimal changes. See bug 1324462
             subprocess.check_call([cargo, 'update', '--manifest-path', mozpath.join(path, 'Cargo.toml'), '-p', lib], cwd=self.topsrcdir)
-            subprocess.check_call([cargo, 'vendor', '--quiet', '--no-delete', '--sync', mozpath.join(path, 'Cargo.lock'), vendor_dir], cwd=self.topsrcdir)
+            lockfiles.append('--sync')
+            lockfiles.append(mozpath.join(path, 'Cargo.lock'))
+
+        subprocess.check_call([cargo, 'vendor', '--quiet', '--no-delete'] + lockfiles + [vendor_dir], cwd=self.topsrcdir)
 
         if not self._check_licenses(vendor_dir):
             self.log(logging.ERROR, 'license_check_failed', {},
                      '''The changes from `mach vendor rust` will NOT be added to version control.''')
             sys.exit(1)
 
         self.repository.add_remove_files(vendor_dir)
 
--- a/security/sandbox/mac/SandboxPolicies.h
+++ b/security/sandbox/mac/SandboxPolicies.h
@@ -224,18 +224,19 @@ static const char contentSandboxRules[] 
   (allow file-read-metadata
     (literal "/private/var")
     (subpath "/private/var/folders"))
 
 ; bug 1303987
   (if (string? debugWriteDir)
     (allow file-write* (subpath debugWriteDir)))
 
-; bug 1324610
-  (allow network-outbound (literal "/private/var/run/cupsd"))
+  ; bug 1324610
+  (allow network-outbound file-read*
+    (literal "/private/var/run/cupsd"))
 
   (allow-shared-list "org.mozilla.plugincontainer")
 
 ; the following rule should be removed when microphone access
 ; is brokered through the content process
   (allow device-microphone)
 
 ; Per-user and system-wide Extensions dir
@@ -270,49 +271,28 @@ static const char contentSandboxRules[] 
                 (require-not (home-subpath "/Library"))
                 (require-not (subpath profileDir))))
             (allow file-read*
                 (profile-subpath "/extensions")
                 (profile-subpath "/chrome")))
           ; we don't have a profile dir
           (allow file-read* (require-not (home-subpath "/Library")))))))
 
-; level 3: global read access permitted, no global write access,
-;          no read access to the home directory,
-;          no read access to /private/var (but read-metadata allowed above),
-;          no read access to /{Volumes,Network,Users}
-;          read access permitted to $PROFILE/{extensions,chrome}
+  ; level 3: no global read/write access,
+  ;          read access permitted to $PROFILE/{extensions,chrome}
   (if (string=? sandbox-level-3 "TRUE")
     (if (string=? hasFilePrivileges "TRUE")
       ; This process has blanket file read privileges
       (allow file-read*)
       ; This process does not have blanket file read privileges
       (if (string=? hasProfileDir "TRUE")
         ; we have a profile dir
-        (begin
-          (allow file-read* (require-all
-              (require-not (subpath home-path))
-              (require-not (subpath profileDir))
-              (require-not (subpath "/Volumes"))
-              (require-not (subpath "/Network"))
-              (require-not (subpath "/Users"))
-              (require-not (subpath "/private/var"))))
-          (allow file-read* (literal "/private/var/run/cupsd"))
           (allow file-read*
-              (profile-subpath "/extensions")
-              (profile-subpath "/chrome")))
-        ; we don't have a profile dir
-        (begin
-          (allow file-read* (require-all
-            (require-not (subpath home-path))
-            (require-not (subpath "/Volumes"))
-            (require-not (subpath "/Network"))
-            (require-not (subpath "/Users"))
-            (require-not (subpath "/private/var"))))
-          (allow file-read* (literal "/private/var/run/cupsd"))))))
+            (profile-subpath "/extensions")
+            (profile-subpath "/chrome")))))
 
 ; accelerated graphics
   (allow-shared-preferences-read "com.apple.opengl")
   (allow-shared-preferences-read "com.nvidia.OpenGL")
   (allow mach-lookup
       (global-name "com.apple.cvmsServ"))
   (allow iokit-open
       (iokit-connection "IOAccelerator")
@@ -325,16 +305,16 @@ static const char contentSandboxRules[] 
       (iokit-user-client-class "AppleGraphicsControlClient")
       (iokit-user-client-class "AppleGraphicsPolicyClient"))
 
 ; bug 1153809
   (allow iokit-open
       (iokit-user-client-class "NVDVDContextTesla")
       (iokit-user-client-class "Gen6DVDContext"))
 
-; bug 1237847
+  ; bug 1237847
   (allow file-read* file-write*
       (subpath appTempDir))
 )";
 
 }
 
 #endif // mozilla_SandboxPolicies_h
--- a/services/sync/modules/service.js
+++ b/services/sync/modules/service.js
@@ -807,31 +807,24 @@ Sync11Service.prototype = {
   login: function login() {
     function onNotify() {
       this._loggedIn = false;
       if (Services.io.offline) {
         this.status.login = LOGIN_FAILED_NETWORK_ERROR;
         throw "Application is offline, login should not be called";
       }
 
-      if (this._checkSetup() == CLIENT_NOT_CONFIGURED) {
-        throw "Aborting login, client not configured.";
+      this._log.info("Logging in the user.");
+      // Just let any errors bubble up - they've more context than we do!
+      try {
+        Async.promiseSpinningly(this.identity.ensureLoggedIn());
+      } finally {
+        this._checkSetup(); // _checkSetup has a side effect of setting the right state.
       }
 
-      // Ask the identity manager to explicitly login now.
-      this._log.info("Logging in the user.");
-      let cb = Async.makeSpinningCallback();
-      this.identity.ensureLoggedIn().then(
-        () => cb(null),
-        err => cb(err || "ensureLoggedIn failed")
-      );
-
-      // Just let any errors bubble up - they've more context than we do!
-      cb.wait();
-
       this._updateCachedURLs();
 
       this._log.info("User logged in successfully - verifying login.");
       if (!this.verifyLogin()) {
         // verifyLogin sets the failure states here.
         throw "Login failed: " + this.status.login;
       }
 
--- a/services/sync/tests/unit/test_service_login.js
+++ b/services/sync/tests/unit/test_service_login.js
@@ -80,16 +80,27 @@ add_task(async function test_login_logou
 
     _("Try again with a configured account");
     await configureIdentity({ username: "johndoe" }, server);
     Service.login();
     do_check_eq(Service.status.service, STATUS_OK);
     do_check_eq(Service.status.login, LOGIN_SUCCEEDED);
     do_check_true(Service.isLoggedIn);
 
+    _("Profile refresh edge case: FxA configured but prefs reset");
+    Service.startOver();
+    let config = makeIdentityConfig({ username: "johndoe" }, server);
+    config.fxaccount.token.endpoint = server.baseURI + "/1.1/" + config.username + "/";
+    configureFxAccountIdentity(Service.identity, config);
+
+    Service.login();
+    do_check_eq(Service.status.service, STATUS_OK);
+    do_check_eq(Service.status.login, LOGIN_SUCCEEDED);
+    do_check_true(Service.isLoggedIn);
+
     _("Logout.");
     Service.logout();
     do_check_false(Service.isLoggedIn);
 
     _("Logging out again won't do any harm.");
     Service.logout();
     do_check_false(Service.isLoggedIn);
 
--- a/servo/components/style/gecko/generated/bindings.rs
+++ b/servo/components/style/gecko/generated/bindings.rs
@@ -2140,16 +2140,26 @@ extern "C" {
                                                  index: u32,
                                                  specificity: *mut u64);
 }
 extern "C" {
     pub fn Servo_StyleRule_GetSelectorCount(rule: RawServoStyleRuleBorrowed,
                                             count: *mut u32);
 }
 extern "C" {
+    pub fn Servo_StyleRule_SelectorMatchesElement(arg1:
+                                                      RawServoStyleRuleBorrowed,
+                                                  arg2:
+                                                      RawGeckoElementBorrowed,
+                                                  index: u32,
+                                                  pseudo_type:
+                                                      CSSPseudoElementType)
+     -> bool;
+}
+extern "C" {
     pub fn Servo_ImportRule_GetHref(rule: RawServoImportRuleBorrowed,
                                     result: *mut nsAString);
 }
 extern "C" {
     pub fn Servo_ImportRule_GetSheet(rule: RawServoImportRuleBorrowed)
      -> *const ServoStyleSheet;
 }
 extern "C" {
--- a/servo/components/style/gecko/wrapper.rs
+++ b/servo/components/style/gecko/wrapper.rs
@@ -689,16 +689,21 @@ impl<'le> GeckoElement<'le> {
         self.as_node().get_bool_flag(nsINode_BooleanFlag::ElementMayHaveStyle)
     }
 
     #[inline]
     fn get_document_theme(&self) -> DocumentTheme {
         let node = self.as_node();
         unsafe { Gecko_GetDocumentLWTheme(node.owner_doc()) }
     }
+
+    /// Owner document quirks mode getter.
+    pub fn owner_document_quirks_mode(&self) -> QuirksMode {
+        self.as_node().owner_doc().mCompatMode.into()
+    }
 }
 
 /// Converts flags from the layout used by rust-selectors to the layout used
 /// by Gecko. We could align these and then do this without conditionals, but
 /// it's probably not worth the trouble.
 fn selector_flags_to_node_flags(flags: ElementSelectorFlags) -> u32 {
     use gecko_bindings::structs::*;
     use selectors::matching::*;
--- a/servo/components/style/properties/data.py
+++ b/servo/components/style/properties/data.py
@@ -55,32 +55,33 @@ def parse_aliases(value):
 
 
 class Keyword(object):
     def __init__(self, name, values, gecko_constant_prefix=None,
                  gecko_enum_prefix=None, custom_consts=None,
                  extra_gecko_values=None, extra_servo_values=None,
                  aliases=None,
                  extra_gecko_aliases=None, extra_servo_aliases=None,
-                 gecko_strip_moz_prefix=True,
+                 gecko_strip_moz_prefix=None,
                  gecko_inexhaustive=None):
         self.name = name
         self.values = values.split()
         if gecko_constant_prefix and gecko_enum_prefix:
             raise TypeError("Only one of gecko_constant_prefix and gecko_enum_prefix can be specified")
         self.gecko_constant_prefix = gecko_constant_prefix or \
             "NS_STYLE_" + self.name.upper().replace("-", "_")
         self.gecko_enum_prefix = gecko_enum_prefix
         self.extra_gecko_values = (extra_gecko_values or "").split()
         self.extra_servo_values = (extra_servo_values or "").split()
         self.aliases = parse_aliases(aliases or "")
         self.extra_gecko_aliases = parse_aliases(extra_gecko_aliases or "")
         self.extra_servo_aliases = parse_aliases(extra_servo_aliases or "")
         self.consts_map = {} if custom_consts is None else custom_consts
-        self.gecko_strip_moz_prefix = gecko_strip_moz_prefix
+        self.gecko_strip_moz_prefix = True \
+            if gecko_strip_moz_prefix is None else gecko_strip_moz_prefix
         self.gecko_inexhaustive = gecko_inexhaustive or (gecko_enum_prefix is None)
 
     def gecko_values(self):
         return self.values + self.extra_gecko_values
 
     def servo_values(self):
         return self.values + self.extra_servo_values
 
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -6,16 +6,17 @@
 
 <%!
     from data import to_rust_ident, to_camel_case
     from data import Keyword
 %>
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
 use app_units::Au;
+use custom_properties::CustomPropertiesMap;
 use gecko_bindings::bindings;
 % for style_struct in data.style_structs:
 use gecko_bindings::structs::${style_struct.gecko_ffi_name};
 use gecko_bindings::bindings::Gecko_Construct_Default_${style_struct.gecko_ffi_name};
 use gecko_bindings::bindings::Gecko_CopyConstruct_${style_struct.gecko_ffi_name};
 use gecko_bindings::bindings::Gecko_Destroy_${style_struct.gecko_ffi_name};
 % endfor
 use gecko_bindings::bindings::Gecko_Construct_nsStyleVariables;
@@ -48,33 +49,189 @@ use gecko_bindings::sugar::ns_style_coor
 use gecko_bindings::sugar::ownership::HasArcFFI;
 use gecko::values::convert_nscolor_to_rgba;
 use gecko::values::convert_rgba_to_nscolor;
 use gecko::values::GeckoStyleCoordConvertible;
 use gecko::values::round_border_to_device_pixels;
 use logical_geometry::WritingMode;
 use media_queries::Device;
 use properties::animated_properties::TransitionProperty;
-use properties::{longhands, ComputedValues, LonghandId, PropertyDeclarationId};
+use properties::computed_value_flags::ComputedValueFlags;
+use properties::{longhands, FontComputationData, Importance, LonghandId};
+use properties::{PropertyDeclaration, PropertyDeclarationBlock, PropertyDeclarationId};
+use rule_tree::StrongRuleNode;
 use std::fmt::{self, Debug};
 use std::mem::{forget, transmute, zeroed};
 use std::ptr;
 use stylearc::Arc;
 use std::cmp;
 use values::{Auto, CustomIdent, Either, KeyframesName};
+use values::computed::ToComputedValue;
 use values::computed::effects::{BoxShadow, Filter, SimpleShadow};
 use values::specified::length::Percentage;
 use computed_values::border_style;
 
 pub mod style_structs {
     % for style_struct in data.style_structs:
     pub use super::${style_struct.gecko_struct_name} as ${style_struct.name};
     % endfor
 }
 
+// FIXME(emilio): Unify both definitions, since they're equal now.
+#[derive(Clone)]
+pub struct ComputedValues {
+    % for style_struct in data.style_structs:
+    ${style_struct.ident}: Arc<style_structs::${style_struct.name}>,
+    % endfor
+    custom_properties: Option<Arc<CustomPropertiesMap>>,
+    pub writing_mode: WritingMode,
+    pub font_computation_data: FontComputationData,
+    pub flags: ComputedValueFlags,
+
+    /// The rule node representing the ordered list of rules matched for this
+    /// node.  Can be None for default values and text nodes.  This is
+    /// essentially an optimization to avoid referencing the root rule node.
+    pub rules: Option<StrongRuleNode>,
+    /// The element's computed values if visited, only computed if there's a
+    /// relevant link for this element. A element's "relevant link" is the
+    /// element being matched if it is a link or the nearest ancestor link.
+    visited_style: Option<Arc<ComputedValues>>,
+}
+
+impl ComputedValues {
+    pub fn new(custom_properties: Option<Arc<CustomPropertiesMap>>,
+               writing_mode: WritingMode,
+               font_size_keyword: Option<(longhands::font_size::KeywordSize, f32)>,
+               flags: ComputedValueFlags,
+               rules: Option<StrongRuleNode>,
+               visited_style: Option<Arc<ComputedValues>>,
+               % for style_struct in data.style_structs:
+               ${style_struct.ident}: Arc<style_structs::${style_struct.name}>,
+               % endfor
+    ) -> Self {
+        ComputedValues {
+            custom_properties,
+            writing_mode,
+            font_computation_data: FontComputationData::new(font_size_keyword),
+            flags,
+            rules,
+            visited_style: visited_style,
+            % for style_struct in data.style_structs:
+            ${style_struct.ident},
+            % endfor
+        }
+    }
+
+    pub fn default_values(pres_context: RawGeckoPresContextBorrowed) -> Arc<Self> {
+        Arc::new(ComputedValues {
+            custom_properties: None,
+            writing_mode: WritingMode::empty(), // FIXME(bz): This seems dubious
+            font_computation_data: FontComputationData::default_values(),
+            flags: ComputedValueFlags::initial(),
+            rules: None,
+            visited_style: None,
+            % for style_struct in data.style_structs:
+                ${style_struct.ident}: style_structs::${style_struct.name}::default(pres_context),
+            % endfor
+        })
+    }
+
+    #[inline]
+    pub fn is_display_contents(&self) -> bool {
+        self.get_box().clone_display() == longhands::display::computed_value::T::contents
+    }
+
+    /// Returns true if the value of the `content` property would make a
+    /// pseudo-element not rendered.
+    #[inline]
+    pub fn ineffective_content_property(&self) -> bool {
+        self.get_counters().ineffective_content_property()
+    }
+
+    % for style_struct in data.style_structs:
+    #[inline]
+    pub fn clone_${style_struct.name_lower}(&self) -> Arc<style_structs::${style_struct.name}> {
+        self.${style_struct.ident}.clone()
+    }
+    #[inline]
+    pub fn get_${style_struct.name_lower}(&self) -> &style_structs::${style_struct.name} {
+        &self.${style_struct.ident}
+    }
+
+    pub fn ${style_struct.name_lower}_arc(&self) -> &Arc<style_structs::${style_struct.name}> {
+        &self.${style_struct.ident}
+    }
+
+    #[inline]
+    pub fn mutate_${style_struct.name_lower}(&mut self) -> &mut style_structs::${style_struct.name} {
+        Arc::make_mut(&mut self.${style_struct.ident})
+    }
+    % endfor
+
+    /// Gets a reference to the rule node. Panic if no rule node exists.
+    pub fn rules(&self) -> &StrongRuleNode {
+        self.rules.as_ref().unwrap()
+    }
+
+    /// Gets a reference to the visited style, if any.
+    pub fn get_visited_style(&self) -> Option<<&Arc<ComputedValues>> {
+        self.visited_style.as_ref()
+    }
+
+    /// Gets a reference to the visited style. Panic if no visited style exists.
+    pub fn visited_style(&self) -> &Arc<ComputedValues> {
+        self.get_visited_style().unwrap()
+    }
+
+    /// Clone the visited style.  Used for inheriting parent styles in
+    /// StyleBuilder::for_inheritance.
+    pub fn clone_visited_style(&self) -> Option<Arc<ComputedValues>> {
+        self.visited_style.clone()
+    }
+
+    pub fn custom_properties(&self) -> Option<Arc<CustomPropertiesMap>> {
+        self.custom_properties.clone()
+    }
+
+    #[allow(non_snake_case)]
+    pub fn has_moz_binding(&self) -> bool {
+        !self.get_box().gecko.mBinding.mPtr.mRawPtr.is_null()
+    }
+
+    // FIXME(bholley): Implement this properly.
+    #[inline]
+    pub fn is_multicol(&self) -> bool { false }
+
+    pub fn to_declaration_block(&self, property: PropertyDeclarationId) -> PropertyDeclarationBlock {
+        match property {
+            % for prop in data.longhands:
+                % if prop.animatable:
+                    PropertyDeclarationId::Longhand(LonghandId::${prop.camel_case}) => {
+                         PropertyDeclarationBlock::with_one(
+                            PropertyDeclaration::${prop.camel_case}(
+                                % if prop.boxed:
+                                    Box::new(
+                                % endif
+                                longhands::${prop.ident}::SpecifiedValue::from_computed_value(
+                                  &self.get_${prop.style_struct.ident.strip("_")}().clone_${prop.ident}())
+                                % if prop.boxed:
+                                    )
+                                % endif
+                            ),
+                            Importance::Normal
+                        )
+                    },
+                % endif
+            % endfor
+            PropertyDeclarationId::Custom(_name) => unimplemented!(),
+            _ => unimplemented!()
+        }
+    }
+}
+
 <%def name="declare_style_struct(style_struct)">
 pub struct ${style_struct.gecko_struct_name} {
     gecko: ${style_struct.gecko_ffi_name},
 }
 impl ${style_struct.gecko_struct_name} {
     pub fn gecko(&self) -> &${style_struct.gecko_ffi_name} {
         &self.gecko
     }
--- a/servo/components/style/properties/helpers.mako.rs
+++ b/servo/components/style/properties/helpers.mako.rs
@@ -696,17 +696,17 @@
 
 <%def name="single_keyword_computed(name, values, vector=False,
             extra_specified=None, needs_conversion=False, **kwargs)">
     <%
         keyword_kwargs = {a: kwargs.pop(a, None) for a in [
             'gecko_constant_prefix', 'gecko_enum_prefix',
             'extra_gecko_values', 'extra_servo_values',
             'aliases', 'extra_gecko_aliases', 'extra_servo_aliases',
-            'custom_consts', 'gecko_inexhaustive',
+            'custom_consts', 'gecko_inexhaustive', 'gecko_strip_moz_prefix',
         ]}
     %>
 
     <%def name="inner_body(keyword, extra_specified=None, needs_conversion=False)">
         % if extra_specified or keyword.aliases_for(product):
             use style_traits::ToCss;
             define_css_keyword_enum! { SpecifiedValue:
                 values {
--- a/servo/components/style/properties/longhand/ui.mako.rs
+++ b/servo/components/style/properties/longhand/ui.mako.rs
@@ -12,21 +12,23 @@
 // TODO spec says that UAs should not support this
 // we should probably remove from gecko (https://bugzilla.mozilla.org/show_bug.cgi?id=1328331)
 ${helpers.single_keyword("ime-mode", "auto normal active disabled inactive",
                          products="gecko", gecko_ffi_name="mIMEMode",
                          animation_value_type="discrete",
                          spec="https://drafts.csswg.org/css-ui/#input-method-editor")}
 
 ${helpers.single_keyword("-moz-user-select", "auto text none all element elements" +
-                            " toggle tri-state -moz-all -moz-none -moz-text",
+                            " toggle tri-state -moz-all -moz-text",
                          products="gecko",
                          alias="-webkit-user-select",
                          gecko_ffi_name="mUserSelect",
                          gecko_enum_prefix="StyleUserSelect",
+                         gecko_strip_moz_prefix=False,
+                         aliases="-moz-none=none",
                          animation_value_type="none",
                          spec="https://drafts.csswg.org/css-ui-4/#propdef-user-select")}
 
 ${helpers.single_keyword("-moz-window-dragging", "default drag no-drag", products="gecko",
                          gecko_ffi_name="mWindowDragging",
                          gecko_enum_prefix="StyleWindowDragging",
                          gecko_inexhaustive=True,
                          animation_value_type="discrete",
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -1802,27 +1802,31 @@ pub mod style_structs {
                     }
                 }
             }
         % endif
     % endfor
 % endfor
 
 
+#[cfg(feature = "gecko")]
+pub use gecko_properties::ComputedValues;
+
 /// A legacy alias for a servo-version of ComputedValues. Should go away soon.
 #[cfg(feature = "servo")]
 pub type ServoComputedValues = ComputedValues;
 
 /// The struct that Servo uses to represent computed values.
 ///
 /// This struct contains an immutable atomically-reference-counted pointer to
 /// every kind of style struct.
 ///
 /// When needed, the structs may be copied in order to get mutated.
-#[derive(Clone)]
+#[cfg(feature = "servo")]
+#[cfg_attr(feature = "servo", derive(Clone))]
 pub struct ComputedValues {
     % for style_struct in data.active_style_structs():
         ${style_struct.ident}: Arc<style_structs::${style_struct.name}>,
     % endfor
     custom_properties: Option<Arc<::custom_properties::CustomPropertiesMap>>,
     /// The writing mode of this computed values struct.
     pub writing_mode: WritingMode,
     /// The keyword behind the current font-size property, if any
@@ -1837,16 +1841,17 @@ pub struct ComputedValues {
     pub rules: Option<StrongRuleNode>,
 
     /// The element's computed values if visited, only computed if there's a
     /// relevant link for this element. A element's "relevant link" is the
     /// element being matched if it is a link or the nearest ancestor link.
     visited_style: Option<Arc<ComputedValues>>,
 }
 
+#[cfg(feature = "servo")]
 impl ComputedValues {
     /// Construct a `ComputedValues` instance.
     pub fn new(
         custom_properties: Option<Arc<::custom_properties::CustomPropertiesMap>>,
         writing_mode: WritingMode,
         font_size_keyword: Option<(longhands::font_size::KeywordSize, f32)>,
         flags: ComputedValueFlags,
         rules: Option<StrongRuleNode>,
@@ -1864,16 +1869,19 @@ impl ComputedValues {
             rules,
             visited_style,
         % for style_struct in data.active_style_structs():
             ${style_struct.ident},
         % endfor
         }
     }
 
+    /// Get the initial computed values.
+    pub fn initial_values() -> &'static Self { &*INITIAL_SERVO_VALUES }
+
     % for style_struct in data.active_style_structs():
         /// Clone the ${style_struct.name} struct.
         #[inline]
         pub fn clone_${style_struct.name_lower}(&self) -> Arc<style_structs::${style_struct.name}> {
             self.${style_struct.ident}.clone()
         }
 
         /// Get a immutable reference to the ${style_struct.name} struct.
@@ -1890,40 +1898,16 @@ impl ComputedValues {
 
         /// Get a mutable reference to the ${style_struct.name} struct.
         #[inline]
         pub fn mutate_${style_struct.name_lower}(&mut self) -> &mut style_structs::${style_struct.name} {
             Arc::make_mut(&mut self.${style_struct.ident})
         }
     % endfor
 
-    /// Get the initial computed values.
-    #[cfg(feature = "servo")]
-    pub fn initial_values() -> &'static Self { &*INITIAL_SERVO_VALUES }
-
-    /// Get the default computed values for a given document.
-    ///
-    /// This takes into account zoom, etc.
-    #[cfg(feature = "gecko")]
-    pub fn default_values(
-        pres_context: bindings::RawGeckoPresContextBorrowed
-    ) -> Arc<Self> {
-        Arc::new(ComputedValues {
-            custom_properties: None,
-            writing_mode: WritingMode::empty(), // FIXME(bz): This seems dubious
-            font_computation_data: FontComputationData::default_values(),
-            flags: ComputedValueFlags::initial(),
-            rules: None,
-            visited_style: None,
-            % for style_struct in data.style_structs:
-                ${style_struct.ident}: style_structs::${style_struct.name}::default(pres_context),
-            % endfor
-        })
-    }
-
     /// Gets a reference to the rule node. Panic if no rule node exists.
     pub fn rules(&self) -> &StrongRuleNode {
         self.rules.as_ref().unwrap()
     }
 
     /// Gets a reference to the visited style, if any.
     pub fn get_visited_style(&self) -> Option<<&Arc<ComputedValues>> {
         self.visited_style.as_ref()
@@ -1950,122 +1934,50 @@ impl ComputedValues {
     /// Get the custom properties map if necessary.
     ///
     /// Cloning the Arc here is fine because it only happens in the case where
     /// we have custom properties, and those are both rare and expensive.
     pub fn custom_properties(&self) -> Option<Arc<::custom_properties::CustomPropertiesMap>> {
         self.custom_properties.clone()
     }
 
-    /// Get a declaration block representing the computed value for a given
-    /// property.
-    ///
-    /// Currently only implemented for animated properties.
-    pub fn to_declaration_block(
-        &self,
-        property: PropertyDeclarationId
-    ) -> PropertyDeclarationBlock {
-        use values::computed::ToComputedValue;
-
-        match property {
-            % for prop in data.longhands:
-                % if prop.animatable:
-                    PropertyDeclarationId::Longhand(LonghandId::${prop.camel_case}) => {
-                         PropertyDeclarationBlock::with_one(
-                            PropertyDeclaration::${prop.camel_case}(
-                                % if prop.boxed:
-                                    Box::new(
-                                % endif
-                                longhands::${prop.ident}::SpecifiedValue::from_computed_value(
-                                  &self.get_${prop.style_struct.ident.strip("_")}().clone_${prop.ident}())
-                                % if prop.boxed:
-                                    )
-                                % endif
-                            ),
-                            Importance::Normal
-                        )
-                    },
-                % endif
-            % endfor
-            PropertyDeclarationId::Custom(_name) => unimplemented!(),
-            _ => unimplemented!()
-        }
-    }
-
     /// Whether this style has a -moz-binding value. This is always false for
     /// Servo for obvious reasons.
-    #[inline]
-    #[cfg(feature = "servo")]
     pub fn has_moz_binding(&self) -> bool { false }
 
-    /// Whether this style has a -moz-binding value.
-    #[inline]
-    #[cfg(feature = "gecko")]
-    pub fn has_moz_binding(&self) -> bool {
-        !self.get_box().gecko().mBinding.mPtr.mRawPtr.is_null()
-    }
-
     /// Returns whether this style's display value is equal to contents.
     ///
     /// Since this isn't supported in Servo, this is always false for Servo.
-    #[inline]
-    #[cfg(feature = "servo")]
     pub fn is_display_contents(&self) -> bool { false }
 
-    /// Returns whether this style's display value is equal to contents.
     #[inline]
-    #[cfg(feature = "gecko")]
-    pub fn is_display_contents(&self) -> bool {
-        self.get_box().clone_display() == longhands::display::computed_value::T::contents
-    }
-
     /// Returns whether the "content" property for the given style is completely
     /// ineffective, and would yield an empty `::before` or `::after`
     /// pseudo-element.
-    #[inline]
-    #[cfg(feature = "servo")]
     pub fn ineffective_content_property(&self) -> bool {
         use properties::longhands::content::computed_value::T;
         match self.get_counters().content {
             T::Normal | T::None => true,
             T::Items(ref items) => items.is_empty(),
         }
     }
 
-    /// Returns true if the value of the `content` property would make a
-    /// pseudo-element not rendered.
-    #[inline]
-    #[cfg(feature = "gecko")]
-    pub fn ineffective_content_property(&self) -> bool {
-        self.get_counters().ineffective_content_property()
-    }
-
+    /// Whether the current style is multicolumn.
     #[inline]
-    #[cfg(feature = "gecko")]
-    /// Whether the current style is multicolumn.
-    /// FIXME(bholley): Implement this properly.
-    pub fn is_multicol(&self) -> bool { false }
-
-    #[inline]
-    #[cfg(feature = "servo")]
-    /// Whether the current style is multicolumn.
     pub fn is_multicol(&self) -> bool {
         let style = self.get_column();
         match style.column_width {
             Either::First(_width) => true,
             Either::Second(_auto) => match style.column_count {
                 Either::First(_n) => true,
                 Either::Second(_auto) => false,
             }
         }
     }
-}
 
-#[cfg(feature = "servo")]
-impl ComputedValues {
     /// Resolves the currentColor keyword.
     ///
     /// Any color value from computed values (except for the 'color' property
     /// itself) should go through this method.
     ///
     /// Usage example:
     /// let top_color = style.resolve_color(style.Border.border_top_color);
     #[inline]
--- a/servo/components/style/rule_tree/mod.rs
+++ b/servo/components/style/rule_tree/mod.rs
@@ -1403,26 +1403,54 @@ impl Drop for StrongRuleNode {
             let _ = unsafe { Box::from_raw(self.ptr()) };
             return;
         }
 
         let root = unsafe { &*node.root.as_ref().unwrap().ptr() };
         let free_list = &root.next_free;
         let mut old_head = free_list.load(Ordering::Relaxed);
 
-        // If the free list is null, that means the last GC has already occurred.
-        // We require that any callers freeing at this point are on the main
-        // thread, and we drop the rule node synchronously.
+        // If the free list is null, that means that the rule tree has been
+        // formally torn down, and the last standard GC has already occurred.
+        // We require that any callers using the rule tree at this point are
+        // on the main thread only, which lets us trigger a synchronous GC
+        // here to avoid leaking anything. We use the GC machinery, rather
+        // than just dropping directly, so that we benefit from the iterative
+        // destruction and don't trigger unbounded recursion during drop. See
+        // [1] and the associated crashtest.
+        //
+        // [1] https://bugzilla.mozilla.org/show_bug.cgi?id=439184
         if old_head.is_null() {
             debug_assert!(!thread_state::get().is_worker() &&
                           (thread_state::get().is_layout() ||
                            thread_state::get().is_script()));
-            unsafe { node.remove_from_child_list(); }
-            log_drop(self.ptr());
-            let _ = unsafe { Box::from_raw(self.ptr()) };
+            // Add the node as the sole entry in the free list.
+            debug_assert!(node.next_free.load(Ordering::Relaxed).is_null());
+            node.next_free.store(FREE_LIST_SENTINEL, Ordering::Relaxed);
+            free_list.store(node as *const _ as *mut _, Ordering::Relaxed);
+
+            // Invoke the GC.
+            //
+            // Note that we need hold a strong reference to the root so that it
+            // doesn't go away during the GC (which would happen if we're freeing
+            // the last external reference into the rule tree). This is nicely
+            // enforced by having the gc() method live on StrongRuleNode rather than
+            // RuleNode.
+            let strong_root: StrongRuleNode = node.root.as_ref().unwrap().upgrade();
+            unsafe { strong_root.gc(); }
+
+            // Leave the free list null, like we found it, such that additional
+            // drops for straggling rule nodes will take this same codepath.
+            debug_assert_eq!(root.next_free.load(Ordering::Relaxed),
+                             FREE_LIST_SENTINEL);
+            root.next_free.store(ptr::null_mut(), Ordering::Relaxed);
+
+            // Return. If strong_root is the last strong reference to the root,
+            // this re-enter StrongRuleNode::drop, and take the root-dropping
+            // path earlier in this function.
             return;
         }
 
         // We're sure we're already in the free list, don't spinloop if we're.
         // Note that this is just a fast path, so it doesn't need to have an
         // strong memory ordering.
         if node.next_free.load(Ordering::Relaxed) != ptr::null_mut() {
             return;
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -2,16 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use atomic_refcell::AtomicRefMut;
 use cssparser::{Parser, ParserInput};
 use cssparser::ToCss as ParserToCss;
 use env_logger::LogBuilder;
 use selectors::Element;
+use selectors::matching::{MatchingContext, MatchingMode, matches_selector};
 use std::env;
 use std::fmt::Write;
 use std::ptr;
 use style::context::{CascadeInputs, QuirksMode, SharedStyleContext, StyleContext};
 use style::context::ThreadLocalStyleContext;
 use style::data::{ElementData, ElementStyles, RestyleData};
 use style::dom::{AnimationOnlyDirtyDescendants, DirtyDescendants};
 use style::dom::{ShowSubtreeData, TElement, TNode};
@@ -1251,16 +1252,57 @@ pub extern "C" fn Servo_StyleRule_GetSpe
             *specificity = 0;
             return;
         }
         *specificity = rule.selectors.0[index].selector.specificity() as u64;
     })
 }
 
 #[no_mangle]
+pub extern "C" fn Servo_StyleRule_SelectorMatchesElement(rule: RawServoStyleRuleBorrowed,
+                                                         element: RawGeckoElementBorrowed,
+                                                         index: u32,
+                                                         pseudo_type: CSSPseudoElementType) -> bool {
+    read_locked_arc(rule, |rule: &StyleRule| {
+        let index = index as usize;
+        if index >= rule.selectors.0.len() {
+            return false;
+        }
+
+        let selector_and_hashes = &rule.selectors.0[index];
+        let mut matching_mode = MatchingMode::Normal;
+
+        match PseudoElement::from_pseudo_type(pseudo_type) {
+            Some(pseudo) => {
+                // We need to make sure that the requested pseudo element type
+                // matches the selector pseudo element type before proceeding.
+                match selector_and_hashes.selector.pseudo_element() {
+                    Some(selector_pseudo) if *selector_pseudo == pseudo => {
+                        matching_mode = MatchingMode::ForStatelessPseudoElement
+                    },
+                    _ => return false,
+                };
+            },
+            None => {
+                // Do not attempt to match if a pseudo element is requested and
+                // this is not a pseudo element selector, or vice versa.
+                if selector_and_hashes.selector.has_pseudo_element() {
+                    return false;
+                }
+            },
+        };
+
+        let element = GeckoElement(element);
+        let mut ctx = MatchingContext::new(matching_mode, None, element.owner_document_quirks_mode());
+        matches_selector(&selector_and_hashes.selector, 0, &selector_and_hashes.hashes,
+                         &element, &mut ctx, &mut |_, _| {})
+    })
+}
+
+#[no_mangle]
 pub extern "C" fn Servo_ImportRule_GetHref(rule: RawServoImportRuleBorrowed, result: *mut nsAString) {
     read_locked_arc(rule, |rule: &ImportRule| {
         write!(unsafe { &mut *result }, "{}", rule.url.as_str()).unwrap();
     })
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_ImportRule_GetSheet(
--- a/servo/tests/unit/style/parsing/ui.rs
+++ b/servo/tests/unit/style/parsing/ui.rs
@@ -15,18 +15,19 @@ fn test_moz_user_select() {
     assert_roundtrip_with_context!(_moz_user_select::parse, "auto");
     assert_roundtrip_with_context!(_moz_user_select::parse, "text");
     assert_roundtrip_with_context!(_moz_user_select::parse, "none");
     assert_roundtrip_with_context!(_moz_user_select::parse, "element");
     assert_roundtrip_with_context!(_moz_user_select::parse, "elements");
     assert_roundtrip_with_context!(_moz_user_select::parse, "toggle");
     assert_roundtrip_with_context!(_moz_user_select::parse, "tri-state");
     assert_roundtrip_with_context!(_moz_user_select::parse, "-moz-all");
-    assert_roundtrip_with_context!(_moz_user_select::parse, "-moz-none");
     assert_roundtrip_with_context!(_moz_user_select::parse, "-moz-text");
+    assert_eq!(parse(_moz_user_select::parse, "-moz-none"),
+               Ok(_moz_user_select::SpecifiedValue::none));
 
     assert!(parse(_moz_user_select::parse, "potato").is_err());
 }
 
 #[test]
 fn test_caret_color() {
     use style::properties::longhands::caret_color;
 
--- a/taskcluster/docker/rust-build/VERSION
+++ b/taskcluster/docker/rust-build/VERSION
@@ -1,1 +1,1 @@
-0.4.6
+0.4.7
--- a/taskcluster/docker/rust-build/repack_rust.py
+++ b/taskcluster/docker/rust-build/repack_rust.py
@@ -201,16 +201,17 @@ android = "armv7-linux-androideabi"
 android_x86 = "i686-linux-android"
 android_aarch64 = "aarch64-linux-android"
 linux64 = "x86_64-unknown-linux-gnu"
 linux32 = "i686-unknown-linux-gnu"
 mac64 = "x86_64-apple-darwin"
 mac32 = "i686-apple-darwin"
 win64 = "x86_64-pc-windows-msvc"
 win32 = "i686-pc-windows-msvc"
+mingw32 = "i686-pc-windows-gnu"
 
 
 def args():
     '''Read command line arguments and return options.'''
     parser = argparse.ArgumentParser()
     parser.add_argument('--channel',
                         help='Release channel to use: '
                              'stable, beta, or nightly',
@@ -229,8 +230,9 @@ if __name__ == '__main__':
     args = vars(args())
     repack(mac64, [mac64], **args)
     repack(win32, [win32], **args)
     repack(win64, [win64], **args)
     repack(linux64, [linux64, linux32], **args)
     repack(linux64, [linux64, mac64], suffix='mac-cross', **args)
     repack(linux64, [linux64, android, android_x86, android_aarch64],
            suffix='android-cross', **args)
+    repack(linux64, [linux64, win32, mingw32], suffix='mingw32-cross', **args)
--- a/taskcluster/docker/rust-build/splat_rust.py
+++ b/taskcluster/docker/rust-build/splat_rust.py
@@ -67,16 +67,19 @@ TARGETS = {
             'browser/config/tooltool-manifests/linux64/msan.manifest',
             'browser/config/tooltool-manifests/linux64/releng.manifest',
             ],
         'x86_64-unknown-linux-gnu-android-cross-repack': [
             'mobile/android/config/tooltool-manifests/android/releng.manifest',
             'mobile/android/config/tooltool-manifests/android-x86/releng.manifest',
             'mobile/android/config/tooltool-manifests/android-gradle-dependencies/releng.manifest',
             ],
+        'x86_64-unknown-linux-gnu-mingw32-cross-repack': [
+            'browser/config/tooltool-manifests/mingw32/releng.manifest',
+            ],
         'x86_64-unknown-linux-gnu-mac-cross-repack': [
             'browser/config/tooltool-manifests/macosx64/cross-releng.manifest',
             ],
         'x86_64-apple-darwin-repack': [
             'browser/config/tooltool-manifests/macosx64/clang.manifest',
             'browser/config/tooltool-manifests/macosx64/releng.manifest',
             ],
         'x86_64-pc-windows-msvc-repack': [
--- a/testing/talos/talos.json
+++ b/testing/talos/talos.json
@@ -77,20 +77,20 @@
         "svgr": {
             "tests": ["tsvgx", "tsvgr_opacity", "tart", "tscrollx", "cart", "tsvg_static"],
             "talos_options": ["--disable-e10s"]
         },
         "svgr-e10s": {
             "tests": ["tsvgx", "tsvgr_opacity", "tart", "tscrollx", "cart", "tsvg_static"]
         },
         "perf-reftest": {
-            "tests": ["bloom_basic", "bloom_basic_ref"]
+            "tests": ["bloom_basic"]
         },
         "perf-reftest-e10s": {
-            "tests": ["bloom_basic", "bloom_basic_ref"]
+            "tests": ["bloom_basic"]
         },
         "tp5o": {
             "tests": ["tp5o"],
             "pagesets_name": "tp5n.zip",
             "talos_options": ["--disable-e10s"]
         },
         "tp5o-e10s": {
             "tests": ["tp5o"],
--- a/testing/talos/talos/config.py
+++ b/testing/talos/talos/config.py
@@ -37,16 +37,17 @@ DEFAULTS = dict(
         shutdown=False,
         timeout=3600,
         tpchrome=True,
         tpcycles=10,
         tpmozafterpaint=False,
         firstpaint=False,
         userready=False,
         testeventmap=[],
+        base_vs_ref=False,
         tpdisable_e10s=False,
         tpnoisy=True,
         tppagecycles=1,
         tploadnocache=False,
         tpscrolltest=False,
         tprender=False,
         win_counters=[],
         w7_counters=[],
--- a/testing/talos/talos/output.py
+++ b/testing/talos/talos/output.py
@@ -49,18 +49,17 @@ class Output(object):
                     'extraOptions': self.results.extra_options or [],
                     'subtests': subtests
                 }
 
                 suites.append(suite)
                 vals = []
                 replicates = {}
 
-                # TODO: counters!!!! we don't have any, but they suffer the
-                # same
+                # TODO: counters!!!! we don't have any, but they suffer the same
                 for result in test.results:
                     # XXX this will not work for manifests which list
                     # the same page name twice. It also ignores cycles
                     for page, val in result.raw_values():
                         if page == 'NULL':
                             page = test.name()
                             if tsresult is None:
                                 tsresult = r = TalosResults.Results()
@@ -83,16 +82,24 @@ class Output(object):
                         if page == 'NULL':
                             # no real subtests
                             page = test.name()
                         subtest = {
                             'name': page,
                             'value': val['filtered'],
                             'replicates': replicates[page],
                         }
+                        # if results are from a comparison test i.e. perf-reftest, it will also
+                        # contain replicates for 'base' and 'reference'; we wish to keep those
+                        # to reference; actual results were calculated as the difference of those
+                        base_runs = result.results[0].get('base_runs', None)
+                        ref_runs = result.results[0].get('ref_runs', None)
+                        if base_runs and ref_runs:
+                            subtest['base_replicates'] = base_runs
+                            subtest['ref_replicates'] = ref_runs
                         subtests.append(subtest)
                         if test.test_config.get('lower_is_better') is not None:
                             subtest['lowerIsBetter'] = \
                                 test.test_config['lower_is_better']
                         if test.test_config.get('alert_threshold') is not None:
                             subtest['alertThreshold'] = \
                                 test.test_config['alert_threshold']
                         if test.test_config.get('unit'):
--- a/testing/talos/talos/run_tests.py
+++ b/testing/talos/talos/run_tests.py
@@ -88,17 +88,16 @@ def setup_webserver(webserver):
 def run_tests(config, browser_config):
     """Runs the talos tests on the given configuration and generates a report.
     """
     # get the test data
     tests = config['tests']
     tests = useBaseTestDefaults(config.get('basetest', {}), tests)
     paths = ['profile_path', 'tpmanifest', 'extensions', 'setup', 'cleanup']
     for test in tests:
-
         # Check for profile_path, tpmanifest and interpolate based on Talos
         # root https://bugzilla.mozilla.org/show_bug.cgi?id=727711
         # Build command line from config
         for path in paths:
             if test.get(path):
                 test[path] = utils.interpolate(test[path])
         if test.get('tpmanifest'):
             test['tpmanifest'] = \
@@ -250,20 +249,28 @@ def run_tests(config, browser_config):
 
                 # parse out the multi-value results, and 'fake it' to appear like separate tests
                 separate_results_list = convert_to_separate_test_results(multi_value_result,
                                                                          test_event_map)
 
                 # now we have three separate test results, store them
                 for test_result in separate_results_list:
                     talos_results.add(test_result)
+
+            # some tests like bloom_basic run two separate tests and then compare those values
+            # we want the results in perfherder to only be the actual difference between those
+            # and store the base and reference test replicates in results.json for upload
+            elif test.get('base_vs_ref', False):
+                # run the test, results will be reported for each page like two tests in the suite
+                base_and_reference_results = mytest.runTest(browser_config, test)
+                # now compare each test, and create a new test object for the comparison
+                talos_results.add(make_comparison_result(base_and_reference_results))
             else:
                 # just expecting regular test - one result value per iteration
                 talos_results.add(mytest.runTest(browser_config, test))
-
             LOG.test_end(testname, status='OK')
 
     except TalosRegression as exc:
         LOG.error("Detected a regression for %s" % testname)
         # by returning 1, we report an orange to buildbot
         # http://docs.buildbot.net/latest/developer/results.html
         LOG.test_end(testname, status='FAIL', message=str(exc),
                      stack=traceback.format_exc())
@@ -293,16 +300,66 @@ def run_tests(config, browser_config):
             print("Thanks for running Talos locally. Results are in %s"
                   % (results_urls['output_urls']))
 
     # we will stop running tests on a failed test, or we will return 0 for
     # green
     return 0
 
 
+def make_comparison_result(base_and_reference_results):
+    ''' Receive a test result object meant to be used as a base vs reference test. The result
+    object will have one test with two subtests; instead of traditional subtests we want to
+    treat them as separate tests, comparing them together and reporting the comparison results.
+
+    Results with multiple pages used as subtests would look like this normally, with the overall
+    result value being the mean of the pages/subtests:
+
+    PERFHERDER_DATA: {"framework": {"name": "talos"}, "suites": [{"extraOptions": ["e10s"],
+    "name": "bloom_basic", "lowerIsBetter": true, "alertThreshold": 5.0, "value": 594.81,
+    "subtests": [{"name": ".html", "lowerIsBetter": true, "alertThreshold": 5.0, "replicates":
+    [586.52, ...], "value": 586.52], "unit": "ms"}, {"name": "-ref.html", "lowerIsBetter": true,
+    "alertThreshold": 5.0, "replicates": [603.225, ...], "value": 603.225, "unit": "ms"}]}]}
+
+    We want to compare the subtests against eachother (base vs ref) and create a new single test
+    results object with the comparison results, that will look like traditional single test results
+    like this:
+
+    PERFHERDER_DATA: {"framework": {"name": "talos"}, "suites": [{"lowerIsBetter": true,
+    "subtests": [{"name": "", "lowerIsBetter": true, "alertThreshold": 5.0, "replicates":
+    [16.705, ...], "value": 16.705, "unit": "ms"}], "extraOptions": ["e10s"], "name":
+    "bloom_basic", "alertThreshold": 5.0}]}
+    '''
+    # separate the 'base' and 'reference' result run values
+    base_result_runs = base_and_reference_results.results[0].results[0]['runs']
+    ref_result_runs = base_and_reference_results.results[0].results[1]['runs']
+
+    # create a new results object for the comparison result; keep replicates from both pages
+    comparison_result = copy.deepcopy(base_and_reference_results)
+
+    # remove original results from our copy as they will be replaced by one comparison result
+    comparison_result.results[0].results = []
+
+    # populate our new comparison result with 'base' and 'ref' replicates
+    comparison_result.results[0].results.append({'index': 0,
+                                                 'runs': [],
+                                                 'page': '',
+                                                 'base_runs': base_result_runs,
+                                                 'ref_runs': ref_result_runs})
+
+    # now step thru each result, compare 'base' vs 'ref', and store the difference in 'runs'
+    _index = 0
+    for next_ref in comparison_result.results[0].results[0]['ref_runs']:
+        diff = abs(next_ref - comparison_result.results[0].results[0]['base_runs'][_index])
+        comparison_result.results[0].results[0]['runs'].append(round(diff, 3))
+        _index += 1
+
+    return comparison_result
+
+
 def convert_to_separate_test_results(multi_value_result, test_event_map):
     ''' Receive a test result that actually contains multiple values in a single iteration, and
     parse it out in order to 'fake' three seprate test results.
 
     Incoming result looks like this:
 
     [{'index': 0, 'runs': {'event_1': [1338, ...], 'event_2': [1438, ...], 'event_3':
     [1538, ...]}, 'page': 'NULL'}]
--- a/testing/talos/talos/test.py
+++ b/testing/talos/talos/test.py
@@ -102,16 +102,17 @@ class TsBase(Test):
         'xperf_counters',
         'xperf_providers',
         'xperf_user_providers',
         'xperf_stackwalk',
         'tpmozafterpaint',
         'firstpaint',
         'userready',
         'testeventmap',
+        'base_vs_ref',
         'extensions',
         'filters',
         'setup',
         'cleanup',
         'webextensions',
         'reinstall',     # A list of files from the profile directory that
                          # should be copied to the temporary profile prior to
                          # running each cycle, to avoid one cycle overwriting
@@ -246,17 +247,17 @@ class tresize(TsBase):
 class PageloaderTest(Test):
     """abstract base class for a Talos Pageloader test"""
     tpmanifest = None  # test manifest
     tpcycles = 1  # number of time to run each page
     cycles = None
     timeout = None
     keys = ['tpmanifest', 'tpcycles', 'tppagecycles', 'tprender', 'tpchrome',
             'tpmozafterpaint', 'tploadnocache', 'firstpaint', 'userready',
-            'testeventmap', 'rss', 'mainthread', 'resolution', 'cycles',
+            'testeventmap', 'base_vs_ref', 'rss', 'mainthread', 'resolution', 'cycles',
             'gecko_profile', 'gecko_profile_interval', 'gecko_profile_entries',
             'tptimeout', 'win_counters', 'w7_counters', 'linux_counters', 'mac_counters',
             'tpscrolltest', 'xperf_counters', 'timeout', 'shutdown', 'responsiveness',
             'profile_path', 'xperf_providers', 'xperf_user_providers', 'xperf_stackwalk',
             'filters', 'preferences', 'extensions', 'setup', 'cleanup',
             'lower_is_better', 'alert_threshold', 'unit', 'webextensions']
 
 
@@ -796,46 +797,31 @@ class a11yr(PageloaderTest):
     preferences = {'dom.send_after_paint_to_content': False}
     unit = 'ms'
     alert_threshold = 5.0
 
 
 @register_test()
 class bloom_basic(PageloaderTest):
     """
-    Stylo bloom_basic test
+    Stylo bloom_basic: runs bloom_basic and bloom_basic_ref and reports difference
     """
+    base_vs_ref = True  # compare the two test pages with eachother and report comparison
     tpmanifest = '${talos}/tests/perf-reftest/bloom_basic.manifest'
     tpcycles = 1
     tppagecycles = 25
     gecko_profile_interval = 1
     gecko_profile_entries = 2000000
     filters = filter.ignore_first.prepare(5) + filter.median.prepare()
     unit = 'ms'
     lower_is_better = True
     alert_threshold = 5.0
 
 
 @register_test()
-class bloom_basic_ref(PageloaderTest):
-    """
-    Stylo bloom_basic_ref test
-    """
-    tpmanifest = '${talos}/tests/perf-reftest/bloom_basic_ref.manifest'
-    tpcycles = 1
-    tppagecycles = 25
-    gecko_profile_interval = 1
-    gecko_profile_entries = 2000000
-    filters = filter.ignore_first.prepare(5) + filter.median.prepare()
-    unit = 'ms'
-    lower_is_better = True
-    alert_threshold = 5.0
-
-
-@register_test()
 class quantum_pageload_google(QuantumPageloadTest):
     """
     Quantum Pageload Test - Google
     """
     tpmanifest = '${talos}/tests/quantum_pageload/quantum_pageload_google.manifest'
 
 
 @register_test()
--- a/testing/talos/talos/tests/perf-reftest/bloom_basic.manifest
+++ b/testing/talos/talos/tests/perf-reftest/bloom_basic.manifest
@@ -1,1 +1,4 @@
+# base_vs_ref is set in test.py for this test, so each of these pages are run as separate
+# tests, but then compared against eachother; and the reported results are the comparison
 % http://localhost/tests/perf-reftest/bloom-basic.html
+% http://localhost/tests/perf-reftest/bloom-basic-ref.html
--- a/toolkit/components/places/Bookmarks.jsm
+++ b/toolkit/components/places/Bookmarks.jsm
@@ -227,17 +227,17 @@ var Bookmarks = Object.freeze({
       notify(observers, "onItemAdded", [ itemId, parent._id, item.index,
                                          item.type, uri, item.title,
                                          PlacesUtils.toPRTime(item.dateAdded), item.guid,
                                          item.parentGuid, item.source ],
                                        { isTagging: isTagging || isTagsFolder });
 
       // If it's a tag, notify OnItemChanged to all bookmarks for this URL.
       if (isTagging) {
-        for (let entry of (await fetchBookmarksByURL(item))) {
+        for (let entry of (await fetchBookmarksByURL(item, true))) {
           notify(observers, "onItemChanged", [ entry._id, "tags", false, "",
                                                PlacesUtils.toPRTime(entry.lastModified),
                                                entry.type, entry._parentId,
                                                entry.guid, entry.parentGuid,
                                                "", item.source ]);
         }
       }
 
@@ -637,24 +637,40 @@ var Bookmarks = Object.freeze({
                                                updatedItem.type,
                                                updatedItem._parentId,
                                                updatedItem.guid,
                                                updatedItem.parentGuid,
                                                "",
                                                updatedItem.source ]);
         }
         if (updateInfo.hasOwnProperty("title")) {
+          let isTagging = updatedItem.parentGuid == Bookmarks.tagsGuid;
           notify(observers, "onItemChanged", [ updatedItem._id, "title",
                                                false, updatedItem.title,
                                                PlacesUtils.toPRTime(updatedItem.lastModified),
                                                updatedItem.type,
                                                updatedItem._parentId,
                                                updatedItem.guid,
                                                updatedItem.parentGuid, "",
-                                               updatedItem.source ]);
+                                               updatedItem.source ],
+                                               { isTagging });
+          // If we're updating a tag, we must notify all the tagged bookmarks
+          // about the change.
+          if (isTagging) {
+            let URIs = PlacesUtils.tagging.getURIsForTag(updatedItem.title);
+            for (let uri of URIs) {
+              for (let entry of (await fetchBookmarksByURL({ url: new URL(uri.spec) }, true))) {
+                notify(observers, "onItemChanged", [ entry._id, "tags", false, "",
+                                                     PlacesUtils.toPRTime(entry.lastModified),
+                                                     entry.type, entry._parentId,
+                                                     entry.guid, entry.parentGuid,
+                                                     "", updatedItem.source ]);
+              }
+            }
+          }
         }
         if (updateInfo.hasOwnProperty("url")) {
           notify(observers, "onItemChanged", [ updatedItem._id, "uri",
                                                false, updatedItem.url.href,
                                                PlacesUtils.toPRTime(updatedItem.lastModified),
                                                updatedItem.type,
                                                updatedItem._parentId,
                                                updatedItem.guid,
@@ -733,17 +749,17 @@ var Bookmarks = Object.freeze({
       let isUntagging = item._grandParentId == PlacesUtils.tagsFolderId;
       notify(observers, "onItemRemoved", [ item._id, item._parentId, item.index,
                                            item.type, uri, item.guid,
                                            item.parentGuid,
                                            options.source ],
                                          { isTagging: isUntagging });
 
       if (isUntagging) {
-        for (let entry of (await fetchBookmarksByURL(item))) {
+        for (let entry of (await fetchBookmarksByURL(item, true))) {
           notify(observers, "onItemChanged", [ entry._id, "tags", false, "",
                                                PlacesUtils.toPRTime(entry.lastModified),
                                                entry.type, entry._parentId,
                                                entry.guid, entry.parentGuid,
                                                "", options.source ]);
         }
       }
 
@@ -2219,17 +2235,17 @@ async function(db, folderGuids, options)
                                          item.guid, item.parentGuid,
                                          source ],
                                        // Notify observers that this item is being
                                        // removed as a descendent.
                                        { isDescendantRemoval: true });
 
     let isUntagging = item._grandParentId == PlacesUtils.tagsFolderId;
     if (isUntagging) {
-      for (let entry of (await fetchBookmarksByURL(item))) {
+      for (let entry of (await fetchBookmarksByURL(item, true))) {
         notify(observers, "onItemChanged", [ entry._id, "tags", false, "",
                                              PlacesUtils.toPRTime(entry.lastModified),
                                              entry.type, entry._parentId,
                                              entry.guid, entry.parentGuid,
                                              "", source ]);
       }
     }
   }
--- a/toolkit/components/places/PlacesSyncUtils.jsm
+++ b/toolkit/components/places/PlacesSyncUtils.jsm
@@ -1415,19 +1415,21 @@ function tagItem(item, tags) {
 
   // Remove leading and trailing whitespace, then filter out empty tags.
   let newTags = tags ? tags.map(tag => tag.trim()).filter(Boolean) : [];
 
   // Removing the last tagged item will also remove the tag. To preserve
   // tag IDs, we temporarily tag a dummy URI, ensuring the tags exist.
   let dummyURI = PlacesUtils.toURI("about:weave#BStore_tagURI");
   let bookmarkURI = PlacesUtils.toURI(item.url.href);
-  PlacesUtils.tagging.tagURI(dummyURI, newTags, SOURCE_SYNC);
+  if (newTags && newTags.length > 0)
+    PlacesUtils.tagging.tagURI(dummyURI, newTags, SOURCE_SYNC);
   PlacesUtils.tagging.untagURI(bookmarkURI, null, SOURCE_SYNC);
-  PlacesUtils.tagging.tagURI(bookmarkURI, newTags, SOURCE_SYNC);
+  if (newTags && newTags.length > 0)
+    PlacesUtils.tagging.tagURI(bookmarkURI, newTags, SOURCE_SYNC);
   PlacesUtils.tagging.untagURI(dummyURI, null, SOURCE_SYNC);
 
   return newTags;
 }
 
 // `PlacesUtils.bookmarks.update` checks if we've supplied enough properties,
 // but doesn't know about additional livemark properties. We check this to avoid
 // having it throw in case we only pass properties like `{ guid, feedURI }`.
--- a/toolkit/components/places/PlacesTransactions.jsm
+++ b/toolkit/components/places/PlacesTransactions.jsm
@@ -1265,17 +1265,17 @@ PT.EditUrl.prototype = Object.seal({
       updatedInfo = await PlacesUtils.bookmarks.update(updatedInfo);
       // Move tags from the original URI to the new URI.
       if (originalTags.length > 0) {
         // Untag the original URI only if this was the only bookmark.
         if (!(await PlacesUtils.bookmarks.fetch({ url: originalInfo.url })))
           PlacesUtils.tagging.untagURI(originalURI, originalTags);
         let currentNewURITags = PlacesUtils.tagging.getTagsForURI(uri);
         newURIAdditionalTags = originalTags.filter(t => !currentNewURITags.includes(t));
-        if (newURIAdditionalTags)
+        if (newURIAdditionalTags && newURIAdditionalTags.length > 0)
           PlacesUtils.tagging.tagURI(uri, newURIAdditionalTags);
       }
     }
     await updateItem();
 
     this.undo = async function() {
       await PlacesUtils.bookmarks.update(originalInfo);
       // Move tags from new URI to original URI.
@@ -1554,22 +1554,28 @@ PT.Untag.prototype = {
       let uri = Services.io.newURI(url.href);
       let tagsToRemove;
       let tagsSet = PlacesUtils.tagging.getTagsForURI(uri);
       if (tags.length > 0) {
         tagsToRemove = tags.filter(t => tagsSet.includes(t));
       } else {
         tagsToRemove = tagsSet;
       }
-      PlacesUtils.tagging.untagURI(uri, tagsToRemove);
+      if (tagsToRemove.length > 0) {
+        PlacesUtils.tagging.untagURI(uri, tagsToRemove);
+      }
       onUndo.unshift(() => {
-        PlacesUtils.tagging.tagURI(uri, tagsToRemove);
+        if (tagsToRemove.length > 0) {
+          PlacesUtils.tagging.tagURI(uri, tagsToRemove);
+        }
       });
       onRedo.push(() => {
-        PlacesUtils.tagging.untagURI(uri, tagsToRemove);
+        if (tagsToRemove.length > 0) {
+          PlacesUtils.tagging.untagURI(uri, tagsToRemove);
+        }
       });
     }
     this.undo = async function() {
       for (let f of onUndo) {
         await f();
       }
     };
     this.redo = async function() {
--- a/toolkit/components/places/nsNavBookmarks.cpp
+++ b/toolkit/components/places/nsNavBookmarks.cpp
@@ -1982,29 +1982,29 @@ nsNavBookmarks::SetItemTitle(int64_t aIt
 
     rv = transaction.Commit();
     NS_ENSURE_SUCCESS(rv, rv);
   } else {
     rv = SetItemTitleInternal(bookmark, title, syncChangeDelta);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
-  NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
-                   nsINavBookmarkObserver,
-                   OnItemChanged(bookmark.id,
-                                 NS_LITERAL_CSTRING("title"),
-                                 false,
-                                 title,
-                                 bookmark.lastModified,
-                                 bookmark.type,
-                                 bookmark.parentId,
-                                 bookmark.guid,
-                                 bookmark.parentGuid,
-                                 EmptyCString(),
-                                 aSource));
+  NOTIFY_BOOKMARKS_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
+                             SKIP_TAGS(isChangingTagFolder),
+                             OnItemChanged(bookmark.id,
+                                           NS_LITERAL_CSTRING("title"),
+                                           false,
+                                           title,
+                                           bookmark.lastModified,
+                                           bookmark.type,
+                                           bookmark.parentId,
+                                           bookmark.guid,
+                                           bookmark.parentGuid,
+                                           EmptyCString(),
+                                           aSource));
   return NS_OK;
 }
 
 
 nsresult
 nsNavBookmarks::SetItemTitleInternal(BookmarkData& aBookmark,
                                      const nsACString& aTitle,
                                      int64_t aSyncChangeDelta)
--- a/toolkit/components/places/nsTaggingService.js
+++ b/toolkit/components/places/nsTaggingService.js
@@ -123,26 +123,26 @@ TaggingService.prototype = {
       } else if (typeof(idOrName) == "string" && idOrName.length > 0 &&
                idOrName.length <= Ci.nsITaggingService.MAX_TAG_LENGTH) {
         // This is a tag name.
         tag.name = trim ? idOrName.trim() : idOrName;
         // We can't know the id at this point, since a previous tag could
         // have created it.
         tag.__defineGetter__("id", () => this._getItemIdForTag(tag.name));
       } else {
-        throw Cr.NS_ERROR_INVALID_ARG;
+        throw Components.Exception("Invalid tag value", Cr.NS_ERROR_INVALID_ARG);
       }
       return tag;
     });
   },
 
   // nsITaggingService
   tagURI: function TS_tagURI(aURI, aTags, aSource) {
-    if (!aURI || !aTags || !Array.isArray(aTags)) {
-      throw Cr.NS_ERROR_INVALID_ARG;
+    if (!aURI || !aTags || !Array.isArray(aTags) || aTags.length == 0) {
+      throw Components.Exception("Invalid value for tags", Cr.NS_ERROR_INVALID_ARG);
     }
 
     // This also does some input validation.
     let tags = this._convertInputMixedTagsArray(aTags, true);
 
     let taggingFunction = () => {
       for (let tag of tags) {
         if (tag.id == -1) {
@@ -210,18 +210,18 @@ TaggingService.prototype = {
 
     if (count == 0) {
       PlacesUtils.bookmarks.removeItem(aTagId, aSource);
     }
   },
 
   // nsITaggingService
   untagURI: function TS_untagURI(aURI, aTags, aSource) {
-    if (!aURI || (aTags && !Array.isArray(aTags))) {
-      throw Cr.NS_ERROR_INVALID_ARG;
+    if (!aURI || (aTags && (!Array.isArray(aTags) || aTags.length == 0))) {
+      throw Components.Exception("Invalid value for tags", Cr.NS_ERROR_INVALID_ARG);
     }
 
     if (!aTags) {
       // Passing null should clear all tags for aURI, see the IDL.
       // XXXmano: write a perf-sensitive version of this code path...
       aTags = this.getTagsForURI(aURI);
     }
 
@@ -252,18 +252,19 @@ TaggingService.prototype = {
       untaggingFunction();
     } else {
       PlacesUtils.bookmarks.runInBatchMode(untaggingFunction, null);
     }
   },
 
   // nsITaggingService
   getURIsForTag: function TS_getURIsForTag(aTagName) {
-    if (!aTagName || aTagName.length == 0)
-      throw Cr.NS_ERROR_INVALID_ARG;
+    if (!aTagName || aTagName.length == 0) {
+      throw Components.Exception("Invalid tag name", Cr.NS_ERROR_INVALID_ARG);
+    }
 
     if (/^\s|\s$/.test(aTagName)) {
       Deprecated.warning("Tag passed to getURIsForTag was not trimmed",
                          "https://bugzilla.mozilla.org/show_bug.cgi?id=967196");
     }
 
     let uris = [];
     let tagId = this._getItemIdForTag(aTagName);
@@ -288,18 +289,19 @@ TaggingService.prototype = {
       stmt.finalize();
     }
 
     return uris;
   },
 
   // nsITaggingService
   getTagsForURI: function TS_getTagsForURI(aURI, aCount) {
-    if (!aURI)
-      throw Cr.NS_ERROR_INVALID_ARG;
+    if (!aURI) {
+      throw Components.Exception("Invalid uri", Cr.NS_ERROR_INVALID_ARG);
+    }
 
     let tags = [];
     let db = PlacesUtils.history.DBConnection;
     let stmt = db.createStatement(
       `SELECT t.id AS folderId
        FROM moz_bookmarks b
        JOIN moz_bookmarks t on t.id = b.parent
        WHERE b.fk = (SELECT id FROM moz_places WHERE url_hash = hash(:url) AND url = :url) AND
--- a/toolkit/components/places/tests/PlacesTestUtils.jsm
+++ b/toolkit/components/places/tests/PlacesTestUtils.jsm
@@ -320,9 +320,34 @@ this.PlacesTestUtils = Object.freeze({
       SELECT guid, dateRemoved
       FROM moz_bookmarks_deleted
       ORDER BY guid`);
     return rows.map(row => ({
       guid: row.getResultByName("guid"),
       dateRemoved: PlacesUtils.toDate(row.getResultByName("dateRemoved")),
     }));
   },
+
+  waitForNotification(notification, conditionFn = () => true, type = "bookmarks") {
+    let iface = type == "bookmarks" ? Ci.nsINavBookmarkObserver
+                                    : Ci.nsINavHistoryObserver;
+    return new Promise(resolve => {
+      let proxifiedObserver = new Proxy({}, {
+        get: (target, name) => {
+          if (name == "QueryInterface")
+            return XPCOMUtils.generateQI([iface]);
+          if (name == notification)
+            return (...args) => {
+              if (conditionFn.apply(this, args)) {
+                PlacesUtils[type].removeObserver(proxifiedObserver);
+                resolve();
+              }
+            }
+          if (name == "skipTags" || name == "skipDescendantsOnItemRemoval") {
+            return false;
+          }
+          return () => false;
+        }
+      });
+      PlacesUtils[type].addObserver(proxifiedObserver);
+    });
+  },
 });
--- a/toolkit/components/places/tests/bookmarks/test_async_observers.js
+++ b/toolkit/components/places/tests/bookmarks/test_async_observers.js
@@ -1,166 +1,118 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /* This test checks that bookmarks service is correctly forwarding async
  * events like visit or favicon additions. */
 
 const NOW = Date.now() * 1000;
-
-var observer = {
-  bookmarks: [],
-  observedBookmarks: 0,
-  observedVisitId: 0,
-  deferred: null,
-
-  /**
-   * Returns a promise that is resolved when the observer determines that the
-   * test can continue.  This is required rather than calling run_next_test
-   * directly in the observer because there are cases where we must wait for
-   * other asynchronous events to be completed in addition to this.
-   */
-  setupCompletionPromise() {
-    this.observedBookmarks = 0;
-    this.deferred = Promise.defer();
-    return this.deferred.promise;
-  },
+let gBookmarkGuids = [];
 
-  onBeginUpdateBatch() {},
-  onEndUpdateBatch() {},
-  onItemAdded() {},
-  onItemRemoved() {},
-  onItemMoved() {},
-  onItemChanged(aItemId, aProperty, aIsAnnotation, aNewValue,
-                          aLastModified, aItemType) {
-    do_print("Check that we got the correct change information.");
-    do_check_neq(this.bookmarks.indexOf(aItemId), -1);
-    if (aProperty == "favicon") {
-      do_check_false(aIsAnnotation);
-      do_check_eq(aNewValue, SMALLPNG_DATA_URI.spec);
-      do_check_eq(aLastModified, 0);
-      do_check_eq(aItemType, PlacesUtils.bookmarks.TYPE_BOOKMARK);
-    } else if (aProperty == "cleartime") {
-      do_check_false(aIsAnnotation);
-      do_check_eq(aNewValue, "");
-      do_check_eq(aLastModified, 0);
-      do_check_eq(aItemType, PlacesUtils.bookmarks.TYPE_BOOKMARK);
-    } else {
-      do_throw("Unexpected property change " + aProperty);
-    }
-
-    if (++this.observedBookmarks == this.bookmarks.length) {
-      this.deferred.resolve();
-    }
-  },
-  onItemVisited(aItemId, aVisitId, aTime) {
-    do_print("Check that we got the correct visit information.");
-    do_check_neq(this.bookmarks.indexOf(aItemId), -1);
-    this.observedVisitId = aVisitId;
-    do_check_eq(aTime, NOW);
-    if (++this.observedBookmarks == this.bookmarks.length) {
-      this.deferred.resolve();
-    }
-  },
-
-  QueryInterface: XPCOMUtils.generateQI([
-    Ci.nsINavBookmarkObserver,
-  ])
-};
-PlacesUtils.bookmarks.addObserver(observer);
+add_task(async function setup() {
+  // Add multiple bookmarks to the same uri.
+  gBookmarkGuids.push((await PlacesUtils.bookmarks.insert({
+    parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+    url: "http://book.ma.rk/"
+  })).guid);
+  gBookmarkGuids.push((await PlacesUtils.bookmarks.insert({
+    parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+    url: "http://book.ma.rk/"
+  })).guid);
+  Assert.equal(gBookmarkGuids.length, 2);
+});
 
 add_task(async function test_add_visit() {
-  let observerPromise = observer.setupCompletionPromise();
+  // Add a visit to the bookmark and wait for the observer.
+  let guids = new Set(gBookmarkGuids);
+  Assert.equal(guids.size, 2);
+  let promiseNotifications = PlacesTestUtils.waitForNotification("onItemVisited",
+    (id, visitId, time, transition, uri, parentId, guid, parentGuid) => {
+      do_print(`Got a visit notification for ${guid}.`);
+      Assert.ok(visitId > 0);
+      guids.delete(guid);
+      return guids.size == 0;
+  });
 
-  // Add a visit to the bookmark and wait for the observer.
-  let visitId;
-  await new Promise((resolve, reject) => {
-    PlacesUtils.asyncHistory.updatePlaces({
-      uri: NetUtil.newURI("http://book.ma.rk/"),
-      visits: [{ transitionType: TRANSITION_TYPED, visitDate: NOW }]
-    }, {
-      handleError: function TAV_handleError() {
-        reject(new Error("Unexpected error in adding visit."));
-      },
-      handleResult(aPlaceInfo) {
-        visitId = aPlaceInfo.visits[0].visitId;
-      },
-      handleCompletion: function TAV_handleCompletion() {
-        resolve();
-      }
-    });
-
-    // Wait for both the observer and the asynchronous update, in any order.
+  await PlacesTestUtils.addVisits({
+    uri: "http://book.ma.rk/",
+    transition: TRANSITION_TYPED,
+    visitDate: NOW
   });
-  await observerPromise;
-
-  // Check that both asynchronous results are consistent.
-  do_check_eq(observer.observedVisitId, visitId);
+  await promiseNotifications;
 });
 
 add_task(async function test_add_icon() {
-  let observerPromise = observer.setupCompletionPromise();
+  // Add a visit to the bookmark and wait for the observer.
+  let guids = new Set(gBookmarkGuids);
+  Assert.equal(guids.size, 2);
+  let promiseNotifications = PlacesTestUtils.waitForNotification("onItemChanged",
+    (id, property, isAnno, newValue, lastModified, itemType, parentId, guid) => {
+      do_print(`Got a changed notification for ${guid}.`);
+      Assert.equal(property, "favicon");
+      Assert.ok(!isAnno);
+      Assert.equal(newValue, SMALLPNG_DATA_URI.spec);
+      Assert.equal(lastModified, 0);
+      Assert.equal(itemType, PlacesUtils.bookmarks.TYPE_BOOKMARK);
+      guids.delete(guid);
+      return guids.size == 0;
+  });
+
   PlacesUtils.favicons.setAndFetchFaviconForPage(NetUtil.newURI("http://book.ma.rk/"),
                                                  SMALLPNG_DATA_URI, true,
                                                  PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
                                                  null,
                                                  Services.scriptSecurityManager.getSystemPrincipal());
-  await observerPromise;
+  await promiseNotifications;
 });
 
 add_task(async function test_remove_page() {
-  let observerPromise = observer.setupCompletionPromise();
+  // Add a visit to the bookmark and wait for the observer.
+  let guids = new Set(gBookmarkGuids);
+  Assert.equal(guids.size, 2);
+  let promiseNotifications = PlacesTestUtils.waitForNotification("onItemChanged",
+    (id, property, isAnno, newValue, lastModified, itemType, parentId, guid) => {
+      do_print(`Got a changed notification for ${guid}.`);
+      Assert.equal(property, "cleartime");
+      Assert.ok(!isAnno);
+      Assert.equal(newValue, "");
+      Assert.equal(lastModified, 0);
+      Assert.equal(itemType, PlacesUtils.bookmarks.TYPE_BOOKMARK);
+      guids.delete(guid);
+      return guids.size == 0;
+  });
+
   await PlacesUtils.history.remove("http://book.ma.rk/");
-  await observerPromise;
-});
-
-add_task(function cleanup() {
-  PlacesUtils.bookmarks.removeObserver(observer, false);
+  await promiseNotifications;
 });
 
 add_task(async function shutdown() {
   // Check that async observers don't try to create async statements after
   // shutdown.  That would cause assertions, since the async thread is gone
   // already.  Note that in such a case the notifications are not fired, so we
   // cannot test for them.
   // Put an history notification that triggers AsyncGetBookmarksForURI between
   // asyncClose() and the actual connection closing.  Enqueuing a main-thread
   // event just after places-will-close-connection should ensure it runs before
   // places-connection-closed.
   // Notice this code is not using helpers cause it depends on a very specific
   // order, a change in the helpers code could make this test useless.
   await new Promise(resolve => {
-
     Services.obs.addObserver(function onNotification() {
       Services.obs.removeObserver(onNotification, "places-will-close-connection");
       do_check_true(true, "Observed fake places shutdown");
 
       Services.tm.dispatchToMainThread(() => {
         // WARNING: this is very bad, never use out of testing code.
         PlacesUtils.bookmarks.QueryInterface(Ci.nsINavHistoryObserver)
                              .onPageChanged(NetUtil.newURI("http://book.ma.rk/"),
                                             Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON,
                                             "test", "test");
         resolve(promiseTopicObserved("places-connection-closed"));
       });
     }, "places-will-close-connection");
     shutdownPlaces();
-
   });
 });
 
-function run_test() {
-  // Add multiple bookmarks to the same uri.
-  observer.bookmarks.push(
-    PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
-                                         NetUtil.newURI("http://book.ma.rk/"),
-                                         PlacesUtils.bookmarks.DEFAULT_INDEX,
-                                         "Bookmark")
-  );
-  observer.bookmarks.push(
-    PlacesUtils.bookmarks.insertBookmark(PlacesUtils.toolbarFolderId,
-                                         NetUtil.newURI("http://book.ma.rk/"),
-                                         PlacesUtils.bookmarks.DEFAULT_INDEX,
-                                         "Bookmark")
-  );
+
 
-  run_next_test();
-}
+
--- a/toolkit/components/places/tests/head_common.js
+++ b/toolkit/components/places/tests/head_common.js
@@ -27,18 +27,16 @@ const TITLE_LENGTH_MAX = 4096;
 
 Cu.importGlobalProperties(["URL"]);
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Promise",
-                                  "resource://gre/modules/Promise.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
                                   "resource://gre/modules/PromiseUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "BookmarkJSONUtils",
                                   "resource://gre/modules/BookmarkJSONUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "BookmarkHTMLUtils",
                                   "resource://gre/modules/BookmarkHTMLUtils.jsm");
--- a/toolkit/components/places/tests/queries/test_async.js
+++ b/toolkit/components/places/tests/queries/test_async.js
@@ -100,17 +100,17 @@ var tests = [
  * Instances of this class become the prototypes of the test objects above.
  * Each test can therefore use the methods of this class, or they can override
  * them if they want.  To run a test, call setup() and then run().
  */
 function Test() {
   // This maps a state name to the number of times it's been observed.
   this.stateCounts = {};
   // Promise object resolved when the next test can be run.
-  this.deferNextTest = Promise.defer();
+  this.deferNextTest = PromiseUtils.defer();
 }
 
 Test.prototype = {
   /**
    * Call this when an observer observes a container state change to sanity
    * check the arguments.
    *
    * @param aNewState
--- a/toolkit/components/places/tests/unit/test_bookmarks_html.js
+++ b/toolkit/components/places/tests/unit/test_bookmarks_html.js
@@ -314,23 +314,17 @@ function checkItem(aExpected, aNode) {
           do_check_eq(PlacesUtils.bookmarks.getItemLastModified(id),
                       aExpected.lastModified);
           break;
         case "url":
           if (!("feedUrl" in aExpected))
             do_check_eq(aNode.uri, aExpected.url)
           break;
         case "icon":
-          let deferred = Promise.defer();
-          PlacesUtils.favicons.getFaviconDataForPage(
-            NetUtil.newURI(aExpected.url),
-            function(aURI, aDataLen, aData, aMimeType) {
-              deferred.resolve(aData);
-            });
-          let data = await deferred.promise;
+          let {data} = await getFaviconDataForPage(aExpected.url);
           let base64Icon = "data:image/png;base64," +
                            base64EncodeString(String.fromCharCode.apply(String, data));
           do_check_true(base64Icon == aExpected.icon);
           break;
         case "keyword": {
           let entry = await PlacesUtils.keywords.fetch({ url: aNode.uri });
           Assert.equal(entry.keyword, aExpected.keyword);
           break;
--- a/toolkit/components/places/tests/unit/test_bookmarks_json.js
+++ b/toolkit/components/places/tests/unit/test_bookmarks_json.js
@@ -181,23 +181,17 @@ async function checkItem(aExpected, aNod
         do_check_eq(PlacesUtils.toPRTime(bookmark.lastModified),
                     aExpected.lastModified);
         break;
       case "url":
         if (!("feedUrl" in aExpected))
           do_check_eq(aNode.uri, aExpected.url);
         break;
       case "icon":
-        let deferred = Promise.defer();
-        PlacesUtils.favicons.getFaviconDataForPage(
-          NetUtil.newURI(aExpected.url),
-          function(aURI, aDataLen, aData, aMimeType) {
-            deferred.resolve(aData);
-          });
-        let data = await deferred.promise;
+        let {data} = await getFaviconDataForPage(aExpected.url);
         let base64Icon = "data:image/png;base64," +
                          base64EncodeString(String.fromCharCode.apply(String, data));
         do_check_eq(base64Icon, aExpected.icon);
         break;
       case "keyword": {
         let entry = await PlacesUtils.keywords.fetch({ url: aNode.uri });
         Assert.equal(entry.keyword, aExpected.keyword);
         break;
--- a/toolkit/components/places/tests/unit/test_frecency.js
+++ b/toolkit/components/places/tests/unit/test_frecency.js
@@ -66,50 +66,53 @@ AutoCompleteInput.prototype = {
     if (iid.equals(Ci.nsISupports) ||
         iid.equals(Ci.nsIAutoCompleteInput))
       return this;
 
     throw Components.results.NS_ERROR_NO_INTERFACE;
   }
 }
 
-function ensure_results(uris, searchTerm) {
-  PlacesTestUtils.promiseAsyncUpdates()
-                 .then(() => ensure_results_internal(uris, searchTerm));
+async function ensure_results(uris, searchTerm) {
+  await PlacesTestUtils.promiseAsyncUpdates()
+  await ensure_results_internal(uris, searchTerm);
 }
 
-function ensure_results_internal(uris, searchTerm) {
+async function ensure_results_internal(uris, searchTerm) {
   var controller = Components.classes["@mozilla.org/autocomplete/controller;1"].
                    getService(Components.interfaces.nsIAutoCompleteController);
 
   // Make an AutoCompleteInput that uses our searches
   // and confirms results on search complete
   var input = new AutoCompleteInput(["unifiedcomplete"]);
 
   controller.input = input;
 
   var numSearchesStarted = 0;
   input.onSearchBegin = function() {
     numSearchesStarted++;
     do_check_eq(numSearchesStarted, 1);
   };
 
-  input.onSearchComplete = function() {
-    do_check_eq(numSearchesStarted, 1);
-    do_check_eq(controller.searchStatus,
-                Ci.nsIAutoCompleteController.STATUS_COMPLETE_MATCH);
-    do_check_eq(controller.matchCount, uris.length);
-    for (var i = 0; i < controller.matchCount; i++) {
-      do_check_eq(controller.getValueAt(i), uris[i].spec);
-    }
+  let promise = new Promise(resolve => {
+    input.onSearchComplete = function() {
+      do_check_eq(numSearchesStarted, 1);
+      do_check_eq(controller.searchStatus,
+                  Ci.nsIAutoCompleteController.STATUS_COMPLETE_MATCH);
+      do_check_eq(controller.matchCount, uris.length);
+      for (var i = 0; i < controller.matchCount; i++) {
+        do_check_eq(controller.getValueAt(i), uris[i].spec);
+      }
 
-    deferEnsureResults.resolve();
-  };
+      resolve();
+    };
+  });
 
   controller.startSearch(searchTerm);
+  await promise;
 }
 
 // Get history service
 try {
   var tagssvc = Cc["@mozilla.org/browser/tagging-service;1"].
                 getService(Ci.nsITaggingService);
   var bmksvc = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
                 getService(Ci.nsINavBookmarksService);
@@ -151,136 +154,128 @@ var c2 = 1;
 
 var tests = [
 // test things without a search term
 async function() {
   print("TEST-INFO | Test 0: same count, different date");
   await task_setCountDate(uri1, c1, d1);
   await task_setCountDate(uri2, c1, d2);
   tagURI(uri1, ["site"]);
-  ensure_results([uri1, uri2], "");
+  await ensure_results([uri1, uri2], "");
 },
 async function() {
   print("TEST-INFO | Test 1: same count, different date");
   await task_setCountDate(uri1, c1, d2);
   await task_setCountDate(uri2, c1, d1);
   tagURI(uri1, ["site"]);
-  ensure_results([uri2, uri1], "");
+  await ensure_results([uri2, uri1], "");
 },
 async function() {
   print("TEST-INFO | Test 2: different count, same date");
   await task_setCountDate(uri1, c1, d1);
   await task_setCountDate(uri2, c2, d1);
   tagURI(uri1, ["site"]);
-  ensure_results([uri1, uri2], "");
+  await ensure_results([uri1, uri2], "");
 },
 async function() {
   print("TEST-INFO | Test 3: different count, same date");
   await task_setCountDate(uri1, c2, d1);
   await task_setCountDate(uri2, c1, d1);
   tagURI(uri1, ["site"]);
-  ensure_results([uri2, uri1], "");
+  await ensure_results([uri2, uri1], "");
 },
 
 // test things with a search term
 async function() {
   print("TEST-INFO | Test 4: same count, different date");
   await task_setCountDate(uri1, c1, d1);
   await task_setCountDate(uri2, c1, d2);
   tagURI(uri1, ["site"]);
-  ensure_results([uri1, uri2], "site");
+  await ensure_results([uri1, uri2], "site");
 },
 async function() {
   print("TEST-INFO | Test 5: same count, different date");
   await task_setCountDate(uri1, c1, d2);
   await task_setCountDate(uri2, c1, d1);
   tagURI(uri1, ["site"]);
-  ensure_results([uri2, uri1], "site");
+  await ensure_results([uri2, uri1], "site");
 },
 async function() {
   print("TEST-INFO | Test 6: different count, same date");
   await task_setCountDate(uri1, c1, d1);
   await task_setCountDate(uri2, c2, d1);
   tagURI(uri1, ["site"]);
-  ensure_results([uri1, uri2], "site");
+  await ensure_results([uri1, uri2], "site");
 },
 async function() {
   print("TEST-INFO | Test 7: different count, same date");
   await task_setCountDate(uri1, c2, d1);
   await task_setCountDate(uri2, c1, d1);
   tagURI(uri1, ["site"]);
-  ensure_results([uri2, uri1], "site");
+  await ensure_results([uri2, uri1], "site");
 },
 // There are multiple tests for 8, hence the multiple functions
 // Bug 426166 section
-function() {
+async function() {
   print("TEST-INFO | Test 8.1a: same count, same date");
   setBookmark(uri3);
   setBookmark(uri4);
-  ensure_results([uri4, uri3], "a");
+  await ensure_results([uri4, uri3], "a");
 },
-function() {
+async function() {
   print("TEST-INFO | Test 8.1b: same count, same date");
   setBookmark(uri3);
   setBookmark(uri4);
-  ensure_results([uri4, uri3], "aa");
+  await ensure_results([uri4, uri3], "aa");
 },
-function() {
+async function() {
   print("TEST-INFO | Test 8.2: same count, same date");
   setBookmark(uri3);
   setBookmark(uri4);
-  ensure_results([uri4, uri3], "aaa");
+  await ensure_results([uri4, uri3], "aaa");
 },
-function() {
+async function() {
   print("TEST-INFO | Test 8.3: same count, same date");
   setBookmark(uri3);
   setBookmark(uri4);
-  ensure_results([uri4, uri3], "aaaa");
+  await ensure_results([uri4, uri3], "aaaa");
 },
-function() {
+async function() {
   print("TEST-INFO | Test 8.4: same count, same date");
   setBookmark(uri3);
   setBookmark(uri4);
-  ensure_results([uri4, uri3], "aaa");
+  await ensure_results([uri4, uri3], "aaa");
 },
-function() {
+async function() {
   print("TEST-INFO | Test 8.5: same count, same date");
   setBookmark(uri3);
   setBookmark(uri4);
-  ensure_results([uri4, uri3], "aa");
+  await ensure_results([uri4, uri3], "aa");
 },
-function() {
+async function() {
   print("TEST-INFO | Test 8.6: same count, same date");
   setBookmark(uri3);
   setBookmark(uri4);
-  ensure_results([uri4, uri3], "a");
+  await ensure_results([uri4, uri3], "a");
 }
 ];
 
-/**
- * This deferred object contains a promise that is resolved when the
- * ensure_results_internal function has finished its execution.
- */
-var deferEnsureResults;
-
 add_task(async function test_frecency() {
   // Disable autoFill for this test.
   Services.prefs.setBoolPref("browser.urlbar.autoFill", false);
   do_register_cleanup(() => Services.prefs.clearUserPref("browser.urlbar.autoFill"));
   // always search in history + bookmarks, no matter what the default is
   var prefs = Cc["@mozilla.org/preferences-service;1"].
               getService(Ci.nsIPrefBranch);
 
   prefs.setBoolPref("browser.urlbar.suggest.history", true);
   prefs.setBoolPref("browser.urlbar.suggest.bookmark", true);
   prefs.setBoolPref("browser.urlbar.suggest.openpage", false);
   for (let test of tests) {
     await PlacesUtils.bookmarks.eraseEverything();
     await PlacesTestUtils.clearHistory();
 
-    deferEnsureResults = Promise.defer();
     await test();
-    await deferEnsureResults.promise;
   }
   for (let type of ["history", "bookmark", "openpage"]) {
     prefs.clearUserPref("browser.urlbar.suggest." + type);
   }
 });
--- a/toolkit/components/telemetry/TelemetrySession.jsm
+++ b/toolkit/components/telemetry/TelemetrySession.jsm
@@ -87,16 +87,20 @@ const TELEMETRY_TEST_DELAY = 1;
 // Execute a scheduler tick every 5 minutes.
 const SCHEDULER_TICK_INTERVAL_MS = Preferences.get("toolkit.telemetry.scheduler.tickInterval", 5 * 60) * 1000;
 // When user is idle, execute a scheduler tick every 60 minutes.
 const SCHEDULER_TICK_IDLE_INTERVAL_MS = Preferences.get("toolkit.telemetry.scheduler.idleTickInterval", 60 * 60) * 1000;
 
 // The tolerance we have when checking if it's midnight (15 minutes).
 const SCHEDULER_MIDNIGHT_TOLERANCE_MS = 15 * 60 * 1000;
 
+// The maximum time (ms) until the tick should moved from the idle
+// queue to the regular queue if it hasn't been executed yet.
+const SCHEDULER_TICK_MAX_IDLE_DELAY_MS = 60 * 1000;
+
 // Seconds of idle time before pinging.
 // On idle-daily a gather-telemetry notification is fired, during it probes can
 // start asynchronous tasks to gather data.
 const IDLE_TIMEOUT_SECONDS = Preferences.get("toolkit.telemetry.idleTimeout", 5 * 60);
 
 // The frequency at which we persist session data to the disk to prevent data loss
 // in case of aborted sessions (currently 5 minutes).
 const ABORTED_SESSION_UPDATE_INTERVAL_MS = 5 * 60 * 1000;
@@ -409,45 +413,53 @@ var TelemetryScheduler = {
     switch (aTopic) {
       case "idle":
         // If the user is idle, increase the tick interval.
         this._isUserIdle = true;
         return this._onSchedulerTick();
       case "active":
         // User is back to work, restore the original tick interval.
         this._isUserIdle = false;
-        return this._onSchedulerTick();
+        return this._onSchedulerTick(true);
       case "wake_notification":
         // The machine woke up from sleep, trigger a tick to avoid sessions
         // spanning more than a day.
         // This is needed because sleep time does not count towards timeouts
         // on Mac & Linux - see bug 1262386, bug 1204823 et al.
-        return this._onSchedulerTick();
+        return this._onSchedulerTick(true);
     }
     return undefined;
   },
 
   /**
    * Performs a scheduler tick. This function manages Telemetry recurring operations.
+   * @param {Boolean} [dispatchOnIdle=false] If true, the tick is dispatched in the
+   *                  next idle cycle of the main thread.
    * @return {Promise} A promise, only used when testing, resolved when the scheduled
    *                   operation completes.
    */
-  _onSchedulerTick() {
+  _onSchedulerTick(dispatchOnIdle = false) {
     // This call might not be triggered from a timeout. In that case we don't want to
     // leave any previously scheduled timeouts pending.
     this._clearTimeout();
 
     if (this._shuttingDown) {
       this._log.warn("_onSchedulerTick - already shutdown.");
       return Promise.reject(new Error("Already shutdown."));
     }
 
     let promise = Promise.resolve();
     try {
-      promise = this._schedulerTickLogic();
+      if (dispatchOnIdle) {
+        promise = new Promise((resolve, reject) =>
+          Services.tm.idleDispatchToMainThread(() => this._schedulerTickLogic().then(resolve, reject)),
+                                               SCHEDULER_TICK_MAX_IDLE_DELAY_MS);
+      } else {
+        promise = this._schedulerTickLogic();
+      }
     } catch (e) {
       Telemetry.getHistogramById("TELEMETRY_SCHEDULER_TICK_EXCEPTION").add(1);
       this._log.error("_onSchedulerTick - There was an exception", e);
     } finally {
       this._rescheduleTimeout();
     }
 
     // This promise is returned to make testing easier.
--- a/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js
@@ -1864,19 +1864,20 @@ add_task(async function test_schedulerEn
 
   // Reset the test preference.
   const PREF_TEST = "toolkit.telemetry.test.pref1";
   Preferences.reset(PREF_TEST);
   const PREFS_TO_WATCH = new Map([
     [PREF_TEST, {what: TelemetryEnvironment.RECORD_PREF_VALUE}],
   ]);
 
+  await TelemetryController.testReset();
+  await TelemetryController.testShutdown();
   await TelemetryStorage.testClearPendingPings();
   PingServer.clearRequests();
-  await TelemetryController.testReset();
 
   // Set a fake current date and start Telemetry.
   let nowDate = fakeNow(2060, 10, 18, 0, 0, 0);
   gMonotonicNow = fakeMonotonicNow(gMonotonicNow + 10 * MILLISECONDS_PER_MINUTE);
   let schedulerTickCallback = null;
   fakeSchedulerTimer(callback => schedulerTickCallback = callback, () => {});
   await TelemetryController.testReset();
   TelemetryEnvironment.testWatchPreferences(PREFS_TO_WATCH);
@@ -2024,17 +2025,17 @@ add_task(async function test_schedulerUs
 
   // Send an "idle" notification to the scheduler.
   fakeIdleNotification("idle");
 
   // When idle, the scheduler should have a 1hr tick interval.
   Assert.equal(schedulerTimeout, SCHEDULER_TICK_IDLE_INTERVAL_MS);
 
   // Send an "active" notification to the scheduler.
-  fakeIdleNotification("active");
+  await fakeIdleNotification("active");
 
   // When user is back active, the scheduler tick should be 5 minutes again.
   Assert.equal(schedulerTimeout, SCHEDULER_TICK_INTERVAL_MS);
 
   // We should not miss midnight when going to idle.
   now.setHours(23);
   now.setMinutes(50);
   fakeNow(now);
--- a/toolkit/content/widgets/autocomplete.xml
+++ b/toolkit/content/widgets/autocomplete.xml
@@ -1220,17 +1220,22 @@ extends="chrome://global/content/binding
         </getter>
       </property>
 
       <method name="_collapseUnusedItems">
         <body>
           <![CDATA[
             let existingItemsCount = this.richlistbox.childNodes.length;
             for (let i = this._matchCount; i < existingItemsCount; ++i) {
-              this.richlistbox.childNodes[i].collapsed = true;
+              let item = this.richlistbox.childNodes[i];
+
+              item.collapsed = true;
+              if (typeof item._onCollapse == "function") {
+                item._onCollapse();
+              }
             }
           ]]>
         </body>
       </method>
 
       <method name="adjustHeight">
         <body>
           <![CDATA[
@@ -1339,17 +1344,18 @@ extends="chrome://global/content/binding
               originalValue = item.getAttribute("ac-value");
               originalText = item.getAttribute("ac-text");
               originalType = item.getAttribute("originaltype");
 
               // All of types are reusable except for autofill-profile,
               // which has different structure of <content> and overrides
               // _adjustAcItem().
               reusable = originalType === style ||
-                         (style !== "autofill-profile" && originalType !== "autofill-profile");
+                         (style !== "autofill-profile" && originalType !== "autofill-profile" &&
+                         style !== "autofill-footer" && originalType !== "autofill-footer");
             } else {
               // need to create a new item
               item = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "richlistitem");
             }
 
             item.setAttribute("dir", this.style.direction);
             item.setAttribute("ac-image", image);
             item.setAttribute("ac-value", value);
--- a/toolkit/content/widgets/tree.xml
+++ b/toolkit/content/widgets/tree.xml
@@ -680,22 +680,29 @@
           return this.changeOpenState(this.currentIndex);
         ]]></body>
       </method>
     </implementation>
 
     <handlers>
       <handler event="touchstart">
         <![CDATA[
-          if (event.touches.length > 1) {
+          function isScrollbarElement(target) {
+            return (target.localName == "thumb" || target.localName == "slider")
+                && target.namespaceURI == "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+          }
+          if (event.touches.length > 1 || isScrollbarElement(event.touches[0].target)) {
             // Multiple touch points detected, abort. In particular this aborts
             // the panning gesture when the user puts a second finger down after
             // already panning with one finger. Aborting at this point prevents
             // the pan gesture from being resumed until all fingers are lifted
             // (as opposed to when the user is back down to one finger).
+            // Additionally, if the user lands on the scrollbar don't use this
+            // code for scrolling, instead allow gecko to handle scrollbar
+            // interaction normally.
             this._touchY = -1;
           } else {
             this._touchY = event.touches[0].screenY;
           }
         ]]>
       </handler>
       <handler event="touchmove">
         <![CDATA[